Back to Learn
intermediate20 min
Creating MCP Tools
Learn how to define powerful MCP tools with proper validation and error handling.
Prerequisites
- Complete the "Your First MCP Server" tutorial
- Basic understanding of TypeScript/JavaScript
- Familiarity with async/await
1
Tool Definition
How to define tools with server.tool()
MCP tools are defined using the server.tool() method. Each tool consists of four key components:
1. Tool Name
Unique identifier for the tool. Use kebab-case or camelCase.
2. Description
Clear explanation of what the tool does. The AI uses this to decide when to call it.
3. Input Schema
Zod schema defining the tool's parameters and validation rules.
4. Handler Function
Async function that executes the tool's logic and returns results.
index.js
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "tools-demo",
version: "1.0.0",
});
// Basic tool definition
server.tool(
"capitalize", // 1. Tool name (unique identifier)
"Capitalize the first letter of a string", // 2. Description for AI
{ text: z.string() }, // 3. Input schema using Zod
async ({ text }) => { // 4. Handler function
const result = text.charAt(0).toUpperCase() + text.slice(1);
return {
content: [{ type: "text", text: result }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);Tool Response Format: All tools must return an object with a
content array. Each content item must have a type (usually "text") and the corresponding data.Write descriptive tool names and descriptions! The AI model reads these to understand when and how to use your tool. Clear descriptions lead to better AI interactions.
2
Zod Schemas
Use Zod for powerful parameter validation
3
Error Handling
Handle errors and edge cases properly
Best Practices & Advanced Patterns
Real-world examples showing best practices
Here are some well-designed tools demonstrating best practices:
best-practices.js
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "best-practices-demo",
version: "1.0.0",
});
// ✅ Good: Clear names and descriptions
server.tool(
"calculate-mortgage-payment",
"Calculate monthly mortgage payment based on loan amount, interest rate, and term",
{
loanAmount: z.number().min(0)
.describe("Total loan amount in dollars"),
annualInterestRate: z.number().min(0).max(100)
.describe("Annual interest rate as a percentage (e.g., 5.5 for 5.5%)"),
termYears: z.number().min(1).max(50)
.describe("Loan term in years"),
},
async ({ loanAmount, annualInterestRate, termYears }) => {
// Validate inputs
if (loanAmount <= 0) {
return {
isError: true,
content: [{ type: "text", text: "Loan amount must be greater than 0" }],
};
}
// Calculate
const monthlyRate = annualInterestRate / 100 / 12;
const numPayments = termYears * 12;
if (monthlyRate === 0) {
// Special case: 0% interest
const payment = loanAmount / numPayments;
return {
content: [{
type: "text",
text: `Monthly payment: $${payment.toFixed(2)}`
}],
};
}
const payment = loanAmount *
(monthlyRate * Math.pow(1 + monthlyRate, numPayments)) /
(Math.pow(1 + monthlyRate, numPayments) - 1);
return {
content: [{
type: "text",
text: `Monthly payment: $${payment.toFixed(2)} over ${termYears} years`
}],
};
}
);
// ✅ Good: Multiple content types
server.tool(
"analyze-data",
"Analyze data and return summary statistics",
{
numbers: z.array(z.number()).min(1),
},
async ({ numbers }) => {
if (numbers.length === 0) {
return {
isError: true,
content: [{ type: "text", text: "Array cannot be empty" }],
};
}
const sum = numbers.reduce((a, b) => a + b, 0);
const mean = sum / numbers.length;
const sorted = [...numbers].sort((a, b) => a - b);
const median = sorted.length % 2 === 0
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
: sorted[Math.floor(sorted.length / 2)];
return {
content: [
{
type: "text",
text: `Analysis of ${numbers.length} numbers:`
},
{
type: "text",
text: `Mean: ${mean.toFixed(2)}`
},
{
type: "text",
text: `Median: ${median}`
},
{
type: "text",
text: `Min: ${sorted[0]}`
},
{
type: "text",
text: `Max: ${sorted[sorted.length - 1]}`
},
],
};
}
);
// ✅ Good: Proper async handling
server.tool(
"fetch-weather",
"Fetch current weather for a city",
{
city: z.string().min(1).describe("City name"),
country: z.string().length(2).optional()
.describe("2-letter country code (e.g., US, UK)"),
},
async ({ city, country }) => {
try {
const location = country ? `${city},${country}` : city;
const response = await fetch(
`https://api.example.com/weather?q=${encodeURIComponent(location)}`
);
if (!response.ok) {
return {
isError: true,
content: [{
type: "text",
text: `Failed to fetch weather: ${response.statusText}`
}],
};
}
const data = await response.json();
return {
content: [{
type: "text",
text: `Weather in ${city}: ${data.temperature}°C, ${data.condition}`
}],
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
};
}
}
);Interactive Playground
Try the code in our playground instead of setting up locally
Experiment with these tools in the interactive playground. Try different inputs and see how the validation and error handling works:
MCP Tools Playground
Test the tools with different inputs to see validation and error handling in action
Loading...
Try these tests in the playground:
- • Test reverse-string with empty text to see error handling
- • Try calculate with divide by zero
- • Use power operation with very large numbers
- • Validate different email formats
Quick Reference
Tool Definition Structure
server.tool(
"tool-name", // Unique identifier
"Description", // What it does
{ /* Zod schema */ }, // Input validation
async (params) => { // Handler function
return {
content: [{ type: "text", text: "result" }]
};
}
);Error Response
return {
isError: true,
content: [{ type: "text", text: "Error message" }]
};Success Response
return {
content: [
{ type: "text", text: "Result text" },
// Can include multiple content items
]
};