Skip to content

Migrate MCP Apps support from insiders mode to feature flag with insiders opt-in#2335

Open
SamMorrowDrums wants to merge 2 commits intomainfrom
sammorrowdrums/matt-mcp-apps-feature-flag-rebase
Open

Migrate MCP Apps support from insiders mode to feature flag with insiders opt-in#2335
SamMorrowDrums wants to merge 2 commits intomainfrom
sammorrowdrums/matt-mcp-apps-feature-flag-rebase

Conversation

@SamMorrowDrums
Copy link
Copy Markdown
Collaborator

Summary

Rebases #2282 onto main (post-#2332) and unifies the feature flag allowlists into a single source of truth. MCP Apps is now controlled by the remote_mcp_ui_apps feature flag rather than a hardcoded insiders mode check.

Why

Closes https://github.com/github/copilot-mcp-core/issues/1471

What changed

Feature flag centralization (pkg/github/feature_flags.go):

  • AllowedFeatureFlags — single source of truth for all user-controllable flags (MCPApps + granular)
  • InsidersFeatureFlags — only MCPAppsFeatureFlag (granular flags are NOT insiders features)
  • ResolveFeatureFlags() — resolves explicit features + insiders expansion against the allowlist
  • MCPAppsFeatureFlag constant

Unified allowlist (pkg/github/tools.go):

  • HeaderAllowedFeatureFlags() now delegates to AllowedFeatureFlags instead of maintaining a separate list

Builder refactor (pkg/inventory/builder.go):

  • Removed WithInsidersMode() — replaced by feature checker
  • Build() uses checkFeatureFlag(mcpAppsFeatureFlag) to decide UI metadata stripping
  • Renamed stripInsidersFeaturesstripMCPAppsMetadata, stripInsidersMetaFromToolstripMetaKeys

Server changes:

  • internal/ghmcp/server.go: createFeatureChecker now takes insidersMode and uses ResolveFeatureFlags
  • pkg/http/server.go: createHTTPFeatureChecker uses ResolveFeatureFlags per-request
  • pkg/http/handler.go: Removed WithInsidersMode calls from inventory builders

Tool changes:

  • issues.go, pullrequests.go: Check MCPAppsFeatureFlag via IsFeatureEnabled instead of InsidersMode
  • Removed InsidersOnly field from ServerTool

