logging refactoring

This commit is contained in:
azykov@mail.ru
2026-07-09 10:32:10 +03:00
parent 812e36d0af
commit bc5b3572df
10 changed files with 152 additions and 70 deletions
+2
View File
@@ -19,7 +19,9 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fastify/cors": "^11.2.0",
"@modelcontextprotocol/sdk": "^1.13.1", "@modelcontextprotocol/sdk": "^1.13.1",
"fast-xml-parser": "^5.9.3",
"fastify": "^5.2.1", "fastify": "^5.2.1",
"zod": "^3.24.2", "zod": "^3.24.2",
"zod-to-json-schema": "^3.25.2" "zod-to-json-schema": "^3.25.2"
+74
View File
@@ -207,9 +207,15 @@ importers:
.: .:
dependencies: dependencies:
'@fastify/cors':
specifier: ^11.2.0
version: 11.2.0
'@modelcontextprotocol/sdk': '@modelcontextprotocol/sdk':
specifier: ^1.13.1 specifier: ^1.13.1
version: 1.29.0(zod@3.25.76) version: 1.29.0(zod@3.25.76)
fast-xml-parser:
specifier: ^5.9.3
version: 5.9.3
fastify: fastify:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.9.0 version: 5.9.0
@@ -444,6 +450,9 @@ packages:
'@fastify/ajv-compiler@4.0.5': '@fastify/ajv-compiler@4.0.5':
resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
'@fastify/cors@11.2.0':
resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==}
'@fastify/error@4.2.0': '@fastify/error@4.2.0':
resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
@@ -495,6 +504,9 @@ packages:
'@cfworker/json-schema': '@cfworker/json-schema':
optional: true optional: true
'@nodable/entities@2.2.0':
resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==}
'@pinojs/redact@0.4.0': '@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
@@ -601,6 +613,9 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
anynum@1.0.1:
resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==}
argparse@2.0.1: argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -850,6 +865,16 @@ packages:
fast-uri@3.1.2: fast-uri@3.1.2:
resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
fast-xml-builder@1.2.1:
resolution: {integrity: sha512-tPb5TTWfgfVx5BNSi2xV0eLr89POeXXn0dXIsCJ9m1narrWxeIyx6je9d7Rce/3NyXLbvuQmLkxq+RuxMWejvw==}
fast-xml-parser@5.9.3:
resolution: {integrity: sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==}
hasBin: true
fastify-plugin@5.1.0:
resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==}
fastify@5.9.0: fastify@5.9.0:
resolution: {integrity: sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==} resolution: {integrity: sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==}
@@ -993,6 +1018,9 @@ packages:
is-promise@4.0.0: is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-unsafe@1.0.1:
resolution: {integrity: sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==}
isexe@2.0.0: isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -1125,6 +1153,10 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
path-expression-matcher@1.6.1:
resolution: {integrity: sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ==}
engines: {node: '>=14.0.0'}
path-key@3.1.1: path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1296,6 +1328,9 @@ packages:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
strnum@2.4.1:
resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==}
supports-color@7.2.0: supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1373,6 +1408,10 @@ packages:
wrappy@1.0.2: wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xml-naming@0.1.0:
resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==}
engines: {node: '>=16.0.0'}
yocto-queue@0.1.0: yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1517,6 +1556,11 @@ snapshots:
ajv-formats: 3.0.1(ajv@8.20.0) ajv-formats: 3.0.1(ajv@8.20.0)
fast-uri: 3.1.2 fast-uri: 3.1.2
'@fastify/cors@11.2.0':
dependencies:
fastify-plugin: 5.1.0
toad-cache: 3.7.1
'@fastify/error@4.2.0': {} '@fastify/error@4.2.0': {}
'@fastify/fast-json-stringify-compiler@5.0.3': '@fastify/fast-json-stringify-compiler@5.0.3':
@@ -1576,6 +1620,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@nodable/entities@2.2.0': {}
'@pinojs/redact@0.4.0': {} '@pinojs/redact@0.4.0': {}
'@types/estree@1.0.9': {} '@types/estree@1.0.9': {}
@@ -1712,6 +1758,8 @@ snapshots:
dependencies: dependencies:
color-convert: 2.0.1 color-convert: 2.0.1
anynum@1.0.1: {}
argparse@2.0.1: {} argparse@2.0.1: {}
atomic-sleep@1.0.0: {} atomic-sleep@1.0.0: {}
@@ -2008,6 +2056,22 @@ snapshots:
fast-uri@3.1.2: {} fast-uri@3.1.2: {}
fast-xml-builder@1.2.1:
dependencies:
path-expression-matcher: 1.6.1
xml-naming: 0.1.0
fast-xml-parser@5.9.3:
dependencies:
'@nodable/entities': 2.2.0
fast-xml-builder: 1.2.1
is-unsafe: 1.0.1
path-expression-matcher: 1.6.1
strnum: 2.4.1
xml-naming: 0.1.0
fastify-plugin@5.1.0: {}
fastify@5.9.0: fastify@5.9.0:
dependencies: dependencies:
'@fastify/ajv-compiler': 4.0.5 '@fastify/ajv-compiler': 4.0.5
@@ -2153,6 +2217,8 @@ snapshots:
is-promise@4.0.0: {} is-promise@4.0.0: {}
is-unsafe@1.0.1: {}
isexe@2.0.0: {} isexe@2.0.0: {}
jose@6.2.3: {} jose@6.2.3: {}
@@ -2265,6 +2331,8 @@ snapshots:
path-exists@4.0.0: {} path-exists@4.0.0: {}
path-expression-matcher@1.6.1: {}
path-key@3.1.1: {} path-key@3.1.1: {}
path-to-regexp@8.4.2: {} path-to-regexp@8.4.2: {}
@@ -2454,6 +2522,10 @@ snapshots:
strip-json-comments@5.0.3: {} strip-json-comments@5.0.3: {}
strnum@2.4.1:
dependencies:
anynum: 1.0.1
supports-color@7.2.0: supports-color@7.2.0:
dependencies: dependencies:
has-flag: 4.0.0 has-flag: 4.0.0
@@ -2522,6 +2594,8 @@ snapshots:
wrappy@1.0.2: {} wrappy@1.0.2: {}
xml-naming@0.1.0: {}
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod-to-json-schema@3.25.2(zod@3.25.76): zod-to-json-schema@3.25.2(zod@3.25.76):
+1 -1
View File
@@ -26,7 +26,7 @@ export default class McpSession {
private buildMcpServer(): McpServer { private buildMcpServer(): McpServer {
const server = new McpServer( const server = new McpServer(
{ name: "mcp-ping-server", version: "1.0.0" }, { name: "home-mcp-server", version: "1.0.0" },
{ capabilities: { tools: { listChanged: true } } }, { capabilities: { tools: { listChanged: true } } },
); );
+2 -2
View File
@@ -1,4 +1,4 @@
export * from './ping'; export * from './ping';
export * from './listTools'; // export * from './listTools';
export * from './invokeTool'; // export * from './invokeTool';
export * from './testAddRemoveTool'; export * from './testAddRemoveTool';
+2 -3
View File
@@ -1,7 +1,6 @@
import z from "zod"; import z from "zod";
import { server } from "../server"; import { server } from "../server";
import { ToolDef } from "../types"; import { ToolDef } from "../types";
import { logged } from "../utils/logged";
export const invokeToolTool: ToolDef = { export const invokeToolTool: ToolDef = {
title: "invoke_tool", title: "invoke_tool",
@@ -12,10 +11,10 @@ export const invokeToolTool: ToolDef = {
args: z.record(z.any()).default({}), args: z.record(z.any()).default({}),
}, },
}, },
handler: logged('invoke_tool', async ({ name, args }) => { handler: async ({ name, args }) => {
const tool = server.tools[name]; const tool = server.tools[name];
if (!tool) if (!tool)
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] }; return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] };
return tool.handler(args); // dispatch to the real implementation return tool.handler(args); // dispatch to the real implementation
}), },
} }
+2 -3
View File
@@ -2,7 +2,6 @@ import z from "zod";
import { zodToJsonSchema } from "zod-to-json-schema"; import { zodToJsonSchema } from "zod-to-json-schema";
import { server } from "../server"; import { server } from "../server";
import { ToolDef } from "../types"; import { ToolDef } from "../types";
import { logged } from "../utils/logged";
export const listToolsTool: ToolDef = { export const listToolsTool: ToolDef = {
title: "list_tools", title: "list_tools",
@@ -10,7 +9,7 @@ export const listToolsTool: ToolDef = {
description: "Return the authoritative current list of available local tools with their schemas.", description: "Return the authoritative current list of available local tools with their schemas.",
inputSchema: {}, inputSchema: {},
}, },
handler: logged('list_tools', async () => ({ handler: async () => ({
content: [{ content: [{
type: "text", type: "text",
text: JSON.stringify( text: JSON.stringify(
@@ -26,5 +25,5 @@ export const listToolsTool: ToolDef = {
2, 2,
) )
}], }],
})), }),
} }
+3 -3
View File
@@ -1,6 +1,6 @@
import z from "zod"; import z from "zod";
import { ToolDef } from "../types" import { ToolDef } from "../types"
import { isValidHost, logged } from "../utils/logged"; import { isValidHost } from "../utils/logged";
import { exec } from "node:child_process"; import { exec } from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
@@ -15,7 +15,7 @@ export const pingTool: ToolDef = {
count: z.number().int().min(1).max(10).default(4).describe("Echo requests to send"), count: z.number().int().min(1).max(10).default(4).describe("Echo requests to send"),
}, },
}, },
handler: logged('ping', async ({ host, count }) => { handler: async ({ host, count }) => {
if (!isValidHost(host)) { if (!isValidHost(host)) {
return { return {
isError: true, isError: true,
@@ -40,5 +40,5 @@ export const pingTool: ToolDef = {
content: [{ type: "text", text: `ping failed:\n${message}` }], content: [{ type: "text", text: `ping failed:\n${message}` }],
}; };
} }
}), },
}; };
+6 -7
View File
@@ -1,5 +1,4 @@
import { ToolDef } from "../types" import { ToolDef } from "../types"
import { logged } from "../utils/logged";
import { server } from "../server"; import { server } from "../server";
const dynamicTool: ToolDef = { const dynamicTool: ToolDef = {
@@ -8,9 +7,9 @@ const dynamicTool: ToolDef = {
description: "Says hello", description: "Says hello",
inputSchema: {}, inputSchema: {},
}, },
handler: logged('say_hello', async () => { handler: async () => {
return { content: [{ type: "text", text: "hello" }] }; return { content: [{ type: "text", text: "hello" }] };
}), },
} }
export const testAddTool: ToolDef = { export const testAddTool: ToolDef = {
@@ -19,10 +18,10 @@ export const testAddTool: ToolDef = {
description: "Dynamically add a new tool in runtime", description: "Dynamically add a new tool in runtime",
inputSchema: {}, inputSchema: {},
}, },
handler: logged('test_add_tool', async () => { handler: async () => {
server.registerTools(dynamicTool); server.registerTools(dynamicTool);
return { content: [{ type: "text", text: "New tool added" }] }; return { content: [{ type: "text", text: "New tool added" }] };
}), },
}; };
export const testRemoveTool: ToolDef = { export const testRemoveTool: ToolDef = {
@@ -31,8 +30,8 @@ export const testRemoveTool: ToolDef = {
description: "Dynamically remove a tool in runtime", description: "Dynamically remove a tool in runtime",
inputSchema: {}, inputSchema: {},
}, },
handler: logged('test_remove_tool', async () => { handler: async () => {
server.unregisterTools(dynamicTool.title); server.unregisterTools(dynamicTool.title);
return { content: [{ type: "text", text: "Tool removed" }] }; return { content: [{ type: "text", text: "Tool removed" }] };
}), },
}; };
+34 -30
View File
@@ -2,7 +2,9 @@ import Fastify, { type FastifyInstance } from "fastify";
import { ToolDef } from "./types"; import { ToolDef } from "./types";
import McpSession from "./mcpConnection"; import McpSession from "./mcpConnection";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { pingTool, testAddTool, testRemoveTool, listToolsTool, invokeToolTool } from "./mcpTools"; import * as mcpTools from "./mcpTools";
import { logged } from "./utils/logged";
import cors from '@fastify/cors';
export class Server { export class Server {
private readonly app: FastifyInstance; private readonly app: FastifyInstance;
@@ -14,34 +16,20 @@ export class Server {
this.port = port; this.port = port;
this.app = Fastify({ this.app = Fastify({
trustProxy: true, trustProxy: true,
logger: {
serializers: {
req(request) {
return {
method: request.method,
url: request.url,
host: request.headers.host,
remoteAddress: request.ip,
headers: request.headers, // <-- full header set
};
},
},
},
// logger: { // logger: {
// level: process.env.LOG_LEVEL ?? "info", // serializers: {
// ...(process.env.NODE_ENV !== "production" // req(request) {
// ? { // return {
// transport: { // method: request.method,
// target: "pino-pretty", // url: request.url,
// options: { colorize: true, translateTime: "HH:MM:ss.l", ignore: "pid,hostname" }, // host: request.headers.host,
// }, // remoteAddress: request.ip,
// } // headers: request.headers, // <-- full header set
// : {}), // };
// },
// },
// }, // },
}); });
this.registerLogging();
this.registerRoutes();
} }
private registerLogging(): void { private registerLogging(): void {
@@ -73,8 +61,10 @@ export class Server {
public registerTools(...tools: ToolDef[]): void { public registerTools(...tools: ToolDef[]): void {
for (const tool of tools) { for (const tool of tools) {
if (!this.getTool(tool.title)) if (!this.getTool(tool.title)) {
this.tools[tool.title] = tool; // console.log(`Registering new tool ${tool.title}`);
this.tools[tool.title] = logged(tool);
}
} }
this.emitToolListChange(); this.emitToolListChange();
@@ -88,8 +78,11 @@ export class Server {
} }
private emitToolListChange() { private emitToolListChange() {
const toolList = Object.values(this.tools);
console.log(`Advertising updated tool list: ${toolList.map((tool) => tool.title)}`);
for (const session of Object.values(this.sessions)) for (const session of Object.values(this.sessions))
session.handleToolListChange(Object.values(this.tools)); session.handleToolListChange(toolList);
} }
public getSession(sessionId: string): McpSession | undefined { public getSession(sessionId: string): McpSession | undefined {
@@ -101,6 +94,9 @@ export class Server {
// underlying Node req/res it expects. // underlying Node req/res it expects.
private registerRoutes(): void { private registerRoutes(): void {
this.app.post("/mcp", async (request, reply) => { this.app.post("/mcp", async (request, reply) => {
reply.raw.setHeader('Access-Control-Allow-Origin', request.headers.origin ?? '*');
reply.raw.setHeader('Access-Control-Allow-Credentials', 'true');
reply.raw.setHeader('Vary', 'Origin');
let session: McpSession | undefined; let session: McpSession | undefined;
@@ -157,10 +153,18 @@ export class Server {
} }
async start(): Promise<void> { async start(): Promise<void> {
await this.app.register(cors, {
origin: true,
credentials: true,
});
await this.registerLogging();
await this.registerRoutes();
await this.app.listen({ port: this.port, host: "0.0.0.0" }); await this.app.listen({ port: this.port, host: "0.0.0.0" });
this.app.log.info(`MCP ping server listening on http://0.0.0.0:${this.port}/mcp`); this.app.log.info(`MCP ping server listening on http://0.0.0.0:${this.port}/mcp`);
} }
} }
export const server = new Server(); export const server = new Server();
server.registerTools(pingTool, listToolsTool, invokeToolTool, testAddTool, testRemoveTool); server.registerTools(...Object.values(mcpTools));
+26 -21
View File
@@ -1,29 +1,34 @@
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { TextResult } from "../types"; import { TextResult, ToolDef } from "../types";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function logged<A extends any[]>( export function logged<A extends any[]>(
name: string, tool: ToolDef,
handler: (...args: A) => Promise<TextResult>, ): ToolDef {
): (...args: A) => Promise<TextResult> {
return async (...args: A) => { console.dir(tool);
const id = randomUUID().slice(0, 8);
const t0 = process.hrtime.bigint(); return {
console.log(`[tool>] ${name} #${id} args=${JSON.stringify(args)}`); ...tool,
try { handler: async (...args: A): Promise<TextResult> => {
const result = await handler(...args); const id = randomUUID().slice(0, 8);
const ms = Number(process.hrtime.bigint() - t0) / 1e6; const t0 = process.hrtime.bigint();
console.log( console.log(`[tool>] ${tool.title} #${id} args=${JSON.stringify(args)}`);
`[tool<] ${name} #${id} ${ms.toFixed(1)}ms` + try {
`${result.isError ? " ERROR" : ""} result=${JSON.stringify(result).slice(0, 2000)}`, const result = await tool.handler(...args);
); const ms = Number(process.hrtime.bigint() - t0) / 1e6;
return result; console.log(
} catch (err) { `[tool<] ${tool.title} #${id} ${ms.toFixed(1)}ms` +
const ms = Number(process.hrtime.bigint() - t0) / 1e6; `${result.isError ? " ERROR" : ""} result=${JSON.stringify(result).slice(0, 2000)}`,
console.error(`[tool!] ${name} #${id} ${ms.toFixed(1)}ms threw:`, err); );
throw err; // let the SDK turn it into a JSON-RPC error return result;
} catch (err) {
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.error(`[tool!] ${tool.title} #${id} ${ms.toFixed(1)}ms threw:`, err);
throw err; // let the SDK turn it into a JSON-RPC error
}
} }
}; }
} }
export function isValidHost(host: string): boolean { export function isValidHost(host: string): boolean {