feat(connect): dedicated Claude.ai / ChatGPT browser connect picker
- Replace single 'Connect to Claude / ChatGPT' button with a modal picker offering Claude.ai web, Claude Desktop, Codex CLI, and ChatGPT/GPT Actions. - Add /oauth/connect-claude-ai backend route that redirects to Anthropic's official https://claude.ai/api/mcp/auth_callback OAuth callback. - Update MCP callback result page with browser-specific instructions for Claude.ai web, Claude Desktop, ChatGPT/GPT Actions, and Codex CLI. - Deploy new app and hermes images to K8s.
This commit is contained in:
55
src/index.ts
55
src/index.ts
@@ -724,6 +724,23 @@ app.get('/oauth/connect-mcp', (req, res) => {
|
||||
res.redirect(`/oauth/authorize?${params}`);
|
||||
});
|
||||
|
||||
// Dedicated entry point for the Claude.ai web MCP client. It uses the official
|
||||
// Anthropic redirect_uri so Claude.ai receives the authorization code directly.
|
||||
app.get('/oauth/connect-claude-ai', (req, res) => {
|
||||
const clientId = process.env.OAUTH_CLIENT_ID;
|
||||
if (!clientId) {
|
||||
res.status(503).send('MCP OAuth app not configured (OAUTH_CLIENT_ID missing)');
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
redirect_uri: 'https://claude.ai/api/mcp/auth_callback',
|
||||
response_type: 'code',
|
||||
scope: 'mcp',
|
||||
});
|
||||
res.redirect(`/oauth/authorize?${params}`);
|
||||
});
|
||||
|
||||
// Callback — exchange code for token and render the config snippet page
|
||||
app.get('/oauth/mcp-callback', async (req, res) => {
|
||||
const code = req.query.code as string | undefined;
|
||||
@@ -762,11 +779,12 @@ h1{color:#dc2626;margin:0 0 12px}p{color:#888;margin:0}</style></head>
|
||||
}
|
||||
|
||||
const { token, serverUrl } = opts;
|
||||
const mcpUrl = `${serverUrl}/mcp`;
|
||||
const claudeConfig = JSON.stringify({
|
||||
mcpServers: { 'hermes-mcp': { type: 'http', url: `${serverUrl}/mcp`, headers: { Authorization: `Bearer ${token}` } } }
|
||||
mcpServers: { 'hermes-mcp': { type: 'http', url: mcpUrl, headers: { Authorization: `Bearer ${token}` } } }
|
||||
}, null, 2);
|
||||
const codexConfig = JSON.stringify({
|
||||
mcpServers: { 'hermes-mcp': { type: 'http', url: `${serverUrl}/mcp`, headers: { Authorization: `Bearer ${token}` } } }
|
||||
mcpServers: { 'hermes-mcp': { type: 'http', url: mcpUrl, headers: { Authorization: `Bearer ${token}` } } }
|
||||
}, null, 2);
|
||||
|
||||
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
@@ -781,12 +799,18 @@ body{font-family:system-ui,sans-serif;background:#0f0f10;color:#e5e5e5;margin:0;
|
||||
.card{background:#1a1a1b;border:1px solid #2a2a2b;border-radius:12px;padding:32px;max-width:680px;margin:0 auto}
|
||||
h1{font-size:22px;margin:0 0 8px;color:#10a37f}
|
||||
.subtitle{color:#888;margin:0 0 28px;font-size:14px}
|
||||
h2{font-size:14px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.05em;margin:20px 0 8px}
|
||||
h2{font-size:14px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.05em;margin:24px 0 10px}
|
||||
pre{background:#0f0f10;border:1px solid #2a2a2b;border-radius:8px;padding:16px;font-size:12px;overflow-x:auto;position:relative}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:#2a2a2b;border:none;color:#888;padding:4px 10px;border-radius:6px;cursor:pointer;font-size:11px}
|
||||
.copy-btn:hover{color:#e5e5e5}
|
||||
.token-box{background:#0f0f10;border:1px solid #2a2a2b;border-radius:8px;padding:12px 16px;font-family:monospace;font-size:13px;word-break:break-all;margin-bottom:8px}
|
||||
.warn{color:#888;font-size:12px;margin:4px 0 20px}
|
||||
.instruct{color:#a1a1aa;font-size:13px;line-height:1.6;margin:8px 0}
|
||||
.instruct code{background:#0f0f10;border:1px solid #2a2a2b;border-radius:4px;padding:2px 5px;font-size:12px}
|
||||
.instruct ol{margin:8px 0;padding-left:20px}
|
||||
.instruct li{margin:6px 0}
|
||||
.client-section{border-top:1px solid #2a2a2b;padding-top:18px;margin-top:18px}
|
||||
.client-section:first-of-type{border-top:none;padding-top:0;margin-top:0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -798,11 +822,28 @@ pre{background:#0f0f10;border:1px solid #2a2a2b;border-radius:8px;padding:16px;f
|
||||
<div class="token-box">${esc(token!)}</div>
|
||||
<p class="warn">Store this securely — it won't be shown again.</p>
|
||||
|
||||
<h2>Claude Desktop <code>claude_desktop_config.json</code></h2>
|
||||
<pre id="claude-cfg">${esc(claudeConfig)}<button class="copy-btn" onclick="copy('claude-cfg')">Copy</button></pre>
|
||||
<div class="client-section">
|
||||
<h2>Claude.ai (browser)</h2>
|
||||
<p class="instruct">In <a href="https://claude.ai" target="_blank" rel="noopener" style="color:#10a37f">claude.ai</a> go to <strong>Settings → Integrations → Add MCP server</strong> and paste:</p>
|
||||
<pre id="claude-web-cfg">${esc(mcpUrl)}<button class="copy-btn" onclick="copy('claude-web-cfg')">Copy</button></pre>
|
||||
<p class="instruct">When prompted, use the access token above.</p>
|
||||
</div>
|
||||
|
||||
<h2>Codex CLI / opencode config</h2>
|
||||
<pre id="codex-cfg">${esc(codexConfig)}<button class="copy-btn" onclick="copy('codex-cfg')">Copy</button></pre>
|
||||
<div class="client-section">
|
||||
<h2>Claude Desktop</h2>
|
||||
<p class="instruct">Paste this into <code>claude_desktop_config.json</code>:</p>
|
||||
<pre id="claude-cfg">${esc(claudeConfig)}<button class="copy-btn" onclick="copy('claude-cfg')">Copy</button></pre>
|
||||
</div>
|
||||
|
||||
<div class="client-section">
|
||||
<h2>ChatGPT / GPT Actions</h2>
|
||||
<p class="instruct">For ChatGPT, use the OpenAPI spec at <code>${esc(serverUrl!)}/openapi.json</code> and add a Bearer token header with the token above. Native MCP support in chatgpt.com is not yet available.</p>
|
||||
</div>
|
||||
|
||||
<div class="client-section">
|
||||
<h2>Codex CLI / OpenCode</h2>
|
||||
<pre id="codex-cfg">${esc(codexConfig)}<button class="copy-btn" onclick="copy('codex-cfg')">Copy</button></pre>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function copy(id) {
|
||||
|
||||
Reference in New Issue
Block a user