$npx -y skills add aws-samples/sample-agent-skills-for-builders --skill agentcore-mcp-oauth-facadeBuild an OAuth + MCP protocol facade in front of Bedrock AgentCore Gateway or Runtime so strict MCP clients (Claude Code, Cursor, Amazon Quick, mcp-remote) can connect. Use when an MCP client fails OAuth discovery ("Fix highlighted fields", DCR errors), when tools beyond 30 silen
| 1 | # AgentCore MCP OAuth Facade |
| 2 | |
| 3 | Bedrock AgentCore Gateway and Runtime expose MCP endpoints, but real-world MCP |
| 4 | clients expect a *complete* OAuth 2.0 + MCP implementation: RFC 9728 protected |
| 5 | resource metadata, RFC 8414 authorization server metadata, Dynamic Client |
| 6 | Registration, arbitrary loopback redirect URIs, `Mcp-Session-Id` continuity, |
| 7 | SSE responses, and un-paginated tool lists. Neither Cognito nor AgentCore |
| 8 | Gateway provides all of that out of the box. |
| 9 | |
| 10 | This skill captures a production-tested pattern: a small stateless Lambda |
| 11 | facade (CloudFront → HTTP API → Lambda) that bridges every gap while **never |
| 12 | issuing tokens itself** — Cognito remains the issuer, and the Gateway's JWT |
| 13 | authorizer validates tokens unchanged. |
| 14 | |
| 15 | ``` |
| 16 | MCP client (Claude Code / Cursor / Quick / mcp-remote) |
| 17 | │ OAuth discovery, /oauth/authorize, /oauth/token, /register, /mcp |
| 18 | ▼ |
| 19 | OAuth Facade (CloudFront → HTTP API $default stage → Lambda) |
| 20 | │ injects routing session-id, rewrites tool names, aggregates pages, |
| 21 | │ re-wraps JSON as SSE, synthesizes Mcp-Session-Id |
| 22 | ▼ |
| 23 | AgentCore Gateway (CUSTOM_JWT authorizer) ──► Lambda target ──► tools |
| 24 | ``` |
| 25 | |
| 26 | ## When to Apply |
| 27 | |
| 28 | Reference this skill when: |
| 29 | |
| 30 | - Putting Bedrock AgentCore Gateway or Runtime behind OAuth for third-party |
| 31 | MCP clients (Claude Code, Cursor, Amazon Quick, anything built on |
| 32 | `mcp-remote`). |
| 33 | - An MCP client's connect wizard fails with a manual-credentials form (e.g. |
| 34 | Quick's "Fix highlighted fields") — almost always a broken discovery chain. |
| 35 | - Tools past the first 30 are missing from `tools/list` — Gateway pagination |
| 36 | that the client never follows. |
| 37 | - Tool calls intermittently return 404 "no valid session" or `-32602 Unknown |
| 38 | tool` after migrating from an MCP Runtime to AgentCore Gateway. |
| 39 | - Cognito must serve clients that expect DCR (RFC 7591) or arbitrary |
| 40 | `redirect_uri` values. |
| 41 | |
| 42 | **Not for:** general AgentCore service setup (Gateway targets, Runtime |
| 43 | deployment, IAM) — see the `aws-agentic-ai` skill for that. |
| 44 | |
| 45 | ## How It Works |
| 46 | |
| 47 | The facade solves three groups of problems. Each has a deep-dive reference: |
| 48 | |
| 49 | ### 1. OAuth discovery and Cognito gap-filling |
| 50 | |
| 51 | Cognito supports OAuth flows but is an *incomplete* OIDC provider for MCP |
| 52 | purposes: no DCR API, metadata only under Cognito's own domain, registered |
| 53 | redirect URIs only. The facade: |
| 54 | |
| 55 | 1. Serves three metadata documents at the **root domain** (CloudFront exists |
| 56 | purely to give a clean root — some clients do not support path-prefixed |
| 57 | discovery): RFC 9728 protected-resource, RFC 8414 authorization-server, |
| 58 | and OIDC `openid-configuration`. Interactive endpoints point at the |
| 59 | facade; `issuer` and `jwks_uri` stay on the real Cognito issuer so token |
| 60 | validation is untouched. |
| 61 | 2. Serves each document at both the bare path **and** the path-insertion |
| 62 | variant (`/.well-known/openid-configuration/mcp`) — clients probe both. |
| 63 | 3. Implements **fake DCR**: `POST /register` echoes the request and returns a |
| 64 | pre-provisioned public client ID (201). No Cognito API call needed. |
| 65 | 4. Rewrites `redirect_uri`: the client's original `{redirect_uri, state}` is |
| 66 | packed into an HMAC-SHA256-signed `state` (10-min max age), Cognito always |
| 67 | sees the facade's single registered callback, and the callback handler |
| 68 | verifies the signature and bounces the code back to the client's real |
| 69 | redirect URI. The facade stays fully stateless — no session store. |
| 70 | 5. Injects the resource-server scope during `/oauth/authorize` (union with |
| 71 | what the client asked for) — Gateway enforces scopes per `tools/call`, |
| 72 | and generic clients only request `openid email`. |
| 73 | |
| 74 | See [references/oauth-discovery-and-dcr.md](references/oauth-discovery-and-dcr.md). |
| 75 | |
| 76 | ### 2. MCP session and protocol bridging |
| 77 | |
| 78 | AgentCore uses **two unrelated session identifiers**: |
| 79 | |
| 80 | | Header | Owner | Purpose | |
| 81 | |---|---|---| |
| 82 | | `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` | AgentCore platform | Container routing key, **must be ≥ 33 chars** | |
| 83 | | `Mcp-Session-Id` | MCP protocol | Protocol session continuity | |
| 84 | |
| 85 | The facade mints a fresh routing key per request (`facade-<uuid4hex>`, 39 |
| 86 | chars) and passes the protocol header through untouched — with one surgical |
| 87 | exception: if the upstream echoes the injected routing key back as an |
| 88 | `mcp-session-id` response header (AgentCore does this on session-less |
| 89 | responses), the facade suppresses that echo. Spec-compliant clients adopt any |
| 90 | ne |