diff --git a/README.md b/README.md index 047b8aa2..e0e4f78e 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,36 @@ GitHub MCP Server running on stdio ``` +## i18n / Overriding descriptions + +The descriptions of the tools can be overridden by creating a github-mcp-server.json file in the same directory as the binary. +The file should contain a JSON object with the tool names as keys and the new descriptions as values. +For example: + +```json +{ + "TOOL_ADD_ISSUE_COMMENT_DESCRIPTION": "an alternative description", + "TOOL_CREATE_BRANCH_DESCRIPTION": "Create a new branch in a GitHub repository" +} +``` + +You can create an export of the current translations by running the binary with the `--export-translations` flag. +This flag will preserve any translations/overrides you have made, while adding any new translations that have been added to the binary since the last time you exported. + +```sh +./github-mcp-server --export-translations +cat github-mcp-server.json +``` + +You can also use ENV vars to override the descriptions. The environment variable names are the same as the keys in the JSON file, +prefixed with `GITHUB_MCP_` and all uppercase. + +For example, to override the `TOOL_ADD_ISSUE_COMMENT_DESCRIPTION` tool, you can set the following environment variable: + +```sh +export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description" +``` + ## Testing on VS Code Insiders First of all, install `github-mcp-server` with: diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index bfe5df2b..a3e8d1dd 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -11,6 +11,7 @@ import ( "github.com/github/github-mcp-server/pkg/github" iolog "github.com/github/github-mcp-server/pkg/log" + "github.com/github/github-mcp-server/pkg/translations" gogithub "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/server" log "github.com/sirupsen/logrus" @@ -36,7 +37,7 @@ var ( stdlog.Fatal("Failed to initialize logger:", err) } logCommands := viper.GetBool("enable-command-logging") - if err := runStdioServer(logger, logCommands); err != nil { + if err := runStdioServer(logger, logCommands, viper.GetBool("export-translations")); err != nil { stdlog.Fatal("failed to run stdio server:", err) } }, @@ -49,10 +50,12 @@ func init() { // Add global flags that will be shared by all commands rootCmd.PersistentFlags().String("log-file", "", "Path to log file") rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file") + rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file") // Bind flag to viper viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file")) viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging")) + viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations")) // Add subcommands rootCmd.AddCommand(stdioCmd) @@ -81,7 +84,7 @@ func initLogger(outPath string) (*log.Logger, error) { return logger, nil } -func runStdioServer(logger *log.Logger, logCommands bool) error { +func runStdioServer(logger *log.Logger, logCommands bool, exportTranslations bool) error { // Create app context ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() @@ -93,13 +96,20 @@ func runStdioServer(logger *log.Logger, logCommands bool) error { } ghClient := gogithub.NewClient(nil).WithAuthToken(token) + t, dumpTranslations := translations.TranslationHelper() + // Create server - ghServer := github.NewServer(ghClient) + ghServer := github.NewServer(ghClient, t) stdioServer := server.NewStdioServer(ghServer) stdLogger := stdlog.New(logger.Writer(), "stdioserver", 0) stdioServer.SetErrorLogger(stdLogger) + if exportTranslations { + // Once server is initialized, all translations are loaded + dumpTranslations() + } + // Start listening for messages errC := make(chan error, 1) go func() { diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index 0d9547eb..6fc0936a 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -7,14 +7,15 @@ import ( "io" "net/http" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) -func getCodeScanningAlert(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getCodeScanningAlert(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_code_scanning_alert", - mcp.WithDescription("Get details of a specific code scanning alert in a GitHub repository."), + mcp.WithDescription(t("TOOL_GET_CODE_SCANNING_ALERT_DESCRIPTION", "Get details of a specific code scanning alert in a GitHub repository.")), mcp.WithString("owner", mcp.Required(), mcp.Description("The owner of the repository."), @@ -56,9 +57,9 @@ func getCodeScanningAlert(client *github.Client) (tool mcp.Tool, handler server. } } -func listCodeScanningAlerts(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func listCodeScanningAlerts(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_code_scanning_alerts", - mcp.WithDescription("List code scanning alerts in a GitHub repository."), + mcp.WithDescription(t("TOOL_LIST_CODE_SCANNING_ALERTS_DESCRIPTION", "List code scanning alerts in a GitHub repository.")), mcp.WithString("owner", mcp.Required(), mcp.Description("The owner of the repository."), diff --git a/pkg/github/code_scanning_test.go b/pkg/github/code_scanning_test.go index 149c8b03..89012334 100644 --- a/pkg/github/code_scanning_test.go +++ b/pkg/github/code_scanning_test.go @@ -6,6 +6,7 @@ import ( "net/http" "testing" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" @@ -15,7 +16,7 @@ import ( func Test_GetCodeScanningAlert(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getCodeScanningAlert(mockClient) + tool, _ := getCodeScanningAlert(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_code_scanning_alert", tool.Name) assert.NotEmpty(t, tool.Description) @@ -81,7 +82,7 @@ func Test_GetCodeScanningAlert(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getCodeScanningAlert(client) + _, handler := getCodeScanningAlert(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -117,7 +118,7 @@ func Test_GetCodeScanningAlert(t *testing.T) { func Test_ListCodeScanningAlerts(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := listCodeScanningAlerts(mockClient) + tool, _ := listCodeScanningAlerts(mockClient, translations.NullTranslationHelper) assert.Equal(t, "list_code_scanning_alerts", tool.Name) assert.NotEmpty(t, tool.Description) @@ -194,7 +195,7 @@ func Test_ListCodeScanningAlerts(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := listCodeScanningAlerts(client) + _, handler := listCodeScanningAlerts(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 0151a67a..0748e626 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -8,15 +8,16 @@ import ( "net/http" "time" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // getIssue creates a tool to get details of a specific issue in a GitHub repository. -func getIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getIssue(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_issue", - mcp.WithDescription("Get details of a specific issue in a GitHub repository."), + mcp.WithDescription(t("TOOL_GET_ISSUE_DESCRIPTION", "Get details of a specific issue in a GitHub repository.")), mcp.WithString("owner", mcp.Required(), mcp.Description("The owner of the repository."), @@ -59,9 +60,9 @@ func getIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerF } // addIssueComment creates a tool to add a comment to an issue. -func addIssueComment(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func addIssueComment(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("add_issue_comment", - mcp.WithDescription("Add a comment to an existing issue"), + mcp.WithDescription(t("TOOL_ADD_ISSUE_COMMENT_DESCRIPTION", "Add a comment to an existing issue")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -113,9 +114,9 @@ func addIssueComment(client *github.Client) (tool mcp.Tool, handler server.ToolH } // searchIssues creates a tool to search for issues and pull requests. -func searchIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("search_issues", - mcp.WithDescription("Search for issues and pull requests across GitHub repositories"), + mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues and pull requests across GitHub repositories")), mcp.WithString("q", mcp.Required(), mcp.Description("Search query using GitHub issues search syntax"), @@ -185,9 +186,9 @@ func searchIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHand } // createIssue creates a tool to create a new issue in a GitHub repository. -func createIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func createIssue(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_issue", - mcp.WithDescription("Create a new issue in a GitHub repository"), + mcp.WithDescription(t("TOOL_CREATE_ISSUE_DESCRIPTION", "Create a new issue in a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -265,9 +266,9 @@ func createIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandl } // listIssues creates a tool to list and filter repository issues -func listIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func listIssues(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_issues", - mcp.WithDescription("List issues in a GitHub repository with filtering options"), + mcp.WithDescription(t("TOOL_LIST_ISSUES_DESCRIPTION", "List issues in a GitHub repository with filtering options")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -382,4 +383,4 @@ func parseISOTimestamp(timestamp string) (time.Time, error) { // Return error with supported formats return time.Time{}, fmt.Errorf("invalid ISO 8601 timestamp: %s (supported formats: YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DD)", timestamp) -} \ No newline at end of file +} diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index fe500c9f..b54d8fb4 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/migueleliasweb/go-github-mock/src/mock" @@ -17,7 +18,7 @@ import ( func Test_GetIssue(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getIssue(mockClient) + tool, _ := getIssue(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_issue", tool.Name) assert.NotEmpty(t, tool.Description) @@ -81,7 +82,7 @@ func Test_GetIssue(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getIssue(client) + _, handler := getIssue(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -113,7 +114,7 @@ func Test_GetIssue(t *testing.T) { func Test_AddIssueComment(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := addIssueComment(mockClient) + tool, _ := addIssueComment(mockClient, translations.NullTranslationHelper) assert.Equal(t, "add_issue_comment", tool.Name) assert.NotEmpty(t, tool.Description) @@ -184,7 +185,7 @@ func Test_AddIssueComment(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := addIssueComment(client) + _, handler := addIssueComment(client, translations.NullTranslationHelper) // Create call request request := mcp.CallToolRequest{ @@ -229,7 +230,7 @@ func Test_AddIssueComment(t *testing.T) { func Test_SearchIssues(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := searchIssues(mockClient) + tool, _ := searchIssues(mockClient, translations.NullTranslationHelper) assert.Equal(t, "search_issues", tool.Name) assert.NotEmpty(t, tool.Description) @@ -333,7 +334,7 @@ func Test_SearchIssues(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := searchIssues(client) + _, handler := searchIssues(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -374,7 +375,7 @@ func Test_SearchIssues(t *testing.T) { func Test_CreateIssue(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := createIssue(mockClient) + tool, _ := createIssue(mockClient, translations.NullTranslationHelper) assert.Equal(t, "create_issue", tool.Name) assert.NotEmpty(t, tool.Description) @@ -475,7 +476,7 @@ func Test_CreateIssue(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := createIssue(client) + _, handler := createIssue(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -529,7 +530,7 @@ func Test_CreateIssue(t *testing.T) { func Test_ListIssues(t *testing.T) { // Verify tool definition mockClient := github.NewClient(nil) - tool, _ := listIssues(mockClient) + tool, _ := listIssues(mockClient, translations.NullTranslationHelper) assert.Equal(t, "list_issues", tool.Name) assert.NotEmpty(t, tool.Description) @@ -650,7 +651,7 @@ func Test_ListIssues(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := listIssues(client) + _, handler := listIssues(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index b2f191b4..ba8b0aef 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -7,15 +7,16 @@ import ( "io" "net/http" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // getPullRequest creates a tool to get details of a specific pull request. -func getPullRequest(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getPullRequest(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request", - mcp.WithDescription("Get details of a specific pull request"), + mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_DESCRIPTION", "Get details of a specific pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -58,9 +59,9 @@ func getPullRequest(client *github.Client) (tool mcp.Tool, handler server.ToolHa } // listPullRequests creates a tool to list and filter repository pull requests. -func listPullRequests(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func listPullRequests(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_pull_requests", - mcp.WithDescription("List and filter repository pull requests"), + mcp.WithDescription(t("TOOL_LIST_PULL_REQUESTS_DESCRIPTION", "List and filter repository pull requests")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -159,9 +160,9 @@ func listPullRequests(client *github.Client) (tool mcp.Tool, handler server.Tool } // mergePullRequest creates a tool to merge a pull request. -func mergePullRequest(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func mergePullRequest(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("merge_pull_request", - mcp.WithDescription("Merge a pull request"), + mcp.WithDescription(t("TOOL_MERGE_PULL_REQUEST_DESCRIPTION", "Merge a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -230,9 +231,9 @@ func mergePullRequest(client *github.Client) (tool mcp.Tool, handler server.Tool } // getPullRequestFiles creates a tool to get the list of files changed in a pull request. -func getPullRequestFiles(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getPullRequestFiles(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_files", - mcp.WithDescription("Get the list of files changed in a pull request"), + mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_FILES_DESCRIPTION", "Get the list of files changed in a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -276,9 +277,9 @@ func getPullRequestFiles(client *github.Client) (tool mcp.Tool, handler server.T } // getPullRequestStatus creates a tool to get the combined status of all status checks for a pull request. -func getPullRequestStatus(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getPullRequestStatus(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_status", - mcp.WithDescription("Get the combined status of all status checks for a pull request"), + mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_STATUS_DESCRIPTION", "Get the combined status of all status checks for a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -337,9 +338,9 @@ func getPullRequestStatus(client *github.Client) (tool mcp.Tool, handler server. } // updatePullRequestBranch creates a tool to update a pull request branch with the latest changes from the base branch. -func updatePullRequestBranch(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func updatePullRequestBranch(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("update_pull_request_branch", - mcp.WithDescription("Update a pull request branch with the latest changes from the base branch"), + mcp.WithDescription(t("TOOL_UPDATE_PULL_REQUEST_BRANCH_DESCRIPTION", "Update a pull request branch with the latest changes from the base branch")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -399,9 +400,9 @@ func updatePullRequestBranch(client *github.Client) (tool mcp.Tool, handler serv } // getPullRequestComments creates a tool to get the review comments on a pull request. -func getPullRequestComments(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getPullRequestComments(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_comments", - mcp.WithDescription("Get the review comments on a pull request"), + mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_COMMENTS_DESCRIPTION", "Get the review comments on a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -450,9 +451,9 @@ func getPullRequestComments(client *github.Client) (tool mcp.Tool, handler serve } // getPullRequestReviews creates a tool to get the reviews on a pull request. -func getPullRequestReviews(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getPullRequestReviews(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_pull_request_reviews", - mcp.WithDescription("Get the reviews on a pull request"), + mcp.WithDescription(t("TOOL_GET_PULL_REQUEST_REVIEWS_DESCRIPTION", "Get the reviews on a pull request")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index bbafc921..beecb1cc 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" @@ -16,7 +17,7 @@ import ( func Test_GetPullRequest(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getPullRequest(mockClient) + tool, _ := getPullRequest(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_pull_request", tool.Name) assert.NotEmpty(t, tool.Description) @@ -93,7 +94,7 @@ func Test_GetPullRequest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getPullRequest(client) + _, handler := getPullRequest(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -128,7 +129,7 @@ func Test_GetPullRequest(t *testing.T) { func Test_ListPullRequests(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := listPullRequests(mockClient) + tool, _ := listPullRequests(mockClient, translations.NullTranslationHelper) assert.Equal(t, "list_pull_requests", tool.Name) assert.NotEmpty(t, tool.Description) @@ -212,7 +213,7 @@ func Test_ListPullRequests(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := listPullRequests(client) + _, handler := listPullRequests(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -250,7 +251,7 @@ func Test_ListPullRequests(t *testing.T) { func Test_MergePullRequest(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := mergePullRequest(mockClient) + tool, _ := mergePullRequest(mockClient, translations.NullTranslationHelper) assert.Equal(t, "merge_pull_request", tool.Name) assert.NotEmpty(t, tool.Description) @@ -321,7 +322,7 @@ func Test_MergePullRequest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := mergePullRequest(client) + _, handler := mergePullRequest(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -355,7 +356,7 @@ func Test_MergePullRequest(t *testing.T) { func Test_GetPullRequestFiles(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getPullRequestFiles(mockClient) + tool, _ := getPullRequestFiles(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_pull_request_files", tool.Name) assert.NotEmpty(t, tool.Description) @@ -433,7 +434,7 @@ func Test_GetPullRequestFiles(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getPullRequestFiles(client) + _, handler := getPullRequestFiles(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -471,7 +472,7 @@ func Test_GetPullRequestFiles(t *testing.T) { func Test_GetPullRequestStatus(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getPullRequestStatus(mockClient) + tool, _ := getPullRequestStatus(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_pull_request_status", tool.Name) assert.NotEmpty(t, tool.Description) @@ -593,7 +594,7 @@ func Test_GetPullRequestStatus(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getPullRequestStatus(client) + _, handler := getPullRequestStatus(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -632,7 +633,7 @@ func Test_GetPullRequestStatus(t *testing.T) { func Test_UpdatePullRequestBranch(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := updatePullRequestBranch(mockClient) + tool, _ := updatePullRequestBranch(mockClient, translations.NullTranslationHelper) assert.Equal(t, "update_pull_request_branch", tool.Name) assert.NotEmpty(t, tool.Description) @@ -714,7 +715,7 @@ func Test_UpdatePullRequestBranch(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := updatePullRequestBranch(client) + _, handler := updatePullRequestBranch(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -742,7 +743,7 @@ func Test_UpdatePullRequestBranch(t *testing.T) { func Test_GetPullRequestComments(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getPullRequestComments(mockClient) + tool, _ := getPullRequestComments(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_pull_request_comments", tool.Name) assert.NotEmpty(t, tool.Description) @@ -830,7 +831,7 @@ func Test_GetPullRequestComments(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getPullRequestComments(client) + _, handler := getPullRequestComments(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -869,7 +870,7 @@ func Test_GetPullRequestComments(t *testing.T) { func Test_GetPullRequestReviews(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getPullRequestReviews(mockClient) + tool, _ := getPullRequestReviews(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_pull_request_reviews", tool.Name) assert.NotEmpty(t, tool.Description) @@ -953,7 +954,7 @@ func Test_GetPullRequestReviews(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getPullRequestReviews(client) + _, handler := getPullRequestReviews(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 6bd964e5..9e0540b8 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -8,15 +8,16 @@ import ( "net/http" "github.com/aws/smithy-go/ptr" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // listCommits creates a tool to get commits of a branch in a repository. -func listCommits(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func listCommits(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("list_commits", - mcp.WithDescription("Get list of commits of a branch in a GitHub repository"), + mcp.WithDescription(t("TOOL_LIST_COMMITS_DESCRIPTION", "Get list of commits of a branch in a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -83,9 +84,9 @@ func listCommits(client *github.Client) (tool mcp.Tool, handler server.ToolHandl } // createOrUpdateFile creates a tool to create or update a file in a GitHub repository. -func createOrUpdateFile(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func createOrUpdateFile(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_or_update_file", - mcp.WithDescription("Create or update a single file in a GitHub repository"), + mcp.WithDescription(t("TOOL_CREATE_OR_UPDATE_FILE_DESCRIPTION", "Create or update a single file in a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner (username or organization)"), @@ -162,9 +163,9 @@ func createOrUpdateFile(client *github.Client) (tool mcp.Tool, handler server.To } // createRepository creates a tool to create a new GitHub repository. -func createRepository(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func createRepository(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_repository", - mcp.WithDescription("Create a new GitHub repository in your account"), + mcp.WithDescription(t("TOOL_CREATE_REPOSITORY_DESCRIPTION", "Create a new GitHub repository in your account")), mcp.WithString("name", mcp.Required(), mcp.Description("Repository name"), @@ -225,9 +226,9 @@ func createRepository(client *github.Client) (tool mcp.Tool, handler server.Tool } // getFileContents creates a tool to get the contents of a file or directory from a GitHub repository. -func getFileContents(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getFileContents(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_file_contents", - mcp.WithDescription("Get the contents of a file or directory from a GitHub repository"), + mcp.WithDescription(t("TOOL_GET_FILE_CONTENTS_DESCRIPTION", "Get the contents of a file or directory from a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner (username or organization)"), @@ -285,9 +286,9 @@ func getFileContents(client *github.Client) (tool mcp.Tool, handler server.ToolH } // forkRepository creates a tool to fork a repository. -func forkRepository(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func forkRepository(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("fork_repository", - mcp.WithDescription("Fork a GitHub repository to your account or specified organization"), + mcp.WithDescription(t("TOOL_FORK_REPOSITORY_DESCRIPTION", "Fork a GitHub repository to your account or specified organization")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), @@ -342,9 +343,9 @@ func forkRepository(client *github.Client) (tool mcp.Tool, handler server.ToolHa } // createBranch creates a tool to create a new branch. -func createBranch(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func createBranch(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("create_branch", - mcp.WithDescription("Create a new branch in a GitHub repository"), + mcp.WithDescription(t("TOOL_CREATE_BRANCH_DESCRIPTION", "Create a new branch in a GitHub repository")), mcp.WithString("owner", mcp.Required(), mcp.Description("Repository owner"), diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 4e39b47f..e65ff151 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/migueleliasweb/go-github-mock/src/mock" @@ -17,7 +18,7 @@ import ( func Test_GetFileContents(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := getFileContents(mockClient) + tool, _ := getFileContents(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_file_contents", tool.Name) assert.NotEmpty(t, tool.Description) @@ -125,7 +126,7 @@ func Test_GetFileContents(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getFileContents(client) + _, handler := getFileContents(client, translations.NullTranslationHelper) // Create call request request := mcp.CallToolRequest{ @@ -182,7 +183,7 @@ func Test_GetFileContents(t *testing.T) { func Test_ForkRepository(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := forkRepository(mockClient) + tool, _ := forkRepository(mockClient, translations.NullTranslationHelper) assert.Equal(t, "fork_repository", tool.Name) assert.NotEmpty(t, tool.Description) @@ -252,7 +253,7 @@ func Test_ForkRepository(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := forkRepository(client) + _, handler := forkRepository(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -280,7 +281,7 @@ func Test_ForkRepository(t *testing.T) { func Test_CreateBranch(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := createBranch(mockClient) + tool, _ := createBranch(mockClient, translations.NullTranslationHelper) assert.Equal(t, "create_branch", tool.Name) assert.NotEmpty(t, tool.Description) @@ -433,7 +434,7 @@ func Test_CreateBranch(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := createBranch(client) + _, handler := createBranch(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -466,7 +467,7 @@ func Test_CreateBranch(t *testing.T) { func Test_ListCommits(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := listCommits(mockClient) + tool, _ := listCommits(mockClient, translations.NullTranslationHelper) assert.Equal(t, "list_commits", tool.Name) assert.NotEmpty(t, tool.Description) @@ -591,7 +592,7 @@ func Test_ListCommits(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := listCommits(client) + _, handler := listCommits(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -629,7 +630,7 @@ func Test_ListCommits(t *testing.T) { func Test_CreateOrUpdateFile(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := createOrUpdateFile(mockClient) + tool, _ := createOrUpdateFile(mockClient, translations.NullTranslationHelper) assert.Equal(t, "create_or_update_file", tool.Name) assert.NotEmpty(t, tool.Description) @@ -739,7 +740,7 @@ func Test_CreateOrUpdateFile(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := createOrUpdateFile(client) + _, handler := createOrUpdateFile(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -779,7 +780,7 @@ func Test_CreateOrUpdateFile(t *testing.T) { func Test_CreateRepository(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := createRepository(mockClient) + tool, _ := createRepository(mockClient, translations.NullTranslationHelper) assert.Equal(t, "create_repository", tool.Name) assert.NotEmpty(t, tool.Description) @@ -873,7 +874,7 @@ func Test_CreateRepository(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := createRepository(client) + _, handler := createRepository(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index dd8597e3..1aad08db 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -7,29 +7,30 @@ import ( "path/filepath" "strings" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // getRepositoryContent defines the resource template and handler for the Repository Content API. -func getRepositoryContent(client *github.Client) (mainTemplate mcp.ResourceTemplate, reftemplate mcp.ResourceTemplate, shaTemplate mcp.ResourceTemplate, tagTemplate mcp.ResourceTemplate, prTemplate mcp.ResourceTemplate, handler server.ResourceTemplateHandlerFunc) { +func getRepositoryContent(client *github.Client, t translations.TranslationHelperFunc) (mainTemplate mcp.ResourceTemplate, reftemplate mcp.ResourceTemplate, shaTemplate mcp.ResourceTemplate, tagTemplate mcp.ResourceTemplate, prTemplate mcp.ResourceTemplate, handler server.ResourceTemplateHandlerFunc) { return mcp.NewResourceTemplate( "repo://{owner}/{repo}/contents{/path*}", // Resource template - "Repository Content", // Description + t("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION", "Repository Content"), ), mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/heads/{branch}/contents{/path*}", // Resource template - "Repository Content for specific branch", // Description + t("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION", "Repository Content for specific branch"), ), mcp.NewResourceTemplate( "repo://{owner}/{repo}/sha/{sha}/contents{/path*}", // Resource template - "Repository Content for specific commit", // Description + t("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION", "Repository Content for specific commit"), ), mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/tags/{tag}/contents{/path*}", // Resource template - "Repository Content for specific tag", // Description + t("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION", "Repository Content for specific tag"), ), mcp.NewResourceTemplate( "repo://{owner}/{repo}/refs/pull/{pr_number}/head/contents{/path*}", // Resource template - "Repository Content for specific pull request", // Description + t("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION", "Repository Content for specific pull request"), ), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { // Extract parameters from request.Params.URI diff --git a/pkg/github/search.go b/pkg/github/search.go index ff3aa04d..353c6fb2 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -6,15 +6,16 @@ import ( "fmt" "io" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // searchRepositories creates a tool to search for GitHub repositories. -func searchRepositories(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func searchRepositories(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("search_repositories", - mcp.WithDescription("Search for GitHub repositories"), + mcp.WithDescription(t("TOOL_SEARCH_REPOSITORIES_DESCRIPTION", "Search for GitHub repositories")), mcp.WithString("query", mcp.Required(), mcp.Description("Search query"), @@ -68,9 +69,9 @@ func searchRepositories(client *github.Client) (tool mcp.Tool, handler server.To } // searchCode creates a tool to search for code across GitHub repositories. -func searchCode(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func searchCode(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("search_code", - mcp.WithDescription("Search for code across GitHub repositories"), + mcp.WithDescription(t("TOOL_SEARCH_CODE_DESCRIPTION", "Search for code across GitHub repositories")), mcp.WithString("q", mcp.Required(), mcp.Description("Search query using GitHub code search syntax"), @@ -140,9 +141,9 @@ func searchCode(client *github.Client) (tool mcp.Tool, handler server.ToolHandle } // searchUsers creates a tool to search for GitHub users. -func searchUsers(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func searchUsers(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("search_users", - mcp.WithDescription("Search for GitHub users"), + mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users")), mcp.WithString("q", mcp.Required(), mcp.Description("Search query using GitHub users search syntax"), diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index d43fd843..bee2d8d9 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -6,6 +6,7 @@ import ( "net/http" "testing" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" @@ -15,7 +16,7 @@ import ( func Test_SearchRepositories(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := searchRepositories(mockClient) + tool, _ := searchRepositories(mockClient, translations.NullTranslationHelper) assert.Equal(t, "search_repositories", tool.Name) assert.NotEmpty(t, tool.Description) @@ -109,7 +110,7 @@ func Test_SearchRepositories(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := searchRepositories(client) + _, handler := searchRepositories(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -150,7 +151,7 @@ func Test_SearchRepositories(t *testing.T) { func Test_SearchCode(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := searchCode(mockClient) + tool, _ := searchCode(mockClient, translations.NullTranslationHelper) assert.Equal(t, "search_code", tool.Name) assert.NotEmpty(t, tool.Description) @@ -246,7 +247,7 @@ func Test_SearchCode(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := searchCode(client) + _, handler := searchCode(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) @@ -287,7 +288,7 @@ func Test_SearchCode(t *testing.T) { func Test_SearchUsers(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) - tool, _ := searchUsers(mockClient) + tool, _ := searchUsers(mockClient, translations.NullTranslationHelper) assert.Equal(t, "search_users", tool.Name) assert.NotEmpty(t, tool.Description) @@ -387,7 +388,7 @@ func Test_SearchUsers(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := searchUsers(client) + _, handler := searchUsers(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/github/server.go b/pkg/github/server.go index 248fe748..6eb1bf3e 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -9,13 +9,14 @@ import ( "net/http" "strings" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // NewServer creates a new GitHub MCP server with the specified GH client and logger. -func NewServer(client *github.Client) *server.MCPServer { +func NewServer(client *github.Client, t translations.TranslationHelperFunc) *server.MCPServer { // Create a new MCP server s := server.NewMCPServer( "github-mcp-server", @@ -24,7 +25,7 @@ func NewServer(client *github.Client) *server.MCPServer { server.WithLogging()) // Add GitHub Resources - defaultTemplate, branchTemplate, tagTemplate, shaTemplate, prTemplate, handler := getRepositoryContent(client) + defaultTemplate, branchTemplate, tagTemplate, shaTemplate, prTemplate, handler := getRepositoryContent(client, t) s.AddResourceTemplate(defaultTemplate, handler) s.AddResourceTemplate(branchTemplate, handler) @@ -33,49 +34,48 @@ func NewServer(client *github.Client) *server.MCPServer { s.AddResourceTemplate(prTemplate, handler) // Add GitHub tools - Issues - s.AddTool(getIssue(client)) - s.AddTool(addIssueComment(client)) - s.AddTool(createIssue(client)) - s.AddTool(searchIssues(client)) - s.AddTool(listIssues(client)) + s.AddTool(getIssue(client, t)) + s.AddTool(addIssueComment(client, t)) + s.AddTool(createIssue(client, t)) + s.AddTool(searchIssues(client, t)) + s.AddTool(listIssues(client, t)) // Add GitHub tools - Pull Requests - s.AddTool(getPullRequest(client)) - s.AddTool(listPullRequests(client)) - s.AddTool(mergePullRequest(client)) - s.AddTool(getPullRequestFiles(client)) - s.AddTool(getPullRequestStatus(client)) - s.AddTool(updatePullRequestBranch(client)) - s.AddTool(getPullRequestComments(client)) - s.AddTool(getPullRequestReviews(client)) + s.AddTool(getPullRequest(client, t)) + s.AddTool(listPullRequests(client, t)) + s.AddTool(mergePullRequest(client, t)) + s.AddTool(getPullRequestFiles(client, t)) + s.AddTool(getPullRequestStatus(client, t)) + s.AddTool(updatePullRequestBranch(client, t)) + s.AddTool(getPullRequestComments(client, t)) + s.AddTool(getPullRequestReviews(client, t)) // Add GitHub tools - Repositories - s.AddTool(createOrUpdateFile(client)) - s.AddTool(searchRepositories(client)) - s.AddTool(createRepository(client)) - s.AddTool(getFileContents(client)) - s.AddTool(forkRepository(client)) - s.AddTool(createBranch(client)) - s.AddTool(listCommits(client)) + s.AddTool(createOrUpdateFile(client, t)) + s.AddTool(searchRepositories(client, t)) + s.AddTool(createRepository(client, t)) + s.AddTool(getFileContents(client, t)) + s.AddTool(forkRepository(client, t)) + s.AddTool(createBranch(client, t)) + s.AddTool(listCommits(client, t)) // Add GitHub tools - Search - s.AddTool(searchCode(client)) - s.AddTool(searchUsers(client)) + s.AddTool(searchCode(client, t)) + s.AddTool(searchUsers(client, t)) // Add GitHub tools - Users - s.AddTool(getMe(client)) + s.AddTool(getMe(client, t)) // Add GitHub tools - Code Scanning - s.AddTool(getCodeScanningAlert(client)) - s.AddTool(listCodeScanningAlerts(client)) - + s.AddTool(getCodeScanningAlert(client, t)) + s.AddTool(listCodeScanningAlerts(client, t)) return s } // getMe creates a tool to get details of the authenticated user. -func getMe(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func getMe(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_me", - mcp.WithDescription("Get details of the authenticated GitHub user"), + mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION", "Get details of the authenticated GitHub user")), mcp.WithString("reason", mcp.Description("Optional: reason the session was created"), ), diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index 5515c881..316a0efa 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" @@ -17,7 +18,7 @@ import ( func Test_GetMe(t *testing.T) { // Verify tool definition mockClient := github.NewClient(nil) - tool, _ := getMe(mockClient) + tool, _ := getMe(mockClient, translations.NullTranslationHelper) assert.Equal(t, "get_me", tool.Name) assert.NotEmpty(t, tool.Description) @@ -95,7 +96,7 @@ func Test_GetMe(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockedClient) - _, handler := getMe(client) + _, handler := getMe(client, translations.NullTranslationHelper) // Create call request request := createMCPRequest(tc.requestArgs) diff --git a/pkg/translations/translations.go b/pkg/translations/translations.go new file mode 100644 index 00000000..dd23323e --- /dev/null +++ b/pkg/translations/translations.go @@ -0,0 +1,77 @@ +package translations + +import ( + "encoding/json" + "log" + "os" + "strings" + + "github.com/spf13/viper" +) + +type TranslationHelperFunc func(key string, defaultValue string) string + +func NullTranslationHelper(key string, defaultValue string) string { + return defaultValue +} + +func TranslationHelper() (TranslationHelperFunc, func()) { + var translationKeyMap = map[string]string{} + v := viper.New() + + v.SetEnvPrefix("GITHUB_MCP_") + v.AutomaticEnv() + + // Load from JSON file + v.SetConfigName("github-mcp-server") + v.SetConfigType("json") + v.AddConfigPath(".") + + if err := v.ReadInConfig(); err != nil { + // ignore error if file not found as it is not required + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + log.Printf("Could not read JSON config: %v", err) + } + } + + // create a function that takes both a key, and a default value and returns either the default value or an override value + return func(key string, defaultValue string) string { + key = strings.ToUpper(key) + if value, exists := translationKeyMap[key]; exists { + return value + } + // check if the env var exists + if value, exists := os.LookupEnv("GITHUB_MCP_" + key); exists { + // TODO I could not get Viper to play ball reading the env var + translationKeyMap[key] = value + return value + } + + v.SetDefault(key, defaultValue) + translationKeyMap[key] = v.GetString(key) + return translationKeyMap[key] + }, func() { + // dump the translationKeyMap to a json file + DumpTranslationKeyMap(translationKeyMap) + } +} + +// dump translationKeyMap to a json file called github-mcp-server.json +func DumpTranslationKeyMap(translationKeyMap map[string]string) { + file, err := os.Create("github-mcp-server.json") + if err != nil { + log.Fatalf("Error creating file: %v", err) + } + defer file.Close() + + // marshal the map to json + jsonData, err := json.MarshalIndent(translationKeyMap, "", " ") + if err != nil { + log.Fatalf("Error marshaling map to JSON: %v", err) + } + + // write the json data to the file + if _, err := file.Write(jsonData); err != nil { + log.Fatalf("Error writing to file: %v", err) + } +}