MCP Tools
Tools allow LLMs to execute actions through MCP servers. Learn how to create effective and secure tools.
What are Tools?
Tools are functions that the LLM can execute through the MCP server. Unlike resources (which are read-only), tools allow performing actions: modifying data, executing operations, interacting with external systems, etc.tools
List
The client can request a list of all available tools.
Execute
The LLM can call a tool with specific parameters.
Result
The tool executes the action and returns a structured result.
How to Create Tools in MCP
To create tools in your MCP server, you need to implement two methods: list_tools to list available tools, and call_tool to execute them.
Example: Python SDK
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.types as types
import os
app = Server("file-tools-server")
@app.list_tools()
async def list_tools() -> list[Tool]:
"""Lista todas las herramientas disponibles"""
return [
Tool(
name="create_file",
description="Crea un nuevo archivo con el contenido especificado",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Ruta del archivo a crear"
},
"content": {
"type": "string",
"description": "Contenido del archivo"
}
},
"required": ["path", "content"]
}
),
Tool(
name="read_file",
description="Lee el contenido de un archivo",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Ruta del archivo a leer"
}
},
"required": ["path"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""Ejecuta una herramienta específica"""
if name == "create_file":
path = arguments.get("path")
content = arguments.get("content")
# Validar entrada
if not path or not content:
raise ValueError("path y content son requeridos")
# Crear archivo
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return [TextContent(
type="text",
text=f"Archivo creado exitosamente: {path}"
)]
elif name == "read_file":
path = arguments.get("path")
if not path:
raise ValueError("path es requerido")
if not os.path.exists(path):
raise FileNotFoundError(f"Archivo no encontrado: {path}")
with open(path, "r", encoding="utf-8") as f:
content = f.read()
return [TextContent(
type="text",
text=content
)]
else:
raise ValueError(f"Herramienta desconocida: {name}")
# Ejecutar servidor
if __name__ == "__main__":
from mcp.server.stdio import stdio_server
stdio_server(app)Example: TypeScript SDK
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { writeFileSync, readFileSync, existsSync } from "fs";
const server = new Server(
{
name: "file-tools-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "create_file",
description: "Crea un nuevo archivo con el contenido especificado",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Ruta del archivo a crear",
},
content: {
type: "string",
description: "Contenido del archivo",
},
},
required: ["path", "content"],
},
},
{
name: "read_file",
description: "Lee el contenido de un archivo",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Ruta del archivo a leer",
},
},
required: ["path"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "create_file") {
const path = args?.path as string;
const content = args?.content as string;
if (!path || !content) {
throw new Error("path y content son requeridos");
}
writeFileSync(path, content, "utf-8");
return {
content: [
{
type: "text",
text: `Archivo creado exitosamente: ${path}`,
},
],
};
}
if (name === "read_file") {
const path = args?.path as string;
if (!path) {
throw new Error("path es requerido");
}
if (!existsSync(path)) {
throw new Error(`Archivo no encontrado: ${path}`);
}
const content = readFileSync(path, "utf-8");
return {
content: [
{
type: "text",
text: content,
},
],
};
}
throw new Error(`Herramienta desconocida: ${name}`);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();Tool Types
Tools can perform different types of operations. Each type has its own characteristics and use cases.
File Manipulation
Create, read, modify and delete files. Useful for document and code management.
Examples:
create_file, read_file, delete_file, write_file
Use case:
Documentation management, code editing, file organization.
Database Queries
Execute SQL queries, insert data, update records. Allows interaction with databases.
Examples:
query_database, insert_record, update_user
Use case:
Data analysis, user management, dynamic reports.
External Communication
Send emails, messages, notifications. Connect with communication services.
Examples:
send_email, post_message, send_notification
Use case:
Automatic notifications, report sending, user communication.
Web Search
Search information on the internet, fetch data from APIs, perform scraping.
Examples:
web_search, fetch_url, get_weather
Use case:
Research, real-time data fetching, service integration.
Data Processing
Transform, validate, calculate and process information. Operations on data.
Examples:
transform_json, validate_data, calculate_metrics
Use case:
ETL, data validation, complex calculations, transformations.
System Operations
Execute commands, manage processes, interact with the operating system.
Examples:
run_command, list_processes, system_info
Use case:
Automation, server management, system operations.
Practical Examples
Here are complete examples of different types of tools.
Example 1: Database Tool
Tool to execute SQL queries on a database
from mcp.server import Server
from mcp.types import Tool, TextContent
import sqlite3
import json
app = Server("database-tools")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="query_database",
description="Ejecuta una consulta SQL SELECT en la base de datos",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Consulta SQL SELECT a ejecutar"
}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "query_database":
query = arguments.get("query", "").strip()
# Validación de seguridad: solo SELECT
if not query.upper().startswith("SELECT"):
raise ValueError("Solo se permiten consultas SELECT")
conn = sqlite3.connect("example.db")
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
cursor.execute(query)
rows = cursor.fetchall()
# Convertir a JSON
results = [dict(row) for row in rows]
return [TextContent(
type="text",
text=json.dumps(results, indent=2, ensure_ascii=False)
)]
except Exception as e:
raise ValueError(f"Error ejecutando consulta: {str(e)}")
finally:
conn.close()
raise ValueError(f"Herramienta desconocida: {name}")Example 2: Web Search Tool
Tool to search for information on the internet
from mcp.server import Server
from mcp.types import Tool, TextContent
import httpx
app = Server("web-search-tools")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="web_search",
description="Busca información en internet usando una API de búsqueda",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Término de búsqueda"
},
"max_results": {
"type": "number",
"description": "Número máximo de resultados (default: 5)",
"default": 5,
"minimum": 1,
"maximum": 20
}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "web_search":
query = arguments.get("query")
max_results = arguments.get("max_results", 5)
if not query:
raise ValueError("query es requerido")
# Llamar a API de búsqueda (ejemplo con DuckDuckGo)
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.duckduckgo.com/",
params={
"q": query,
"format": "json",
"no_html": "1",
"skip_disambig": "1"
}
)
results = response.json()
# Formatear resultados
formatted_results = []
for i, result in enumerate(results.get("Results", [])[:max_results]):
formatted_results.append(
f"{i+1}. {result.get('Text', 'Sin descripción')}\n"
f" URL: {result.get('FirstURL', 'N/A')}"
)
return [TextContent(
type="text",
text="\n\n".join(formatted_results) if formatted_results else "No se encontraron resultados"
)]
raise ValueError(f"Herramienta desconocida: {name}")Example 3: Processing Tool
Tool to transform and process JSON data
from mcp.server import Server
from mcp.types import Tool, TextContent
import json
app = Server("data-processing-tools")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="transform_json",
description="Transforma un objeto JSON aplicando una función de transformación",
inputSchema={
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "JSON string a transformar"
},
"operation": {
"type": "string",
"enum": ["uppercase_keys", "lowercase_keys", "sort_keys"],
"description": "Operación a realizar"
}
},
"required": ["data", "operation"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "transform_json":
data_str = arguments.get("data")
operation = arguments.get("operation")
try:
data = json.loads(data_str)
except json.JSONDecodeError:
raise ValueError("data debe ser un JSON válido")
if operation == "uppercase_keys":
transformed = {k.upper(): v for k, v in data.items()}
elif operation == "lowercase_keys":
transformed = {k.lower(): v for k, v in data.items()}
elif operation == "sort_keys":
transformed = dict(sorted(data.items()))
else:
raise ValueError(f"Operación desconocida: {operation}")
return [TextContent(
type="text",
text=json.dumps(transformed, indent=2)
)]
raise ValueError(f"Herramienta desconocida: {name}")Best Practices
Follow these recommendations to create secure, efficient and easy-to-use tools.
Clear Descriptions
Provide detailed and specific descriptions for each tool. The LLM uses these descriptions to decide when to use them.
Validation Schemas
Define complete JSON Schema schemas for parameters. Include types, required fields, default values and validations.
Error Handling
Implement robust error handling. Return clear and useful messages when something fails to help the LLM understand what happened.
Idempotency
Design idempotent tools when possible. Executing the same tool multiple times should have the same effect.
Input Validation
Validate and sanitize all input parameters before processing them. Never trust user input.
Structured Responses
Return structured and consistent responses. Use TextContent or ImageContent as appropriate, with clear formatting.
Ready to create your own tools?
Explore more about resources, prompts and the complete MCP architecture.