#!/usr/bin/env node import Fastify from 'fastify'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { JsonStore } from './storage/json-store.js'; import { Collections } from './storage/collections.js'; import { requireAuth } from './middleware/auth.js'; import { registerRoutes } from './routes/api.js'; import { registerMcpTools } from './mcp/tools.js'; const HOST = process.env['HOST'] ?? '0.0.0.0'; const PORT = Number(process.env['PORT'] ?? 8100); const DATA_DIR = process.env['MOSAIC_BRAIN_DATA_DIR'] ?? './data'; /** * Create a fresh MCP server instance with all tools registered. * Each HTTP request gets its own instance to avoid state leaks. */ function createMcpServer(collections: Collections): McpServer { const mcp = new McpServer({ name: 'mosaic-brain', version: '0.1.0', }); registerMcpTools(mcp, collections); return mcp; } async function main(): Promise { // --- Storage --- const store = new JsonStore({ dataDir: DATA_DIR }); await store.init(); const collections = new Collections(store); // --- Fastify --- const app = Fastify({ logger: true }); // Auth on all /v1 and /mcp routes app.addHook('onRequest', async (req, reply) => { if (req.url.startsWith('/v1/') || req.url === '/mcp') { await requireAuth(req, reply); } }); // REST API registerRoutes(app, collections); // MCP over HTTP (Streamable HTTP transport) // Each request gets a fresh McpServer + transport to avoid state leaks app.all('/mcp', async (req, reply) => { const mcp = createMcpServer(collections); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // stateless }); await mcp.connect(transport); if (req.method === 'POST') { await transport.handleRequest( req.raw, reply.raw, req.body as Record, ); } else if (req.method === 'GET') { await transport.handleRequest(req.raw, reply.raw); } else if (req.method === 'DELETE') { await transport.handleRequest(req.raw, reply.raw); } else { reply.code(405).send({ error: 'Method not allowed' }); } }); // --- Start --- await app.listen({ host: HOST, port: PORT }); console.log(`mosaic-brain listening on ${HOST}:${PORT}`); console.log(` REST API: http://${HOST}:${PORT}/v1/`); console.log(` MCP: http://${HOST}:${PORT}/mcp`); console.log(` Data dir: ${DATA_DIR}`); } main().catch((err) => { console.error('Failed to start mosaic-brain:', err); process.exit(1); });