Skip to content

fix(mcp-apps): defer _meta.ui strip to per-request RegisterTools#2446

Merged
mattdholloway merged 3 commits intomainfrom
fix/mcp-apps-meta-stripped-on-http
May 8, 2026
Merged

fix(mcp-apps): defer _meta.ui strip to per-request RegisterTools#2446
mattdholloway merged 3 commits intomainfrom
fix/mcp-apps-meta-stripped-on-http

Conversation

@mattdholloway
Copy link
Copy Markdown
Contributor

Problem

The MCP Apps _meta.ui strip lives in Builder.Build(), which calls checkFeatureFlag(context.Background()). The HTTP feature checker (createHTTPFeatureChecker) reads insiders mode from the request context — a background context never has it set, so the FF reports MCP Apps off and the strip runs eagerly at server startup. Per-request inventory factories then serve pre-stripped tools regardless of whether the request actually arrived on the /insiders route.

Symptom

github/github-mcp-server-remote returns 0 tools with _meta.ui over HTTP /insiders, despite the source unconditionally setting it on get_me, issue_write, and create_pull_request. VS Code only renders MCP App UIs because of its persistent tool cache from earlier deploys; new clients see no UIs at all.

Reproduction (10 lines, no remote dependency)

go build -o /tmp/ghmcp ./cmd/github-mcp-server
GITHUB_PERSONAL_ACCESS_TOKEN=$TOK GITHUB_INSIDERS=true /tmp/ghmcp http --insiders &
# initialize then tools/list against http://localhost:8082/insiders
# → 41 tools, 0 with _meta (broken)

# Same binary, stdio:
GITHUB_PERSONAL_ACCESS_TOKEN=$TOK GITHUB_INSIDERS=true /tmp/ghmcp stdio --insiders
# → 41 tools, 3 with _meta (works — confirms the bug is HTTP-specific)

Fix

Drop the strip from Build(). Apply it in RegisterTools(ctx, …) where the per-request context is in scope and the HTTP feature checker can correctly detect insiders mode (or the remote checker can correctly read user identity for Statsig flag lookup).

The same root cause affects github/github-mcp-server-remote — its featureflags.NewComposedFeatureFlagChecker reads requestctx.User(ctx), which background context lacks, so the remote_mcp_ui_apps Statsig flag always returned false. The fix here covers both downstreams since RegisterTools is the single entry point for tool registration.

Stdio is unaffected: it uses a closure-captured insiders mode flag (createFeatureChecker) that doesn't depend on context, and the per-request strip in RegisterTools produces the same outcome.

Verification

End-to-end against the deployed remote tool definitions:

Mode Route _meta.ui count Notes
HTTP /insiders 3 was 0 — fixed
HTTP / 0 correct (no insiders)
stdio --insiders 3 unchanged
stdio 0 correct

Tools that re-appear with _meta.ui over HTTP /insiders after the fix:
get_me, issue_write, create_pull_request.

Tests

  • pkg/http: new TestInsidersRoutePreservesUIMeta pins the regression — verifies the FF check returns true on a context with insiders mode set, and false on a bare context.
  • pkg/inventory: existing TestWithMCPApps_DisabledStripsUIMetadata and TestWithMCPApps_UIOnlyMetaBecomesNil updated to use the new RegisterTools-as-strip-site contract via a captureRegisteredTools helper.

Full go test ./... passes.

mattdholloway and others added 2 commits May 8, 2026 16:20
The MCP Apps `_meta.ui` strip lived in `Builder.Build()`, which calls
`checkFeatureFlag(context.Background())`. The HTTP feature checker
(`createHTTPFeatureChecker`) reads insiders mode from the request
context — a background context never has it set, so the FF reported
MCP Apps off and the strip ran eagerly at server startup. Per-request
inventory factories then served pre-stripped tools regardless of
whether the request actually arrived on the `/insiders` route.

Symptom: `github/github-mcp-server-remote` returns 0 tools with
`_meta.ui` over HTTP `/insiders`, despite the source unconditionally
setting it on `get_me`, `issue_write`, and `create_pull_request`.
VS Code only renders MCP App UIs because of its persistent tool cache
from earlier deploys. Reproducible locally with
`cmd/github-mcp-server http --insiders` plus a vanilla curl tools/list.

Fix: drop the strip from `Build()`. Apply it in `RegisterTools(ctx,…)`
where the per-request context is in scope and the HTTP feature checker
can correctly detect insiders mode (or the remote checker can correctly
read user identity for Statsig flag lookup).

The same root cause affects `github/github-mcp-server-remote` — its
`featureflags.NewComposedFeatureFlagChecker` reads
`requestctx.User(ctx)`, which background context lacks, so the
`remote_mcp_ui_apps` Statsig flag always returned false. The fix here
covers both downstreams since `RegisterTools` is the single entry
point for tool registration.

Stdio mode is unaffected: it uses a closure-captured insiders mode
flag (`createFeatureChecker`) that does not depend on context, and the
per-request strip in `RegisterTools` produces the same outcome.

Verified end-to-end against the deployed remote tool definitions:

  HTTP /insiders     → 3 tools with _meta.ui (was 0)
  HTTP /             → 0 tools with _meta.ui (correct)
  stdio --insiders   → 3 tools with _meta.ui (unchanged)
  stdio              → 0 tools with _meta.ui (correct)

Adds:
  - pkg/http: TestInsidersRoutePreservesUIMeta — pins the regression
  - pkg/inventory: updates the existing strip tests to use the new
    RegisterTools-as-strip-site contract via a captureRegisteredTools
    helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattdholloway mattdholloway marked this pull request as ready for review May 8, 2026 15:26
@mattdholloway mattdholloway requested a review from a team as a code owner May 8, 2026 15:26
Copilot AI review requested due to automatic review settings May 8, 2026 15:26
- Reorder captureRegisteredTools params to put context.Context first
- Remove dead Builder.checkFeatureFlag (was only called by Build's
  former MCP Apps strip, now done in RegisterTools via the Inventory
  receiver's checkFeatureFlag instead)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattdholloway mattdholloway self-assigned this May 8, 2026
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 fixes an HTTP-only regression where MCP Apps UI metadata (_meta.ui) was being stripped at server startup due to feature flag evaluation running against context.Background(), causing /insiders requests to incorrectly receive tools without UI metadata.

Changes:

  • Moved _meta.ui stripping from Builder.Build() to Inventory.RegisterTools(ctx, ...) so the per-request context is used for feature-flag evaluation.
  • Updated inventory tests to validate “wire-visible” tool metadata at registration time.
  • Added an HTTP regression test targeting the /insiders context/feature-flag behavior.
Show a summary per file
File Description
pkg/inventory/registry.go Applies MCP Apps UI meta stripping during per-request tool registration instead of at build time.
pkg/inventory/builder.go Removes build-time stripping so inventories aren’t pre-stripped at startup.
pkg/inventory/registry_test.go Updates tests to validate registration-time stripping and adds a helper to inspect registered tool definitions.
pkg/http/handler_test.go Adds a regression test for the insiders-route feature-flag behavior related to _meta.ui.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment thread pkg/inventory/registry_test.go
Comment thread pkg/http/handler_test.go
@mattdholloway mattdholloway merged commit 0e2fc38 into main May 8, 2026
18 checks passed
@mattdholloway mattdholloway deleted the fix/mcp-apps-meta-stripped-on-http branch May 8, 2026 16:08
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.

3 participants