diff --git a/README.md b/README.md index 047b8aa20..e0e4f78ef 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 bfe5df2b4..a3e8d1dd0 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 0d9547ebc..6fc0936a4 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 149c8b039..890123344 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 0151a67a0..0748e6262 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 fe500c9fd..b54d8fb4c 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 b2f191b49..ba8b0aef6 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 bbafc9211..beecb1cc3 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 6bd964e5f..9e0540b87 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 4e39b47f3..e65ff151d 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 dd8597e38..1aad08db2 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 ff3aa04d1..353c6fb21 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 d43fd843e..bee2d8d90 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 248fe748c..6eb1bf3ec 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 5515c8814..316a0efa2 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 000000000..dd23323eb --- /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) + } +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: