import { useState, useMemo, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { apiFetch } from '../../lib/api'; import { useI18n } from '../../lib/i18n'; import { useLayoutContext } from '../Layout'; import PasswordRevealPrompt from '../../components/PasswordRevealPrompt'; import { MCP_CLIENTS, buildClientConfig, sanitizeServerName, type McpClientId } from '../../lib/mcp-clients'; type Project = { id: string; name: string }; type ApiKeyStatus = { hasKey: boolean; prefix: string | null }; type McpInfo = { projectName: string; openApiVersion: string; totalEndpoints: number; totalModules: number; toolCount: number; }; type TestStatus = | { kind: 'idle' } | { kind: 'loading' } | { kind: 'ok'; info: McpInfo } | { kind: 'error'; message: string }; export default function McpIntegration({ project }: { project: Project }) { const { t } = useI18n(); const { onOpenSettings } = useLayoutContext(); const mcpUrl = `${window.location.origin}/mcp/${project.id}`; const serverName = useMemo(() => sanitizeServerName(project.name), [project.name]); const [activeClient, setActiveClient] = useState('claude-desktop'); const [revealedKey, setRevealedKey] = useState(null); const [passwordPromptOpen, setPasswordPromptOpen] = useState(false); const [copied, setCopied] = useState(null); const [testStatus, setTestStatus] = useState({ kind: 'idle' }); const { data: keyStatus } = useQuery({ queryKey: ['api-key-status'], queryFn: () => apiFetch('/auth/api-key/status'), }); useEffect(() => { setTestStatus({ kind: 'idle' }); }, [mcpUrl]); const client = MCP_CLIENTS.find((c) => c.id === activeClient) ?? MCP_CLIENTS[0]; const configSnippet = buildClientConfig(client, serverName, mcpUrl, revealedKey); const copyText = (text: string, key: string) => { navigator.clipboard.writeText(text); setCopied(key); setTimeout(() => setCopied(null), 2000); }; const requestRevealAndCopy = () => { if (!keyStatus?.hasKey) return; if (revealedKey) { copyText(buildClientConfig(client, serverName, mcpUrl, revealedKey), 'config'); return; } setPasswordPromptOpen(true); }; const handleReveal = (apiKey: string) => { setRevealedKey(apiKey); // Copy synchronously off the user gesture chain — re-using `apiKey` instead // of `revealedKey` because state hasn't propagated yet. copyText(buildClientConfig(client, serverName, mcpUrl, apiKey), 'config'); setPasswordPromptOpen(false); }; const testConnection = async () => { setTestStatus({ kind: 'loading' }); try { const res = await fetch(`${mcpUrl}/info`); if (!res.ok) { setTestStatus({ kind: 'error', message: `HTTP ${res.status}` }); return; } const info = (await res.json()) as McpInfo; setTestStatus({ kind: 'ok', info }); } catch (err) { setTestStatus({ kind: 'error', message: err instanceof Error ? err.message : 'Network error' }); } }; return (
{/* MCP URL */}

{t('dashboard.mcp.urlTitle')}

{t('dashboard.mcp.urlDesc')}

{mcpUrl}
{testStatus.kind === 'ok' && (
{t('dashboard.mcp.testOk')}
{testStatus.info.projectName} · OpenAPI {testStatus.info.openApiVersion} · {testStatus.info.totalModules} {t('common.modules')} · {testStatus.info.totalEndpoints} {t('common.endpoints')} · {testStatus.info.toolCount} tools
)} {testStatus.kind === 'error' && (
{t('dashboard.mcp.testFail')}: {testStatus.message}
)}
{/* Client selector + Config */}

{t('dashboard.mcp.configTitle')}

{t('dashboard.mcp.configDesc')}

{/* Client tabs */}
{MCP_CLIENTS.map((c) => ( ))}
{/* Setup hint */}
{t('dashboard.mcp.configLocation')}: {client.configPath}
{client.configPathAlt && (
{t('dashboard.mcp.configLocationAlt')}: {client.configPathAlt}
)}
{client.restartHint}
{/* Config snippet */}
{configSnippet}
{keyStatus?.hasKey && !revealedKey && ( )}
setPasswordPromptOpen(false)} onReveal={handleReveal} onSetPasswordClick={() => { setPasswordPromptOpen(false); onOpenSettings(); }} />
{/* API Key status */} {keyStatus && !keyStatus.hasKey && (

{t('dashboard.mcp.noKeyWarning')}

)}
{/* Available tools */}

{t('dashboard.mcp.toolsTitle')}

{t('dashboard.mcp.toolsDesc')}

{[ { name: 'get_project_overview', desc: t('dashboard.mcp.tool1Desc'), num: '1' }, { name: 'list_modules', desc: t('dashboard.mcp.tool2Desc'), num: '2' }, { name: 'list_endpoints', desc: t('dashboard.mcp.tool3Desc'), num: '3' }, { name: 'get_endpoint_detail', desc: t('dashboard.mcp.tool4Desc'), num: '4' }, { name: 'search_endpoints', desc: t('dashboard.mcp.tool5Desc'), num: '5' }, ].map((tool) => (
{tool.num}
{tool.name}

{tool.desc}

))}
); }