logging refactoring
This commit is contained in:
@@ -19,7 +19,9 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"@modelcontextprotocol/sdk": "^1.13.1",
|
||||
"fast-xml-parser": "^5.9.3",
|
||||
"fastify": "^5.2.1",
|
||||
"zod": "^3.24.2",
|
||||
"zod-to-json-schema": "^3.25.2"
|
||||
|
||||
Generated
+74
@@ -207,9 +207,15 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@fastify/cors':
|
||||
specifier: ^11.2.0
|
||||
version: 11.2.0
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.13.1
|
||||
version: 1.29.0(zod@3.25.76)
|
||||
fast-xml-parser:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
fastify:
|
||||
specifier: ^5.2.1
|
||||
version: 5.9.0
|
||||
@@ -444,6 +450,9 @@ packages:
|
||||
'@fastify/ajv-compiler@4.0.5':
|
||||
resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
|
||||
|
||||
'@fastify/cors@11.2.0':
|
||||
resolution: {integrity: sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==}
|
||||
|
||||
'@fastify/error@4.2.0':
|
||||
resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
|
||||
|
||||
@@ -495,6 +504,9 @@ packages:
|
||||
'@cfworker/json-schema':
|
||||
optional: true
|
||||
|
||||
'@nodable/entities@2.2.0':
|
||||
resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==}
|
||||
|
||||
'@pinojs/redact@0.4.0':
|
||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||
|
||||
@@ -601,6 +613,9 @@ packages:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
anynum@1.0.1:
|
||||
resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
@@ -850,6 +865,16 @@ packages:
|
||||
fast-uri@3.1.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-VMS5lE0zj+MZlJpQa3Qv5iGjfun0H2N7VRgoBwpcTNQ2bdIQpv7fDpb+HGteGbicBsGkzGS+X+hdx9mmrfWuHQ==}
|
||||
|
||||
@@ -993,6 +1018,9 @@ packages:
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
is-unsafe@1.0.1:
|
||||
resolution: {integrity: sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
@@ -1125,6 +1153,10 @@ packages:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
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:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1296,6 +1328,9 @@ packages:
|
||||
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
strnum@2.4.1:
|
||||
resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1373,6 +1408,10 @@ packages:
|
||||
wrappy@1.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1517,6 +1556,11 @@ snapshots:
|
||||
ajv-formats: 3.0.1(ajv@8.20.0)
|
||||
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/fast-json-stringify-compiler@5.0.3':
|
||||
@@ -1576,6 +1620,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@nodable/entities@2.2.0': {}
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
@@ -1712,6 +1758,8 @@ snapshots:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
anynum@1.0.1: {}
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
@@ -2008,6 +2056,22 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
'@fastify/ajv-compiler': 4.0.5
|
||||
@@ -2153,6 +2217,8 @@ snapshots:
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
is-unsafe@1.0.1: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jose@6.2.3: {}
|
||||
@@ -2265,6 +2331,8 @@ snapshots:
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-expression-matcher@1.6.1: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-to-regexp@8.4.2: {}
|
||||
@@ -2454,6 +2522,10 @@ snapshots:
|
||||
|
||||
strip-json-comments@5.0.3: {}
|
||||
|
||||
strnum@2.4.1:
|
||||
dependencies:
|
||||
anynum: 1.0.1
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
@@ -2522,6 +2594,8 @@ snapshots:
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
xml-naming@0.1.0: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zod-to-json-schema@3.25.2(zod@3.25.76):
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class McpSession {
|
||||
|
||||
private buildMcpServer(): 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 } } },
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './ping';
|
||||
export * from './listTools';
|
||||
export * from './invokeTool';
|
||||
// export * from './listTools';
|
||||
// export * from './invokeTool';
|
||||
export * from './testAddRemoveTool';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import z from "zod";
|
||||
import { server } from "../server";
|
||||
import { ToolDef } from "../types";
|
||||
import { logged } from "../utils/logged";
|
||||
|
||||
export const invokeToolTool: ToolDef = {
|
||||
title: "invoke_tool",
|
||||
@@ -12,10 +11,10 @@ export const invokeToolTool: ToolDef = {
|
||||
args: z.record(z.any()).default({}),
|
||||
},
|
||||
},
|
||||
handler: logged('invoke_tool', async ({ name, args }) => {
|
||||
handler: async ({ name, args }) => {
|
||||
const tool = server.tools[name];
|
||||
if (!tool)
|
||||
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
||||
return tool.handler(args); // dispatch to the real implementation
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import z from "zod";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
import { server } from "../server";
|
||||
import { ToolDef } from "../types";
|
||||
import { logged } from "../utils/logged";
|
||||
|
||||
export const listToolsTool: ToolDef = {
|
||||
title: "list_tools",
|
||||
@@ -10,7 +9,7 @@ export const listToolsTool: ToolDef = {
|
||||
description: "Return the authoritative current list of available local tools with their schemas.",
|
||||
inputSchema: {},
|
||||
},
|
||||
handler: logged('list_tools', async () => ({
|
||||
handler: async () => ({
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
@@ -26,5 +25,5 @@ export const listToolsTool: ToolDef = {
|
||||
2,
|
||||
)
|
||||
}],
|
||||
})),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import z from "zod";
|
||||
import { ToolDef } from "../types"
|
||||
import { isValidHost, logged } from "../utils/logged";
|
||||
import { isValidHost } from "../utils/logged";
|
||||
import { exec } from "node:child_process";
|
||||
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"),
|
||||
},
|
||||
},
|
||||
handler: logged('ping', async ({ host, count }) => {
|
||||
handler: async ({ host, count }) => {
|
||||
if (!isValidHost(host)) {
|
||||
return {
|
||||
isError: true,
|
||||
@@ -40,5 +40,5 @@ export const pingTool: ToolDef = {
|
||||
content: [{ type: "text", text: `ping failed:\n${message}` }],
|
||||
};
|
||||
}
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ToolDef } from "../types"
|
||||
import { logged } from "../utils/logged";
|
||||
import { server } from "../server";
|
||||
|
||||
const dynamicTool: ToolDef = {
|
||||
@@ -8,9 +7,9 @@ const dynamicTool: ToolDef = {
|
||||
description: "Says hello",
|
||||
inputSchema: {},
|
||||
},
|
||||
handler: logged('say_hello', async () => {
|
||||
handler: async () => {
|
||||
return { content: [{ type: "text", text: "hello" }] };
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
export const testAddTool: ToolDef = {
|
||||
@@ -19,10 +18,10 @@ export const testAddTool: ToolDef = {
|
||||
description: "Dynamically add a new tool in runtime",
|
||||
inputSchema: {},
|
||||
},
|
||||
handler: logged('test_add_tool', async () => {
|
||||
handler: async () => {
|
||||
server.registerTools(dynamicTool);
|
||||
return { content: [{ type: "text", text: "New tool added" }] };
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const testRemoveTool: ToolDef = {
|
||||
@@ -31,8 +30,8 @@ export const testRemoveTool: ToolDef = {
|
||||
description: "Dynamically remove a tool in runtime",
|
||||
inputSchema: {},
|
||||
},
|
||||
handler: logged('test_remove_tool', async () => {
|
||||
handler: async () => {
|
||||
server.unregisterTools(dynamicTool.title);
|
||||
return { content: [{ type: "text", text: "Tool removed" }] };
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
+33
-29
@@ -2,7 +2,9 @@ import Fastify, { type FastifyInstance } from "fastify";
|
||||
import { ToolDef } from "./types";
|
||||
import McpSession from "./mcpConnection";
|
||||
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 {
|
||||
private readonly app: FastifyInstance;
|
||||
@@ -14,34 +16,20 @@ export class Server {
|
||||
this.port = port;
|
||||
this.app = Fastify({
|
||||
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: {
|
||||
// level: process.env.LOG_LEVEL ?? "info",
|
||||
// ...(process.env.NODE_ENV !== "production"
|
||||
// ? {
|
||||
// transport: {
|
||||
// target: "pino-pretty",
|
||||
// options: { colorize: true, translateTime: "HH:MM:ss.l", ignore: "pid,hostname" },
|
||||
// serializers: {
|
||||
// req(request) {
|
||||
// return {
|
||||
// method: request.method,
|
||||
// url: request.url,
|
||||
// host: request.headers.host,
|
||||
// remoteAddress: request.ip,
|
||||
// headers: request.headers, // <-- full header set
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// : {}),
|
||||
// },
|
||||
});
|
||||
this.registerLogging();
|
||||
this.registerRoutes();
|
||||
}
|
||||
|
||||
private registerLogging(): void {
|
||||
@@ -73,8 +61,10 @@ export class Server {
|
||||
public registerTools(...tools: ToolDef[]): void {
|
||||
|
||||
for (const tool of tools) {
|
||||
if (!this.getTool(tool.title))
|
||||
this.tools[tool.title] = tool;
|
||||
if (!this.getTool(tool.title)) {
|
||||
// console.log(`Registering new tool ${tool.title}`);
|
||||
this.tools[tool.title] = logged(tool);
|
||||
}
|
||||
}
|
||||
|
||||
this.emitToolListChange();
|
||||
@@ -88,8 +78,11 @@ export class Server {
|
||||
}
|
||||
|
||||
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))
|
||||
session.handleToolListChange(Object.values(this.tools));
|
||||
session.handleToolListChange(toolList);
|
||||
}
|
||||
|
||||
public getSession(sessionId: string): McpSession | undefined {
|
||||
@@ -101,6 +94,9 @@ export class Server {
|
||||
// underlying Node req/res it expects.
|
||||
private registerRoutes(): void {
|
||||
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;
|
||||
|
||||
@@ -157,10 +153,18 @@ export class Server {
|
||||
}
|
||||
|
||||
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" });
|
||||
this.app.log.info(`MCP ping server listening on http://0.0.0.0:${this.port}/mcp`);
|
||||
}
|
||||
}
|
||||
|
||||
export const server = new Server();
|
||||
server.registerTools(pingTool, listToolsTool, invokeToolTool, testAddTool, testRemoveTool);
|
||||
server.registerTools(...Object.values(mcpTools));
|
||||
+15
-10
@@ -1,29 +1,34 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { TextResult } from "../types";
|
||||
import { TextResult, ToolDef } from "../types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function logged<A extends any[]>(
|
||||
name: string,
|
||||
handler: (...args: A) => Promise<TextResult>,
|
||||
): (...args: A) => Promise<TextResult> {
|
||||
return async (...args: A) => {
|
||||
tool: ToolDef,
|
||||
): ToolDef {
|
||||
|
||||
console.dir(tool);
|
||||
|
||||
return {
|
||||
...tool,
|
||||
handler: async (...args: A): Promise<TextResult> => {
|
||||
const id = randomUUID().slice(0, 8);
|
||||
const t0 = process.hrtime.bigint();
|
||||
console.log(`[tool>] ${name} #${id} args=${JSON.stringify(args)}`);
|
||||
console.log(`[tool>] ${tool.title} #${id} args=${JSON.stringify(args)}`);
|
||||
try {
|
||||
const result = await handler(...args);
|
||||
const result = await tool.handler(...args);
|
||||
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
|
||||
console.log(
|
||||
`[tool<] ${name} #${id} ${ms.toFixed(1)}ms` +
|
||||
`[tool<] ${tool.title} #${id} ${ms.toFixed(1)}ms` +
|
||||
`${result.isError ? " ERROR" : ""} result=${JSON.stringify(result).slice(0, 2000)}`,
|
||||
);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
|
||||
console.error(`[tool!] ${name} #${id} ${ms.toFixed(1)}ms threw:`, err);
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user