Skip to content

Detect MCP requests that omit the optional transport headers - #130

Merged
deepakrajpal-one merged 1 commit into
masterfrom
fix/mcp-request-detection
Aug 25, 2026
Merged

deepakrajpal-one merged 1 commit into
masterfrom
fix/mcp-request-detection

Conversation

@akshat-trigunait

Copy link
Copy Markdown
Collaborator

The bug

MarketplaceAbilitiesTracking::is_mcp_request() recognised an MCP call by exactly two headers:

$is_mcp = ( ! empty( $_SERVER['HTTP_MCP_SESSION_ID'] ) || ! empty( $_SERVER['HTTP_MCP_PROTOCOL_VERSION'] ) );

Both are optional. Mcp-Session-Id exists only if the server issued one at initialize; MCP-Protocol-Version only entered the spec in the 2025-06-18 revision, and mcp-adapter's own validator documents that "A missing header is accepted".

ChatGPT's client sends neither. Captured from a real request to an Easy MCP AI endpoint:

"HTTP_ACCEPT":      "application/json, text/event-stream"
"HTTP_USER_AGENT":  "openai-mcp/1.0.0"
"HTTP_X_ORIGINAL_URL": "/wp-json/easy-mcp-ai/v1/mcp"
"HTTP_AUTHORIZATION": "Bearer wpmcp_oat_…"

It uses X-OpenAI-Session in place of Mcp-Session-Id. So record() returned early, the ability executed normally, and no event was ever emitted — silently. Gemini, which does send a header, worked fine, which is what made this look client-specific rather than like a detection bug.

The fix

Detection becomes a union of signals:

Signal Why
Mcp-Session-Id / MCP-Protocol-Version Authoritative when present. Unchanged.
Accept: text/event-stream The streamable-HTTP transport requires clients to accept both application/json and text/event-stream on POST — so it is present exactly where the optional headers are not.
Path resolves to an MCP endpoint Covers pretty REST permalinks, the ?rest_route= fallback, and X-Original-URL for proxies that rewrite REQUEST_URI (group.one's Varnish tier does).

It still fails closed. This is only consulted from wp_after_execute_ability, so the request is already known to be an ability execution; the only non-MCP callers there are our admin-ajax UI and the plain REST abilities route, and neither sends text/event-stream nor posts to an /mcp path. The onecom_abilities_is_mcp_request filter still overrides everything.

Verified

Exercised against the captured headers plus the paths that must not be treated as MCP:

ChatGPT (openai-mcp/1.0.0, from the log)   => MCP  ✓
Gemini-style (session header)              => MCP  ✓
Client sending only protocol header        => MCP  ✓
MCP via ?rest_route= (no pretty links)     => MCP  ✓
MCP via mcp-adapter pretty route           => MCP  ✓
--- negatives ---
Our admin UI (admin-ajax)                  => not MCP
Our marketplace REST route                 => not MCP
Plain admin page load                      => not MCP
WP-CLI / no headers at all                 => not MCP

Companion MR

Onecom_Abilities_Tracking in the plugin repo (wp-in/one.com-wp-plugin-onecom-themes-plugins) has the byte-identical check and the same bug — in fact the ability in the captured log (onecom/create-post) is one of its abilities, so this PR alone does not fix the reported case. A matching MR is raised there.

Notes

  • No version bump, so merging will not publish. A bump to 2.0.8-beta.3 (in both composer.json and Marketplace::VERSION) is needed to ship it.
  • CHANGELOG.md will conflict trivially with Capitalise the MCP ability Mixpanel event name #129 depending on merge order — both add to ## [Unreleased].
  • CI shows the pre-existing failures already red on master (missing frontend/package-lock.json; pre-existing PHPCS violations), unrelated to this change.

`is_mcp_request()` gated on `Mcp-Session-Id` or `MCP-Protocol-Version`. Both are
optional: the session header only exists if the server issued one at
`initialize`, and the protocol header only entered the spec in the 2025-06-18
revision (mcp-adapter's own validator treats it as absent-is-fine).

ChatGPT's client (`openai-mcp/1.0.0`) sends neither — it carries its own
`X-OpenAI-Session`. So `record()` returned early and every ability it executed
produced no Mixpanel event, silently, while clients that do send a header worked.
Confirmed from a captured request against an Easy MCP AI endpoint.

Widen detection to a union of signals:

- the two transport headers (authoritative when present)
- `Accept: text/event-stream` — the streamable-HTTP transport requires clients to
  accept both `application/json` and `text/event-stream` on POST, so it is
  present exactly where the headers above are not
- a request path resolving to an MCP endpoint, covering pretty REST permalinks,
  the `?rest_route=` fallback, and `X-Original-URL` for proxies that rewrite
  `REQUEST_URI`

Still fails closed: this is only consulted from `wp_after_execute_ability`, where
the only non-MCP callers are our admin-ajax UI and the plain REST abilities route
— neither sends `text/event-stream` nor posts to an `/mcp` path.
@deepakrajpal-one
deepakrajpal-one merged commit e880f1c into master Aug 25, 2026
2 of 11 checks passed
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