import type { InstallTarget } from '@bmm/types'; 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 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 baseUrl = stripTrailingSlash(input.publicUrl); const mcpUrl = `${baseUrl}/mcp`; const hasCreds = !!(input.oauthClientId && input.oauthClientSecret); switch (target) { 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 · 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', code: JSON.stringify( { mcpServers: { [key]: { url: mcpUrl, auth: 'oauth2', }, }, }, null, 2, ), note: 'Place at the project root or in ~/.cursor/mcp.json for global use. Restart Cursor after saving.', }; case 'vscode': return { 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.', }; } }