Repository navigation
Conversation
Resolved in-process requests carry a marked copy of the outer introspection context. Derive event token IDs from that context's grant ID, so deleting an aborted scope cannot make its nonce eligible for persistence. Ordinary requests keep their existing event identity. Assisted-by: AI Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every
POST /mcptool call introspects the caller's token twice, or three times for an expanded read: once on the way in, and again when the hosted route calls the resource server (RS) over a loopback HTTP request carrying the same bearer. That second call is also the MCP authorization spec's forbidden pass-through: an MCP server must not forward the token it received. A cross-request cache would remove the cost but breaks revocation, so this change keeps one introspection per outer request and no cache.For client (non-package) tokens, the hosted route now introspects once and runs each tool sub-call through the real
/v1Fastify route chain in process (fastify.inject), carrying the already-introspected context. The context lives in a per-request registry (server/internal-dispatch.ts) keyed by a 128-bit random nonce. The inner request sends that nonce as its bearer, never the user's token. The registry entry holds a clone of the context, is deleted when the request completes, throws or aborts, and is honored only for in-process calls (a real socket presenting the nonce goes through ordinary introspection and gets401 invalid_token). Expiry is re-checked on each dispatch, and event token ids come from the cloned context'sgrant_id, so the nonce is never persisted.vendor/mcp-server'sRsClientis unchanged. Package tokens keep their per-child path until the package binding lands.Measured on real routes, 300 samples per arm after warmup, alternating order, p50 in milliseconds (HTTP self-call, one-per-request memo, this change): MCP
schema10.6, 9.5, 8.6 with 2, 1, 1 introspections; MCPquery_records9.6, 8.6, 8.1 with 2, 1, 1. A memo alone captures most of the gain; this change adds about 0.5 to 1 ms and removes the pass-through, and it is the base for serving package tokens through package endpoints. Scripts, raw samples and counts:p1-measure.mdin the review notes.Tests cover exactly one introspection per call (two before), revocation seen on the next request, no user bearer on any inner request, nonce hygiene (never in logs, events, responses, or after abort at the first event write), forged and replayed nonces, concurrent isolation, and cleanup on abort before authentication. 493 tests pass, with the type check. An independent review by a different model ran four rounds; its findings (abort during introspection, a reusable marker header, an unrestricted client capability, a persisted nonce) are fixed, and the capability design was replaced by the nonce bearer. Not verified: the per-mount audience check (
/mcpversus/v1) is a separate change and its test is a marked todo; the vendortool-footprinttest still fails on its missing operator document.This PR is stacked on #382 (and so #381); retarget it after they merge.
Assisted-by: AI