Skip to content
Open
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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,34 @@ set the following environment variable:
export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description"
```

### Overriding Server Name and Title

The same override mechanism can be used to customize the MCP server's `name` and
`title` fields in the initialization response. This is useful when running
multiple GitHub MCP Server instances (e.g., one for github.com and one for
GitHub Enterprise Server) so that agents can distinguish between them.

| Key | Environment Variable | Default |
|-----|---------------------|---------|
| `SERVER_NAME` | `GITHUB_MCP_SERVER_NAME` | `github-mcp-server` |
| `SERVER_TITLE` | `GITHUB_MCP_SERVER_TITLE` | `GitHub MCP Server` |

For example, to configure a server instance for GitHub Enterprise Server:

```json
{
"SERVER_NAME": "ghes-mcp-server",
"SERVER_TITLE": "GHES MCP Server"
}
```

Or using environment variables:

```sh
export GITHUB_MCP_SERVER_NAME="ghes-mcp-server"
export GITHUB_MCP_SERVER_TITLE="GHES MCP Server"
```

## Library Usage

The exported Go API of this module should currently be considered unstable, and subject to breaking changes. In the future, we may offer stability; please file an issue if there is a use case where this would be valuable.
Expand Down
1 change: 1 addition & 0 deletions docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ We currently support the following ways in which the GitHub MCP Server can be co
| Lockdown Mode | `X-MCP-Lockdown` header | `--lockdown-mode` flag or `GITHUB_LOCKDOWN_MODE` env var |
| Insiders Mode | `X-MCP-Insiders` header or `/insiders` URL | `--insiders` flag or `GITHUB_INSIDERS` env var |
| Scope Filtering | Always enabled | Always enabled |
| Server Name/Title | Not available | `GITHUB_MCP_SERVER_NAME` / `GITHUB_MCP_SERVER_TITLE` env vars or `github-mcp-server-config.json` |

> **Default behavior:** If you don't specify any configuration, the server uses the **default toolsets**: `context`, `issues`, `pull_requests`, `repos`, `users`.

Expand Down
19 changes: 14 additions & 5 deletions pkg/github/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci
}
}

ghServer := NewServer(cfg.Version, 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.
Expand Down Expand Up @@ -176,16 +176,25 @@ 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 {
// NewServer creates a new GitHub MCP server with the given version, server
// name, display title, and options. If name or title are empty the defaults
// "github-mcp-server" and "GitHub MCP Server" are used.
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)
Expand Down
88 changes: 88 additions & 0 deletions pkg/github/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -150,6 +151,93 @@ func TestNewMCPServer_CreatesSuccessfully(t *testing.T) {
// is already tested in pkg/github/*_test.go.
}

// 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
translator translations.TranslationHelperFunc
expectedName string
expectedTitle string
}{
{
name: "defaults when using NullTranslationHelper",
translator: translations.NullTranslationHelper,
expectedName: "github-mcp-server",
expectedTitle: "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 via translator",
translator: func(key, defaultValue string) string {
if key == "SERVER_NAME" {
return "ghes-server"
}
return defaultValue
},
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.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)

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 {
clientResultCh <- clientResult{err: err}
return
}
t.Cleanup(func() { _ = cs.Close() })
clientResultCh <- clientResult{result: cs.InitializeResult()}
}()

ss, err := srv.Connect(context.Background(), st, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = ss.Close() })

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()
Expand Down
Loading