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:
co-authored by
Claude Fable 5.1
parent
61a76b1669
commit
6a85efb00a
@@ -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],
|
||||
|
||||
@@ -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' },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user