From c64896667341be14a29d3f12f682a41881f1684d Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 11 Feb 2026 08:32:39 +0100 Subject: [PATCH 1/4] Add shared SchemaCache for streamable-http server Create a shared mcp.SchemaCache in RunHTTPServer and pass it through to each per-request MCP Server via ServerOptions. This avoids repeated JSON schema reflection and resolution when a new Server is created for every request in stateless mode, matching the pattern used by the remote server. --- pkg/http/handler.go | 12 ++++++++++++ pkg/http/server.go | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/http/handler.go b/pkg/http/handler.go index df0b819fc..4e1fbb143 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -17,6 +17,14 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" ) +// WithSchemaCache sets a shared SchemaCache for the handler. +// This avoids repeated schema reflection when a new MCP Server is created per request. +func WithSchemaCache(cache *mcp.SchemaCache) HandlerOption { + return func(o *HandlerOptions) { + o.SchemaCache = cache + } +} + type InventoryFactoryFunc func(r *http.Request) (*inventory.Inventory, error) type GitHubMCPServerFactoryFunc func(r *http.Request, deps github.ToolDependencies, inventory *inventory.Inventory, cfg *github.MCPServerConfig) (*mcp.Server, error) @@ -31,6 +39,7 @@ type Handler struct { inventoryFactoryFunc InventoryFactoryFunc oauthCfg *oauth.Config scopeFetcher scopes.FetcherInterface + schemaCache *mcp.SchemaCache } type HandlerOptions struct { @@ -39,6 +48,7 @@ type HandlerOptions struct { OAuthConfig *oauth.Config ScopeFetcher scopes.FetcherInterface FeatureChecker inventory.FeatureFlagChecker + SchemaCache *mcp.SchemaCache } type HandlerOption func(*HandlerOptions) @@ -112,6 +122,7 @@ func NewHTTPMcpHandler( inventoryFactoryFunc: inventoryFactory, oauthCfg: opts.OAuthConfig, scopeFetcher: scopeFetcher, + schemaCache: opts.SchemaCache, } } @@ -195,6 +206,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Resources: &mcp.ResourceCapabilities{}, Prompts: &mcp.PromptCapabilities{}, } + so.SchemaCache = h.schemaCache }, }, }) diff --git a/pkg/http/server.go b/pkg/http/server.go index 7397e54a8..058edaa3c 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -21,6 +21,7 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-chi/chi/v5" + "github.com/modelcontextprotocol/go-sdk/mcp" ) // knownFeatureFlags are the feature flags that can be enabled via X-MCP-Features header. @@ -134,8 +135,12 @@ func RunHTTPServer(cfg ServerConfig) error { serverOptions = append(serverOptions, WithScopeFetcher(scopeFetcher)) } + // Create a shared schema cache to avoid repeated JSON schema reflection + // when a new MCP Server is created per request in stateless mode. + schemaCache := mcp.NewSchemaCache() + r := chi.NewRouter() - handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(serverOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...) + handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(serverOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg), WithSchemaCache(schemaCache))...) oauthHandler, err := oauth.NewAuthHandler(oauthCfg) if err != nil { return fmt.Errorf("failed to create OAuth handler: %w", err) From 1d2ad778b80fd83071abb3b9efe3065f27ecfa8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:50:48 +0000 Subject: [PATCH 2/4] Fix lint CI: use Go 1.25 to match golangci-lint v2.5.0 Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a1647446f..e77593285 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: - go-version: stable + go-version: '1.25' - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: From 487cf0b7f0c2e1fbfc372966b1504e3ca3c22cdd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:22:25 +0000 Subject: [PATCH 3/4] Co-locate WithSchemaCache with other With functions Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- pkg/http/handler.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 4e1fbb143..c45929a20 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -17,14 +17,6 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" ) -// WithSchemaCache sets a shared SchemaCache for the handler. -// This avoids repeated schema reflection when a new MCP Server is created per request. -func WithSchemaCache(cache *mcp.SchemaCache) HandlerOption { - return func(o *HandlerOptions) { - o.SchemaCache = cache - } -} - type InventoryFactoryFunc func(r *http.Request) (*inventory.Inventory, error) type GitHubMCPServerFactoryFunc func(r *http.Request, deps github.ToolDependencies, inventory *inventory.Inventory, cfg *github.MCPServerConfig) (*mcp.Server, error) @@ -83,6 +75,14 @@ func WithFeatureChecker(checker inventory.FeatureFlagChecker) HandlerOption { } } +// WithSchemaCache sets a shared SchemaCache for the handler. +// This avoids repeated schema reflection when a new MCP Server is created per request. +func WithSchemaCache(cache *mcp.SchemaCache) HandlerOption { + return func(o *HandlerOptions) { + o.SchemaCache = cache + } +} + func NewHTTPMcpHandler( ctx context.Context, cfg *ServerConfig, From 0e71f57bb576ca030520ea8e7bc02cae762fa0bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:56:24 +0000 Subject: [PATCH 4/4] Make schema cache an opinionated default for HTTP handlers Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- pkg/http/handler.go | 15 +++++---------- pkg/http/server.go | 7 +------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/pkg/http/handler.go b/pkg/http/handler.go index c45929a20..875d54bbb 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -40,7 +40,6 @@ type HandlerOptions struct { OAuthConfig *oauth.Config ScopeFetcher scopes.FetcherInterface FeatureChecker inventory.FeatureFlagChecker - SchemaCache *mcp.SchemaCache } type HandlerOption func(*HandlerOptions) @@ -75,14 +74,6 @@ func WithFeatureChecker(checker inventory.FeatureFlagChecker) HandlerOption { } } -// WithSchemaCache sets a shared SchemaCache for the handler. -// This avoids repeated schema reflection when a new MCP Server is created per request. -func WithSchemaCache(cache *mcp.SchemaCache) HandlerOption { - return func(o *HandlerOptions) { - o.SchemaCache = cache - } -} - func NewHTTPMcpHandler( ctx context.Context, cfg *ServerConfig, @@ -111,6 +102,10 @@ func NewHTTPMcpHandler( inventoryFactory = DefaultInventoryFactory(cfg, t, opts.FeatureChecker, scopeFetcher) } + // Create a shared schema cache to avoid repeated JSON schema reflection + // when a new MCP Server is created per request in stateless mode. + schemaCache := mcp.NewSchemaCache() + return &Handler{ ctx: ctx, config: cfg, @@ -122,7 +117,7 @@ func NewHTTPMcpHandler( inventoryFactoryFunc: inventoryFactory, oauthCfg: opts.OAuthConfig, scopeFetcher: scopeFetcher, - schemaCache: opts.SchemaCache, + schemaCache: schemaCache, } } diff --git a/pkg/http/server.go b/pkg/http/server.go index 058edaa3c..7397e54a8 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -21,7 +21,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-chi/chi/v5" - "github.com/modelcontextprotocol/go-sdk/mcp" ) // knownFeatureFlags are the feature flags that can be enabled via X-MCP-Features header. @@ -135,12 +134,8 @@ func RunHTTPServer(cfg ServerConfig) error { serverOptions = append(serverOptions, WithScopeFetcher(scopeFetcher)) } - // Create a shared schema cache to avoid repeated JSON schema reflection - // when a new MCP Server is created per request in stateless mode. - schemaCache := mcp.NewSchemaCache() - r := chi.NewRouter() - handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(serverOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg), WithSchemaCache(schemaCache))...) + handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(serverOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...) oauthHandler, err := oauth.NewAuthHandler(oauthCfg) if err != nil { return fmt.Errorf("failed to create OAuth handler: %w", err)