Skip to main content
Glama

Cursor DB MCP Server

by TaylorChen
index.ts10.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { Command } from 'commander'; import { CursorDatabase } from './database.js'; import { chatHistoryTools } from './tools/chat-history.js'; import { workspaceTools } from './tools/workspace-tools.js'; import { diagnosticsTools } from './tools/diagnostics.js'; const program = new Command(); program .name('cursor-db-mcp') .description('MCP Server for Cursor IDE database access (new workspaceStorage format)') .version('2.0.0') .option('--workspace-path <path>', 'Custom path to Cursor workspaceStorage directory') .option('--port <port>', 'Port to run the server on (for HTTP transport)') .parse(); const options = program.opts(); class CursorMCPServer { private server: Server; private database: CursorDatabase; constructor(workspacePath?: string) { this.server = new Server( { name: 'cursor-db-mcp', version: '2.0.0' }, { capabilities: { tools: {} } } ); this.database = new CursorDatabase(workspacePath); this.setupHandlers(); } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [...chatHistoryTools, ...workspaceTools, ...diagnosticsTools] }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params as any; try { switch (name) { case 'list_workspaces': { const { recent_days } = args as any; let result; if (recent_days) { result = await this.database.getRecentWorkspaces(recent_days); } else { result = await this.database.getAllWorkspaces(); } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'get_workspace_conversations': { const { workspace_hash } = args as any; if (!workspace_hash) throw new Error('workspace_hash is required'); const result = await this.database.getWorkspaceConversations(workspace_hash); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'get_all_conversations': { const { limit = 50 } = args as any; const result = await this.database.getAllConversations(); if (result.success && Array.isArray(result.data)) { const limitedData = result.data.slice(0, limit); return { content: [{ type: 'text', text: JSON.stringify({ ...result, data: limitedData }, null, 2) }] }; } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'search_conversations': { const { query, limit = 20 } = args as any; if (!query) throw new Error('query is required'); const result = await this.database.searchConversations(query, limit); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'analyze_conversation': { const { conversation_id } = args as any; if (!conversation_id) throw new Error('conversation_id is required'); const result = await this.database.analyzeConversation(conversation_id); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'export_conversations': { const { format = 'json', conversation_id, conversation_ids } = args as any; const result = await this.database.exportConversations(format, { conversation_id, conversation_ids }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'analyze_code_statistics': { const { days = 30, group_by = 'day' } = args as any; const result = await this.analyzeCodeStatistics(days, group_by); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'diagnose_storage': { const { limit = 30 } = args as any; const result = await this.diagnoseStorage(limit); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true } as any; } }); } private async analyzeCodeStatistics(days: number, groupBy: string) { try { const conversationsResult = await this.database.getAllConversations(); if (!conversationsResult.success) return conversationsResult; const conversations = conversationsResult.data as any[]; const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); const recentConversations = conversations.filter((conv: any) => new Date(conv.updatedAt) >= cutoffDate); const statistics = { totalConversations: recentConversations.length, totalMessages: 0, totalCodeBlocks: 0, totalLinesAdded: 0, totalLinesModified: 0, totalLinesDeleted: 0, languageStats: {} as Record<string, any>, workspaceStats: {} as Record<string, any>, dailyStats: {} as Record<string, any>, fileChanges: [] as any[] }; for (const conversation of recentConversations) { statistics.totalMessages += conversation.messages.length; const analysisResult = await this.database.analyzeConversation(conversation.id); if (analysisResult.success && analysisResult.data) { const analysis = (analysisResult.data as any).analysis; statistics.totalCodeBlocks += analysis.codeBlocks; statistics.totalLinesAdded += analysis.totalLinesAdded; statistics.totalLinesModified += analysis.totalLinesModified; statistics.totalLinesDeleted += analysis.totalLinesDeleted; const workspace = conversation.workspaceFolder || 'Unknown'; if (!statistics.workspaceStats[workspace]) { statistics.workspaceStats[workspace] = { conversations: 0, linesAdded: 0, linesModified: 0, linesDeleted: 0 }; } statistics.workspaceStats[workspace].conversations++; statistics.workspaceStats[workspace].linesAdded += analysis.totalLinesAdded; statistics.workspaceStats[workspace].linesModified += analysis.totalLinesModified; statistics.workspaceStats[workspace].linesDeleted += analysis.totalLinesDeleted; if (analysis.fileChanges) { analysis.fileChanges.forEach((change: any) => { const extension = change.file.split('.').pop() || 'unknown'; if (!statistics.languageStats[extension]) { statistics.languageStats[extension] = { files: 0, linesAdded: 0, linesModified: 0, linesDeleted: 0 }; } statistics.languageStats[extension].files++; statistics.languageStats[extension].linesAdded += change.additions; statistics.languageStats[extension].linesDeleted += change.deletions; }); statistics.fileChanges.push(...analysis.fileChanges); } const day = conversation.updatedAt.split('T')[0]; if (!statistics.dailyStats[day]) { statistics.dailyStats[day] = { conversations: 0, messages: 0, linesAdded: 0, linesModified: 0, linesDeleted: 0 }; } statistics.dailyStats[day].conversations++; statistics.dailyStats[day].messages += conversation.messages.length; statistics.dailyStats[day].linesAdded += analysis.totalLinesAdded; statistics.dailyStats[day].linesModified += analysis.totalLinesModified; statistics.dailyStats[day].linesDeleted += analysis.totalLinesDeleted; } } return { success: true, data: { period: `${days} days`, groupBy, statistics, generatedAt: new Date().toISOString() } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } private async diagnoseStorage(limit: number) { try { const conversationsResult = await this.database.getAllWorkspaces(); if (!conversationsResult.success) return conversationsResult; const workspaces = (conversationsResult.data as any[]) || []; const sqlite3 = (await import('sqlite3')).default; const path = (await import('path')).default; const summary: any[] = []; for (const ws of workspaces) { const dbPath = path.join(ws.path, 'state.vscdb'); const db = new sqlite3.Database(dbPath); const rows: any = await new Promise((resolve) => { const q = `SELECT [key], length(value) AS len FROM ItemTable ORDER BY len DESC LIMIT ?`; db.all(q, [limit], (err, result) => { db.close(() => resolve(err ? [] : result)); }); }); summary.push({ workspace: ws.hash, projectPath: ws.projectPath || null, topKeys: rows }); } return { success: true, data: summary }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } } async start() { try { await this.database.initialize(); console.error('Initialized Cursor workspace scanner'); const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Cursor MCP Server (v2.0) running on stdio'); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } async stop() { } } async function main() { const server = new CursorMCPServer(options.workspacePath); process.on('SIGINT', async () => { console.error('Shutting down...'); await server.stop(); process.exit(0); }); await server.start(); } if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error); } export { CursorMCPServer };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TaylorChen/cursor-db-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server