feat: add React frontend with auth, project list, import, and project detail pages
Converts packages/web from vanilla TypeScript Vite scaffold to React with: - React 19, react-router-dom v7, @tanstack/react-query v5, Tailwind CSS v4 - JWT auth context with auto-refresh token support - Login/Register pages, protected Layout with auth guard - Projects list with grid cards and delete action - ImportDialog supporting URL or file upload with API key display - ProjectDetail with 4 tabs: Documentation, Modules, MCP Integration, Settings - All TypeScript compiles cleanly (noEmit check passes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
74
packages/web/src/pages/tabs/McpIntegration.tsx
Normal file
74
packages/web/src/pages/tabs/McpIntegration.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
|
||||
type Project = { id: string; name: string };
|
||||
|
||||
export default function McpIntegration({ project }: { project: Project }) {
|
||||
const [apiKey, setApiKey] = useState<string | null>(null);
|
||||
const mcpBaseUrl = window.location.origin;
|
||||
const mcpUrl = `${mcpBaseUrl}/mcp/${project.id}`;
|
||||
|
||||
const rotateMutation = useMutation({
|
||||
mutationFn: () => apiFetch<{ apiKey: string }>(`/projects/${project.id}/api-key/rotate`, { method: 'POST' }),
|
||||
onSuccess: (data) => setApiKey(data.apiKey),
|
||||
});
|
||||
|
||||
const configSnippet = JSON.stringify({
|
||||
mcpServers: {
|
||||
[project.name.toLowerCase().replace(/\s+/g, '-')]: {
|
||||
url: mcpUrl,
|
||||
headers: { Authorization: `Bearer ${apiKey || '<your-api-key>'}` },
|
||||
},
|
||||
},
|
||||
}, null, 2);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-2xl">
|
||||
<div>
|
||||
<h3 className="font-medium mb-2">MCP Service URL</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 px-3 py-2 bg-gray-100 rounded text-sm font-mono">{mcpUrl}</code>
|
||||
<button onClick={() => navigator.clipboard.writeText(mcpUrl)} className="px-3 py-2 text-sm bg-gray-200 rounded hover:bg-gray-300">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-2">API Key</h3>
|
||||
{apiKey ? (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded">
|
||||
<p className="text-xs text-yellow-700 mb-1">Save this key — it won't be shown again.</p>
|
||||
<code className="text-sm break-all">{apiKey}</code>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">API key is hidden. Rotate to generate a new one.</p>
|
||||
)}
|
||||
<button onClick={() => { if (confirm('This will invalidate the current API key. Continue?')) rotateMutation.mutate(); }}
|
||||
className="mt-2 px-3 py-1 text-sm bg-orange-100 text-orange-700 rounded hover:bg-orange-200">Rotate API Key</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-2">Configuration for Claude Code / Cursor</h3>
|
||||
<div className="relative">
|
||||
<pre className="bg-gray-900 text-green-400 p-4 rounded text-sm overflow-auto">{configSnippet}</pre>
|
||||
<button onClick={() => navigator.clipboard.writeText(configSnippet)} className="absolute top-2 right-2 px-2 py-1 text-xs bg-gray-700 text-white rounded hover:bg-gray-600">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-2">Available Tools</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
{[
|
||||
{ name: 'get_project_overview', desc: 'Get project name, version, base URL, and module summary. Call this first.' },
|
||||
{ name: 'list_modules', desc: 'List all modules with descriptions and endpoint counts.' },
|
||||
{ name: 'list_endpoints', desc: 'List endpoints in a module. Provide moduleId.' },
|
||||
{ name: 'get_endpoint_detail', desc: 'Get full endpoint details: parameters, request body, responses.' },
|
||||
{ name: 'search_endpoints', desc: 'Search by keyword across all endpoints. Optional moduleId filter.' },
|
||||
].map((t) => (
|
||||
<div key={t.name} className="p-3 bg-gray-50 rounded">
|
||||
<code className="font-medium">{t.name}</code>
|
||||
<p className="text-gray-500 mt-1">{t.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user