Skip to main content
Glama
by bbajor
cursor-mcp-server.js10.7 kB
#!/usr/bin/env node /** * Cursor MCP Server * * Stellt Cursor-Tools als MCP Server bereit: * - codebase_search: Semantische Suche im Codebase * - file_read: Dateien lesen * - file_list: Verzeichnisse auflisten * - file_write: Dateien schreiben (optional) */ 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 * as fs from 'fs/promises'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Workspace-Pfad aus Environment (default: aktuelles Verzeichnis) const WORKSPACE_PATH = process.env.WORKSPACE_PATH || process.cwd(); const server = new Server( { name: process.env.MCP_SERVER_NAME || 'cursor-mcp', version: process.env.MCP_SERVER_VERSION || '1.0.0', }, { capabilities: { tools: {}, }, } ); // Tools auflisten server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'codebase_search', description: 'Führt eine semantische Suche im Codebase durch. Sucht nach Code, Funktionen, Klassen oder Konzepten basierend auf einer natürlichen Sprachanfrage.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Die Suchanfrage in natürlicher Sprache (z.B. "Wie wird die Patient-Suche implementiert?")', }, target_directories: { type: 'array', items: { type: 'string' }, description: 'Optionale Liste von Verzeichnissen, in denen gesucht werden soll. Leer = gesamter Workspace', }, }, required: ['query'], }, }, { name: 'file_read', description: 'Liest den Inhalt einer Datei aus dem Workspace.', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Relativer Pfad zur Datei (relativ zum Workspace-Root)', }, offset: { type: 'number', description: 'Optionale Zeilennummer zum Starten (1-basiert)', }, limit: { type: 'number', description: 'Optionale maximale Anzahl von Zeilen zum Lesen', }, }, required: ['path'], }, }, { name: 'file_list', description: 'Listet Dateien und Verzeichnisse in einem Verzeichnis auf.', inputSchema: { type: 'object', properties: { directory: { type: 'string', description: 'Relativer Pfad zum Verzeichnis (relativ zum Workspace-Root). Leer = Root', }, recursive: { type: 'boolean', description: 'Soll rekursiv gelistet werden?', default: false, }, ignore_globs: { type: 'array', items: { type: 'string' }, description: 'Optionale Glob-Patterns zum Ignorieren (z.B. ["node_modules/**", "*.log"])', }, }, required: ['directory'], }, }, { name: 'file_write', description: 'Schreibt oder aktualisiert eine Datei im Workspace. Vorsicht: Kann bestehende Dateien überschreiben!', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Relativer Pfad zur Datei (relativ zum Workspace-Root)', }, contents: { type: 'string', description: 'Der Inhalt, der in die Datei geschrieben werden soll', }, }, required: ['path', 'contents'], }, }, ], }; }); // Tool-Aufrufe verarbeiten server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'codebase_search': { // Für echte semantische Suche bräuchtest du eine Vector-DB oder LLM // Hier eine einfache Text-Suche als Fallback const query = args.query || ''; const targetDirs = args.target_directories || []; // Einfache Implementierung: Suche nach Dateien, die den Query-Text enthalten // In Produktion: Nutze echte semantische Suche (z.B. über Cursor API) const results = await searchInFiles(query, targetDirs); return { content: [ { type: 'text', text: JSON.stringify({ query, results: results.slice(0, 10), // Top 10 Ergebnisse total: results.length, }, null, 2), }, ], }; } case 'file_read': { const filePath = path.resolve(WORKSPACE_PATH, args.path); // Sicherheitsprüfung: Nur innerhalb Workspace if (!filePath.startsWith(path.resolve(WORKSPACE_PATH))) { throw new Error('Pfad außerhalb des Workspace nicht erlaubt'); } const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); let output = content; if (args.offset || args.limit) { const start = (args.offset || 1) - 1; const end = args.limit ? start + args.limit : lines.length; output = lines.slice(start, end).join('\n'); } return { content: [ { type: 'text', text: output, }, ], }; } case 'file_list': { const dirPath = args.directory ? path.resolve(WORKSPACE_PATH, args.directory) : WORKSPACE_PATH; // Sicherheitsprüfung if (!dirPath.startsWith(path.resolve(WORKSPACE_PATH))) { throw new Error('Pfad außerhalb des Workspace nicht erlaubt'); } const items = await listDirectory(dirPath, args.recursive || false, args.ignore_globs || []); return { content: [ { type: 'text', text: JSON.stringify(items, null, 2), }, ], }; } case 'file_write': { const filePath = path.resolve(WORKSPACE_PATH, args.path); // Sicherheitsprüfung if (!filePath.startsWith(path.resolve(WORKSPACE_PATH))) { throw new Error('Pfad außerhalb des Workspace nicht erlaubt'); } // Verzeichnis erstellen, falls nicht vorhanden await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, args.contents, 'utf-8'); return { content: [ { type: 'text', text: `Datei erfolgreich geschrieben: ${args.path}`, }, ], }; } default: throw new Error(`Unbekanntes Tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Fehler: ${error.message}`, }, ], isError: true, }; } }); // Hilfsfunktionen async function searchInFiles(query, targetDirs) { const results = []; const searchDirs = targetDirs.length > 0 ? targetDirs.map(d => path.resolve(WORKSPACE_PATH, d)) : [WORKSPACE_PATH]; for (const dir of searchDirs) { try { const files = await getAllFiles(dir); for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); if (content.toLowerCase().includes(query.toLowerCase())) { results.push({ file: path.relative(WORKSPACE_PATH, file), matches: content.split('\n').filter(line => line.toLowerCase().includes(query.toLowerCase()) ).length, }); } } catch (err) { // Datei kann nicht gelesen werden (binär, etc.) - überspringen } } } catch (err) { // Verzeichnis existiert nicht - überspringen } } return results; } async function getAllFiles(dir) { const files = []; try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { // Ignoriere node_modules, .git, etc. if (!entry.name.startsWith('.') && entry.name !== 'node_modules') { files.push(...await getAllFiles(fullPath)); } } else if (entry.isFile()) { files.push(fullPath); } } } catch (err) { // Verzeichnis kann nicht gelesen werden } return files; } async function listDirectory(dirPath, recursive, ignoreGlobs) { const items = []; try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); const relativePath = path.relative(WORKSPACE_PATH, fullPath); // Ignore-Patterns prüfen if (shouldIgnore(relativePath, ignoreGlobs)) { continue; } if (entry.isDirectory()) { items.push({ name: entry.name, path: relativePath, type: 'directory', }); if (recursive) { items.push(...await listDirectory(fullPath, true, ignoreGlobs)); } } else { items.push({ name: entry.name, path: relativePath, type: 'file', }); } } } catch (err) { // Verzeichnis kann nicht gelesen werden } return items; } function shouldIgnore(filePath, ignoreGlobs) { // Einfache Glob-Implementierung (für Produktion: nutze minimatch oder ähnlich) for (const pattern of ignoreGlobs) { const regex = new RegExp( pattern .replace(/\*\*/g, '.*') .replace(/\*/g, '[^/]*') .replace(/\./g, '\\.') ); if (regex.test(filePath)) { return true; } } return false; } // Server starten async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Cursor MCP Server gestartet'); } main().catch((error) => { console.error('Fehler beim Starten des MCP Servers:', error); process.exit(1); });

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/bbajor/mcpo'

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