feat(mcp): allowlist Grok's connector callback and document the Grok path (#2158)

* feat(mcp): allowlist Grok's connector callback and document the Grok path

Grok custom connectors self-register through /api/mcp-oauth/register with
redirect_uri https://grok.com/connectors-oauth-exchange-code/, which the
built-in allowlist rejected with invalid_redirect_uri before consent. Add
the callback as an exact-path BUILT_IN_PATTERNS entry (trailing slash
optional, no prefix) with provider 'grok', named "Grok (xAI)" on the
consent page. Tests: accept, foreign-host and other-path rejection,
provider mapping, and a register route test for the Grok DCR shape.

Surface Grok next to ChatGPT: a "Using Grok?" side door on the onboarding
Claude step (one side door open at a time, telemetry step grok), a Grok row
under "Other clients" in the API & MCP settings tab using ?client=grok, and
sv/en strings for both. Docs: mcp-server rule, ARCHITECTURE, README,
registry entry (install section), DECISIONS.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGbspj3hiNqvqTWZqdwysa
Signed-off-by: Emil <emilmattsson14@gmail.com>

* fix(mcp): cite X Corp's published Grok callback, test the consent label

Review pass on #2158: the allowlist comment and DECISIONS entry claimed
xAI publishes no callback and the value came from a live observation; X
Corp lists https://grok.com/connectors-oauth-exchange-code/ as the "Grok
(web)" redirect URL at docs.x.com/x-ads-api/mcp, and grok.com serves the
path itself (slash form 308s to no-slash on the same origin). Reworded
both to cite that. Adds the consent-page test for "Grok (xAI)" next to
the ChatGPT one and a JSDoc on the onboarding side-door toggle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGbspj3hiNqvqTWZqdwysa
Signed-off-by: Emil <emilmattsson14@gmail.com>

---------

Signed-off-by: Emil <emilmattsson14@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-09-02 14:42:39 +02:00
committed by GitHub
co-authored by Claude Fable 5.1
parent 61a76b1669
commit 6a85efb00a
14 changed files with 131 additions and 32 deletions
@@ -19,6 +19,13 @@ describe('isBuiltInRedirectUri', () => {
['https://chatgpt.com/connector_platform_oauth_redirect/extra', false],
['https://chatgpt.com/other/path', false],
['https://chatgpt.com.evil.com/connector/oauth/x', false],
['https://grok.com/connectors-oauth-exchange-code/', true],
['https://grok.com/connectors-oauth-exchange-code', true],
['https://grok.com/connectors-oauth-exchange-code/extra', false],
['https://grok.com/connectors-oauth-exchange-code/?next=x', false],
['https://grok.com/other/path', false],
['https://grok.com.evil.com/connectors-oauth-exchange-code/', false],
['http://grok.com/connectors-oauth-exchange-code/', false],
['http://localhost:3000/cb', true],
['http://localhost/cb', true],
['http://127.0.0.1:8080/cb', true],
@@ -37,6 +44,8 @@ describe('builtInRedirectProvider', () => {
['https://claude.com/api/oauth/callback', 'claude'],
['https://chatgpt.com/connector/oauth/abc123', 'chatgpt'],
['https://chatgpt.com/connector_platform_oauth_redirect', 'chatgpt'],
['https://grok.com/connectors-oauth-exchange-code/', 'grok'],
['https://grok.com/connectors-oauth-exchange-code/extra', null],
['http://localhost:3000/cb', 'local'],
['http://127.0.0.1:8080/cb', 'local'],
['https://claude-login.example/cb', null],
+13 -3
View File
@@ -14,25 +14,35 @@ import { scopeKind, type ApiKeyScope } from './scope-catalog'
/**
* Identity of a built-in client, derived from the redirect URI pattern that
* matched. Rendered on the consent page so the user can tell a real Claude /
* ChatGPT connector from a look-alike registration.
* ChatGPT / Grok connector from a look-alike registration.
*/
export type BuiltInProvider = 'claude' | 'chatgpt' | 'local'
export type BuiltInProvider = 'claude' | 'chatgpt' | 'grok' | 'local'
/**
* Built-in redirect URI patterns. These bypass the DB lookup entirely so
* Claude's and ChatGPT's connectors keep working without seeded rows, and so
* the Claude, ChatGPT and Grok connectors keep working without seeded rows, and so
* local development never depends on having a registration.
*
* ChatGPT uses a per-connector-instance callback path
* (https://chatgpt.com/connector/oauth/{callback_id}) plus the legacy fixed
* callback for already-published apps; both are documented at
* developers.openai.com/apps-sdk/build/auth.
*
* Grok (grok.com custom connectors) registers itself through /register as a
* public client and sends a single fixed callback,
* https://grok.com/connectors-oauth-exchange-code/, published by X Corp as
* the "Grok (web)" redirect URL at docs.x.com/x-ads-api/mcp (xAI's own
* connector docs at docs.x.ai do not state it). grok.com serves the path
* itself: the slash form 308s to the no-slash form on the same origin, so
* both are accepted. Matched as an exact path, never a prefix, so a future
* grok.com path cannot ride on this entry.
*/
const BUILT_IN_PATTERNS: readonly { pattern: RegExp; provider: BuiltInProvider }[] = [
{ pattern: /^https:\/\/claude\.ai\/api\//, provider: 'claude' },
{ pattern: /^https:\/\/claude\.com\/api\//, provider: 'claude' },
{ pattern: /^https:\/\/chatgpt\.com\/connector\/oauth\//, provider: 'chatgpt' },
{ pattern: /^https:\/\/chatgpt\.com\/connector_platform_oauth_redirect$/, provider: 'chatgpt' },
{ pattern: /^https:\/\/grok\.com\/connectors-oauth-exchange-code\/?$/, provider: 'grok' },
{ pattern: /^http:\/\/localhost(:\d+)?(\/|$)/, provider: 'local' },
{ pattern: /^http:\/\/127\.0\.0\.1(:\d+)?(\/|$)/, provider: 'local' },
]