-
Notifications
You must be signed in to change notification settings - Fork 4k
Migrate MCP Apps support from insiders mode to feature flag with insiders opt-in #2335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,56 @@ | ||
| package github | ||
|
|
||
| // MCPAppsFeatureFlag is the feature flag name for MCP Apps (interactive UI forms). | ||
| const MCPAppsFeatureFlag = "remote_mcp_ui_apps" | ||
|
|
||
| // AllowedFeatureFlags is the allowlist of feature flags that can be enabled | ||
| // by users via --features CLI flag or X-MCP-Features HTTP header. | ||
| // Only flags in this list are accepted; unknown flags are silently ignored. | ||
| // This is the single source of truth for which flags are user-controllable. | ||
| var AllowedFeatureFlags = []string{ | ||
| MCPAppsFeatureFlag, | ||
| FeatureFlagIssuesGranular, | ||
| FeatureFlagPullRequestsGranular, | ||
| } | ||
|
|
||
| // InsidersFeatureFlags is the list of feature flags that insiders mode enables. | ||
| // When insiders mode is active, all flags in this list are treated as enabled. | ||
| // This is the single source of truth for what "insiders" means in terms of | ||
| // feature flag expansion. | ||
| var InsidersFeatureFlags = []string{ | ||
| MCPAppsFeatureFlag, | ||
| } | ||
|
|
||
| // FeatureFlags defines runtime feature toggles that adjust tool behavior. | ||
| type FeatureFlags struct { | ||
| LockdownMode bool | ||
| InsidersMode bool | ||
| } | ||
|
|
||
| // ResolveFeatureFlags computes the effective set of enabled feature flags by: | ||
| // 1. Taking explicitly enabled features (from CLI flags or HTTP headers) | ||
| // 2. Adding insiders-expanded features when insiders mode is active | ||
| // 3. Validating all features against the AllowedFeatureFlags allowlist | ||
| // | ||
| // Returns a set (map) for O(1) lookup by the feature checker. | ||
| func ResolveFeatureFlags(enabledFeatures []string, insidersMode bool) map[string]bool { | ||
| allowed := make(map[string]bool, len(AllowedFeatureFlags)) | ||
| for _, f := range AllowedFeatureFlags { | ||
| allowed[f] = true | ||
| } | ||
|
|
||
| effective := make(map[string]bool) | ||
| for _, f := range enabledFeatures { | ||
| if allowed[f] { | ||
| effective[f] = true | ||
| } | ||
| } | ||
| if insidersMode { | ||
| for _, f := range InsidersFeatureFlags { | ||
| if allowed[f] { | ||
| effective[f] = true | ||
| } | ||
| } | ||
| } | ||
| return effective | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ import ( | |
| "net/http" | ||
| "os" | ||
| "os/signal" | ||
| "slices" | ||
| "syscall" | ||
| "time" | ||
|
|
||
|
|
@@ -25,10 +24,6 @@ import ( | |
| "github.com/go-chi/chi/v5" | ||
| ) | ||
|
|
||
| // 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() | ||
|
|
||
| type ServerConfig struct { | ||
| // Version of the server | ||
| Version string | ||
|
|
@@ -233,19 +228,14 @@ func initGlobalToolScopeMap(t translations.TranslationHelperFunc) error { | |
| return nil | ||
| } | ||
|
|
||
| // createHTTPFeatureChecker creates a feature checker that reads header features from context | ||
| // and validates them against the knownFeatureFlags whitelist | ||
| // createHTTPFeatureChecker creates a feature checker that resolves features | ||
| // per-request by reading header features and insiders mode from context, | ||
| // then validating against the centralized AllowedFeatureFlags allowlist. | ||
| func createHTTPFeatureChecker() inventory.FeatureFlagChecker { | ||
| // Pre-compute whitelist as set for O(1) lookup | ||
| knownSet := make(map[string]bool, len(knownFeatureFlags)) | ||
| for _, f := range knownFeatureFlags { | ||
| knownSet[f] = true | ||
| } | ||
|
|
||
| return func(ctx context.Context, flag string) (bool, error) { | ||
| if knownSet[flag] && slices.Contains(ghcontext.GetHeaderFeatures(ctx), flag) { | ||
| return true, nil | ||
| } | ||
| return false, nil | ||
| headerFeatures := ghcontext.GetHeaderFeatures(ctx) | ||
| insidersMode := ghcontext.IsInsidersMode(ctx) | ||
| effective := github.ResolveFeatureFlags(headerFeatures, insidersMode) | ||
| return effective[flag], nil | ||
| } | ||
|
Comment on lines
+231
to
240
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AllowedFeatureFlags is exported as a mutable slice variable. External callers can accidentally append/modify it, which can lead to hard-to-debug behavior changes and potential data races in concurrent programs. Consider making the underlying allowlist unexported and exposing an exported function that returns a cloned slice (similar to HeaderAllowedFeatureFlags), or otherwise ensuring callers cannot mutate the shared backing array.