UNPKG

@briefhq/mcp-server

Version:

Brief MCP server and CLI – connect Cursor/Claude MCP to your Brief organization

175 lines (174 loc) 6.26 kB
#!/usr/bin/env node import readline from 'node:readline'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; import { tools } from './tools/index.js'; import { TOOL_SCHEMAS } from './tool-schemas.js'; function sendMessage(output, message) { output.write(JSON.stringify(message) + '\n'); } export async function runServer() { const input = process.stdin; const output = process.stdout; const require = createRequire(import.meta.url); let VERSION = '0.0.0'; try { const pkg = require('../package.json'); VERSION = pkg?.version || VERSION; } catch { } console.error(`[MCP] Starting Brief MCP server v${VERSION}`); const rl = readline.createInterface({ input, crlfDelay: Infinity }); async function handleRequest(request) { const { method, id, params } = request; if (method === 'initialize') { if (id !== null && id !== undefined) sendMessage(output, { jsonrpc: '2.0', id, result: { protocolVersion: '2024-11-05', capabilities: { tools: { listChanged: false } }, serverInfo: { name: 'brief-mcp', version: VERSION } } }); return; } if (method === 'initialized') { // No response needed for initialized notification return; } if (method === 'tools/list') { // Use the flat JSON schemas that Cursor can understand const toolsList = Object.values(TOOL_SCHEMAS); console.error(`[MCP] tools/list called - returning ${toolsList.length} tools`); if (id !== null && id !== undefined) sendMessage(output, { jsonrpc: '2.0', id, result: { tools: toolsList } }); return; } if (method === 'tools/call') { const toolParams = params; const name = typeof toolParams?.name === 'string' ? toolParams.name : ''; const args = toolParams?.arguments ?? {}; if (!name) { if (id !== null && id !== undefined) { sendMessage(output, { jsonrpc: '2.0', id, error: { code: -32602, message: 'Invalid "tools/call" params: missing or invalid "name"' } }); } return; } try { console.error(`[MCP] Executing tool: ${name}`); const tool = tools[name]; if (!tool) { throw new Error(`Unknown tool: ${name}`); } let input; try { input = tool.inputSchema.parse(args ?? {}); } catch (e) { throw new Error(`Invalid arguments for tool "${name}": ${e?.message ?? e}`); } const result = await tool.execute(input); console.error(`[MCP] Tool ${name} executed successfully`); if (id !== null && id !== undefined) { sendMessage(output, { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] } }); } } catch (error) { console.error(`[MCP] Tool ${name} failed: ${String(error?.message ?? error)}`); if (id !== null && id !== undefined) { sendMessage(output, { jsonrpc: '2.0', id, error: { code: -32000, message: error.message || 'Tool execution failed' } }); } } return; } // Unknown method if (id !== null && id !== undefined) { sendMessage(output, { jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } }); } } rl.on('line', (line) => { const text = line.trim(); if (!text) return; try { const request = JSON.parse(text); handleRequest(request).catch(err => { console.error(`Error handling request: ${err}`); if (request.id !== null && request.id !== undefined) { sendMessage(output, { jsonrpc: '2.0', id: request.id, error: { code: -32603, message: 'Internal error', data: err.message } }); } }); } catch (err) { console.error(`Failed to parse JSON-RPC request: ${err}`); sendMessage(output, { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }); } }); rl.on('close', () => { process.exit(0); }); console.error("[MCP] Server connected and ready"); } // Only run the server when this module is the entrypoint if (import.meta.url === pathToFileURL(process.argv[1] || '').href) { runServer().catch((err) => { console.error(err); process.exit(1); }); }