/** * Vibn MCP HTTP API * * Exposes MCP capabilities over HTTP for web-based AI assistants */ import { NextResponse } from 'next/server'; import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin'; export async function POST(request: Request) { try { // Authenticate user const authHeader = request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const token = authHeader.split('Bearer ')[1]; const adminAuth = getAdminAuth(); const adminDb = getAdminDb(); let userId: string; // Try MCP API key first (for ChatGPT integration) if (token.startsWith('vibn_mcp_')) { const mcpKeysSnapshot = await adminDb .collection('mcpKeys') .where('key', '==', token) .limit(1) .get(); if (mcpKeysSnapshot.empty) { return NextResponse.json({ error: 'Invalid MCP API key' }, { status: 401 }); } const keyDoc = mcpKeysSnapshot.docs[0]; userId = keyDoc.data().userId; // Update last used timestamp await keyDoc.ref.update({ lastUsed: new Date().toISOString(), }); } else { // Try Firebase ID token (for direct user access) try { const decodedToken = await adminAuth.verifyIdToken(token); userId = decodedToken.uid; } catch (error) { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } } const body = await request.json(); const { action, params } = body; // Handle different MCP actions switch (action) { case 'list_resources': { return NextResponse.json({ resources: [ { uri: `vibn://projects/${userId}`, name: 'My Projects', description: 'All your Vibn projects', mimeType: 'application/json', }, { uri: `vibn://sessions/${userId}`, name: 'My Sessions', description: 'All your coding sessions', mimeType: 'application/json', }, ], }); } case 'read_resource': { const { uri } = params; if (uri === `vibn://projects/${userId}`) { const projectsSnapshot = await adminDb .collection('projects') .where('userId', '==', userId) .orderBy('createdAt', 'desc') .limit(50) .get(); const projects = projectsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), })); return NextResponse.json({ contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(projects, null, 2), }, ], }); } if (uri.startsWith('vibn://projects/') && uri.split('/').length === 4) { const projectId = uri.split('/')[3]; const projectDoc = await adminDb.collection('projects').doc(projectId).get(); if (!projectDoc.exists || projectDoc.data()?.userId !== userId) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } return NextResponse.json({ contents: [ { uri, mimeType: 'application/json', text: JSON.stringify({ id: projectDoc.id, ...projectDoc.data() }, null, 2), }, ], }); } if (uri === `vibn://sessions/${userId}`) { const sessionsSnapshot = await adminDb .collection('sessions') .where('userId', '==', userId) .orderBy('createdAt', 'desc') .limit(50) .get(); const sessions = sessionsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), })); return NextResponse.json({ contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(sessions, null, 2), }, ], }); } return NextResponse.json({ error: 'Unknown resource' }, { status: 404 }); } case 'call_tool': { const { name, arguments: args } = params; if (name === 'get_project_summary') { const { projectId } = args; const projectDoc = await adminDb.collection('projects').doc(projectId).get(); if (!projectDoc.exists || projectDoc.data()?.userId !== userId) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } const project = { id: projectDoc.id, ...projectDoc.data() }; const sessionsSnapshot = await adminDb .collection('sessions') .where('projectId', '==', projectId) .where('userId', '==', userId) .get(); const sessions = sessionsSnapshot.docs.map(doc => doc.data()); const totalCost = sessions.reduce((sum, s: any) => sum + (s.cost || 0), 0); const totalTokens = sessions.reduce((sum, s: any) => sum + (s.tokensUsed || 0), 0); const totalDuration = sessions.reduce((sum, s: any) => sum + (s.duration || 0), 0); const summary = { project, stats: { totalSessions: sessions.length, totalCost, totalTokens, totalDuration, }, recentSessions: sessions.slice(0, 5), }; return NextResponse.json({ content: [ { type: 'text', text: JSON.stringify(summary, null, 2), }, ], }); } if (name === 'search_sessions') { const { projectId, workspacePath } = args; let query = adminDb.collection('sessions').where('userId', '==', userId); if (projectId) { query = query.where('projectId', '==', projectId) as any; } if (workspacePath) { query = query.where('workspacePath', '==', workspacePath) as any; } const snapshot = await (query as any).orderBy('createdAt', 'desc').limit(50).get(); const sessions = snapshot.docs.map((doc: any) => ({ id: doc.id, ...doc.data(), })); return NextResponse.json({ content: [ { type: 'text', text: JSON.stringify(sessions, null, 2), }, ], }); } if (name === 'get_conversation_context') { const { projectId, limit = 50 } = args; const projectDoc = await adminDb.collection('projects').doc(projectId).get(); if (!projectDoc.exists || projectDoc.data()?.userId !== userId) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } const conversationsSnapshot = await adminDb .collection('projects') .doc(projectId) .collection('aiConversations') .orderBy('createdAt', 'asc') .limit(limit) .get(); const conversations = conversationsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), })); return NextResponse.json({ content: [ { type: 'text', text: JSON.stringify(conversations, null, 2), }, ], }); } return NextResponse.json({ error: 'Unknown tool' }, { status: 404 }); } default: return NextResponse.json({ error: 'Unknown action' }, { status: 400 }); } } catch (error) { console.error('MCP API error:', error); return NextResponse.json( { error: 'Failed to process MCP request', details: error instanceof Error ? error.message : String(error), }, { status: 500 } ); } } // GET endpoint to list capabilities export async function GET(request: Request) { return NextResponse.json({ name: 'vibn-mcp-server', version: '1.0.0', capabilities: { resources: { supported: true, endpoints: [ 'vibn://projects/{userId}', 'vibn://projects/{userId}/{projectId}', 'vibn://sessions/{userId}', ], }, tools: { supported: true, available: [ 'get_project_summary', 'search_sessions', 'get_conversation_context', ], }, }, documentation: 'https://vibnai.com/docs/mcp', }); }