diff --git a/go.mod b/go.mod index 1d8801f5a0..0e7a554ef7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/josephburnett/jd/v2 v2.5.0 github.com/lithammer/fuzzysearch v1.1.8 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/modelcontextprotocol/go-sdk v1.7.0-pre.3 + github.com/modelcontextprotocol/go-sdk v1.7.0 github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 diff --git a/go.sum b/go.sum index f6a655d510..0923851dbb 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8 github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/modelcontextprotocol/go-sdk v1.7.0-pre.3 h1:SEAY9IduDif4iApnZgpFkjFIdo3askSGZVbZIYyTy6I= -github.com/modelcontextprotocol/go-sdk v1.7.0-pre.3/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= diff --git a/pkg/github/server.go b/pkg/github/server.go index 43e0940017..b8f0197889 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -89,6 +89,18 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci Instructions: inv.Instructions(), Logger: cfg.Logger, CompletionHandler: CompletionsHandler(deps.GetClient), + // Advertise tools, prompts, and resources without list-changed + // notifications. The server has a static set of tools/prompts/resources + // and never mutates them at runtime, so it never emits list_changed + // notifications. Left unset, the SDK would infer listChanged:true from + // the presence of items and advertise a capability we don't support - + // which the 2026-07-28 spec (subscriptions/listen) makes stricter still. + // Explicitly declaring these keeps the advertised capabilities honest. + Capabilities: &mcp.ServerCapabilities{ + Tools: &mcp.ToolCapabilities{}, + Prompts: &mcp.PromptCapabilities{}, + Resources: &mcp.ResourceCapabilities{}, + }, } // Apply any additional server options diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index bc891fac1f..07cb63c85f 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/observability" "github.com/github/github-mcp-server/pkg/observability/metrics" @@ -191,6 +192,97 @@ func TestNewMCPServer_CreatesSuccessfully(t *testing.T) { // is already tested in pkg/github/*_test.go. } +// advertisedServerCapabilities connects an in-memory client to the given server +// and returns the capabilities the server advertised during initialization. +func advertisedServerCapabilities(t *testing.T, server *mcp.Server) *mcp.ServerCapabilities { + t.Helper() + + ctx := context.Background() + clientTransport, serverTransport := mcp.NewInMemoryTransports() + + serverSession, err := server.Connect(ctx, serverTransport, nil) + require.NoError(t, err, "expected server to connect") + t.Cleanup(func() { _ = serverSession.Close() }) + + client := mcp.NewClient(&mcp.Implementation{Name: "test-client", Version: "1.0.0"}, nil) + clientSession, err := client.Connect(ctx, clientTransport, nil) + require.NoError(t, err, "expected client to connect") + t.Cleanup(func() { _ = clientSession.Close() }) + + result := clientSession.InitializeResult() + require.NotNil(t, result, "expected an initialize result") + return result.Capabilities +} + +// TestNewMCPServer_AdvertisedCapabilities locks in the capability contract set by +// NewMCPServer: tools, prompts, and resources are advertised without list-changed +// notifications (the server has a static item set and never emits list_changed), +// the deprecated logging capability is not advertised, and the inferred +// completions capability is preserved. This is asserted for both the stdio path +// (full inventory, items present) and the HTTP path (inventory emptied for the +// discovery/initialize request), which share the same NewMCPServer entry point. +func TestNewMCPServer_AdvertisedCapabilities(t *testing.T) { + t.Parallel() + + cfg := MCPServerConfig{ + Version: "test", + Token: "test-token", + EnabledToolsets: []string{"context"}, + Translator: translations.NullTranslationHelper, + ContentWindowSize: 5000, + } + + deps := stubDeps{obsv: stubExporters()} + + fullInventory, err := NewInventory(cfg.Translator). + WithDeprecatedAliases(DeprecatedToolAliases). + WithToolsets(cfg.EnabledToolsets). + Build() + require.NoError(t, err, "expected inventory build to succeed") + + tests := []struct { + name string + inv *inventory.Inventory + }{ + { + name: "stdio path with registered items", + inv: fullInventory, + }, + { + // The HTTP handler registers only the items relevant to a request; + // for initialize/discover that is nothing, so capabilities must come + // from the explicit declaration rather than being inferred from items. + name: "http path with no registered items for discovery", + inv: fullInventory.ForMCPRequest(inventory.MCPMethodDiscover, ""), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + server, err := NewMCPServer(context.Background(), &cfg, deps, tt.inv) + require.NoError(t, err, "expected server creation to succeed") + + caps := advertisedServerCapabilities(t, server) + + require.NotNil(t, caps.Tools, "tools capability should be advertised") + assert.False(t, caps.Tools.ListChanged, "tools list-changed must not be advertised") + + require.NotNil(t, caps.Prompts, "prompts capability should be advertised") + assert.False(t, caps.Prompts.ListChanged, "prompts list-changed must not be advertised") + + require.NotNil(t, caps.Resources, "resources capability should be advertised") + assert.False(t, caps.Resources.ListChanged, "resources list-changed must not be advertised") + assert.False(t, caps.Resources.Subscribe, "resources subscribe must not be advertised") + + assert.NotNil(t, caps.Completions, "completions capability should be preserved") + // Intentionally asserting the deprecated logging capability is absent. + assert.Nil(t, caps.Logging, "deprecated logging capability should not be advertised") //nolint:staticcheck // SA1019: verifying the deprecated capability is not advertised + }) + } +} + // 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 diff --git a/pkg/http/handler.go b/pkg/http/handler.go index eca628a47b..94ee11db04 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -205,14 +205,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ContentWindowSize: h.config.ContentWindowSize, Logger: h.logger, RepoAccessTTL: h.config.RepoAccessCacheTTL, - // Explicitly set empty capabilities. inv.ForMCPRequest currently returns nothing for Initialize. + // Capabilities (no list-changed advertising) are set by NewMCPServer; + // here we only supply the remote-specific schema cache. ServerOptions: []github.MCPServerOption{ func(so *mcp.ServerOptions) { - so.Capabilities = &mcp.ServerCapabilities{ - Tools: &mcp.ToolCapabilities{}, - Resources: &mcp.ResourceCapabilities{}, - Prompts: &mcp.PromptCapabilities{}, - } so.SchemaCache = h.schemaCache }, }, diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 2bf5e86eae..b06ea0832d 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -24,8 +24,8 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v2.5.0/v2/LICENSE)) - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 4caa5f58b2..6773ff673e 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -24,8 +24,8 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v2.5.0/v2/LICENSE)) - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index a7164a2aad..47fc8f19e8 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -25,8 +25,8 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v2.5.0/v2/LICENSE)) - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0-pre.3/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.7.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE))