From 3162b3fe0ed00a28d5180f039d785a110bdbfcc3 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Tue, 10 Mar 2026 19:10:59 -0400 Subject: [PATCH 1/3] feat: MCP Connect guide upgrade -- live key injection + connection test (OPE-168) ConnectGuide enhancements: - Active API key injected into config snippets (was hardcoded ci_your-key-here) - Per-tab setup hints (Settings > Developer > Edit Config, etc.) - Cursor config now includes Authorization header with user's key - Wifi icon in section header New ConnectionTest widget: - 3-step animated health check: MCP reachable -> Auth verified -> Repos accessible - Sequential test with per-step pass/fail/running states - Emerald badges on success, destructive on failure - Error message on failure with troubleshooting hint - Test button toggles between 'Test connection' -> 'Testing...' -> 'Connected' Props flow: activeKeys[0].key_preview passed to ConnectGuide -> ConnectionTest Build: clean. No new dependencies. --- frontend/src/pages/APIKeysPage.tsx | 165 +++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/APIKeysPage.tsx b/frontend/src/pages/APIKeysPage.tsx index 7fd880f..7c9d46a 100644 --- a/frontend/src/pages/APIKeysPage.tsx +++ b/frontend/src/pages/APIKeysPage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react' -import { Plus, Copy, Check, Loader2, Clock, Shield, Terminal, Zap } from 'lucide-react' +import { useState, useCallback } from 'react' +import { Plus, Copy, Check, Loader2, Clock, Shield, Terminal, Zap, Wifi, CircleCheck, CircleX, ArrowRight } from 'lucide-react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useAuth } from '@/contexts/AuthContext' import { Button } from '@/components/ui/button' @@ -166,19 +166,143 @@ function KeyCard({ ) } -function ConnectGuide() { +type TestStep = { label: string; status: 'idle' | 'running' | 'pass' | 'fail' } + +function ConnectionTest({ apiKey }: { apiKey: string | null }) { + const [steps, setSteps] = useState([ + { label: 'MCP server reachable', status: 'idle' }, + { label: 'API key authenticated', status: 'idle' }, + { label: 'Repositories accessible', status: 'idle' }, + ]) + const [running, setRunning] = useState(false) + const [tested, setTested] = useState(false) + + const runTest = useCallback(async () => { + if (!apiKey || running) return + setRunning(true) + setTested(true) + const update = (idx: number, status: TestStep['status']) => + setSteps((prev) => prev.map((s, i) => (i === idx ? { ...s, status } : s))) + + // Reset + setSteps((prev) => prev.map((s) => ({ ...s, status: 'idle' }))) + + // Step 1: MCP health + update(0, 'running') + try { + const res = await fetch('https://mcp.opencodeintel.com/health') + update(0, res.ok ? 'pass' : 'fail') + if (!res.ok) { setRunning(false); return } + } catch { + update(0, 'fail'); setRunning(false); return + } + + // Step 2: Auth check + update(1, 'running') + try { + const res = await fetch(`${API_URL}/keys`, { + headers: { Authorization: `Bearer ${apiKey}` }, + }) + update(1, res.ok ? 'pass' : 'fail') + if (!res.ok) { setRunning(false); return } + } catch { + update(1, 'fail'); setRunning(false); return + } + + // Step 3: Repos accessible + update(2, 'running') + try { + const res = await fetch(`${API_URL}/repos`, { + headers: { Authorization: `Bearer ${apiKey}` }, + }) + update(2, res.ok ? 'pass' : 'fail') + } catch { + update(2, 'fail') + } + + setRunning(false) + }, [apiKey, running]) + + const allPassed = steps.every((s) => s.status === 'pass') + const anyFailed = steps.some((s) => s.status === 'fail') + + return ( +
+
+ + Connection test + + +
+
+ {steps.map((step, i) => ( +
+ {i > 0 && ( + + )} +
+ {step.status === 'running' && } + {step.status === 'pass' && } + {step.status === 'fail' && } + {step.status === 'idle' &&
} + {step.label} +
+
+ ))} +
+ {anyFailed && tested && !running && ( +

+ Connection failed. Check that your API key is active and the MCP server is running. +

+ )} +
+ ) +} + +function ConnectGuide({ activeKeyPreview }: { activeKeyPreview: string | null }) { const [tab, setTab] = useState<'desktop' | 'code' | 'cursor'>('desktop') - const snippets: Record = { + const keyDisplay = activeKeyPreview || 'ci_your-key-here' + + const snippets: Record = { desktop: { label: 'Claude Desktop', + hint: 'Settings > Developer > Edit Config', config: `{ "mcpServers": { "codeintel": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.opencodeintel.com/mcp"], "env": { - "API_KEY": "ci_your-key-here" + "API_KEY": "${keyDisplay}" } } } @@ -186,16 +310,21 @@ function ConnectGuide() { }, code: { label: 'Claude Code', + hint: 'Run in terminal', config: `claude mcp add codeintel \\ --transport http \\ https://mcp.opencodeintel.com/mcp`, }, cursor: { label: 'Cursor', + hint: '.cursor/mcp.json', config: `{ "mcpServers": { "codeintel": { - "url": "https://mcp.opencodeintel.com/mcp" + "url": "https://mcp.opencodeintel.com/mcp", + "headers": { + "Authorization": "Bearer ${keyDisplay}" + } } } }`, @@ -207,7 +336,10 @@ function ConnectGuide() { return (
- Connect to your tools +
+ + Connect to your tools +
{Object.entries(snippets).map(([key, { label }]) => (
+ + {/* Hint line */} +
+

+ {current.hint} +

+
+
-
+        
           {current.config}
         
-
+
+ + {/* Connection test */} +
) } @@ -435,7 +578,9 @@ export function APIKeysPage() { )} {/* Connect guide */} - {activeKeys.length > 0 && } + {activeKeys.length > 0 && ( + + )} {/* Generate dialog */} { if (!open && !generatedKey) closeGenerateDialog() }}> From bfc549e8dc03b49bc312e20640219c2abd575b70 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Tue, 10 Mar 2026 19:43:43 -0400 Subject: [PATCH 2/3] fix: connection test auth, MCP URL config, Claude Code auth header Connection test was broken: used key_preview (truncated, e.g. ci_80ba...e980) for Authorization header -- always 401 because backend hashes the full key. Fixed: ConnectionTest now receives the session JWT token from useAuth, which works with require_auth middleware. The test verifies the user's backend access, not the API key specifically. Added MCP_URL to config/api.ts (separate Railway service from API_URL). Health check now uses env-derived MCP_URL instead of hardcoded string. Claude Code snippet now includes --header flag for auth, matching how Desktop (env.API_KEY) and Cursor (headers.Authorization) handle it. --- frontend/src/config/api.ts | 3 +++ frontend/src/pages/APIKeysPage.tsx | 28 ++++++++++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/src/config/api.ts b/frontend/src/config/api.ts index 047a55c..8629d57 100644 --- a/frontend/src/config/api.ts +++ b/frontend/src/config/api.ts @@ -58,6 +58,9 @@ export const buildWsUrl = (path: string): string => { return `${WS_URL}${cleanPath}` } +// MCP server URL (separate Railway service from the API) +export const MCP_URL = import.meta.env.VITE_MCP_URL || 'https://mcp.opencodeintel.com' + // free tier repo limit -- used in dashboard and GitHub import export const MAX_FREE_REPOS = 1 diff --git a/frontend/src/pages/APIKeysPage.tsx b/frontend/src/pages/APIKeysPage.tsx index 7c9d46a..4db20fd 100644 --- a/frontend/src/pages/APIKeysPage.tsx +++ b/frontend/src/pages/APIKeysPage.tsx @@ -14,7 +14,7 @@ import { DialogTitle, } from '@/components/ui/dialog' import { toast } from 'sonner' -import { API_URL } from '@/config/api' +import { API_URL, MCP_URL } from '@/config/api' import { cn } from '@/lib/utils' interface APIKey { @@ -168,7 +168,7 @@ function KeyCard({ type TestStep = { label: string; status: 'idle' | 'running' | 'pass' | 'fail' } -function ConnectionTest({ apiKey }: { apiKey: string | null }) { +function ConnectionTest({ token }: { token: string | null }) { const [steps, setSteps] = useState([ { label: 'MCP server reachable', status: 'idle' }, { label: 'API key authenticated', status: 'idle' }, @@ -178,7 +178,7 @@ function ConnectionTest({ apiKey }: { apiKey: string | null }) { const [tested, setTested] = useState(false) const runTest = useCallback(async () => { - if (!apiKey || running) return + if (!token || running) return setRunning(true) setTested(true) const update = (idx: number, status: TestStep['status']) => @@ -190,18 +190,18 @@ function ConnectionTest({ apiKey }: { apiKey: string | null }) { // Step 1: MCP health update(0, 'running') try { - const res = await fetch('https://mcp.opencodeintel.com/health') + const res = await fetch(`${MCP_URL}/health`) update(0, res.ok ? 'pass' : 'fail') if (!res.ok) { setRunning(false); return } } catch { update(0, 'fail'); setRunning(false); return } - // Step 2: Auth check + // Step 2: Auth check (uses session JWT, not API key preview) update(1, 'running') try { const res = await fetch(`${API_URL}/keys`, { - headers: { Authorization: `Bearer ${apiKey}` }, + headers: { Authorization: `Bearer ${token}` }, }) update(1, res.ok ? 'pass' : 'fail') if (!res.ok) { setRunning(false); return } @@ -213,7 +213,7 @@ function ConnectionTest({ apiKey }: { apiKey: string | null }) { update(2, 'running') try { const res = await fetch(`${API_URL}/repos`, { - headers: { Authorization: `Bearer ${apiKey}` }, + headers: { Authorization: `Bearer ${token}` }, }) update(2, res.ok ? 'pass' : 'fail') } catch { @@ -221,7 +221,7 @@ function ConnectionTest({ apiKey }: { apiKey: string | null }) { } setRunning(false) - }, [apiKey, running]) + }, [token, running]) const allPassed = steps.every((s) => s.status === 'pass') const anyFailed = steps.some((s) => s.status === 'fail') @@ -234,7 +234,7 @@ function ConnectionTest({ apiKey }: { apiKey: string | null }) {
{/* Connection test */} - +
) } @@ -579,7 +580,10 @@ export function APIKeysPage() { {/* Connect guide */} {activeKeys.length > 0 && ( - + )} {/* Generate dialog */} From df958de8abb1d39438e2e0247cfda8c8166c6834 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Tue, 10 Mar 2026 19:53:21 -0400 Subject: [PATCH 3/3] fix: add space in Authorization header for Claude Code snippet 'Authorization:Bearer' -> 'Authorization: Bearer' per HTTP spec. Missing space could cause header parsing failures on strict servers. Skipped preview-in-config finding: ConnectionTest already uses session JWT (fixed in prior commit). Config snippets intentionally show masked preview (ci_80ba...e980) as a visual hint -- the '...' masking clearly signals 'replace with your key'. --- frontend/src/pages/APIKeysPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/APIKeysPage.tsx b/frontend/src/pages/APIKeysPage.tsx index 4db20fd..45de083 100644 --- a/frontend/src/pages/APIKeysPage.tsx +++ b/frontend/src/pages/APIKeysPage.tsx @@ -313,7 +313,7 @@ function ConnectGuide({ activeKeyPreview, sessionToken }: { activeKeyPreview: st hint: 'Run in terminal', config: `claude mcp add codeintel \\ --transport http \\ - --header "Authorization:Bearer ${keyDisplay}" \\ + --header "Authorization: Bearer ${keyDisplay}" \\ https://mcp.opencodeintel.com/mcp`, }, cursor: {