diff --git a/epicshop/epic-me/app/routes/oauth/authorize.tsx b/epicshop/epic-me/app/routes/oauth/authorize.tsx index 82bac67..d5204cd 100644 --- a/epicshop/epic-me/app/routes/oauth/authorize.tsx +++ b/epicshop/epic-me/app/routes/oauth/authorize.tsx @@ -67,9 +67,17 @@ export async function action({ request, context }: Route.ActionArgs) { Object.fromEntries(url.searchParams), ) + // Strip `resource` from the request — the MCP client sends its own URL + // (e.g. http://localhost:56000/mcp) as the resource, but API calls go to + // a different host (the Worker). Storing that as the token audience would + // cause a permanent mismatch on every /db-api call. With no audience set, + // the library skips audience validation entirely. + const { resource: _ignored, ...requestWithoutResource } = requestParams as + typeof requestParams & { resource?: string } + const { redirectTo } = await context.cloudflare.env.OAUTH_PROVIDER.completeAuthorization({ - request: requestParams, + request: requestWithoutResource, userId: String(user.id), metadata: { label: user.email, diff --git a/epicshop/epic-me/workers/app.ts b/epicshop/epic-me/workers/app.ts index 6eaafe5..37d159b 100644 --- a/epicshop/epic-me/workers/app.ts +++ b/epicshop/epic-me/workers/app.ts @@ -50,7 +50,8 @@ async function stripResourceFromTokenRequest(request: Request): Promise const contentType = request.headers.get('content-type') ?? '' if (!contentType.includes('application/x-www-form-urlencoded')) return request - const body = await request.text() + // Read from a clone so the original request body stays available. + const body = await request.clone().text() const params = new URLSearchParams(body) if (!params.has('resource')) return request