πŸ“„ claude/docs/agent-sdk/custom-tools

File: custom-tools.md | Updated: 11/15/2025

Source: https://docs.claude.com/en/docs/agent-sdk/custom-tools

Agent Skills are now available! Learn more about extending Claude's capabilities with Agent Skills .

Claude Docs home pagelight logodark logo

US

English

Search...

Ctrl K

Search...

Navigation

Guides

Custom Tools

Home Developer Guide API Reference Model Context Protocol (MCP) Resources Release Notes

On this page

Custom tools allow you to extend Claude Code’s capabilities with your own functionality through in-process MCP servers, enabling Claude to interact with external services, APIs, or perform specialized operations.

​

Creating Custom Tools

Use the createSdkMcpServer and tool helper functions to define type-safe custom tools:

TypeScript

Python

Copy

import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Create an SDK MCP server with custom tools
const customServer = createSdkMcpServer({
  name: "my-custom-tools",
  version: "1.0.0",
  tools: [\
    tool(\
      "get_weather",\
      "Get current temperature for a location using coordinates",\
      {\
        latitude: z.number().describe("Latitude coordinate"),\
        longitude: z.number().describe("Longitude coordinate")\
      },\
      async (args) => {\
        const response = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&current=temperature_2m&temperature_unit=fahrenheit`);\
        const data = await response.json();\
\
        return {\
          content: [{\
            type: "text",\
            text: `Temperature: ${data.current.temperature_2m}Β°F`\
          }]\
        };\
      }\
    )\
  ]
});

​

Using Custom Tools

Pass the custom server to the query function via the mcpServers option as a dictionary/object.

Important: Custom MCP tools require streaming input mode. You must use an async generator/iterable for the prompt parameter - a simple string will not work with MCP servers.

​

Tool Name Format

When MCP tools are exposed to Claude, their names follow a specific format:

  • Pattern: mcp__{server_name}__{tool_name}
  • Example: A tool named get_weather in server my-custom-tools becomes mcp__my-custom-tools__get_weather

​

Configuring Allowed Tools

You can control which tools Claude can use via the allowedTools option:

TypeScript

Python

Copy

import { query } from "@anthropic-ai/claude-agent-sdk";

// Use the custom tools in your query with streaming input
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "What's the weather in San Francisco?"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // Use async generator for streaming input
  options: {
    mcpServers: {
      "my-custom-tools": customServer  // Pass as object/dictionary, not array
    },
    // Optionally specify which tools Claude can use
    allowedTools: [\
      "mcp__my-custom-tools__get_weather",  // Allow the weather tool\
      // Add other tools as needed\
    ],
    maxTurns: 3
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

​

Multiple Tools Example

When your MCP server has multiple tools, you can selectively allow them:

TypeScript

Python

Copy

const multiToolServer = createSdkMcpServer({
  name: "utilities",
  version: "1.0.0",
  tools: [\
    tool("calculate", "Perform calculations", { /* ... */ }, async (args) => { /* ... */ }),\
    tool("translate", "Translate text", { /* ... */ }, async (args) => { /* ... */ }),\
    tool("search_web", "Search the web", { /* ... */ }, async (args) => { /* ... */ })\
  ]
});

// Allow only specific tools with streaming input
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "Calculate 5 + 3 and translate 'hello' to Spanish"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // Use async generator for streaming input
  options: {
    mcpServers: {
      utilities: multiToolServer
    },
    allowedTools: [\
      "mcp__utilities__calculate",   // Allow calculator\
      "mcp__utilities__translate",   // Allow translator\
      // "mcp__utilities__search_web" is NOT allowed\
    ]
  }
})) {
  // Process messages
}

​

Type Safety with Python

The @tool decorator supports various schema definition approaches for type safety:

TypeScript

Python

Copy

import { z } from "zod";

tool(
  "process_data",
  "Process structured data with type safety",
  {
    // Zod schema defines both runtime validation and TypeScript types
    data: z.object({
      name: z.string(),
      age: z.number().min(0).max(150),
      email: z.string().email(),
      preferences: z.array(z.string()).optional()
    }),
    format: z.enum(["json", "csv", "xml"]).default("json")
  },
  async (args) => {
    // args is fully typed based on the schema
    // TypeScript knows: args.data.name is string, args.data.age is number, etc.
    console.log(`Processing ${args.data.name}'s data as ${args.format}`);
    
    // Your processing logic here
    return {
      content: [{\
        type: "text",\
        text: `Processed data for ${args.data.name}`\
      }]
    };
  }
)

​

Error Handling

Handle errors gracefully to provide meaningful feedback:

TypeScript

Python

Copy

tool(
  "fetch_data",
  "Fetch data from an API",
  {
    endpoint: z.string().url().describe("API endpoint URL")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);
      
      if (!response.ok) {
        return {
          content: [{\
            type: "text",\
            text: `API error: ${response.status} ${response.statusText}`\
          }]
        };
      }
      
      const data = await response.json();
      return {
        content: [{\
          type: "text",\
          text: JSON.stringify(data, null, 2)\
        }]
      };
    } catch (error) {
      return {
        content: [{\
          type: "text",\
          text: `Failed to fetch data: ${error.message}`\
        }]
      };
    }
  }
)

​

Example Tools

​

Database Query Tool

TypeScript

Python

Copy

const databaseServer = createSdkMcpServer({
  name: "database-tools",
  version: "1.0.0",
  tools: [\
    tool(\
      "query_database",\
      "Execute a database query",\
      {\
        query: z.string().describe("SQL query to execute"),\
        params: z.array(z.any()).optional().describe("Query parameters")\
      },\
      async (args) => {\
        const results = await db.query(args.query, args.params || []);\
        return {\
          content: [{\
            type: "text",\
            text: `Found ${results.length} rows:\n${JSON.stringify(results, null, 2)}`\
          }]\
        };\
      }\
    )\
  ]
});

​

API Gateway Tool

TypeScript

Python

Copy

const apiGatewayServer = createSdkMcpServer({
  name: "api-gateway",
  version: "1.0.0",
  tools: [\
    tool(\
      "api_request",\
      "Make authenticated API requests to external services",\
      {\
        service: z.enum(["stripe", "github", "openai", "slack"]).describe("Service to call"),\
        endpoint: z.string().describe("API endpoint path"),\
        method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("HTTP method"),\
        body: z.record(z.any()).optional().describe("Request body"),\
        query: z.record(z.string()).optional().describe("Query parameters")\
      },\
      async (args) => {\
        const config = {\
          stripe: { baseUrl: "https://api.stripe.com/v1", key: process.env.STRIPE_KEY },\
          github: { baseUrl: "https://api.github.com", key: process.env.GITHUB_TOKEN },\
          openai: { baseUrl: "https://api.openai.com/v1", key: process.env.OPENAI_KEY },\
          slack: { baseUrl: "https://slack.com/api", key: process.env.SLACK_TOKEN }\
        };\
        \
        const { baseUrl, key } = config[args.service];\
        const url = new URL(`${baseUrl}${args.endpoint}`);\
        \
        if (args.query) {\
          Object.entries(args.query).forEach(([k, v]) => url.searchParams.set(k, v));\
        }\
        \
        const response = await fetch(url, {\
          method: args.method,\
          headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },\
          body: args.body ? JSON.stringify(args.body) : undefined\
        });\
        \
        const data = await response.json();\
        return {\
          content: [{\
            type: "text",\
            text: JSON.stringify(data, null, 2)\
          }]\
        };\
      }\
    )\
  ]
});

​

Calculator Tool

TypeScript

Python

Copy

const calculatorServer = createSdkMcpServer({
  name: "calculator",
  version: "1.0.0",
  tools: [\
    tool(\
      "calculate",\
      "Perform mathematical calculations",\
      {\
        expression: z.string().describe("Mathematical expression to evaluate"),\
        precision: z.number().optional().default(2).describe("Decimal precision")\
      },\
      async (args) => {\
        try {\
          // Use a safe math evaluation library in production\
          const result = eval(args.expression); // Example only!\
          const formatted = Number(result).toFixed(args.precision);\
          \
          return {\
            content: [{\
              type: "text",\
              text: `${args.expression} = ${formatted}`\
            }]\
          };\
        } catch (error) {\
          return {\
            content: [{\
              type: "text",\
              text: `Error: Invalid expression - ${error.message}`\
            }]\
          };\
        }\
      }\
    ),\
    tool(\
      "compound_interest",\
      "Calculate compound interest for an investment",\
      {\
        principal: z.number().positive().describe("Initial investment amount"),\
        rate: z.number().describe("Annual interest rate (as decimal, e.g., 0.05 for 5%)"),\
        time: z.number().positive().describe("Investment period in years"),\
        n: z.number().positive().default(12).describe("Compounding frequency per year")\
      },\
      async (args) => {\
        const amount = args.principal * Math.pow(1 + args.rate / args.n, args.n * args.time);\
        const interest = amount - args.principal;\
        \
        return {\
          content: [{\
            type: "text",\
            text: `Investment Analysis:\n` +\
                  `Principal: $${args.principal.toFixed(2)}\n` +\
                  `Rate: ${(args.rate * 100).toFixed(2)}%\n` +\
                  `Time: ${args.time} years\n` +\
                  `Compounding: ${args.n} times per year\n\n` +\
                  `Final Amount: $${amount.toFixed(2)}\n` +\
                  `Interest Earned: $${interest.toFixed(2)}\n` +\
                  `Return: ${((interest / args.principal) * 100).toFixed(2)}%`\
          }]\
        };\
      }\
    )\
  ]
});

​

Related Documentation

Was this page helpful?

YesNo

MCP in the SDK Subagents in the SDK

Assistant

Responses are generated using AI and may contain mistakes.