Skip to content

Hosted MCP: deterministic keyless and account endpoints - #308

Merged
hmishra2250 merged 14 commits into
mainfrom
feature/hosted-mcp-resend-parity-runtime
Jul 24, 2026
Merged

Hosted MCP: deterministic keyless and account endpoints#308
hmishra2250 merged 14 commits into
mainfrom
feature/hosted-mcp-resend-parity-runtime

Conversation

@hmishra2250

@hmishra2250 hmishra2250 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Splits the hosted MCP onboarding surface into deterministic keyless-first and account-connected identities without breaking existing clients.

Endpoint behavior

/v2/mcp

  • anonymous initialize and tools/list succeed without opening OAuth;
  • anonymous sessions expose Search, Scrape, and Parse;
  • account-only tools return structured recovery rather than silently failing;
  • valid API keys and compatible existing OAuth tokens receive the full tool surface;
  • invalid credentials never downgrade to anonymous keyless access.

/v2/mcp-oauth

  • anonymous requests receive the path-scoped OAuth challenge;
  • valid API keys and account OAuth tokens receive the full tool surface;
  • existing /v2/mcp OAuth audiences are accepted only through the explicit legacy bridge;
  • new /v2/mcp-oauth tokens cannot replay against /v2/mcp.

The existing search-profile process remains owned by the keyless/full process; the OAuth process does not start a second search listener.

Runtime and safety changes

  • exact audience and credential-purpose enforcement;
  • fail-closed OAuth introspection with no keyless fallback during outages;
  • short-lived delegated Core credentials for OAuth sessions;
  • session credentials are used consistently across tool implementations, including monitor tools;
  • invalid credentials hide tools and return structured recovery on both the primary and six-tool search surfaces;
  • API-key-in-path compatibility remains available on the full identity, with nginx access logging disabled on credential-bearing routes;
  • legacy /mcp aliases remain intentionally bound to /v2/mcp; ingress routes only /v2/mcp-oauth to the account deployment, so a misroute fails closed rather than changing OAuth identity;
  • metadata-only action events preserve exact string API-key identifiers;
  • readiness fails when required OAuth delegation configuration is absent;
  • unsupported endpoint/profile configuration fails closed at startup.

Dependencies and rollout

DB #216, Core #3973, and Web #2767 deploy before this runtime is publicly routed. Infra #303 provides the separate process, routing, image, canary, and rollback boundary.

Verification

  • final MCP suite: 31/31 tests passed;
  • final GitHub build check passed;
  • package, Docker, nginx ordering, keyless, OAuth, recovery, audience, and legacy-route checks passed locally;
  • the complete local Stage 1 composition verified 3 keyless tools, 26 authenticated tools, the existing six-tool search profile, bidirectional audience isolation, invalid-credential recovery, legacy bridging on/off, key-in-path compatibility, and immediate revocation;
  • real Scrape calls passed keylessly, with API keys on both identities, and through OAuth delegation;
  • a live monitor call proved the session credential was used instead of a hostile shared environment API key;
  • captured nginx and application logs contained no tested credentials.

Real deployed-client checks remain a release gate. Nothing in this PR merges or deploys automatically.

@hmishra2250 hmishra2250 changed the title Hosted MCP Resend-parity runtime hardening OAuth-first hosted MCP runtime hardening Jul 10, 2026
@hmishra2250
hmishra2250 force-pushed the feature/hosted-mcp-resend-parity-runtime branch from 0cf0442 to 3f2ac9f Compare July 11, 2026 21:16
@hmishra2250 hmishra2250 changed the title OAuth-first hosted MCP runtime hardening Hosted MCP: deterministic keyless and account endpoints Jul 11, 2026
Add the hosted Streamable HTTP MCP runtime with OAuth token introspection, API-key fallback, anonymous keyless sessions, and safe request identity handling.

Constrain keyless sessions to the scrape/search/parse boundary, derive account-tool recovery and requires_auth metadata from the registered tool complement, keep account-only tools behind authenticated sessions, and emit safe action metadata without logging request payloads or secrets.

Preserve anonymous /v2/mcp behavior while allowing invalid presented credentials to fail with a clear invalid-token challenge.
Document the hosted MCP endpoints and configure the service image for the hosted HTTP transport.

Add nginx routing for keyless /v2/mcp, account /v2/mcp-oauth, well-known OAuth metadata, deprecated API-key MCP compatibility, legacy /mcp compatibility, readiness, and health paths.

Document account connector setup on /v2/mcp-oauth, keyless/local usage on /v2/mcp, and headless API-key headers.
Add contract and smoke coverage for anonymous keyless /v2/mcp, exact keyless tool listing, registered account-tool recovery metadata, stale hidden tool calls, OAuth audience enforcement, account-door /v2/mcp-oauth behavior, and structured recovery payloads.

Record local client evidence proving keyless /v2/mcp initialization does not trigger automatic browser OAuth.

