From db93029cb0953c87ee831b6ab1d3369fc4ddf525 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Wed, 15 Apr 2026 12:44:37 +0100 Subject: [PATCH 1/3] feat(http): implement HeaderAllowedFeatureFlags for X-MCP-Features header validation --- pkg/github/tools.go | 10 ++++++++++ pkg/http/server.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index e5e950280..a63f7460d 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -145,7 +145,17 @@ var ( // When active, consolidated tools are replaced by single-purpose granular tools. FeatureFlagIssuesGranular = "issues_granular" FeatureFlagPullRequestsGranular = "pull_requests_granular" +) + +// HeaderAllowedFeatureFlags are the feature flags that clients may enable via the +// X-MCP-Features header. Only these flags are accepted from headers; unknown flags +// are silently ignored. +var HeaderAllowedFeatureFlags = []string{ + FeatureFlagIssuesGranular, + FeatureFlagPullRequestsGranular, +} +var ( // Remote-only toolsets - these are only available in the remote MCP server // but are documented here for consistency and to enable automated documentation. ToolsetMetadataCopilotSpaces = inventory.ToolsetMetadata{ diff --git a/pkg/http/server.go b/pkg/http/server.go index 38ea0de30..47533bc9a 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -27,7 +27,7 @@ import ( // knownFeatureFlags are the feature flags that can be enabled via X-MCP-Features header. // Only these flags are accepted from headers. -var knownFeatureFlags = []string{} +var knownFeatureFlags = github.HeaderAllowedFeatureFlags type ServerConfig struct { // Version of the server From 1bb1765a862563ad1f82ed45d353b984bdf1a923 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Wed, 15 Apr 2026 12:58:31 +0100 Subject: [PATCH 2/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/github/tools.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index a63f7460d..72c48aaa6 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -147,14 +147,19 @@ var ( FeatureFlagPullRequestsGranular = "pull_requests_granular" ) -// HeaderAllowedFeatureFlags are the feature flags that clients may enable via the +// headerAllowedFeatureFlags are the feature flags that clients may enable via the // X-MCP-Features header. Only these flags are accepted from headers; unknown flags // are silently ignored. -var HeaderAllowedFeatureFlags = []string{ +var headerAllowedFeatureFlags = []string{ FeatureFlagIssuesGranular, FeatureFlagPullRequestsGranular, } +// HeaderAllowedFeatureFlags returns the feature flags that clients may enable via +// the X-MCP-Features header. +func HeaderAllowedFeatureFlags() []string { + return slices.Clone(headerAllowedFeatureFlags) +} var ( // Remote-only toolsets - these are only available in the remote MCP server // but are documented here for consistency and to enable automated documentation. From e213579498bc4b8f689ec42b42877d73c5006c66 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Wed, 15 Apr 2026 13:00:52 +0100 Subject: [PATCH 3/3] feat(http): update knownFeatureFlags to use HeaderAllowedFeatureFlags() and add tests for feature flag validation --- pkg/github/tools.go | 1 + pkg/http/server.go | 2 +- pkg/http/server_test.go | 86 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 pkg/http/server_test.go diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 72c48aaa6..02b86a9d9 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -160,6 +160,7 @@ var headerAllowedFeatureFlags = []string{ func HeaderAllowedFeatureFlags() []string { return slices.Clone(headerAllowedFeatureFlags) } + var ( // Remote-only toolsets - these are only available in the remote MCP server // but are documented here for consistency and to enable automated documentation. diff --git a/pkg/http/server.go b/pkg/http/server.go index 47533bc9a..83586509b 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -27,7 +27,7 @@ import ( // knownFeatureFlags are the feature flags that can be enabled via X-MCP-Features header. // Only these flags are accepted from headers. -var knownFeatureFlags = github.HeaderAllowedFeatureFlags +var knownFeatureFlags = github.HeaderAllowedFeatureFlags() type ServerConfig struct { // Version of the server diff --git a/pkg/http/server_test.go b/pkg/http/server_test.go new file mode 100644 index 000000000..7aeabc582 --- /dev/null +++ b/pkg/http/server_test.go @@ -0,0 +1,86 @@ +package http + +import ( + "context" + "testing" + + ghcontext "github.com/github/github-mcp-server/pkg/context" + "github.com/github/github-mcp-server/pkg/github" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateHTTPFeatureChecker_Whitelist(t *testing.T) { + checker := createHTTPFeatureChecker() + + tests := []struct { + name string + flagName string + headerFeatures []string + wantEnabled bool + }{ + { + name: "whitelisted issues_granular flag accepted from header", + flagName: github.FeatureFlagIssuesGranular, + headerFeatures: []string{github.FeatureFlagIssuesGranular}, + wantEnabled: true, + }, + { + name: "whitelisted pull_requests_granular flag accepted from header", + flagName: github.FeatureFlagPullRequestsGranular, + headerFeatures: []string{github.FeatureFlagPullRequestsGranular}, + wantEnabled: true, + }, + { + name: "unknown flag in header is ignored", + flagName: "unknown_flag", + headerFeatures: []string{"unknown_flag"}, + wantEnabled: false, + }, + { + name: "whitelisted flag not in header returns false", + flagName: github.FeatureFlagIssuesGranular, + headerFeatures: nil, + wantEnabled: false, + }, + { + name: "whitelisted flag with different flag in header returns false", + flagName: github.FeatureFlagIssuesGranular, + headerFeatures: []string{github.FeatureFlagPullRequestsGranular}, + wantEnabled: false, + }, + { + name: "multiple whitelisted flags in header", + flagName: github.FeatureFlagIssuesGranular, + headerFeatures: []string{github.FeatureFlagIssuesGranular, github.FeatureFlagPullRequestsGranular}, + wantEnabled: true, + }, + { + name: "empty header features", + flagName: github.FeatureFlagIssuesGranular, + headerFeatures: []string{}, + wantEnabled: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if len(tt.headerFeatures) > 0 { + ctx = ghcontext.WithHeaderFeatures(ctx, tt.headerFeatures) + } + + enabled, err := checker(ctx, tt.flagName) + require.NoError(t, err) + assert.Equal(t, tt.wantEnabled, enabled) + }) + } +} + +func TestKnownFeatureFlagsMatchesHeaderAllowed(t *testing.T) { + // Ensure knownFeatureFlags stays in sync with HeaderAllowedFeatureFlags + allowed := github.HeaderAllowedFeatureFlags() + assert.Equal(t, allowed, knownFeatureFlags, + "knownFeatureFlags should match github.HeaderAllowedFeatureFlags()") + assert.NotEmpty(t, knownFeatureFlags, "knownFeatureFlags should not be empty") +}