Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions src/examples/server/mcpServerOutputSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ server.registerTool(
void country;
// Simulate weather API call
const temp_c = Math.round((Math.random() * 35 - 5) * 10) / 10;
const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)];
const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)] as unknown as "sunny" | "cloudy" | "rainy" | "stormy" | "snowy";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)] as unknown as "sunny" | "cloudy" | "rainy" | "stormy" | "snowy";
const weatherConditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"] as const;
const conditions = weatherConditions[Math.floor(Math.random() * 5)];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhosmer-ant yes, that's more concise 🙏
fixed: fb303d7


const structuredContent = {
temperature: {
Expand Down Expand Up @@ -77,4 +77,4 @@ async function main() {
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
});
2 changes: 1 addition & 1 deletion src/server/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ describe("tool()", () => {
resultType: "structured",
// Missing required 'timestamp' field
someExtraField: "unexpected" // Extra field not in schema
},
} as unknown as { processedInput: string; resultType: string; timestamp: string }, // Type assertion to bypass TypeScript validation for testing purposes
})
);

Expand Down
68 changes: 44 additions & 24 deletions src/server/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class McpServer {
}

const args = parseResult.data;
const cb = tool.callback as ToolCallback<ZodRawShape>;
const cb = tool.callback as ToolCallback<ZodRawShape, ZodRawShape>;
try {
result = await Promise.resolve(cb(args, extra));
} catch (error) {
Expand All @@ -184,7 +184,7 @@ export class McpServer {
};
}
} else {
const cb = tool.callback as ToolCallback<undefined>;
const cb = tool.callback as ToolCallback<undefined, ZodRawShape>;
try {
result = await Promise.resolve(cb(extra));
} catch (error) {
Expand Down Expand Up @@ -760,7 +760,7 @@ export class McpServer {
inputSchema: ZodRawShape | undefined,
outputSchema: ZodRawShape | undefined,
annotations: ToolAnnotations | undefined,
callback: ToolCallback<ZodRawShape | undefined>
callback: ToolCallback<ZodRawShape | undefined, ZodRawShape | undefined>
): RegisteredTool {
const registeredTool: RegisteredTool = {
title,
Expand Down Expand Up @@ -917,7 +917,7 @@ export class McpServer {
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
},
cb: ToolCallback<InputArgs>
cb: ToolCallback<InputArgs, OutputArgs>
): RegisteredTool {
if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
Expand All @@ -932,7 +932,7 @@ export class McpServer {
inputSchema,
outputSchema,
annotations,
cb as ToolCallback<ZodRawShape | undefined>
cb as ToolCallback<ZodRawShape | undefined, ZodRawShape | undefined>
);
}

Expand Down Expand Up @@ -1126,6 +1126,16 @@ export class ResourceTemplate {
}
}

/**
* Type helper to create a strongly-typed CallToolResult with structuredContent
*/
type TypedCallToolResult<OutputArgs extends undefined | ZodRawShape> =
OutputArgs extends ZodRawShape
? CallToolResult & {
structuredContent?: z.objectOutputType<OutputArgs, ZodTypeAny>;
}
: CallToolResult;

/**
* Callback for a tool handler registered with Server.tool().
*
Expand All @@ -1136,36 +1146,46 @@ export class ResourceTemplate {
* - `content` if the tool does not have an outputSchema
* - Both fields are optional but typically one should be provided
*/
export type ToolCallback<Args extends undefined | ZodRawShape = undefined> =
Args extends ZodRawShape
export type ToolCallback<
InputArgs extends undefined | ZodRawShape = undefined,
OutputArgs extends undefined | ZodRawShape = undefined
> = InputArgs extends ZodRawShape
? (
args: z.objectOutputType<Args, ZodTypeAny>,
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => CallToolResult | Promise<CallToolResult>
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
args: z.objectOutputType<InputArgs, ZodTypeAny>,
extra: RequestHandlerExtra<ServerRequest, ServerNotification>
) =>
| TypedCallToolResult<OutputArgs>
| Promise<TypedCallToolResult<OutputArgs>>
: (
extra: RequestHandlerExtra<ServerRequest, ServerNotification>
) =>
| TypedCallToolResult<OutputArgs>
| Promise<TypedCallToolResult<OutputArgs>>;

export type RegisteredTool = {
title?: string;
description?: string;
inputSchema?: AnyZodObject;
outputSchema?: AnyZodObject;
annotations?: ToolAnnotations;
callback: ToolCallback<undefined | ZodRawShape>;
callback: ToolCallback<ZodRawShape | undefined, ZodRawShape | undefined>;
enabled: boolean;
enable(): void;
disable(): void;
update<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
updates: {
name?: string | null,
title?: string,
description?: string,
paramsSchema?: InputArgs,
outputSchema?: OutputArgs,
annotations?: ToolAnnotations,
callback?: ToolCallback<InputArgs>,
enabled?: boolean
}): void
remove(): void
update<
InputArgs extends ZodRawShape,
OutputArgs extends ZodRawShape
>(updates: {
name?: string | null;
title?: string;
description?: string;
paramsSchema?: InputArgs;
outputSchema?: OutputArgs;
annotations?: ToolAnnotations;
callback?: ToolCallback<InputArgs, OutputArgs>
enabled?: boolean
}): void;
remove(): void;
};

const EMPTY_OBJECT_JSON_SCHEMA = {
Expand Down