Skip to content

Tools

Tools expose callable functionality to MCP clients. Each tool can have input validation, output validation, and custom metadata.

Define a tool using plain JSON Schema input and output definitions.

server.tool("add", {
description: "Adds two numbers",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" },
},
required: ["a", "b"],
},
outputSchema: {
type: "object",
properties: {
result: { type: "number" },
},
required: ["result"],
},
handler: (args: { a: number; b: number }) => ({
content: [{ type: "text", text: String(args.a + args.b) }],
structuredContent: { result: args.a + args.b },
}),
});

Use a Standard Schema validator (Zod here) to infer handler types automatically.

import { z } from "zod";
const AddInputSchema = z.object({
a: z.number(),
b: z.number(),
});
const AddOutputSchema = z.object({
result: z.number(),
});
server.tool("add", {
description: "Adds two numbers with structured output",
inputSchema: AddInputSchema,
outputSchema: AddOutputSchema,
handler: (args) => ({
// args is automatically typed as { a: number; b: number }
content: [{ type: "text", text: String(args.a + args.b) }],
structuredContent: { result: args.a + args.b },
}),
});

Skip validation entirely for endpoints that return static information.

server.tool("status", {
description: "Returns server status",
handler: () => ({
content: [{ type: "text", text: "Server is running" }],
}),
});

Tools can return both human-readable content and machine-readable structured data. Use outputSchema to define the shape of structuredContent:

See the MCP structured content spec for protocol details.

const WeatherOutputSchema = z.object({
temperature: z.number(),
conditions: z.string(),
});
server.tool("getWeather", {
inputSchema: z.object({ location: z.string() }),
outputSchema: WeatherOutputSchema,
handler: (args) => ({
content: [{
type: "text",
text: `Weather in ${args.location}: 22°C, sunny`
}],
// structuredContent is typed and validated at runtime
structuredContent: {
temperature: 22,
conditions: "sunny",
}
})
})

The outputSchema provides runtime validation and type inference for structuredContent.

Add title and _meta fields to pass arbitrary metadata through tools/list and tools/call responses.

server.tool("experimental-feature", {
description: "An experimental feature",
title: "Experimental Feature",
_meta: {
version: "0.1.0",
stability: "experimental",
tags: ["beta", "preview"],
},
inputSchema: z.object({ input: z.string() }),
handler: (args) => ({
content: [{ type: "text", text: `Processing: ${args.input}` }],
_meta: {
executionTime: 123,
cached: false,
},
}),
});

The _meta and title from the definition appear in tools/list responses. Tool handlers can also return _meta in the result for per-call metadata like execution time or cache status.

The handler context provides typed access to session data, authentication, and client capabilities:

handler: (args, ctx) => {
// Report progress
ctx.progress?.({ progress: 50, total: 100 })
// Access session
ctx.session?.id
// Access auth info
ctx.authInfo?.userId
// Store custom state
ctx.state.myCustomData = "..."
// Validate data
const validated = ctx.validate(MySchema, data)
// Check client capabilities and use elicitation
if (ctx.client.supports("elicitation")) {
const result = await ctx.elicit({
message: "Confirm action?",
schema: z.object({ confirmed: z.boolean() })
})
}
}