Exported API for remote server:

  • AllowedFeatureFlags, HeaderAllowedFeatureFlags(), MCPAppsFeatureFlag, ResolveFeatureFlags() are all exported
  • Remote server (github/github-mcp-server-remote#801) can use HeaderAllowedFeatureFlags() as source of truth instead of hardcoding its own allowlist

MCP impact

  • Tool schema or behavior changed — MCP Apps UI gating now uses feature flag instead of insiders mode

Lint & tests

  • Linted locally with ./script/lint
  • Tested locally with ./script/test

Docs

  • Updated (docs/server-configuration.md — MCP Apps feature flag section)

SamMorrowDrums and others added 2 commits April 15, 2026 17:54
Rebase PR #2282 onto main (post-#2332) and unify feature flag
allowlists into a single source of truth.

- Add MCPAppsFeatureFlag, AllowedFeatureFlags, InsidersFeatureFlags,
  and ResolveFeatureFlags in feature_flags.go
- AllowedFeatureFlags includes all user-controllable flags (MCP Apps +
  granular), InsidersFeatureFlags only includes MCPAppsFeatureFlag
- HeaderAllowedFeatureFlags() now delegates to AllowedFeatureFlags
- Builder uses feature checker instead of insidersMode bool
- Remove InsidersOnly field from ServerTool and WithInsidersMode from
  Builder
- HTTP feature checker uses ResolveFeatureFlags for per-request
  resolution with insiders expansion
- Tool handlers check MCPAppsFeatureFlag via IsFeatureEnabled instead
  of InsidersMode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums SamMorrowDrums marked this pull request as ready for review April 15, 2026 20:59
@SamMorrowDrums SamMorrowDrums requested a review from a team as a code owner April 15, 2026 20:59
Copilot AI review requested due to automatic review settings April 15, 2026 20:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates MCP Apps enablement from a hardcoded “insiders mode” gate to a centralized feature-flag model (remote_mcp_ui_apps), and unifies the allowlists used across HTTP headers and CLI/server behavior.

Changes:

  • Centralizes user-controllable feature flags and insiders-mode expansion via AllowedFeatureFlags, InsidersFeatureFlags, and ResolveFeatureFlags().
  • Refactors inventory building/metadata stripping to use feature-flag checks instead of a dedicated insiders-mode builder option.
  • Updates HTTP and stdio servers to use the centralized feature resolution, and updates docs/tests accordingly.
Show a summary per file
File Description
pkg/inventory/server_tool.go Removes InsidersOnly tool-level gating from the inventory model.
pkg/inventory/builder.go Replaces WithInsidersMode with MCP Apps feature-flag based UI metadata stripping helpers.
pkg/inventory/registry_test.go Updates tests to reflect MCP Apps feature-flag behavior and metadata stripping helpers.
pkg/http/server.go Switches HTTP feature checking to use centralized ResolveFeatureFlags() per request.
pkg/http/server_test.go Updates tests for feature flag behavior, including MCP Apps and insiders expansion.
pkg/http/handler.go Removes static WithInsidersMode usage; relies on feature checker + builder behavior.
pkg/http/handler_test.go Updates static inventory construction to no longer pass insiders mode into the builder.
pkg/github/feature_flags.go Introduces centralized allowlist + insiders expansion + ResolveFeatureFlags().
pkg/github/feature_flags_test.go Adds tests for ResolveFeatureFlags() behavior (filtering/dedup/insiders).
pkg/github/tools.go Makes HeaderAllowedFeatureFlags() delegate to the centralized allowlist.
internal/ghmcp/server.go Uses centralized feature resolution for stdio server and MCP Apps UI resource registration.
docs/server-configuration.md Documents MCP Apps as a feature flag and adds feature flags to the config matrix.

Copilot's findings

  • Files reviewed: 12/12 changed files
  • Comments generated: 3

Comment thread pkg/inventory/builder.go
Comment on lines +217 to 223
// When MCP Apps feature flag is not enabled, strip UI metadata from tools
// so clients won't attempt to load UI resources.
// The feature checker is the single source of truth for flag evaluation.
if !b.checkFeatureFlag(mcpAppsFeatureFlag) {
tools = stripMCPAppsMetadata(tools)
}

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder.Build() strips MCP Apps UI metadata based on b.checkFeatureFlag(), but that helper calls the feature checker with context.Background(). In HTTP mode the feature checker derives flags from request context (X-MCP-Features / insiders), so this build-time check will always evaluate as disabled and permanently strip UI metadata even when the request enables remote_mcp_ui_apps. To support per-request feature flags, move the UI metadata stripping to runtime (e.g., in Inventory.AvailableTools using the provided ctx), or change the builder API so Build can accept/propagate the request context when evaluating feature flags.

Suggested change
// When MCP Apps feature flag is not enabled, strip UI metadata from tools
// so clients won't attempt to load UI resources.
// The feature checker is the single source of truth for flag evaluation.
if !b.checkFeatureFlag(mcpAppsFeatureFlag) {
tools = stripMCPAppsMetadata(tools)
}

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +14
// 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,
}
Copy link

Copilot AI Apr 15, 2026

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.

Copilot uses AI. Check for mistakes.
Comment thread pkg/http/server.go
Comment on lines +231 to 240
// 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
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createHTTPFeatureChecker calls ResolveFeatureFlags on every feature check, rebuilding maps each time. Inventory filtering can invoke the checker many times per request (once per feature-flagged tool), so this adds avoidable per-request overhead. Consider computing the effective feature set once per request (e.g., in middleware when parsing headers, or by caching within the checker using a request-scoped value) and doing O(1) lookups from that cached set.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants