import type { FastifyError, FastifyPluginAsync } from "fastify"; import { handleMcpRequest, type McpDeps } from "./tools.js"; export type McpModuleOptions = McpDeps; // HTTP JSON-RPC 2.0 MCP endpoint. Mounted on its own Fastify instance bound to // 127.0.0.1 (see app.ts) so exec-lua / write-file stay off the public gateway port — // the trust boundary is "local process + authenticated gateway connection". const mcpModule: FastifyPluginAsync = (fastify, opts) => { const deps: McpDeps = opts; fastify.get("/health", () => ({ ok: true, computers: deps.registry.count() })); fastify.post("/", async (request, reply) => { const result = await handleMcpRequest(request.body, deps); // A JSON-RPC notification (e.g. notifications/initialized) yields null -> 204. if (result === null) { return reply.code(204).send(); } return reply.send(result); }); // Malformed JSON never reaches the handler (Fastify rejects it first); surface it // as a JSON-RPC parse error rather than Fastify's default validation envelope. fastify.setErrorHandler((error: FastifyError, _request, reply) => { if (error.statusCode === 400) { return reply.code(400).send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }); } fastify.log.error(error); return reply.code(500).send({ jsonrpc: "2.0", id: null, error: { code: -32603, message: "Internal error" } }); }); return Promise.resolve(); }; export default mcpModule;