Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions src/__tests__/__snapshots__/server.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,6 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1`
[
"Built-in tool at index 0 is missing the static name property, "toolName"",
],
[
"Tool "loremIpsum" has a non Zod inputSchema. This may cause unexpected issues.",
],
],
"hasDebugLogs": true,
"mcpServer": [
Expand Down Expand Up @@ -479,12 +476,6 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
[
"Built-in tool at index 1 is missing the static name property, "toolName"",
],
[
"Tool "loremIpsum" has a non Zod inputSchema. This may cause unexpected issues.",
],
[
"Tool "dolorSit" has a non Zod inputSchema. This may cause unexpected issues.",
],
],
"hasDebugLogs": true,
"mcpServer": [
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/options.context.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { runServer, type McpTool } from '../server';
Expand Down Expand Up @@ -122,7 +123,7 @@ describe('tool creator options context', () => {

return [
'alsContract',
{ description: 'Context test tool', inputSchema: {} },
{ description: 'Context test tool', inputSchema: z.object({}) },
callback
];
};
Expand Down
7 changes: 4 additions & 3 deletions src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { runServer } from '../server';
Expand Down Expand Up @@ -112,7 +113,7 @@ describe('runServer', () => {
tools: [
jest.fn().mockReturnValue([
'loremIpsum',
{ description: 'Lorem Ipsum', inputSchema: {} },
{ description: 'Lorem Ipsum', inputSchema: z.object({}) },
jest.fn()
])
],
Expand All @@ -124,12 +125,12 @@ describe('runServer', () => {
tools: [
jest.fn().mockReturnValue([
'loremIpsum',
{ description: 'Lorem Ipsum', inputSchema: {} },
{ description: 'Lorem Ipsum', inputSchema: z.object({}) },
jest.fn()
]),
jest.fn().mockReturnValue([
'dolorSit',
{ description: 'Dolor Sit', inputSchema: {} },
{ description: 'Dolor Sit', inputSchema: z.object({}) },
jest.fn()
])
],
Expand Down
60 changes: 33 additions & 27 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,49 +386,55 @@ const runServer = async (options: ServerOptions = getOptions(), {
const isZod = isZodSchema(schema?.inputSchema) || isZodRawShape(schema?.inputSchema);
const isSchemaDefined = schema?.inputSchema !== undefined;

log.info(`Registered tool: ${name}`);

if (!isZod) {
log.warn(`Tool "${name}" has a non Zod inputSchema. This may cause unexpected issues.`);
log.warn(`Tool "${name}" has a non Zod inputSchema. Skipping registration.`);
log.debug(
`Tool "${name}" has received a non Zod inputSchema from the tool pipeline.`,
`This will cause unexpected issues, such as failure to pass arguments.`,
`MCP SDK requires Zod. Kneel before Zod.`
);

return;
}

// Lightweight check for malformed schemas that bypass validation.
const isContextLike = (value: unknown) => isPlainObject(value) && 'requestId' in value && 'signal' in value;

server?.registerTool(name, schema, (args: unknown = {}, ..._args: unknown[]) =>
runWithSession(session, async () =>
runWithOptions(options, async () => {
// Basic track for remaining args to account for future MCP SDK alterations.
log.debug(
`Running tool "${name}"`,
`isArgs = ${args !== undefined}`,
`isRemainingArgs = ${_args?.length > 0}`
);

const timedReport = stat.traffic();
const isContextLikeArgs = isContextLike(args);

// Log potential Zod validation errors triggered by context fail.
if (isContextLikeArgs) {
try {
server?.registerTool(name, schema, (args: unknown = {}, ..._args: unknown[]) =>
runWithSession(session, async () =>
runWithOptions(options, async () => {
// Basic track for remaining args to account for future MCP SDK alterations.
log.debug(
`Tool "${name}" handler received a context like object as the first parameter.`,
'If this is unexpected this is likely an undefined schema or a schema not registering as Zod.',
'Review the related schema definition and ensure it is defined and valid.',
`Schema is Defined = ${isSchemaDefined}; Schema is Zod = ${isZod}; Context like = ${isContextLikeArgs};`
`Running tool "${name}"`,
`isArgs = ${args !== undefined}`,
`isRemainingArgs = ${_args?.length > 0}`
);
}

const toolResult = await callback(args);
const timedReport = stat.traffic();
const isContextLikeArgs = isContextLike(args);

// Log potential Zod validation errors triggered by context fail.
if (isContextLikeArgs) {
log.debug(
`Tool "${name}" handler received a context like object as the first parameter.`,
'If this is unexpected this is likely an undefined schema or a schema not registering as Zod.',
'Review the related schema definition and ensure it is defined and valid.',
`Schema is Defined = ${isSchemaDefined}; Schema is Zod = ${isZod}; Context like = ${isContextLikeArgs};`
);
}

const toolResult = await callback(args);

timedReport({ tool: name });
timedReport({ tool: name });

return toolResult;
})));
return toolResult;
})));

log.info(`Registered tool: ${name}`);
} catch (error) {
log.error(`Failed to register tool "${name}":`, error);
}
});

if (enableSigint && !sigintHandler) {
Expand Down
Loading