Skip to content
Cloudflare Docs

Tools

MCP tools are functions that an MCP server exposes for clients to call. When an LLM decides it needs to take an action — look up data, run a calculation, call an API — it invokes a tool. The MCP server executes the tool and returns the result.

Tools are defined using the @modelcontextprotocol/sdk package. The Agents SDK handles transport and lifecycle; the tool definitions are the same regardless of whether you use createMcpHandler or McpAgent.

Defining tools

Use server.tool() to register a tool on an McpServer instance. Each tool has a name, a description (used by the LLM to decide when to call it), an input schema defined with Zod, and a handler function.

JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "Math", version: "1.0.0" });
server.tool(
"add",
"Add two numbers together",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}

The tool handler receives the validated input and must return an object with a content array. Each content item has a type (typically "text") and the corresponding data.

Tool results

Tool results are returned as an array of content parts. The most common type is text, but you can also return images and embedded resources.

JavaScript
server.tool(
"lookup",
"Look up a user by ID",
{ userId: z.string() },
async ({ userId }) => {
const user = await db.getUser(userId);
if (!user) {
return {
isError: true,
content: [{ type: "text", text: `User ${userId} not found` }],
};
}
return {
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
};
},
);

Set isError: true to signal that the tool call failed. The LLM receives the error message and can decide how to proceed.

Tool descriptions

The description parameter is critical — it is what the LLM reads to decide whether and when to call your tool. Write descriptions that are:

  • Specific about what the tool does: "Get the current weather for a city" is better than "Weather tool"
  • Clear about inputs: "Requires a city name as a string" helps the LLM format the call correctly
  • Honest about limitations: "Only supports US cities" prevents the LLM from calling it with unsupported inputs

Input validation with Zod

Tool inputs are defined as Zod schemas and validated automatically before the handler runs. Use Zod's .describe() method to give the LLM context about each parameter.

JavaScript
server.tool(
"search",
"Search for documents by query",
{
query: z.string().describe("The search query"),
limit: z
.number()
.min(1)
.max(100)
.default(10)
.describe("Maximum number of results to return"),
category: z
.enum(["docs", "blog", "api"])
.optional()
.describe("Filter by content category"),
},
async ({ query, limit, category }) => {
const results = await searchIndex(query, { limit, category });
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
},
);

Using tools with createMcpHandler

For stateless MCP servers, define tools inside a factory function and pass the server to createMcpHandler:

JavaScript
import { createMcpHandler } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "My Tools", version: "1.0.0" });
server.tool("ping", "Check if the server is alive", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
return server;
}
export default {
fetch: (request, env, ctx) => {
const server = createServer();
return createMcpHandler(server)(request, env, ctx);
},
};

Using tools with McpAgent

For stateful MCP servers, define tools in the init() method of an McpAgent. Tools have access to the agent instance via this, which means they can read and write state.

JavaScript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "Stateful Tools", version: "1.0.0" });
async init() {
this.server.tool(
"incrementCounter",
"Increment and return a counter",
{},
async () => {
const count = (this.state?.count ?? 0) + 1;
this.setState({ count });
return {
content: [{ type: "text", text: `Counter: ${count}` }],
};
},
);
}
}

Next steps