Cover nginx route ordering, service image config, and CI workflow expectations so deployability remains testable through the service image.
Upgrade CI and image publishing gates to Node 22 with frozen pnpm installs, lint, typecheck, manifest validation, full tests, hosted auth replay coverage, and Docker service-image smoke tests.

Keep image publishing tied to same-SHA verification before mutable tags are pushed, and make Docker smoke reusable locally through scripts/docker-smoke.sh and npm run test:docker-smoke.

Update package and MCP registry metadata for the hosted MCP release.
@hmishra2250
hmishra2250 force-pushed the feature/hosted-mcp-resend-parity-runtime branch from 3f2ac9f to 968497a Compare July 11, 2026 21:49
@hmishra2250

Copy link
Copy Markdown
Collaborator Author

Stage 1 local E2E follow-up at d970dfd59a1b4691ab5f5b52d3ce92e36295fef8:

  • Real keyless Parse exposed that the examples recommended zeroDataRetention: true, while anonymous preview teams cannot use ZDR.
  • The MCP now rejects that option before creating an upload and returns KEYLESS_OPTION_NOT_AVAILABLE with a direct omit-or-connect remedy.
  • Account/API-key sessions remain unchanged; a live API-key check confirmed the ZDR option is preserved for Core/team eligibility enforcement.
  • The keyless examples and privacy copy now describe the actual contract.

Verification:

  • npm run typecheck — pass
  • npm run lint — pass
  • npm test — 67/67 pass
  • npm run test:docker-smoke — pass
  • GitHub verify and docker-smoke — pass
  • Full wired local DB + Core + Web + separate keyless/OAuth MCP run — 17/17 scenarios pass, plus targeted live checks for keyless ZDR rejection and API-key ZDR preservation

No PR was merged or deployed. The draft remains for human review; staging E2E is still required before release.

@hmishra2250

Copy link
Copy Markdown
Collaborator Author

Independent-review follow-up is closed at 43c8455b1552d31038e89a4cf1ea2452e0cab411:

  • ZDR keyless recovery now includes the server-generated request_id.
  • Regression coverage now exercises both keyless Parse phases and proves neither reaches upload/parse upstream work.
  • Authenticated coverage proves API-key Parse preserves ZDR in both the continuation call and Core request.

Final evidence on this head:

  • typecheck and lint pass
  • 68/68 MCP tests pass
  • Docker service smoke passes
  • GitHub verify and docker-smoke pass
  • fresh full wired local DB/Core/Web + separate keyless/OAuth MCP rerun: 17/17 pass
  • additional live checks: both keyless ZDR phases return the specific option error with non-spoofable request IDs; API-key ZDR remains preserved

All Stage 1 PRs remain open drafts. Nothing was merged or deployed. Human review and staging E2E remain mandatory.

@hmishra2250
hmishra2250 marked this pull request as ready for review July 18, 2026 11:45

@erikengervall erikengervall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall looks good. Approving with two non-blocking findings:

  1. Search profile skips the tool wrapper (src/index.ts:3208-3224)

    The primary FastMCP instance wraps server.addTool so invalid credentials receive an empty tools/list and structured recovery on tools/call. The search sidecar uses a fresh searchServer without that wrapper. Because authenticateRequest can succeed with credentialError, /v2/mcp-search may expose its full tool catalog to revoked or malformed keys.

  2. Nginx rewrites ignore the configured OAuth endpoint (docker/nginx.conf:121-134)

    The legacy /mcp and /{apiKey}/v2/mcp routes always rewrite to /v2/mcp. Account deployments configure FASTMCP_ENDPOINT=/v2/mcp-oauth and only bind there, so legacy URLs reaching an account pod can return 404 instead of reaching the OAuth or API-key handler.

@hmishra2250

Copy link
Copy Markdown
Collaborator Author

Addressed the review at 1d1558b:

  1. The search sidecar now uses the same hosted credential guard as the primary server. Invalid credentials receive an empty tools/list and structured CREDENTIAL_INVALID recovery, with regression tests proving no upstream search call occurs.
  2. The legacy nginx rewrite remains intentionally bound to /v2/mcp. Infra fix: use flat MCP arguments in tool usage examples #303 routes legacy aliases only to the full deployment and routes only /v2/mcp-oauth to the account deployment. Rewriting aliases dynamically on the account pod would change OAuth resource identity; a misroute now remains fail-closed. I added an explicit nginx contract comment and regression test.

Verification: lint passed, build passed, and the full MCP suite passed 31/31. Nothing was merged or deployed.

@hmishra2250

Copy link
Copy Markdown
Collaborator Author

Security follow-up coordinated with Core #3973: delegated fcmcp_ credentials now use a dedicated MCP_DELEGATED_CREDENTIAL_SECRET instead of reusing KEYLESS_PROXY_SECRET. Hosted readiness fails closed unless both secrets are present.

Fresh validation: build and all 31 MCP tests pass. Production must provision the same 32+ character delegation secret to Core and both hosted MCP processes before rollout.

@hmishra2250
hmishra2250 merged commit 7232b6d into main Jul 24, 2026
1 check 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