MCP Hosting Pivot — Self-host doc-search (replacing AgentCore Runtime)
Status: DECISION LOCKED (red-teamed 2026-06-26, session 21). Self-host is the decision; Gateway is the rejected fallback. Build next session, Milestone-0 smoke gated. Forces + closes the long-open "self-host vs Gateway" question in
projects/autri/epics/mcp-auth-server(Open questions; Wave-2 "decide in build"). Ladders to the MCP-as-infrastructure wedge (D56) and North Star B3/B6.
The forcing function — direct AgentCore Runtime is blocked for MCP auth
W1-S8 chased one wall all session: every gateway-forwarded MCP request reaches our container header-less, so the container 401s. This session confirmed the root cause is structural, not a config slip:
- The
requestHeaderConfigurationallowlist (["Authorization", "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Token"]) is present and correct on the live runtime — verified viaget-agent-runtime. Misconfiguration ruled out. - A brand-new freshly-CREATED runtime (
autri_mcp_doc_search_v2, created 00:29:59Z) shows the identical strip:[as-diag] hasAuthHeader=false ... headers=[accept,content-length,content-type,host], with zerohasAuthHeader=truelines ever. The gateway authorizer validates the token before forwarding, so every logged line is an already-authenticated request that arrived with the token gone. Kills the CREATE-vs-UPDATE hypothesis. - Research verdict (AWS docs): the header-allowlist +
context.request_headersmechanism is documented only for HTTP-protocol runtimes. MCP-protocol runtimes have nocontextobject and no documented header forwarding; our symptom matches the unresolved AWS samples issue #1030.GetWorkloadAccessTokenFor*are outbound identity (Token Vault), not the inbound caller'ssub.
Conclusion: there is no documented, working way for an MCP-protocol AgentCore Runtime container to read the caller's identity. Our in-container per-user authz design cannot run on direct AgentCore Runtime. Option A is dead.
Decision (LOCKED) — self-host doc-search on Lambda Function URL + CloudFront
Retire the AgentCore Runtime for MCP. Host @autri/mcp-doc-search on a Lambda Function URL (InvokeMode: BUFFERED) behind CloudFront at mcp.autri.ai, cloning the live autri-auth-server stack. Inbound auth still routes through our AS (mcp-oauth.autri.ai); the container reads Authorization directly and runs the existing AutriAsAuth validation. This is option C.
Why C over B (AgentCore Gateway) — Gateway REJECTED
| C — Self-host (Lambda+CloudFront) — CHOSEN | B — AgentCore Gateway + interceptor Lambda — REJECTED | |
|---|---|---|
| Caller identity to container | Authorization arrives natively (AllViewerExceptHostHeader, proven by the auth-server). | JWT_PASSTHROUGH is HTTP-target-only; an interceptor Lambda must re-inject Authorization. Extra hop. |
| Existing container code | Works unchanged (minus the smuggle/diag hacks). | Works, but behind Gateway's tool re-cataloging. |
| Wedge fit (MCP passthrough) | Transparent passthrough. | Gateway re-catalogs tools/list; not transparent; DYNAMIC mode loses semantic search/3LO. |
| New infra | A clone of the in-prod auth-server stack. | Gateway + interceptor Lambda + still needs a host. |
B is retained only as a paper fallback if a specific need for AgentCore governance/observability ever emerges.
Grounding corrections from red-team (the draft overstated the risk)
The original draft listed VPC/RDS/NAT/streaming as new unknowns. Grounding against auth-server-stack.ts shows they are already solved in prod:
- C1 — VPC + RDS is NOT a new delta. The auth-server is already VPC-attached (
PRIVATE_WITH_EGRESS, sharedappSecurityGroupcarrying the RDS:5432ingress, NAT for egress) and already queries RDS (the consent-gate). doc-search needs the identical setup — a clone, not new infra. - C2 — no streaming needed. doc-search sets
enableJsonResponse: true(server.ts:186) → buffered JSON, not SSE; the auth-server usesInvokeMode.BUFFERED. We use BUFFERED. The streaming-throughput-cap risk is gone; the only real constraint is the 6 MB Function-URL buffered-response cap (revisit with RESPONSE_STREAM only ifget_documenthits it — model-token streaming is the client's concern, unaffected by our invoke mode).
What's preserved (do NOT rebuild)
Only the hosting layer changes. Untouched:
- The AS (
mcp-oauth.autri.ai,autri-auth-serverstack) and the entire OAuth/DCR handshake — proven end-to-end this session. - The connector model + Settings UI (W1-S9) + the consumable substrate.
- The container code:
mcp-servers/doc-search/src/auth.ts(AutriAsAuth),server.ts,tools.ts, the 6 tools, scope enforcement, audit. - The branded resource/aud design (
https://mcp.autri.ai/mcp, RFC 8707) + the AS's canonical-aud minting.
Architecture (red-teamed)
- New stack
autri-mcp-server= a clone ofauth-server-stack.ts: VPC-attached Lambda (PRIVATE_WITH_EGRESS, sharedappSecurityGroup, NAT egress), Function URL (authType NONE,InvokeMode.BUFFERED) → CloudFront →mcp.autri.aialias + cert.NodejsFunction/esbuild CJS bundle,@aws-sdk/*externalized. X-Forwarded-Host/Proto pinned +AllViewerExceptHostHeader+CACHING_DISABLED(the proven combo). - H1 — Lambda-ify the Hono app. doc-search currently runs as a long-running server (
serve({fetch: app.fetch}),server.ts:273). Add a Lambda handler (hono/aws-lambdahandle, or mirror the auth-server'slambdaHandler). Top-levelawait buildPoolConfig()means cold-start pays the Secrets fetch + pool connect (same cold-start the auth-server measures). - H2 — wire
OPENAI_API_KEY.vector_search/hybrid_searchcallapi.openai.com(vector-search.ts:41-50) and need the key + NAT egress. It was absent from the AgentCore runtime env, so those tools were already broken in prod — fix as part of the pivot (inject from Secrets Manager). - Auth, simplified: container reads
Authorizationdirectly →AutriAsAuthchecks sig/iss/aud/scope →sub+connector_id→ live ownership assertion (existingserver.tslogic). Drop the custom-header smuggle, the discovery shim's token-copy branch, and both diagnostics ([as-trace],[as-diag]). - M2 — discovery / 401, now owned cleanly: we own the origin, so the MCP server serves the correct PRM (
resource= branded) and the401 WWW-Authenticatewith the rightresource_metadatadirectly from the app — eliminating the CloudFront discovery-shim Function (which couldn't even emit the 401 on a 4xx). - M1 — defense-in-depth: no gateway authorizer means every request (incl. bad tokens, probes) executes the Lambda before the container rejects. A cost/DoS surface, not an authz hole (the container fully validates). Fine at ~1-user beta; add a CloudFront throttle/WAF later.
Migration plan — Milestone-0 GATED (H3)
- Build
autri-mcp-server(clone the auth-server stack); deploy on the raw*.cloudfront.net(no alias). - GATE: real Claude handshake → self-hosted Lambda → a tool call returns, against the raw domain. Do NOT proceed until this is green (
[[feedback_interactive_flows_need_interactive_smoke]]). AgentCore (RuntimeV2) stays live as the rollback until this passes. - Repoint
mcp.autri.aiCNAME → new distribution; redeploy with alias. - Verify end-to-end (DCR → token → tool call, scope-enforced).
cdk destroy AgentCore; remove diagnostics; delete the agentcore stack + its now-unused cert; revert the uncommittedRuntimeV2rename.
Queued (apply on build / at /stop)
- L2 — monitoring: the
autri-monitoringstack is name-keyed toautri_mcp_doc_search(now deleted; we deployed_v2). Reconcile — drop the AgentCore alarms as part of the teardown. - L3 — epic re-scope: S7 (AgentCore extraction) is obsoleted; S8 acceptance reframes around the self-hosted stack; flip the epic's two "self-host vs Gateway — decide in build" open items to "decided: self-host." Apply to
projects/autri/epics/mcp-auth-servernext session.
Sources (key)
- AgentCore header allowlist +
context.request_headers(HTTP-protocol only): https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-header-allowlist.html - AgentCore runtime OAuth / propagate-JWT (HTTP
@app.entrypoint; Workload tokens are outbound): https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html - AgentCore MCP protocol contract (no header/context section): https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp-protocol-contract.html
- Unresolved MCP-auth-doesn't-reach-container issue: https://github.com/awslabs/amazon-bedrock-agentcore-samples/issues/1030
- Gateway → existing MCP-server target + JWT propagation limits / interceptor lambda: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-MCPservers.html · https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-headers.html
- MCP on Lambda Function URL + Lambda Web Adapter: https://github.com/awslabs/run-model-context-protocol-servers-with-aws-lambda · https://github.com/mikegc-aws/Lambda-MCP-Server