Blame
|
1 | --- |
||||||
| 2 | category: reference |
|||||||
| 3 | tags: |
|||||||
| 4 | - P1 |
|||||||
| 5 | - infrastructure |
|||||||
| 6 | - oauth |
|||||||
| 7 | - mcp |
|||||||
| 8 | last_updated: 2026-03-13 |
|||||||
| 9 | --- |
|||||||
| 10 | ||||||||
| 11 | # P1-9: Fix MCP OAuth Discovery Routing |
|||||||
| 12 | ||||||||
| 13 | ## Problem |
|||||||
| 14 | ||||||||
| 15 | Claude.ai needs to discover OAuth endpoints via `/.well-known/oauth-authorization-server` to initiate the OAuth flow with the MCP server. Two issues prevented this: |
|||||||
| 16 | ||||||||
| 17 | 1. **Missing API Gateway route:** `/.well-known/*` paths hit the Otterwiki Lambda (`$default` catch-all) instead of the MCP Lambda, returning HTML instead of OAuth metadata JSON. |
|||||||
| 18 | ||||||||
| 19 | 2. **Double path in resource URL:** `MCP_BASE_URL` was set to `https://dev.wikibot.io/mcp`, but FastMCP's `_get_resource_url()` appends the `mcp_path` parameter (`/mcp`) to the base URL, producing `https://dev.wikibot.io/mcp/mcp`. |
|||||||
| 20 | ||||||||
| 21 | ## Solution |
|||||||
| 22 | ||||||||
| 23 | Two infrastructure changes, no code changes: |
|||||||
| 24 | ||||||||
| 25 | 1. **Added API Gateway route** `ANY /.well-known/{proxy+}` pointing to the MCP Lambda integration. This routes OAuth discovery requests to the correct Lambda. |
|||||||
| 26 | ||||||||
| 27 | 2. **Changed `MCP_BASE_URL`** from `https://dev.wikibot.io/mcp` to `https://dev.wikibot.io`. FastMCP now computes the correct resource URL `https://dev.wikibot.io/mcp` via `_get_resource_url("/mcp")`. |
|||||||
| 28 | ||||||||
| 29 | ## Verification |
|||||||
| 30 | ||||||||
| 31 | All endpoints tested after deploy: |
|||||||
| 32 | ||||||||
| 33 | | Endpoint | Expected | Actual | |
|||||||
| 34 | |----------|----------|--------| |
|||||||
| 35 | | `GET /.well-known/oauth-authorization-server` | WorkOS OAuth metadata JSON | Pass | |
|||||||
| 36 | | `GET /.well-known/oauth-protected-resource/mcp` | Protected resource metadata JSON | Pass | |
|||||||
| 37 | | `POST /mcp` (with bearer token) | MCP initialize response (200) | Pass | |
|||||||
| 38 | | `POST /mcp` (no auth) | 401 Unauthorized | Pass | |
|||||||
| 39 | | `GET /Home` | Otterwiki HTML | Pass | |
|||||||
| 40 | ||||||||
| 41 | ## Files Changed |
|||||||
| 42 | ||||||||
| 43 | - `infra/Pulumi.dev.yaml` — `mcp_base_url` value |
|||||||
| 44 | - `infra/__main__.py` — new `mcp-route-well-known` API Gateway route |
|||||||
| 45 | ||||||||
| 46 | ## How It Works |
|||||||
| 47 | ||||||||
| 48 | ``` |
|||||||
| 49 | Client → GET /.well-known/oauth-authorization-server |
|||||||
| 50 | → API Gateway route: ANY /.well-known/{proxy+} |
|||||||
| 51 | → MCP Lambda |
|||||||
| 52 | → Starlette route: /.well-known/oauth-authorization-server |
|||||||
| 53 | → AuthKitProvider forwards WorkOS metadata |
|||||||
| 54 | ||||||||
| 55 | Client → GET /.well-known/oauth-protected-resource/mcp |
|||||||
| 56 | → API Gateway route: ANY /.well-known/{proxy+} |
|||||||
| 57 | → MCP Lambda |
|||||||
| 58 | → Starlette route: /.well-known/oauth-protected-resource/mcp |
|||||||
| 59 | → Returns resource metadata with authorization_servers |
|||||||
| 60 | ||||||||
| 61 | Client → POST /mcp (MCP protocol) |
|||||||
| 62 | → API Gateway route: ANY /mcp |
|||||||
| 63 | → MCP Lambda |
|||||||
| 64 | → Starlette route: /mcp |
|||||||
| 65 | → StreamableHTTP handler |
|||||||
| 66 | ``` |
|||||||
| 67 | ||||||||
| 68 | ## Commit |
|||||||
| 69 | ||||||||
| 70 | `e46ef4a` on `main` branch (not pushed) |
|||||||