Skip to content

feat: add a framework for translations #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 13 additions & 3 deletions cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
},
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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() {
Expand Down
9 changes: 5 additions & 4 deletions pkg/github/code_scanning.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand Down Expand Up @@ -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."),
Expand Down
9 changes: 5 additions & 4 deletions pkg/github/code_scanning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
23 changes: 12 additions & 11 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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)
}
}
21 changes: 11 additions & 10 deletions pkg/github/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading