fix(oauth): allow generic RFC 7591 DCR + expand install snippets
All checks were successful
Deploy to Production / deploy (push) Successful in 1m28s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m28s
- /oauth/register: drop resource_required check, accept generic registrations (Claude Desktop omits resource in DCR body per spec). serverId stored as NULL; /authorize still enforces org-ownership + access-token aud claim still pinned to resource. Fixes Claude Desktop DCR failure (ofid_d7e39530c109fa7f). - /oauth/authorize: skip strict server.id check when client.serverId is NULL (generic client); org check remains the security boundary. - schema: oauth_clients.server_id no longer NOT NULL. - migration 0002: ALTER COLUMN server_id DROP NOT NULL (already applied on prod). - install-snippets: add Claude Code (CLI), VS Code, Codex, raw URL tabs. Claude Desktop now shows form-field values (Name / Remote MCP Server URL / OAuth Client ID / Secret) matching the new Custom Connector UI instead of the obsolete JSON config. - types: InstallTarget enum extended. - hero-video: clicking the audio toggle restarts the video from frame 0 so unmute aligns with the spoken opening. - marketing: drop em-dashes from rendered copy.
This commit is contained in:
@@ -4,29 +4,69 @@ export interface SnippetInput {
|
||||
name: string;
|
||||
slug: string;
|
||||
publicUrl: string;
|
||||
/**
|
||||
* Optional pre-issued OAuth client credentials. When present, Claude
|
||||
* Desktop / ChatGPT users can paste them into the "OAuth Client ID /
|
||||
* Secret" form fields as a fallback if Dynamic Client Registration
|
||||
* (DCR) is blocked by their network or the client refuses it.
|
||||
*/
|
||||
oauthClientId?: string;
|
||||
oauthClientSecret?: string;
|
||||
}
|
||||
|
||||
export function buildSnippet(target: InstallTarget, input: SnippetInput): { label: string; code: string; note?: string } {
|
||||
export interface SnippetOutput {
|
||||
label: string;
|
||||
code: string;
|
||||
/** Optional short hint shown below the snippet. */
|
||||
note?: string;
|
||||
}
|
||||
|
||||
const stripTrailingSlash = (u: string) => u.replace(/\/$/, '');
|
||||
|
||||
export function buildSnippet(target: InstallTarget, input: SnippetInput): SnippetOutput {
|
||||
const key = input.slug.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const mcpUrl = `${input.publicUrl.replace(/\/$/, '')}/mcp`;
|
||||
const baseUrl = stripTrailingSlash(input.publicUrl);
|
||||
const mcpUrl = `${baseUrl}/mcp`;
|
||||
const hasCreds = !!(input.oauthClientId && input.oauthClientSecret);
|
||||
|
||||
switch (target) {
|
||||
case 'claude-desktop':
|
||||
case 'claude-desktop': {
|
||||
// Claude Desktop now uses a "Custom Connector" UI with form fields:
|
||||
// Name / Remote MCP Server URL / OAuth Client ID / OAuth Client Secret.
|
||||
// We show the exact values to paste so the JSON-config dance is gone.
|
||||
// OAuth fields are blank by default (DCR registers them automatically)
|
||||
// and only filled when the org has pre-issued credentials as a fallback.
|
||||
const lines = [
|
||||
`Name: ${input.name}`,
|
||||
`Remote MCP Server URL: ${mcpUrl}`,
|
||||
];
|
||||
if (hasCreds) {
|
||||
lines.push(
|
||||
`OAuth Client ID: ${input.oauthClientId}`,
|
||||
`OAuth Client Secret: ${input.oauthClientSecret}`,
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
`OAuth Client ID: (leave blank, registers automatically)`,
|
||||
`OAuth Client Secret: (leave blank, registers automatically)`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
label: 'claude_desktop_config.json',
|
||||
code: JSON.stringify(
|
||||
{
|
||||
mcpServers: {
|
||||
[key]: {
|
||||
url: mcpUrl,
|
||||
auth: 'oauth2',
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
note: '~/Library/Application Support/Claude/claude_desktop_config.json (macOS) — Settings → Developer (Windows). Restart Claude after saving.',
|
||||
label: 'Claude Desktop · Custom Connector',
|
||||
code: lines.join('\n'),
|
||||
note: hasCreds
|
||||
? 'Settings → Connectors → Add custom connector. Paste each value into the matching field. OAuth handshake runs on first use.'
|
||||
: 'Settings → Connectors → Add custom connector. Paste Name + URL, leave the OAuth fields blank. If your network blocks Dynamic Client Registration, generate a fixed Client ID / Secret in the server settings and paste them too.',
|
||||
};
|
||||
}
|
||||
|
||||
case 'claude-code':
|
||||
return {
|
||||
label: 'Claude Code (CLI)',
|
||||
code: `claude mcp add ${key} ${mcpUrl} --transport http`,
|
||||
note: `Run in any project. Use \`claude mcp list\` to confirm and \`claude mcp remove ${key}\` to undo.`,
|
||||
};
|
||||
|
||||
case 'cursor':
|
||||
return {
|
||||
label: '.cursor/mcp.json',
|
||||
@@ -42,13 +82,62 @@ export function buildSnippet(target: InstallTarget, input: SnippetInput): { labe
|
||||
null,
|
||||
2,
|
||||
),
|
||||
note: 'Place at the project root or in ~/.cursor/mcp.json for global use.',
|
||||
note: 'Place at the project root or in ~/.cursor/mcp.json for global use. Restart Cursor after saving.',
|
||||
};
|
||||
case 'chatgpt':
|
||||
|
||||
case 'vscode':
|
||||
return {
|
||||
label: 'ChatGPT — Custom Connector',
|
||||
code: `Name: ${input.name}\nURL: ${mcpUrl}\nAuth: OAuth 2.1 (Dynamic Client Registration)`,
|
||||
note: 'ChatGPT → Settings → Connectors → Add custom connector. Paste the values above. OAuth handshake runs on first use.',
|
||||
label: '.vscode/mcp.json',
|
||||
code: JSON.stringify(
|
||||
{
|
||||
servers: {
|
||||
[key]: {
|
||||
type: 'http',
|
||||
url: mcpUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
note: 'VS Code → Copilot Chat → MCP servers. Save the file at the workspace root, then reload the window. OAuth runs on first call.',
|
||||
};
|
||||
|
||||
case 'chatgpt': {
|
||||
const lines = [
|
||||
`Name: ${input.name}`,
|
||||
`URL: ${mcpUrl}`,
|
||||
`Auth: OAuth 2.1`,
|
||||
];
|
||||
if (hasCreds) {
|
||||
lines.push(
|
||||
`Client ID: ${input.oauthClientId}`,
|
||||
`Client Secret: ${input.oauthClientSecret}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
label: 'ChatGPT · Custom Connector',
|
||||
code: lines.join('\n'),
|
||||
note: hasCreds
|
||||
? 'ChatGPT → Settings → Connectors → Add custom connector. Paste the values above.'
|
||||
: 'ChatGPT → Settings → Connectors → Add custom connector. Paste Name + URL. OAuth handshake registers a client automatically on first use.',
|
||||
};
|
||||
}
|
||||
|
||||
case 'codex':
|
||||
return {
|
||||
label: '~/.codex/config.toml',
|
||||
code: `[mcp_servers.${key}]
|
||||
type = "http"
|
||||
url = "${mcpUrl}"`,
|
||||
note: 'Append to your ~/.codex/config.toml. Restart the Codex CLI. OAuth runs on first tool call.',
|
||||
};
|
||||
|
||||
case 'raw-url':
|
||||
return {
|
||||
label: 'Server URL',
|
||||
code: mcpUrl,
|
||||
note: 'Any MCP-compatible client. Add it as a "Remote MCP server" (HTTP transport) and approve OAuth on first use.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user