mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fix(mcp): align oauth protected-resource metadata with MCP 2025-06-18 spec (#945)
## Summary Fixes MCP OAuth discovery so the client-server handshake actually validates against the **MCP 2025-06-18 authorization spec** (which adopts RFC 9728 Protected Resource Metadata + RFC 8707 Resource Indicators). Previously, a client connecting to `https://mcp.dev.supermemory.ai/mcp` would receive `resource: "https://mcp.supermemory.ai"` (bare host, prod fallback) and reject the connection: > Protected resource https://mcp.supermemory.ai does not match expected https://mcp.dev.supermemory.ai/mcp (or origin) ## Changes - **`resource` now includes the `/mcp` endpoint path** — the spec wants the canonical MCP server URI, and the bundled `@modelcontextprotocol/sdk` reference implementation emits the same shape (`new URL(rsPath, base).href`). Bare-host worked with lenient clients that fell back to origin-matching; strict clients rejected it. - **Path-suffixed metadata route** added at `/.well-known/oauth-protected-resource/mcp` alongside the bare path. The SDK's `metadataHandler` mounts under the resource path, so this matches what spec-strict clients probe first. - **`WWW-Authenticate`'s `resource_metadata` URL** points to the canonical full URL (`https://host/.well-known/oauth-protected-resource/mcp`). - **Centralized base-URL derivation** in a new `mcpBaseUrl()` helper, with priority: 1. `MCP_URL` env var — set by portless dev script so dev requests resolve to the tunneled host, not whatever the local proxy sticks in `Host` 2. `x-forwarded-host` / `host` request headers 3. `https://mcp.supermemory.ai` last-resort fallback (only hit when the worker can't see the inbound host at all) ## Production impact `MCP_URL` is dev-only (not in `wrangler.jsonc` vars), so prod falls through to the `Host` header → `https://mcp.supermemory.ai/mcp`. The wire change in prod is that `resource` now ends with `/mcp` instead of being bare — spec-correct, what strict clients require, and tolerated by lenient ones. ## Contributor DX Added `apps/mcp/.dev.vars.example` documenting `API_URL`, `MCP_URL`, and `POSTHOG_API_KEY` for contributors running plain `wrangler dev` without portless. ## Test plan - [x] `curl https://mcp.dev.supermemory.ai/.well-known/oauth-protected-resource` returns `resource: https://mcp.dev.supermemory.ai/mcp` - [x] `curl https://mcp.dev.supermemory.ai/.well-known/oauth-protected-resource/mcp` returns the same payload - [x] 401 from `/mcp` carries `WWW-Authenticate: Bearer resource_metadata="…/oauth-protected-resource/mcp"` - [x] MCP client (vscode extension) connects successfully — previously failed with the resource-mismatch error - [ ] Verify in prod that bare-host clients continue to work after deploy
This commit is contained in:
parent
4e607f9fd7
commit
ee7951f6ba
2 changed files with 23 additions and 13 deletions
8
apps/mcp/.dev.vars.example
Normal file
8
apps/mcp/.dev.vars.example
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Copy to .dev.vars for `wrangler dev`. Optional when running via `bun run dev`
|
||||
# (portless) — that injects API_URL and MCP_URL automatically.
|
||||
|
||||
API_URL=
|
||||
MCP_URL=
|
||||
|
||||
#(optional)
|
||||
POSTHOG_API_KEY=
|
||||
|
|
@ -8,6 +8,7 @@ import type { ContentfulStatusCode } from "hono/utils/http-status"
|
|||
type Bindings = {
|
||||
MCP_SERVER: DurableObjectNamespace
|
||||
API_URL?: string
|
||||
MCP_URL?: string
|
||||
POSTHOG_API_KEY?: string
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +23,14 @@ type Props = {
|
|||
const app = new Hono<{ Bindings: Bindings }>()
|
||||
|
||||
const DEFAULT_API_URL = "https://api.supermemory.ai"
|
||||
const DEFAULT_MCP_URL = "https://mcp.supermemory.ai"
|
||||
|
||||
const mcpBaseUrl = (c: Context<{ Bindings: Bindings }>) => {
|
||||
if (c.env.MCP_URL) return c.env.MCP_URL.replace(/\/$/, "")
|
||||
const host = c.req.header("x-forwarded-host") || c.req.header("host")
|
||||
const proto = c.req.header("x-forwarded-proto") || "https"
|
||||
return host ? `${proto}://${host}` : DEFAULT_MCP_URL
|
||||
}
|
||||
|
||||
// CORS
|
||||
app.use(
|
||||
|
|
@ -57,21 +66,18 @@ app.get("/", (c) => {
|
|||
})
|
||||
|
||||
// MCP clients use this to discover the authorization server
|
||||
app.get("/.well-known/oauth-protected-resource", (c) => {
|
||||
const protectedResourceHandler = (c: Context<{ Bindings: Bindings }>) => {
|
||||
const apiUrl = c.env.API_URL || DEFAULT_API_URL
|
||||
|
||||
const host = c.req.header("x-forwarded-host") || c.req.header("host")
|
||||
const proto = c.req.header("x-forwarded-proto") || "https"
|
||||
const resourceUrl = host ? `${proto}://${host}` : "https://mcp.supermemory.ai"
|
||||
|
||||
return c.json({
|
||||
resource: resourceUrl,
|
||||
resource: `${mcpBaseUrl(c)}/mcp`,
|
||||
authorization_servers: [apiUrl],
|
||||
scopes_supported: ["openid", "profile", "email", "offline_access"],
|
||||
bearer_methods_supported: ["header"],
|
||||
resource_documentation: "https://docs.supermemory.ai/mcp",
|
||||
})
|
||||
})
|
||||
}
|
||||
app.get("/.well-known/oauth-protected-resource", protectedResourceHandler)
|
||||
app.get("/.well-known/oauth-protected-resource/mcp", protectedResourceHandler)
|
||||
|
||||
// Proxy endpoint for MCP clients that don't follow the spec correctly
|
||||
// Some clients look for oauth-authorization-server on the MCP server domain
|
||||
|
|
@ -116,11 +122,7 @@ const handleMcpRequest = async (c: Context<{ Bindings: Bindings }>) => {
|
|||
const containerTag = c.req.header("x-sm-project")
|
||||
const apiUrl = c.env.API_URL || DEFAULT_API_URL
|
||||
|
||||
const reqHost = c.req.header("x-forwarded-host") || c.req.header("host") || ""
|
||||
const reqProto = c.req.header("x-forwarded-proto") || "https"
|
||||
const resourceMetadataUrl = reqHost
|
||||
? `${reqProto}://${reqHost}/.well-known/oauth-protected-resource`
|
||||
: "/.well-known/oauth-protected-resource"
|
||||
const resourceMetadataUrl = `${mcpBaseUrl(c)}/.well-known/oauth-protected-resource/mcp`
|
||||
|
||||
if (!token) {
|
||||
return new Response("Unauthorized", {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue