# How to Create Custom Tools in MCP Servers Using the TypeScript SDK

> Learn to create custom tools in MCP servers with the TypeScript SDK. Define Zod schemas, implement handlers, and register tools efficiently for enhanced functionality.

- Repository: [Model Context Protocol/servers](https://github.com/modelcontextprotocol/servers)
- Tags: how-to-guide
- Published: 2026-03-01

---

**To create custom tools in MCP servers using the TypeScript SDK, define a Zod schema for input validation, create a metadata configuration object with name and description, implement an async handler that returns a `CallToolResult`, and register the tool with the `McpServer` instance via `server.registerTool()`.**

The Model Context Protocol (MCP) enables servers to expose functionality to clients through declarative, schema-validated tools. When you create custom tools in MCP servers using the TypeScript SDK, you follow the modular registration pattern established in the `modelcontextprotocol/servers` repository. This guide demonstrates the exact implementation used by built-in tools, covering schema definition, handler implementation, and server integration.

## Define the Tool's Input Schema with Zod

Every custom tool requires strict input validation to ensure type safety. In [`src/everything/tools/echo.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/echo.ts), the `EchoSchema` uses Zod to describe expected arguments:

```typescript
import { z } from "zod";

export const EchoSchema = z.object({
  message: z.string().describe("Message to echo"),
});

```

The schema automatically validates incoming arguments against defined types before your handler executes, preventing invalid data from reaching your business logic.

## Describe the Tool with Metadata

Create a configuration object containing the tool's **name**, **title**, **description**, and **inputSchema**. In the `modelcontextprotocol/servers` codebase, this metadata object accompanies the schema definition in the same file:

```typescript
const name = "echo";
const config = {
  title: "Echo Tool",
  description: "Echoes back the input string",
  inputSchema: EchoSchema,
};

```

This metadata registers with the MCP server and becomes visible to clients during tool discovery.

## Implement the Handler Function

The handler receives raw arguments, validates them against your Zod schema using `.parse()`, and returns a `CallToolResult`. The `registerEchoTool` function in [`src/everything/tools/echo.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/echo.ts) demonstrates this pattern:

```typescript
export const registerEchoTool = (server: McpServer) => {
  server.registerTool(name, config, async (args): Promise<CallToolResult> => {
    const validated = EchoSchema.parse(args);
    return {
      content: [{ type: "text", text: `Echo: ${validated.message}` }],
    };
  });
};

```

The handler must return an object with a `content` array containing typed content blocks (text, images, or resources).

## Register the Tool with the Server

Export a registration function that accepts an `McpServer` instance and calls `server.registerTool()`. Centralize all tool registrations in [`src/everything/tools/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/index.ts) to maintain clean separation of concerns:

```typescript
import { registerEchoTool } from "./echo.js";

export const registerTools = (server: McpServer) => {
  // Existing tools...
  registerEchoTool(server);
};

```

This centralization pattern allows the server bootstrap code to import a single `registerTools` function that wires up all available functionality.

## Wire Registration into Server Startup

The server entry point in [`src/everything/server/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/index.ts) initializes the `McpServer` and invokes your registration functions during the startup sequence:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerTools, registerConditionalTools } from "./tools/index.js";

export const start = async () => {
  const server = new McpServer();
  registerTools(server);
  // Conditional tools are added after client capability negotiation
  server.on("clientReady", () => registerConditionalTools(server));
  await server.listen();
};

```

Tools registered via `registerTools()` become immediately available to clients, while conditional tools wait for the `clientReady` event.

## Complete Working Example: Sum Calculator

Below is a minimal end-to-end implementation adding a `get-sum` tool that adds two numbers.

### Create the Tool Implementation

Create [`src/everything/tools/get-sum.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/get-sum.ts) with the following content:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

export const SumSchema = z.object({
  a: z.number().describe("First operand"),
  b: z.number().describe("Second operand"),
});

const name = "get-sum";
const config = {
  title: "Sum Numbers",
  description: "Returns the sum of two numbers",
  inputSchema: SumSchema,
};

export const registerGetSumTool = (server: McpServer) => {
  server.registerTool(name, config, async (args): Promise<CallToolResult> => {
    const { a, b } = SumSchema.parse(args);
    return { content: [{ type: "text", text: `Result: ${a + b}` }] };
  });
};

```

### Register the New Tool

Update [`src/everything/tools/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/index.ts) to include the new registration:

```typescript
import { registerGetSumTool } from "./get-sum.js";

export const registerTools = (server: McpServer) => {
  // ...other tools
  registerGetSumTool(server);
};

```

### Invoke from a Client

Once the server is running, clients call the tool by name with validated arguments:

```json
{
  "tool": "get-sum",
  "arguments": { "a": 3, "b": 7 }
}

```

The server responds with:

```json
{
  "content": [{ "type": "text", "text": "Result: 10" }]
}

```

## Summary

- **Input Validation**: Define Zod schemas in your tool file to enforce type safety and validate client inputs before execution.
- **Metadata Configuration**: Create a config object with `name`, `title`, `description`, and `inputSchema` for tool discovery and documentation.
- **Handler Implementation**: Write async functions that parse validated arguments using `Schema.parse()` and return `CallToolResult` objects containing content arrays.
- **Registration Pattern**: Export `register*Tool` functions that call `server.registerTool()`, then import these into [`src/everything/tools/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/index.ts) for centralized management.
- **Server Integration**: Wire tool registration into the server startup flow in [`src/everything/server/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/index.ts) to make tools available immediately or conditionally after client capability negotiation.

## Frequently Asked Questions

### What is the purpose of the Zod schema in MCP tool creation?

The Zod schema enforces runtime type validation for tool inputs. When you create custom tools in MCP servers using the TypeScript SDK, the schema automatically validates incoming arguments against defined types before your handler executes, ensuring that only properly formatted data reaches your business logic.

### How does the server.registerTool() method work?

The `server.registerTool()` method, as implemented in the MCP SDK, accepts three parameters: a string name, a configuration object containing metadata and the input schema, and an async handler function. This method binds the tool to the `McpServer` instance and makes it discoverable and callable by MCP clients through the standardized protocol.

### Can tools be registered conditionally based on client capabilities?

Yes. The `modelcontextprotocol/servers` repository demonstrates conditional registration in [`src/everything/server/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/index.ts) using the `clientReady` event. Tools that require specific client capabilities should be registered inside the `server.on("clientReady", () => registerConditionalTools(server))` callback rather than in the initial `registerTools()` call, as seen in the alternative registration pattern used in [`src/sequentialthinking/lib.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/sequentialthinking/lib.ts).

### Where should custom tool files be located in the project structure?

Custom tool implementations belong in the `src/everything/tools/` directory, with each tool typically residing in its own file (e.g., [`get-sum.ts`](https://github.com/modelcontextprotocol/servers/blob/main/get-sum.ts)). Registration functions are then centralized in [`src/everything/tools/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/index.ts), following the pattern established by built-in tools like `echo` and demonstrated in the reference implementation.