From e1f28e6637858c9104dad60464fad44a9575882f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 17:48:22 +0000 Subject: [PATCH] MCP: accept ChatGPT's per-connector OAuth callback so the custom MCP dialog can register The plugin dialog gives each connector its own https://chatgpt.com/connector/oauth/ redirect. Registration only knew the older fixed one, so ChatGPT reported 'Some app settings were rejected'. Registration now also takes the review tool's callback and several URIs at once, as long as every one is a known callback. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MLHMzQJgKySRBgFiKapP9Y --- MCP_SETUP.md | 2 +- app/api/mcp/_shared.ts | 10 +++++++++- app/api/mcp/oauth.test.ts | 4 ++++ app/api/mcp/register/route.ts | 3 ++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/MCP_SETUP.md b/MCP_SETUP.md index cc600dd..70d80d4 100644 --- a/MCP_SETUP.md +++ b/MCP_SETUP.md @@ -25,7 +25,7 @@ AES-256-GCM under `AKADA_MCP_TOKEN_SECRET` (`lib/mcp-auth.ts`). | --- | --- | | Protected resource metadata (RFC 9728) | `/.well-known/oauth-protected-resource/mcp`, also `/.well-known/oauth-protected-resource/api/mcp` and `/.well-known/oauth-protected-resource` for clients that probe | | Authorization server metadata (RFC 8414) | `/.well-known/oauth-authorization-server`, issuer = `NEXT_PUBLIC_SITE_URL` | -| Dynamic client registration (RFC 7591) | `POST /api/mcp/register`. Only known callbacks: `https://claude.ai/api/mcp/auth_callback`, ChatGPT's, Gemini's, and `http://localhost` / `127.0.0.1` `/callback` or `/oauth/callback` on any port (Claude Code, Gemini CLI) | +| Dynamic client registration (RFC 7591) | `POST /api/mcp/register`. Only known callbacks: `https://claude.ai/api/mcp/auth_callback`, ChatGPT's (`https://chatgpt.com/connector/oauth/`, `…/connector_platform_oauth_redirect`, `platform.openai.com/apps-manage/oauth`), Gemini's, and `http://localhost` / `127.0.0.1` `/callback` or `/oauth/callback` on any port (Claude Code, Gemini CLI) | | Authorization + consent | `GET/POST /api/mcp/authorize`. PKCE S256 required. The user must be signed in to Akada, then confirms their password, which starts a Supabase session that only the connector refreshes | | Token | `POST /api/mcp/token`, form-encoded, public client (`none`). `authorization_code` and `refresh_token` grants | diff --git a/app/api/mcp/_shared.ts b/app/api/mcp/_shared.ts index 673022e..753b389 100644 --- a/app/api/mcp/_shared.ts +++ b/app/api/mcp/_shared.ts @@ -24,7 +24,15 @@ function isLoopbackCallback(uri: string) { // every run, so it's matched by shape instead of an exact string). const KNOWN_CALLBACKS: KnownCallback[] = [ { name: 'Claude', test: (uri) => uri === 'https://claude.ai/api/mcp/auth_callback' }, - { name: 'ChatGPT', test: (uri) => uri === 'https://chatgpt.com/connector_platform_oauth_redirect' }, + { + name: 'ChatGPT', + // The plugin dialog hands each connector its own /connector/oauth/ + // callback; the older apps flow and OpenAI's review tool use fixed ones. + test: (uri) => + uri === 'https://chatgpt.com/connector_platform_oauth_redirect' || + uri === 'https://platform.openai.com/apps-manage/oauth' || + /^https:\/\/chatgpt\.com\/connector\/oauth\/[A-Za-z0-9_-]+$/.test(uri), + }, { name: 'Gemini', test: (uri) => uri === 'https://vertexaisearch.cloud.google.com/oauth-redirect' || isLoopbackCallback(uri), diff --git a/app/api/mcp/oauth.test.ts b/app/api/mcp/oauth.test.ts index 5091ceb..73419a1 100644 --- a/app/api/mcp/oauth.test.ts +++ b/app/api/mcp/oauth.test.ts @@ -72,6 +72,10 @@ test('register: Claude and any-port loopback callbacks are accepted, others are assert.equal((await register([CLAUDE_CALLBACK])).status, 201); assert.equal((await register(['http://localhost:3118/callback'])).status, 201); assert.equal((await register(['http://127.0.0.1:49152/callback'])).status, 201); + assert.equal((await register(['https://chatgpt.com/connector/oauth/abc_123-XYZ'])).status, 201); + assert.equal((await register(['https://chatgpt.com/connector_platform_oauth_redirect', 'https://platform.openai.com/apps-manage/oauth'])).status, 201); + assert.equal((await register(['https://chatgpt.com/connector/oauth/abc/../evil'])).status, 400); + assert.equal((await register([CLAUDE_CALLBACK, 'https://evil.example/callback'])).status, 400); assert.equal((await register(['https://evil.example/callback'])).status, 400); }); diff --git a/app/api/mcp/register/route.ts b/app/api/mcp/register/route.ts index b02bbd3..33b55fb 100644 --- a/app/api/mcp/register/route.ts +++ b/app/api/mcp/register/route.ts @@ -16,7 +16,8 @@ export async function POST(request: NextRequest) { const redirectUris = Array.isArray(body.redirect_uris) ? body.redirect_uris.filter((uri): uri is string => typeof uri === 'string') : []; - const clientName = redirectUris.length === 1 ? matchKnownCallback(redirectUris[0]) : null; + const names = redirectUris.map(matchKnownCallback); + const clientName = redirectUris.length > 0 && names.every((name) => name && name === names[0]) ? names[0] : null; if (!clientName) { return oauthError( 'invalid_redirect_uri',