Files
vibn-frontend/components/mcp-connection-card.tsx

300 lines
10 KiB
TypeScript

"use client";
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { auth } from '@/lib/firebase/config';
import { toast } from 'sonner';
import { Copy, Eye, EyeOff, RefreshCw, Trash2, ExternalLink } from 'lucide-react';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export function MCPConnectionCard() {
const [apiKey, setApiKey] = useState<string>('');
const [loading, setLoading] = useState(false);
const [showKey, setShowKey] = useState(false);
const [hasKey, setHasKey] = useState(false);
const mcpServerUrl = typeof window !== 'undefined'
? `${window.location.origin}/api/mcp`
: 'https://vibnai.com/api/mcp';
useEffect(() => {
loadExistingKey();
}, []);
const loadExistingKey = async () => {
try {
const user = auth.currentUser;
if (!user) return;
const token = await user.getIdToken();
const response = await fetch('/api/mcp/generate-key', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
setApiKey(data.apiKey);
setHasKey(true);
}
} catch (error) {
console.error('Error loading MCP key:', error);
}
};
const generateKey = async () => {
setLoading(true);
try {
const user = auth.currentUser;
if (!user) {
toast.error('Please sign in to generate an API key');
return;
}
const token = await user.getIdToken();
const response = await fetch('/api/mcp/generate-key', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Failed to generate API key');
}
const data = await response.json();
setApiKey(data.apiKey);
setHasKey(true);
toast.success('MCP API key generated!');
} catch (error) {
console.error('Error generating API key:', error);
toast.error('Failed to generate API key');
} finally {
setLoading(false);
}
};
const revokeKey = async () => {
setLoading(true);
try {
const user = auth.currentUser;
if (!user) return;
const token = await user.getIdToken();
const response = await fetch('/api/mcp/generate-key', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Failed to revoke API key');
}
setApiKey('');
setHasKey(false);
toast.success('MCP API key revoked');
} catch (error) {
console.error('Error revoking API key:', error);
toast.error('Failed to revoke API key');
} finally {
setLoading(false);
}
};
const copyToClipboard = (text: string, label: string) => {
navigator.clipboard.writeText(text);
toast.success(`${label} copied to clipboard`);
};
const copyAllSettings = () => {
const settings = `Name: Vibn
Description: Access your Vibn coding projects, sessions, and AI conversation history
MCP Server URL: ${mcpServerUrl}
Authentication: Bearer
API Key: ${apiKey}`;
navigator.clipboard.writeText(settings);
toast.success('All settings copied! Paste into ChatGPT');
};
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>ChatGPT Integration (MCP)</CardTitle>
<CardDescription>
Connect ChatGPT to your Vibn data using the Model Context Protocol
</CardDescription>
</div>
<a
href="https://help.openai.com/en/articles/10206189-connecting-mcp-servers"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground flex items-center gap-1"
>
Setup Guide <ExternalLink className="h-3 w-3" />
</a>
</div>
</CardHeader>
<CardContent className="space-y-6">
{!hasKey ? (
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Generate an API key to connect ChatGPT to your Vibn projects. This key allows
ChatGPT to access your project data, coding sessions, and conversation history.
</p>
<Button onClick={generateKey} disabled={loading}>
{loading ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
'Generate MCP API Key'
)}
</Button>
</div>
) : (
<div className="space-y-6">
{/* MCP Server URL */}
<div className="space-y-2">
<Label htmlFor="mcp-url">MCP Server URL</Label>
<div className="flex gap-2">
<Input
id="mcp-url"
value={mcpServerUrl}
readOnly
className="font-mono text-sm"
/>
<Button
variant="outline"
size="icon"
onClick={() => copyToClipboard(mcpServerUrl, 'MCP Server URL')}
>
<Copy className="h-4 w-4" />
</Button>
</div>
</div>
{/* API Key */}
<div className="space-y-2">
<Label htmlFor="mcp-key">API Key</Label>
<div className="flex gap-2">
<div className="relative flex-1">
<Input
id="mcp-key"
type={showKey ? 'text' : 'password'}
value={apiKey}
readOnly
className="font-mono text-sm pr-10"
/>
<Button
variant="ghost"
size="icon"
className="absolute right-0 top-0 h-full"
onClick={() => setShowKey(!showKey)}
>
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<Button
variant="outline"
size="icon"
onClick={() => copyToClipboard(apiKey, 'API Key')}
>
<Copy className="h-4 w-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
This key doesn't expire. Keep it secure and never share it publicly.
</p>
</div>
{/* Quick Copy Button */}
<div className="rounded-lg border bg-muted/50 p-4">
<p className="text-sm font-medium mb-2">Quick Setup for ChatGPT</p>
<p className="text-xs text-muted-foreground mb-3">
Click below to copy all settings, then paste them into ChatGPT's "New Connector" form
</p>
<Button onClick={copyAllSettings} className="w-full">
<Copy className="mr-2 h-4 w-4" />
Copy All Settings
</Button>
</div>
{/* Setup Instructions */}
<div className="rounded-lg border p-4 space-y-3">
<p className="text-sm font-medium">Setup Steps:</p>
<ol className="text-sm text-muted-foreground space-y-2 list-decimal list-inside">
<li>Click "Copy All Settings" above</li>
<li>Open ChatGPT and go to Settings Personalization Custom Tools</li>
<li>Click "Add New Connector"</li>
<li>Fill in the form with the copied settings</li>
<li>Set Authentication to "Bearer" and paste the API Key</li>
<li>Check "I understand" and click Create</li>
</ol>
</div>
{/* Try It */}
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-4">
<p className="text-sm font-medium text-green-700 dark:text-green-400 mb-2">
Try asking ChatGPT:
</p>
<ul className="text-sm text-muted-foreground space-y-1">
<li> "Show me my Vibn projects"</li>
<li> "What are my recent coding sessions?"</li>
<li> "How much have I spent on AI this month?"</li>
</ul>
</div>
{/* Revoke Key */}
<div className="pt-4 border-t">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm" disabled={loading}>
<Trash2 className="mr-2 h-4 w-4" />
Revoke API Key
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Revoke MCP API Key?</AlertDialogTitle>
<AlertDialogDescription>
This will immediately disconnect ChatGPT from your Vibn data. You'll need
to generate a new key to reconnect.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={revokeKey}>Revoke Key</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
)}
</CardContent>
</Card>
);
}