@briefhq/mcp-server
Version:
Brief MCP server and CLI – connect Cursor/Claude MCP to your Brief organization
175 lines (174 loc) • 6.26 kB
JavaScript
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);
});
}