FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Enable feature flags via URL query parameter (?features=) for headerless hosted connections by CAOShurong · Pull Request #3146 · github/github-mcp-server · GitHub

Enable feature flags via URL query parameter (?features=) for headerless hosted connections - #3146

Open
CAOShurong wants to merge 1 commit into
github:mainfrom
CAOShurong:codex/3145-feature-flags-url-param
Open

Enable feature flags via URL query parameter (?features=) for headerless hosted connections#3146
CAOShurong wants to merge 1 commit into
github:mainfrom
CAOShurong:codex/3145-feature-flags-url-param

Conversation

Copy link
Copy Markdown

Closes #3145

Problem

Feature flags can only be enabled per request through the X-MCP-Features header (or statically via --features / GITHUB_FEATURES). Clients that compose the server URL on the user's behalf — hosted IDEs, agent platforms, harnesses that provision the MCP connection — cannot set custom headers, so every flagged tool is unreachable on those connections.

Solution

Accept a features URL query parameter as an additional channel:

https://api.githubcopilot.com/mcp/x/issues?features=issue_dependencies
  • URL wins over header when both are present, matching how the toolset path segments take precedence over their headers.
  • Validation unchanged: ResolveFeatureFlags still filters every user-supplied flag against AllowedFeatureFlags, so a URL-supplied flag is no more privileged than a header-supplied one — it's the same allowlist arriving through a different channel. Unknown flags are silently ignored exactly as before.
  • Composes freely with existing URL selectors (/x/{toolset}, /readonly, /insiders).

The gate itself stays intact — flagged tools remain out of the default surface, so fixed tool-schema cost doesn't change; this only widens how a user can opt in.

Changes

  • pkg/http/middleware/request_config.go: read ?features= first, fall back to the X-MCP-Features header
  • pkg/http/middleware/request_config_test.go: new unit tests (precedence, fallback, coexistence with other params)
  • pkg/http/handler_test.go: 4 route-level cases (end-to-end enablement, precedence over header, combination with toolset path)
  • docs/feature-flags.md, docs/server-configuration.md: document the new method in the enabling tables

Testing

  • go test ./pkg/http/... — all green, including the new cases:
    • TestWithRequestConfigFeatureFlags (6 subtests)
    • TestHTTPHandlerRoutes/features_query_parameter_* (4 subtests)
  • go build ./..., go vet ./pkg/http/... clean
  • golangci-lint run on touched packages reports zero issues in changed files (the 6 pre-existing gosec hits in token_test.go are identical on unmodified main)

CAOShurong requested a review from a team as a code owner August 24, 2026 09:54

SamMorrowDrums left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The query-string option makes sense for clients that cannot set headers, but I do not think this is release-ready yet because it breaks OAuth protected-resource metadata discovery for query-bearing MCP URLs.

For a server URL such as https://api.githubcopilot.com/mcp/x/issues?features=issue_dependencies, go-sdk v1.7.0 treats the full URL as the resource identifier and validates metadata.resource with exact string equality. Its path-based and root discovery candidates also preserve the query. Today BuildResourceMetadataURL and AuthHandler.buildResourceURL omit r.URL.RawQuery, so the challenge and returned metadata lose ?features=...; standards-compliant clients can reject the metadata as belonging to a different resource.

This should be fixable here: preserve the feature query consistently in both the advertised resource_metadata URL and the metadata document’s resource, then extend TestOAuthChallengeMetadataRouteContracts (or an equivalent client round-trip test) to cover a query-bearing MCP URL and both supported discovery routes. Please also make the header-selection rule below presence-based so routing/query and header features are never combined or used as fallbacks for one another.

Comment thread pkg/http/middleware/request_config.go Outdated
// when it cannot control headers. Unknown flags are dropped later by
// ResolveFeatureFlags against AllowedFeatureFlags, so this channel is
// no more privileged than the header — it is the same allowlist.
features := headers.ParseCommaSeparated(r.URL.Query().Get(queryParamFeatures))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Headers need full precedence here. Please check whether X-MCP-Features is present before reading the query parameter; if it is present, parse only that header and never calculate or fall back to query features. Presence—not parsed length—is important, so an explicitly empty, whitespace-only, or unknown-valued header must still suppress ?features=. Please invert the precedence and add those cases to this test matrix.

Review on github#3146 identified that a query-bearing MCP server URL breaks
OAuth protected-resource metadata discovery: go-sdk v1.7.0 validates
metadata.resource with exact string equality against the full server
URL, but BuildResourceMetadataURL and buildResourceURL dropped the
request's RawQuery, so clients connecting to e.g.

  /mcp/x/issues?features=issue_dependencies

received challenge and metadata URLs without the query and could reject
the metadata as belonging to a different resource (RFC 9728).

- Preserve r.URL.RawQuery in both the advertised resource_metadata URL
  and the metadata document's resource via a shared AppendQuery helper.
- Make feature selection presence-based: the features query parameter
  and the X-MCP-Features header are separate channels that are never
  combined; query wins when both are present.
- Extend TestOAuthChallengeMetadataRouteContracts with a query-bearing
  MCP URL round-trip (challenge URL + metadata.resource exact match).
- Add TestWithRequestConfigFeatureSelection covering all four channel
  combinations, plus unit tests for query preservation in
  TestBuildResourceMetadataURL.
CAOShurong force-pushed the codex/3145-feature-flags-url-param branch from 76eddd5 to 81d4230 Compare August 24, 2026 15:07

Copy link
Copy Markdown
Author

Rebased onto current main (8898db9) and pushed both requested changes as 81d4230:

1. Query preservation in OAuth protected-resource metadata. BuildResourceMetadataURL and AuthHandler.buildResourceURL now append r.URL.RawQuery, via a shared AppendQuery helper, so the advertised resource_metadata URL and the metadata document's resource both carry the exact same query as the MCP server URL — satisfying go-sdk's exact string-equality check for query-bearing URLs like /mcp/x/issues?features=issue_dependencies.

2. Presence-based feature selection. The features query parameter and X-MCP-Features are now separate channels that are never combined or used as fallbacks for one another (except that an empty ?features= value falls back to the header). Query wins when both are present.

Tests added:

  • TestOAuthChallengeMetadataRouteContracts/x/repos?features=issue_dependencies — full round-trip: challenge's resource_metadata URL exact-matches, then GET-ing it returns metadata whose resource equals https://mcp.example.com/mcp/x/repos?features=issue_dependencies.
  • TestWithRequestConfigFeatureSelection — all four channel combinations plus empty-value fallback.
  • TestBuildResourceMetadataURL gains two query-preservation cases.

Verification on this head: go build ./... exit 0, go vet ./pkg/http/... clean, full go test ./pkg/http/... -count=1 green (handler/middleware/oauth/transport all pass), gofmt clean. Docs updated in docs/feature-flags.md and docs/server-configuration.md.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

Feature flags are header-only, making flagged tools unreachable on managed/hosted MCP connections

2 participants


Back | FazBrowse Home | New Git URL