In MCP, tools are executable functions that servers expose to clients. They allow AI applications to perform actions like:
Fetching data from external APIs
Performing calculations
Manipulating data
Interacting with external systems
Managing state (CRUD operations)
Tools are the primary way servers provide active capabilities to AI applications.
Every MCP tool consists of:
Name - Unique identifier for the tool
Description - Explains what the tool does (optional but recommended)
Input Schema - Defines expected parameters with validation
Handler Function - Implements the tool’s logic
From source/servers/basic/src/server.ts:64-99:
server . tool (
"get_random_quotes" , // Tool name
{ count: z . number (). optional (). default ( 5 ) }, // Input schema
async ({ count }) => { // Handler function
try {
// Validate count
if ( count <= 0 ) {
return {
content: [{ type: "text" , text: "Count must be a positive number." }],
isError: true
};
}
if ( count > 10 ) {
return {
content: [{ type: "text" , text: "Maximum number of quotes is 10." }],
isError: true
};
}
// Fetch quotes from API
const quotes = await fetchRandomQuotes ( count );
const formattedQuotes = quotes . map ( formatQuote );
return {
content: [{ type: "text" , text: formattedQuotes . join ( " \n --- \n " ) }]
};
} catch ( error ) {
return {
content: [{ type: "text" , text: `Error: ${ ( error as Error ). message } ` }],
isError: true
};
}
}
);
From source/servers/basic/src/server.ts:102-125:
server . tool (
"lcm" ,
"Calculate the least common multiple of a list of numbers" , // Description
{
numbers: z . array ( z . number ())
. min ( 2 )
. describe ( "A list of numbers to calculate the least common multiple of. The list must contain at least two numbers." )
},
async ({ numbers }) => {
try {
// Calculate LCM
let result = Math . floor ( numbers [ 0 ]);
for ( let i = 1 ; i < numbers . length ; i ++ ) {
const num = Math . floor ( numbers [ i ]);
result = lcm ( result , num );
}
return {
content: [{ type: "text" , text: `The least common multiple is: ${ result } ` }]
};
} catch ( error ) {
return {
content: [{ type: "text" , text: `Error: ${ ( error as Error ). message } ` }],
isError: true
};
}
}
);
Using FastMCP Decorator
From source/servers/calculator-py/server.py:19-30:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP( "Calculator MCP Server" )
@mcp.tool ()
def calculate ( a : float , b : float , operation : str ) -> float :
"""Perform a calculation with two numbers."""
if operation == "add" :
return add(a, b)
elif operation == "subtract" :
return subtract(a, b)
elif operation == "multiply" :
return multiply(a, b)
elif operation == "divide" :
return divide(a, b)
else :
raise ValueError ( "Operación no válida" )
Python type hints are automatically converted to JSON schema for validation. The docstring becomes the tool description.
Fetching data from external APIs:
const GOT_API_BASE = "https://api.gameofthronesquotes.xyz/v1" ;
async function fetchRandomQuotes ( count : number ) : Promise < Quote []> {
if ( count <= 0 ) {
throw new Error ( "count must be a positive number" );
}
if ( count > 10 ) {
throw new Error ( "maximum number of quotes is 10" );
}
const url = ` ${ GOT_API_BASE } /random/ ${ count } ` ;
const response = await fetch ( url );
if ( ! response . ok ) {
throw new Error ( `Error fetching quotes: ${ response . statusText } ` );
}
return await response . json () as Quote [];
}
From source/servers/calculator-py/server.py:5-17:
def add ( a : float , b : float ) -> float :
return float (a + b)
def subtract ( a : float , b : float ) -> float :
return float (a - b)
def multiply ( a : float , b : float ) -> float :
return float (a * b)
def divide ( a : float , b : float ) -> float :
if b == 0 :
raise ValueError ( "No se puede dividir por cero" )
return float (a / b)
CRUD Operations
From source/servers/todo-ts/src/index.ts:15-97:
// Create
server . tool (
"TODO-Create" ,
"Create a new todo item" ,
{
task: z . string (). describe ( "The task to add to the todo list" ),
},
async ({ task }) => {
const todo = createTodo ( task );
return {
content: [{
type: "text" ,
text: `Todo created: ${ todo . task } ` ,
}],
};
}
)
// Read
server . tool (
"TODO-List" ,
"List all todo items" ,
{},
async () => {
const todos = getAllTodos ();
return {
content: [{ type: "text" , text: JSON . stringify ( todos , null , 2 )}],
};
}
)
// Update
server . tool (
"TODO-Update" ,
"Update a todo item" ,
{
id: z . string (). describe ( "The id of the todo item to update" ),
task: z . string (). describe ( "The new task for the todo item" ),
},
async ({ id , task }) => {
const todo = updateTodoTask ( id , task );
return {
content: [{ type: "text" , text: `Todo updated: ${ todo ?. task } ` }],
};
}
)
// Delete
server . tool (
"TODO-Delete" ,
"Delete a todo item" ,
{
id: z . string (). describe ( "The id of the todo item to delete" ),
},
async ({ id }) => {
const success = deleteTodo ( id );
return {
content: [{ type: "text" , text: `Todo deleted: ${ success ? "Success" : "Failed" } ` }],
};
}
)
TypeScript with Zod
Zod provides powerful schema validation:
import { z } from "zod" ;
server . tool (
"advanced_search" ,
{
query: z . string (). min ( 1 ). max ( 100 ),
limit: z . number (). int (). positive (). max ( 50 ). default ( 10 ),
filters: z . object ({
category: z . enum ([ "fiction" , "nonfiction" , "science" ]). optional (),
minRating: z . number (). min ( 0 ). max ( 5 ). optional (),
}). optional (),
sortBy: z . enum ([ "relevance" , "date" , "rating" ]). default ( "relevance" )
},
async ({ query , limit , filters , sortBy }) => {
// All parameters are validated and typed
// ...
}
);
Python Type Validation
from typing import Literal, Optional
from pydantic import BaseModel, Field
class SearchFilters ( BaseModel ):
category: Optional[Literal[ "fiction" , "nonfiction" , "science" ]] = None
min_rating: Optional[ float ] = Field( None , ge = 0 , le = 5 )
@mcp.tool ()
def advanced_search (
query : str = Field( ... , min_length = 1 , max_length = 100 ),
limit : int = Field( 10 , gt = 0 , le = 50 ),
filters : Optional[SearchFilters] = None ,
sort_by : Literal[ "relevance" , "date" , "rating" ] = "relevance"
) -> list :
# All parameters are validated
pass
All tool responses follow a standard format:
return {
content: [
{
type: "text" , // Content type (text, image, resource)
text: "Result" // The actual content
}
],
isError: false // Optional error flag
};
Success Response
return {
content: [{
type: "text" ,
text: "Operation completed successfully"
}]
};
Error Response
return {
content: [{
type: "text" ,
text: "Error: Invalid parameter value"
}],
isError: true
};
Multiple Content Items
return {
content: [
{ type: "text" , text: "Summary: Found 5 results" },
{ type: "text" , text: JSON . stringify ( results , null , 2 ) }
]
};
const result = await client . callTool ({
name: "get_random_quotes" ,
arguments: {
count: 3
}
});
console . log ( result . content [ 0 ]. text );
Best Practices
Tool names should clearly indicate their purpose: // Good
server . tool ( "get_random_quotes" , ... )
server . tool ( "calculate_lcm" , ... )
server . tool ( "TODO-Create" , ... )
// Bad
server . tool ( "tool1" , ... )
server . tool ( "process" , ... )
Catch errors and return helpful messages: try {
const result = await externalAPI . fetch ();
return { content: [{ type: "text" , text: result }] };
} catch ( error ) {
return {
content: [{
type: "text" ,
text: `Failed to fetch data: ${ error . message } `
}],
isError: true
};
}
Add Descriptions and Schema Details
Help AI models understand how to use your tools: server . tool (
"search" ,
"Search the database for matching records" ,
{
query: z . string (). describe ( "The search query string" ),
limit: z . number (). describe ( "Maximum number of results to return" )
},
async ({ query , limit }) => { ... }
);
Tools execute with the same permissions as your server process. Be careful with tools that modify files, execute commands, or access sensitive data.
Test your tools before deploying:
// Create a test client
const client = new Client ( ... );
await client . connect ( transport );
// List available tools
const tools = await client . listTools ();
console . log ( tools );
// Test tool execution
const result = await client . callTool ({
name: "your_tool" ,
arguments: { /* test data */ }
});
if ( result . isError ) {
console . error ( "Tool failed:" , result . content [ 0 ]. text );
} else {
console . log ( "Tool succeeded:" , result . content [ 0 ]. text );
}
Next Steps
Resources Learn how to provide data through resources
Prompts Create reusable prompt templates for AI interactions
Servers Understand MCP server architecture
Clients Learn how clients consume tools