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.