Skip to content

Commit ee78b9a

Browse files
add a framework for translations
1 parent b3b3e02 commit ee78b9a

16 files changed

+267
-138
lines changed

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,36 @@ GitHub MCP Server running on stdio
288288
289289
```
290290
291+
## i18n / Overriding descriptions
292+
293+
The descriptions of the tools can be overridden by creating a github-mcp-server.json file in the same directory as the binary.
294+
The file should contain a JSON object with the tool names as keys and the new descriptions as values.
295+
For example:
296+
297+
```json
298+
{
299+
"TOOL_ADD_ISSUE_COMMENT_DESCRIPTION": "an alternative description",
300+
"TOOL_CREATE_BRANCH_DESCRIPTION": "Create a new branch in a GitHub repository"
301+
}
302+
```
303+
304+
You can create an export of the current translations by running the binary with the `--export-translations` flag.
305+
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.
306+
307+
```sh
308+
./github-mcp-server --export-translations
309+
cat github-mcp-server.json
310+
```
311+
312+
You can also use ENV vars to override the descriptions. The environment variable names are the same as the keys in the JSON file,
313+
prefixed with `GITHUB_MCP_` and all uppercase.
314+
315+
For example, to override the `TOOL_ADD_ISSUE_COMMENT_DESCRIPTION` tool, you can set the following environment variable:
316+
317+
```sh
318+
export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description"
319+
```
320+
291321
## Testing on VS Code Insiders
292322
293323
First of all, install `github-mcp-server` with:

cmd/github-mcp-server/main.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/github/github-mcp-server/pkg/github"
1313
iolog "github.com/github/github-mcp-server/pkg/log"
14+
"github.com/github/github-mcp-server/pkg/translations"
1415
gogithub "github.com/google/go-github/v69/github"
1516
"github.com/mark3labs/mcp-go/server"
1617
log "github.com/sirupsen/logrus"
@@ -36,7 +37,7 @@ var (
3637
stdlog.Fatal("Failed to initialize logger:", err)
3738
}
3839
logCommands := viper.GetBool("enable-command-logging")
39-
if err := runStdioServer(logger, logCommands); err != nil {
40+
if err := runStdioServer(logger, logCommands, viper.GetBool("export-translations")); err != nil {
4041
stdlog.Fatal("failed to run stdio server:", err)
4142
}
4243
},
@@ -49,10 +50,12 @@ func init() {
4950
// Add global flags that will be shared by all commands
5051
rootCmd.PersistentFlags().String("log-file", "", "Path to log file")
5152
rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file")
53+
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
5254

5355
// Bind flag to viper
5456
viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
5557
viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging"))
58+
viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
5659

5760
// Add subcommands
5861
rootCmd.AddCommand(stdioCmd)
@@ -81,7 +84,7 @@ func initLogger(outPath string) (*log.Logger, error) {
8184
return logger, nil
8285
}
8386

84-
func runStdioServer(logger *log.Logger, logCommands bool) error {
87+
func runStdioServer(logger *log.Logger, logCommands bool, exportTranslations bool) error {
8588
// Create app context
8689
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
8790
defer stop()
@@ -93,13 +96,20 @@ func runStdioServer(logger *log.Logger, logCommands bool) error {
9396
}
9497
ghClient := gogithub.NewClient(nil).WithAuthToken(token)
9598

99+
t, dumpTranslations := translations.TranslationHelper()
100+
96101
// Create server
97-
ghServer := github.NewServer(ghClient)
102+
ghServer := github.NewServer(ghClient, t)
98103
stdioServer := server.NewStdioServer(ghServer)
99104

100105
stdLogger := stdlog.New(logger.Writer(), "stdioserver", 0)
101106
stdioServer.SetErrorLogger(stdLogger)
102107

108+
if exportTranslations {
109+
// Once server is initialized, all translations are loaded
110+
dumpTranslations()
111+
}
112+
103113
// Start listening for messages
104114
errC := make(chan error, 1)
105115
go func() {

pkg/github/code_scanning.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import (
77
"io"
88
"net/http"
99

10+
"github.com/github/github-mcp-server/pkg/translations"
1011
"github.com/google/go-github/v69/github"
1112
"github.com/mark3labs/mcp-go/mcp"
1213
"github.com/mark3labs/mcp-go/server"
1314
)
1415

15-
func getCodeScanningAlert(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
16+
func getCodeScanningAlert(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1617
return mcp.NewTool("get_code_scanning_alert",
17-
mcp.WithDescription("Get details of a specific code scanning alert in a GitHub repository."),
18+
mcp.WithDescription(t("TOOL_GET_CODE_SCANNING_ALERT_DESCRIPTION", "Get details of a specific code scanning alert in a GitHub repository.")),
1819
mcp.WithString("owner",
1920
mcp.Required(),
2021
mcp.Description("The owner of the repository."),
@@ -56,9 +57,9 @@ func getCodeScanningAlert(client *github.Client) (tool mcp.Tool, handler server.
5657
}
5758
}
5859

59-
func listCodeScanningAlerts(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
60+
func listCodeScanningAlerts(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
6061
return mcp.NewTool("list_code_scanning_alerts",
61-
mcp.WithDescription("List code scanning alerts in a GitHub repository."),
62+
mcp.WithDescription(t("TOOL_LIST_CODE_SCANNING_ALERTS_DESCRIPTION", "List code scanning alerts in a GitHub repository.")),
6263
mcp.WithString("owner",
6364
mcp.Required(),
6465
mcp.Description("The owner of the repository."),

pkg/github/code_scanning_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"testing"
88

9+
"github.com/github/github-mcp-server/pkg/translations"
910
"github.com/google/go-github/v69/github"
1011
"github.com/migueleliasweb/go-github-mock/src/mock"
1112
"github.com/stretchr/testify/assert"
@@ -15,7 +16,7 @@ import (
1516
func Test_GetCodeScanningAlert(t *testing.T) {
1617
// Verify tool definition once
1718
mockClient := github.NewClient(nil)
18-
tool, _ := getCodeScanningAlert(mockClient)
19+
tool, _ := getCodeScanningAlert(mockClient, translations.NullTranslationHelper)
1920

2021
assert.Equal(t, "get_code_scanning_alert", tool.Name)
2122
assert.NotEmpty(t, tool.Description)
@@ -81,7 +82,7 @@ func Test_GetCodeScanningAlert(t *testing.T) {
8182
t.Run(tc.name, func(t *testing.T) {
8283
// Setup client with mock
8384
client := github.NewClient(tc.mockedClient)
84-
_, handler := getCodeScanningAlert(client)
85+
_, handler := getCodeScanningAlert(client, translations.NullTranslationHelper)
8586

8687
// Create call request
8788
request := createMCPRequest(tc.requestArgs)
@@ -117,7 +118,7 @@ func Test_GetCodeScanningAlert(t *testing.T) {
117118
func Test_ListCodeScanningAlerts(t *testing.T) {
118119
// Verify tool definition once
119120
mockClient := github.NewClient(nil)
120-
tool, _ := listCodeScanningAlerts(mockClient)
121+
tool, _ := listCodeScanningAlerts(mockClient, translations.NullTranslationHelper)
121122

122123
assert.Equal(t, "list_code_scanning_alerts", tool.Name)
123124
assert.NotEmpty(t, tool.Description)
@@ -194,7 +195,7 @@ func Test_ListCodeScanningAlerts(t *testing.T) {
194195
t.Run(tc.name, func(t *testing.T) {
195196
// Setup client with mock
196197
client := github.NewClient(tc.mockedClient)
197-
_, handler := listCodeScanningAlerts(client)
198+
_, handler := listCodeScanningAlerts(client, translations.NullTranslationHelper)
198199

199200
// Create call request
200201
request := createMCPRequest(tc.requestArgs)

pkg/github/issues.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import (
88
"net/http"
99
"time"
1010

11+
"github.com/github/github-mcp-server/pkg/translations"
1112
"github.com/google/go-github/v69/github"
1213
"github.com/mark3labs/mcp-go/mcp"
1314
"github.com/mark3labs/mcp-go/server"
1415
)
1516

1617
// getIssue creates a tool to get details of a specific issue in a GitHub repository.
17-
func getIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
18+
func getIssue(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1819
return mcp.NewTool("get_issue",
19-
mcp.WithDescription("Get details of a specific issue in a GitHub repository."),
20+
mcp.WithDescription(t("TOOL_GET_ISSUE_DESCRIPTION", "Get details of a specific issue in a GitHub repository.")),
2021
mcp.WithString("owner",
2122
mcp.Required(),
2223
mcp.Description("The owner of the repository."),
@@ -59,9 +60,9 @@ func getIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerF
5960
}
6061

6162
// addIssueComment creates a tool to add a comment to an issue.
62-
func addIssueComment(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
63+
func addIssueComment(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
6364
return mcp.NewTool("add_issue_comment",
64-
mcp.WithDescription("Add a comment to an existing issue"),
65+
mcp.WithDescription(t("TOOL_ADD_ISSUE_COMMENT_DESCRIPTION", "Add a comment to an existing issue")),
6566
mcp.WithString("owner",
6667
mcp.Required(),
6768
mcp.Description("Repository owner"),
@@ -113,9 +114,9 @@ func addIssueComment(client *github.Client) (tool mcp.Tool, handler server.ToolH
113114
}
114115

115116
// searchIssues creates a tool to search for issues and pull requests.
116-
func searchIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
117+
func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
117118
return mcp.NewTool("search_issues",
118-
mcp.WithDescription("Search for issues and pull requests across GitHub repositories"),
119+
mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues and pull requests across GitHub repositories")),
119120
mcp.WithString("q",
120121
mcp.Required(),
121122
mcp.Description("Search query using GitHub issues search syntax"),
@@ -185,9 +186,9 @@ func searchIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHand
185186
}
186187

187188
// createIssue creates a tool to create a new issue in a GitHub repository.
188-
func createIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
189+
func createIssue(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
189190
return mcp.NewTool("create_issue",
190-
mcp.WithDescription("Create a new issue in a GitHub repository"),
191+
mcp.WithDescription(t("TOOL_CREATE_ISSUE_DESCRIPTION", "Create a new issue in a GitHub repository")),
191192
mcp.WithString("owner",
192193
mcp.Required(),
193194
mcp.Description("Repository owner"),
@@ -265,9 +266,9 @@ func createIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandl
265266
}
266267

267268
// listIssues creates a tool to list and filter repository issues
268-
func listIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
269+
func listIssues(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
269270
return mcp.NewTool("list_issues",
270-
mcp.WithDescription("List issues in a GitHub repository with filtering options"),
271+
mcp.WithDescription(t("TOOL_LIST_ISSUES_DESCRIPTION", "List issues in a GitHub repository with filtering options")),
271272
mcp.WithString("owner",
272273
mcp.Required(),
273274
mcp.Description("Repository owner"),
@@ -382,4 +383,4 @@ func parseISOTimestamp(timestamp string) (time.Time, error) {
382383

383384
// Return error with supported formats
384385
return time.Time{}, fmt.Errorf("invalid ISO 8601 timestamp: %s (supported formats: YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DD)", timestamp)
385-
}
386+
}

pkg/github/issues_test.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/github/github-mcp-server/pkg/translations"
1011
"github.com/google/go-github/v69/github"
1112
"github.com/mark3labs/mcp-go/mcp"
1213
"github.com/migueleliasweb/go-github-mock/src/mock"
@@ -17,7 +18,7 @@ import (
1718
func Test_GetIssue(t *testing.T) {
1819
// Verify tool definition once
1920
mockClient := github.NewClient(nil)
20-
tool, _ := getIssue(mockClient)
21+
tool, _ := getIssue(mockClient, translations.NullTranslationHelper)
2122

2223
assert.Equal(t, "get_issue", tool.Name)
2324
assert.NotEmpty(t, tool.Description)
@@ -81,7 +82,7 @@ func Test_GetIssue(t *testing.T) {
8182
t.Run(tc.name, func(t *testing.T) {
8283
// Setup client with mock
8384
client := github.NewClient(tc.mockedClient)
84-
_, handler := getIssue(client)
85+
_, handler := getIssue(client, translations.NullTranslationHelper)
8586

8687
// Create call request
8788
request := createMCPRequest(tc.requestArgs)
@@ -113,7 +114,7 @@ func Test_GetIssue(t *testing.T) {
113114
func Test_AddIssueComment(t *testing.T) {
114115
// Verify tool definition once
115116
mockClient := github.NewClient(nil)
116-
tool, _ := addIssueComment(mockClient)
117+
tool, _ := addIssueComment(mockClient, translations.NullTranslationHelper)
117118

118119
assert.Equal(t, "add_issue_comment", tool.Name)
119120
assert.NotEmpty(t, tool.Description)
@@ -184,7 +185,7 @@ func Test_AddIssueComment(t *testing.T) {
184185
t.Run(tc.name, func(t *testing.T) {
185186
// Setup client with mock
186187
client := github.NewClient(tc.mockedClient)
187-
_, handler := addIssueComment(client)
188+
_, handler := addIssueComment(client, translations.NullTranslationHelper)
188189

189190
// Create call request
190191
request := mcp.CallToolRequest{
@@ -229,7 +230,7 @@ func Test_AddIssueComment(t *testing.T) {
229230
func Test_SearchIssues(t *testing.T) {
230231
// Verify tool definition once
231232
mockClient := github.NewClient(nil)
232-
tool, _ := searchIssues(mockClient)
233+
tool, _ := searchIssues(mockClient, translations.NullTranslationHelper)
233234

234235
assert.Equal(t, "search_issues", tool.Name)
235236
assert.NotEmpty(t, tool.Description)
@@ -333,7 +334,7 @@ func Test_SearchIssues(t *testing.T) {
333334
t.Run(tc.name, func(t *testing.T) {
334335
// Setup client with mock
335336
client := github.NewClient(tc.mockedClient)
336-
_, handler := searchIssues(client)
337+
_, handler := searchIssues(client, translations.NullTranslationHelper)
337338

338339
// Create call request
339340
request := createMCPRequest(tc.requestArgs)
@@ -374,7 +375,7 @@ func Test_SearchIssues(t *testing.T) {
374375
func Test_CreateIssue(t *testing.T) {
375376
// Verify tool definition once
376377
mockClient := github.NewClient(nil)
377-
tool, _ := createIssue(mockClient)
378+
tool, _ := createIssue(mockClient, translations.NullTranslationHelper)
378379

379380
assert.Equal(t, "create_issue", tool.Name)
380381
assert.NotEmpty(t, tool.Description)
@@ -475,7 +476,7 @@ func Test_CreateIssue(t *testing.T) {
475476
t.Run(tc.name, func(t *testing.T) {
476477
// Setup client with mock
477478
client := github.NewClient(tc.mockedClient)
478-
_, handler := createIssue(client)
479+
_, handler := createIssue(client, translations.NullTranslationHelper)
479480

480481
// Create call request
481482
request := createMCPRequest(tc.requestArgs)
@@ -529,7 +530,7 @@ func Test_CreateIssue(t *testing.T) {
529530
func Test_ListIssues(t *testing.T) {
530531
// Verify tool definition
531532
mockClient := github.NewClient(nil)
532-
tool, _ := listIssues(mockClient)
533+
tool, _ := listIssues(mockClient, translations.NullTranslationHelper)
533534

534535
assert.Equal(t, "list_issues", tool.Name)
535536
assert.NotEmpty(t, tool.Description)
@@ -650,7 +651,7 @@ func Test_ListIssues(t *testing.T) {
650651
t.Run(tc.name, func(t *testing.T) {
651652
// Setup client with mock
652653
client := github.NewClient(tc.mockedClient)
653-
_, handler := listIssues(client)
654+
_, handler := listIssues(client, translations.NullTranslationHelper)
654655

655656
// Create call request
656657
request := createMCPRequest(tc.requestArgs)

0 commit comments

Comments
 (0)