From e9d8a603f88e655738dc58092612ba4b007a9ec2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:57:27 +0000 Subject: [PATCH 1/3] Initial plan From 883579a6376c91d821bfbdeaa1c1fd9ad95d6a0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:41:39 +0000 Subject: [PATCH 2/3] Add configurable server name and title via env/flag Allows users running multiple GitHub MCP Server instances (e.g., for github.com and GitHub Enterprise Server) to override the server name and title in the MCP initialization response. - Add --server-name / GITHUB_SERVER_NAME flag+env to override name - Add --server-title / GITHUB_SERVER_TITLE flag+env to override title - Defaults remain "github-mcp-server" and "GitHub MCP Server" - Applies to both stdio and HTTP server modes - Add tests for default and custom name/title Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/main.go | 8 ++++ internal/ghmcp/server.go | 10 +++++ pkg/github/server.go | 23 ++++++++++-- pkg/github/server_test.go | 69 ++++++++++++++++++++++++++++++++++- pkg/http/handler.go | 2 + pkg/http/server.go | 8 ++++ 6 files changed, 115 insertions(+), 5 deletions(-) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 05c2c6e0b..220e8e3fb 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -80,6 +80,8 @@ var ( ttl := viper.GetDuration("repo-access-cache-ttl") stdioServerConfig := ghmcp.StdioServerConfig{ Version: version, + Name: viper.GetString("server-name"), + Title: viper.GetString("server-title"), Host: viper.GetString("host"), Token: token, EnabledToolsets: enabledToolsets, @@ -108,6 +110,8 @@ var ( ttl := viper.GetDuration("repo-access-cache-ttl") httpConfig := ghhttp.ServerConfig{ Version: version, + Name: viper.GetString("server-name"), + Title: viper.GetString("server-title"), Host: viper.GetString("host"), Port: viper.GetInt("port"), BaseURL: viper.GetString("base-url"), @@ -133,6 +137,8 @@ func init() { rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n") // Add global flags that will be shared by all commands + rootCmd.PersistentFlags().String("server-name", "", "Override the server name in the MCP initialization response") + rootCmd.PersistentFlags().String("server-title", "", "Override the server title in the MCP initialization response") rootCmd.PersistentFlags().StringSlice("toolsets", nil, github.GenerateToolsetsHelp()) rootCmd.PersistentFlags().StringSlice("tools", nil, "Comma-separated list of specific tools to enable") rootCmd.PersistentFlags().StringSlice("exclude-tools", nil, "Comma-separated list of tool names to disable regardless of other settings") @@ -155,6 +161,8 @@ func init() { httpCmd.Flags().Bool("scope-challenge", false, "Enable OAuth scope challenge responses") // Bind flag to viper + _ = viper.BindPFlag("server-name", rootCmd.PersistentFlags().Lookup("server-name")) + _ = viper.BindPFlag("server-title", rootCmd.PersistentFlags().Lookup("server-title")) _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) _ = viper.BindPFlag("tools", rootCmd.PersistentFlags().Lookup("tools")) _ = viper.BindPFlag("exclude_tools", rootCmd.PersistentFlags().Lookup("exclude-tools")) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 5c4e7f6f1..a987171dd 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -171,6 +171,14 @@ type StdioServerConfig struct { // Version of the server Version string + // Name overrides the server name in the MCP initialization response. + // If empty, defaults to "github-mcp-server". + Name string + + // Title overrides the server title in the MCP initialization response. + // If empty, defaults to "GitHub MCP Server". + Title string + // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string @@ -266,6 +274,8 @@ func RunStdioServer(cfg StdioServerConfig) error { ghServer, err := NewStdioMCPServer(ctx, github.MCPServerConfig{ Version: cfg.Version, + Name: cfg.Name, + Title: cfg.Title, Host: cfg.Host, Token: cfg.Token, EnabledToolsets: cfg.EnabledToolsets, diff --git a/pkg/github/server.go b/pkg/github/server.go index 06c12575d..97c201a28 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -20,6 +20,14 @@ type MCPServerConfig struct { // Version of the server Version string + // Name overrides the server name in the MCP initialization response. + // If empty, defaults to "github-mcp-server". + Name string + + // Title overrides the server title in the MCP initialization response. + // If empty, defaults to "GitHub MCP Server". + Title string + // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string @@ -101,7 +109,7 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci } } - ghServer := NewServer(cfg.Version, serverOpts) + ghServer := NewServer(cfg.Version, cfg.Name, cfg.Title, serverOpts) // Add middlewares. Order matters - for example, the error context middleware should be applied last so that it runs FIRST (closest to the handler) to ensure all errors are captured, // and any middleware that needs to read or modify the context should be before it. @@ -177,15 +185,22 @@ func addGitHubAPIErrorToContext(next mcp.MethodHandler) mcp.MethodHandler { } // NewServer creates a new GitHub MCP server with the specified GH client and logger. -func NewServer(version string, opts *mcp.ServerOptions) *mcp.Server { +func NewServer(version, name, title string, opts *mcp.ServerOptions) *mcp.Server { if opts == nil { opts = &mcp.ServerOptions{} } + if name == "" { + name = "github-mcp-server" + } + if title == "" { + title = "GitHub MCP Server" + } + // Create a new MCP server s := mcp.NewServer(&mcp.Implementation{ - Name: "github-mcp-server", - Title: "GitHub MCP Server", + Name: name, + Title: title, Version: version, Icons: octicons.Icons("mark-github"), }, opts) diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index 2b99cab12..768049f50 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -13,6 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" gogithub "github.com/google/go-github/v82/github" + "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -150,7 +151,73 @@ func TestNewMCPServer_CreatesSuccessfully(t *testing.T) { // is already tested in pkg/github/*_test.go. } -// TestResolveEnabledToolsets verifies the toolset resolution logic. +// TestNewServer_NameAndTitle verifies that the server name and title can be +// overridden and fall back to sensible defaults when empty. +func TestNewServer_NameAndTitle(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + serverName string + serverTitle string + expectedName string + expectedTitle string + }{ + { + name: "defaults when empty", + serverName: "", + serverTitle: "", + expectedName: "github-mcp-server", + expectedTitle: "GitHub MCP Server", + }, + { + name: "custom name and title", + serverName: "my-github-server", + serverTitle: "My GitHub MCP Server", + expectedName: "my-github-server", + expectedTitle: "My GitHub MCP Server", + }, + { + name: "custom name only", + serverName: "ghes-server", + serverTitle: "", + expectedName: "ghes-server", + expectedTitle: "GitHub MCP Server", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + srv := NewServer("v1.0.0", tt.serverName, tt.serverTitle, nil) + require.NotNil(t, srv) + + // Connect a client to retrieve the initialize result and verify ServerInfo. + st, ct := mcp.NewInMemoryTransports() + client := mcp.NewClient(&mcp.Implementation{Name: "test-client"}, nil) + + var initResult *mcp.InitializeResult + go func() { + cs, err := client.Connect(context.Background(), ct, nil) + if err == nil { + initResult = cs.InitializeResult() + } + }() + + _, err := srv.Connect(context.Background(), st, nil) + require.NoError(t, err) + + // Give the goroutine time to complete + // (In-memory transport is synchronous, so this is safe) + require.Eventually(t, func() bool { return initResult != nil }, time.Second, 10*time.Millisecond) + require.NotNil(t, initResult.ServerInfo) + assert.Equal(t, tt.expectedName, initResult.ServerInfo.Name) + assert.Equal(t, tt.expectedTitle, initResult.ServerInfo.Title) + }) + } +} + func TestResolveEnabledToolsets(t *testing.T) { t.Parallel() diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 2e828211d..e8506f75b 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -201,6 +201,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ghServer, err := h.githubMcpServerFactory(r, h.deps, invToUse, &github.MCPServerConfig{ Version: h.config.Version, + Name: h.config.Name, + Title: h.config.Title, Translator: h.t, ContentWindowSize: h.config.ContentWindowSize, Logger: h.logger, diff --git a/pkg/http/server.go b/pkg/http/server.go index 872303940..37ccbad2c 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -31,6 +31,14 @@ type ServerConfig struct { // Version of the server Version string + // Name overrides the server name in the MCP initialization response. + // If empty, defaults to "github-mcp-server". + Name string + + // Title overrides the server title in the MCP initialization response. + // If empty, defaults to "GitHub MCP Server". + Title string + // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string From d417caa541c8d6d1229331dbf7128f843b2edd3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:50:06 +0000 Subject: [PATCH 3/3] Use translation strings for server name/title override Instead of new CLI flags (--server-name, --server-title), reuse the existing string override mechanism that already supports tool title/ description overrides throughout the codebase. Users can now configure the server name and title via: - GITHUB_MCP_SERVER_NAME / GITHUB_MCP_SERVER_TITLE env vars - "SERVER_NAME" / "SERVER_TITLE" keys in github-mcp-server-config.json This is consistent with how all other user-visible strings are overridden (e.g. GITHUB_MCP_TOOL_GET_ME_USER_TITLE). No new struct fields or CLI flags are needed. Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/main.go | 8 ----- internal/ghmcp/server.go | 10 ------ pkg/github/server.go | 10 +----- pkg/github/server_test.go | 67 ++++++++++++++++++++++------------- pkg/http/handler.go | 2 -- pkg/http/server.go | 8 ----- 6 files changed, 44 insertions(+), 61 deletions(-) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 220e8e3fb..05c2c6e0b 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -80,8 +80,6 @@ var ( ttl := viper.GetDuration("repo-access-cache-ttl") stdioServerConfig := ghmcp.StdioServerConfig{ Version: version, - Name: viper.GetString("server-name"), - Title: viper.GetString("server-title"), Host: viper.GetString("host"), Token: token, EnabledToolsets: enabledToolsets, @@ -110,8 +108,6 @@ var ( ttl := viper.GetDuration("repo-access-cache-ttl") httpConfig := ghhttp.ServerConfig{ Version: version, - Name: viper.GetString("server-name"), - Title: viper.GetString("server-title"), Host: viper.GetString("host"), Port: viper.GetInt("port"), BaseURL: viper.GetString("base-url"), @@ -137,8 +133,6 @@ func init() { rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n") // Add global flags that will be shared by all commands - rootCmd.PersistentFlags().String("server-name", "", "Override the server name in the MCP initialization response") - rootCmd.PersistentFlags().String("server-title", "", "Override the server title in the MCP initialization response") rootCmd.PersistentFlags().StringSlice("toolsets", nil, github.GenerateToolsetsHelp()) rootCmd.PersistentFlags().StringSlice("tools", nil, "Comma-separated list of specific tools to enable") rootCmd.PersistentFlags().StringSlice("exclude-tools", nil, "Comma-separated list of tool names to disable regardless of other settings") @@ -161,8 +155,6 @@ func init() { httpCmd.Flags().Bool("scope-challenge", false, "Enable OAuth scope challenge responses") // Bind flag to viper - _ = viper.BindPFlag("server-name", rootCmd.PersistentFlags().Lookup("server-name")) - _ = viper.BindPFlag("server-title", rootCmd.PersistentFlags().Lookup("server-title")) _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) _ = viper.BindPFlag("tools", rootCmd.PersistentFlags().Lookup("tools")) _ = viper.BindPFlag("exclude_tools", rootCmd.PersistentFlags().Lookup("exclude-tools")) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index a987171dd..5c4e7f6f1 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -171,14 +171,6 @@ type StdioServerConfig struct { // Version of the server Version string - // Name overrides the server name in the MCP initialization response. - // If empty, defaults to "github-mcp-server". - Name string - - // Title overrides the server title in the MCP initialization response. - // If empty, defaults to "GitHub MCP Server". - Title string - // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string @@ -274,8 +266,6 @@ func RunStdioServer(cfg StdioServerConfig) error { ghServer, err := NewStdioMCPServer(ctx, github.MCPServerConfig{ Version: cfg.Version, - Name: cfg.Name, - Title: cfg.Title, Host: cfg.Host, Token: cfg.Token, EnabledToolsets: cfg.EnabledToolsets, diff --git a/pkg/github/server.go b/pkg/github/server.go index 97c201a28..16ba6c951 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -20,14 +20,6 @@ type MCPServerConfig struct { // Version of the server Version string - // Name overrides the server name in the MCP initialization response. - // If empty, defaults to "github-mcp-server". - Name string - - // Title overrides the server title in the MCP initialization response. - // If empty, defaults to "GitHub MCP Server". - Title string - // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string @@ -109,7 +101,7 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci } } - ghServer := NewServer(cfg.Version, cfg.Name, cfg.Title, serverOpts) + ghServer := NewServer(cfg.Version, cfg.Translator("SERVER_NAME", "github-mcp-server"), cfg.Translator("SERVER_TITLE", "GitHub MCP Server"), serverOpts) // Add middlewares. Order matters - for example, the error context middleware should be applied last so that it runs FIRST (closest to the handler) to ensure all errors are captured, // and any middleware that needs to read or modify the context should be before it. diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index 768049f50..c815d5fa0 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -151,36 +151,48 @@ func TestNewMCPServer_CreatesSuccessfully(t *testing.T) { // is already tested in pkg/github/*_test.go. } -// TestNewServer_NameAndTitle verifies that the server name and title can be -// overridden and fall back to sensible defaults when empty. -func TestNewServer_NameAndTitle(t *testing.T) { +// TestNewServer_NameAndTitleViaTranslation verifies that server name and title +// can be overridden via the translation helper (GITHUB_MCP_SERVER_NAME / +// GITHUB_MCP_SERVER_TITLE env vars or github-mcp-server-config.json) and +// fall back to sensible defaults when not overridden. +func TestNewServer_NameAndTitleViaTranslation(t *testing.T) { t.Parallel() tests := []struct { name string - serverName string - serverTitle string + translator translations.TranslationHelperFunc expectedName string expectedTitle string }{ { - name: "defaults when empty", - serverName: "", - serverTitle: "", + name: "defaults when using NullTranslationHelper", + translator: translations.NullTranslationHelper, expectedName: "github-mcp-server", expectedTitle: "GitHub MCP Server", }, { - name: "custom name and title", - serverName: "my-github-server", - serverTitle: "My GitHub MCP Server", + name: "custom name and title via translator", + translator: func(key, defaultValue string) string { + switch key { + case "SERVER_NAME": + return "my-github-server" + case "SERVER_TITLE": + return "My GitHub MCP Server" + default: + return defaultValue + } + }, expectedName: "my-github-server", expectedTitle: "My GitHub MCP Server", }, { - name: "custom name only", - serverName: "ghes-server", - serverTitle: "", + name: "custom name only via translator", + translator: func(key, defaultValue string) string { + if key == "SERVER_NAME" { + return "ghes-server" + } + return defaultValue + }, expectedName: "ghes-server", expectedTitle: "GitHub MCP Server", }, @@ -190,34 +202,41 @@ func TestNewServer_NameAndTitle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - srv := NewServer("v1.0.0", tt.serverName, tt.serverTitle, nil) + srv := NewServer("v1.0.0", tt.translator("SERVER_NAME", "github-mcp-server"), tt.translator("SERVER_TITLE", "GitHub MCP Server"), nil) require.NotNil(t, srv) // Connect a client to retrieve the initialize result and verify ServerInfo. st, ct := mcp.NewInMemoryTransports() client := mcp.NewClient(&mcp.Implementation{Name: "test-client"}, nil) - var initResult *mcp.InitializeResult + type clientResult struct { + result *mcp.InitializeResult + err error + } + clientResultCh := make(chan clientResult, 1) go func() { cs, err := client.Connect(context.Background(), ct, nil) - if err == nil { - initResult = cs.InitializeResult() + if err != nil { + clientResultCh <- clientResult{err: err} + return } + clientResultCh <- clientResult{result: cs.InitializeResult()} }() _, err := srv.Connect(context.Background(), st, nil) require.NoError(t, err) - // Give the goroutine time to complete - // (In-memory transport is synchronous, so this is safe) - require.Eventually(t, func() bool { return initResult != nil }, time.Second, 10*time.Millisecond) - require.NotNil(t, initResult.ServerInfo) - assert.Equal(t, tt.expectedName, initResult.ServerInfo.Name) - assert.Equal(t, tt.expectedTitle, initResult.ServerInfo.Title) + got := <-clientResultCh + require.NoError(t, got.err) + require.NotNil(t, got.result) + require.NotNil(t, got.result.ServerInfo) + assert.Equal(t, tt.expectedName, got.result.ServerInfo.Name) + assert.Equal(t, tt.expectedTitle, got.result.ServerInfo.Title) }) } } +// TestResolveEnabledToolsets verifies the toolset resolution logic. func TestResolveEnabledToolsets(t *testing.T) { t.Parallel() diff --git a/pkg/http/handler.go b/pkg/http/handler.go index e8506f75b..2e828211d 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -201,8 +201,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ghServer, err := h.githubMcpServerFactory(r, h.deps, invToUse, &github.MCPServerConfig{ Version: h.config.Version, - Name: h.config.Name, - Title: h.config.Title, Translator: h.t, ContentWindowSize: h.config.ContentWindowSize, Logger: h.logger, diff --git a/pkg/http/server.go b/pkg/http/server.go index 37ccbad2c..872303940 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -31,14 +31,6 @@ type ServerConfig struct { // Version of the server Version string - // Name overrides the server name in the MCP initialization response. - // If empty, defaults to "github-mcp-server". - Name string - - // Title overrides the server title in the MCP initialization response. - // If empty, defaults to "GitHub MCP Server". - Title string - // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) Host string