cc-libs/tools/trapos-server/src/modules/mcp/index.ts

37 lines
1.5 KiB
TypeScript

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<McpModuleOptions> = (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;