Herramientas MCP

Las herramientas permiten que los LLMs ejecuten acciones a través de servidores MCP. Aprende cómo crear herramientas efectivas y seguras.

¿Qué son las Herramientas?

Las herramientas son funciones que el LLM puede ejecutar a través del servidor MCP. A diferencia de los recursos (que son de solo lectura), las herramientas permiten realizar acciones: modificar datos, ejecutar operaciones, interactuar con sistemas externos, etc.

Listar

El cliente puede solicitar una lista de todas las herramientas disponibles.

Ejecutar

El LLM puede llamar a una herramienta con parámetros específicos.

Resultado

La herramienta ejecuta la acción y retorna un resultado estructurado.

Cómo Crear Herramientas en MCP

Para crear herramientas en tu servidor MCP, necesitas implementar dos métodos: list_tools para listar las herramientas disponibles, y call_tool para ejecutarlas.

Ejemplo: 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)

Ejemplo: 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();

Tipos de Herramientas

Las herramientas pueden realizar diferentes tipos de operaciones. Cada tipo tiene sus propias características y casos de uso.

Manipulación de Archivos

Crear, leer, modificar y eliminar archivos. Útil para gestión de documentos y código.

Ejemplos:

create_file, read_file, delete_file, write_file

Caso de uso:

Gestión de documentación, edición de código, organización de archivos.

Consultas de Base de Datos

Ejecutar consultas SQL, insertar datos, actualizar registros. Permite interacción con bases de datos.

Ejemplos:

query_database, insert_record, update_user

Caso de uso:

Análisis de datos, gestión de usuarios, reportes dinámicos.

Comunicación Externa

Enviar emails, mensajes, notificaciones. Conectar con servicios de comunicación.

Ejemplos:

send_email, post_message, send_notification

Caso de uso:

Notificaciones automáticas, envío de reportes, comunicación con usuarios.

Búsqueda Web

Buscar información en internet, obtener datos de APIs, realizar scraping.

Ejemplos:

web_search, fetch_url, get_weather

Caso de uso:

Investigación, obtención de datos en tiempo real, integración con servicios.

Procesamiento de Datos

Transformar, validar, calcular y procesar información. Operaciones sobre datos.

Ejemplos:

transform_json, validate_data, calculate_metrics

Caso de uso:

ETL, validación de datos, cálculos complejos, transformaciones.

Operaciones del Sistema

Ejecutar comandos, gestionar procesos, interactuar con el sistema operativo.

Ejemplos:

run_command, list_processes, system_info

Caso de uso:

Automatización, gestión de servidores, operaciones de sistema.

Ejemplos Prácticos

Aquí tienes ejemplos completos de diferentes tipos de herramientas.

Ejemplo 1: Herramienta de Base de Datos

Herramienta para ejecutar consultas SQL en una base de datos

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}")

Ejemplo 2: Herramienta de Búsqueda Web

Herramienta para buscar información en 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}")

Ejemplo 3: Herramienta de Procesamiento

Herramienta para transformar y procesar datos JSON

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}")

Buenas Prácticas

Sigue estas recomendaciones para crear herramientas seguras, eficientes y fáciles de usar.

Descripciones Claras

Proporciona descripciones detalladas y específicas para cada herramienta. El LLM usa estas descripciones para decidir cuándo usarlas.

Esquemas de Validación

Define esquemas JSON Schema completos para los parámetros. Incluye tipos, requeridos, valores por defecto y validaciones.

Manejo de Errores

Implementa manejo robusto de errores. Retorna mensajes claros y útiles cuando algo falla para ayudar al LLM a entender qué pasó.

Idempotencia

Diseña herramientas idempotentes cuando sea posible. Ejecutar la misma herramienta múltiples veces debería tener el mismo efecto.

Validación de Entrada

Valida y sanitiza todos los parámetros de entrada antes de procesarlos. Nunca confíes en la entrada del usuario.

Respuestas Estructuradas

Retorna respuestas estructuradas y consistentes. Usa TextContent o ImageContent según corresponda, con formato claro.

¿Listo para crear tus propias herramientas?

Explora más sobre recursos, prompts y la arquitectura completa de MCP.