Definición de Herramientas Llamables en un Servidor MCP
Una herramienta llamable es la forma más directa en que un servidor MCP le da algo que hacer a un modelo.
Busca en todas las páginas de la documentación
Una herramienta llamable es la forma más directa en que un servidor MCP le da algo que hacer a un modelo.
Configurar una herramienta correctamente significa que el modelo puede encontrarla, entender qué hace, llamarla con argumentos válidos y dar sentido a lo que regresa.
Una herramienta en el SDK de Python de MCP comienza como una función ordinaria decorada con @mcp.tool().
El nombre de la función, sus parámetros con anotaciones de tipo y su cadena de documentación (docstring) se convierten conjuntamente en el esquema que un cliente utiliza para decidir cuándo y cómo llamarla.
Los manejadores deben ser estrechos y predecibles, haciendo un trabajo bien hecho en lugar de ramificarse en varios comportamientos no relacionados basándose en un argumento de bandera.
Los errores deben elevarse como excepciones con un mensaje claro, no ser suprimidos o devueltos como cadenas ambiguas, para que el modelo obtenga retroalimentación útil cuando una llamada falla.
Las entradas estructuradas y validadas (a través de modelos Pydantic o anotaciones de tipo simples) detectan argumentos incorrectos antes de que su código de manejo se ejecute.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("tool-recipes")
@mcp.tool()
def get_weather(city: str, units: str = "celsius") -> str:
"""Obtener el clima actual de una ciudad.
Args:
city: El nombre de la ciudad, por ejemplo, "Austin" o "Tokio".
units: "celsius" o "fahrenheit". Predeterminado a "celsius".
"""
if units not in ("celsius", "fahrenheit"):
raise ValueError('units debe ser "celsius" o "fahrenheit"')
return f"72 grados {units} y soleado en {city}"Cuándo recurrir a esto:
# server.py
from typing import Literal
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
mcp = FastMCP("task-tools")
# Almacén en memoria para este ejemplo; reemplace con una base de datos real en producción.
_tasks: dict[int, dict] = {}
_next_id = 1
class CreateTaskInput(BaseModel):
title: str = Field(..., min_length=1, max_length=200, description="Título corto de la tarea")
priority: Literal["low", "medium", "high"] = Field(
"medium", description="Nivel de prioridad de la tarea"
)
@mcp.tool()
def create_task(input: CreateTaskInput) -> str:
"""Crear una nueva tarea y devolver su id.
Úselo cuando el usuario pida añadir, seguir o recordar un elemento de la lista de tareas pendientes.
"""
global _next_id
task_id = _next_id
_next_id += 1
_tasks[task_id] = {"title": input.title, "priority": input.priority, "done": False}
return f"Tarea creada {task_id}: {input.title} (prioridad: {input.priority})"
@mcp.tool()
def complete_task(task_id: int) -> str:
"""Marcar una tarea existente como completada.
Args:
task_id: El id numérico devuelto por create_task.
"""
task = _tasks.get(task_id)
if task is None:
raise ValueError(f"No se encontró ninguna tarea con el id {task_id}")
task["done"] = True
return f"Tarea {task_id} ({task['title']}) marcada como completada"
@mcp.tool()
def list_tasks(only_open: bool = True) -> str:
"""Listar las tareas seguidas.
Args:
only_open: Si es True, ocultar las tareas ya marcadas como completadas. Predeterminado a True.
"""
rows = [
f"#{tid} [{t['priority']}] {t['title']}{' (completada)' if t['done'] else ''}"
for tid, t in _tasks.items()
if not (only_open and t["done"])
]
return "\n".join(rows) if rows else "No se encontraron tareas."
if __name__ == "__main__":
mcp.run(transport="stdio")Lo que esto demuestra:
create_task, complete_task, list_tasks) en lugar de una herramienta con un indicador mode.CreateTaskInput) que valida una entrada estructurada con una restricción de longitud y un conjunto restringido de valores permitidos.Literal que restringe priority a exactamente tres cadenas válidas, aplicado antes de que se ejecute el cuerpo del manejador.ValueError con un mensaje que nombra el problema específico.FastMCP inspecciona cada función decorada con @mcp.tool() y construye un Esquema JSON a partir de sus anotaciones de tipo y valores predeterminados.list_tools(), recibe este esquema y descripción para cada herramienta registrada, sin que usted escriba ningún esquema a mano.Literal), el SDK devuelve un error de esquema al cliente sin llamar nunca a su manejador.| Elemento | Buena Práctica | Por Qué Importa |
|---|---|---|
| Nombre de la herramienta | snake_case, empezando por verbo (create_task, no task) | El modelo empareja nombres con la intención; un verbo señala una acción |
| Primera línea del docstring | Una frase que indica exactamente lo que hace la herramienta | Este es el texto principal que el modelo usa para decidir la relevancia |
Args: del docstring | Una línea por parámetro, lenguaje claro | Clarifica unidades, formatos y rangos válidos que la anotación de tipo no puede expresar |
| Nombres de parámetros | Descriptivos, no abreviados (city, no c) | Reduce llamadas ambiguas o mal formadas |
# Prefiera un tipo restringido sobre una cadena suelta cuando el conjunto válido sea pequeño y conocido.
from typing import Literal
@mcp.tool()
def set_status(status: Literal["open", "in_progress", "closed"]) -> str:
"""Establecer el estado del elemento actual."""
return f"Estado establecido a {status}"
# Para validación más rica (rangos, formatos, campos anidados), use un modelo Pydantic
# como parámetro único, como se muestra en CreateTaskInput arriba.| Parámetro | Tipo | Descripción |
|---|---|---|
| nombre de la función | str (a través del nombre def) | Se convierte en el identificador de la herramienta; manténgalo estable una vez que los clientes dependan de él |
| anotaciones de tipo | Tipos de Python / Literal / modelo Pydantic | Compilado automáticamente en el Esquema JSON de la herramienta |
| docstring | str | Se convierte en la descripción de la herramienta mostrada al modelo |
| valor de retorno | str o contenido estructurado | Envuelto en el formato de contenido de MCP y devuelto al cliente |
handle o process. El modelo no tiene nada con qué emparejar la intención. Solución: use un nombre específico, que comience con un verbo, como create_task o send_email.mode o action que se ramifica en comportamientos no relacionados. Esto confunde el descubrimiento basado en esquemas y aumenta la probabilidad de una llamada incorrecta. Solución: divídalo en herramientas separadas y de alcance estrecho.None o una cadena vacía en caso de fallo en lugar de elevar una excepción. El modelo no tiene señal de que algo salió mal y puede informar incorrectamente del éxito. Solución: eleve una excepción con un mensaje específico; deje que el SDK la traduzca a un error de herramienta.Args:.Literal, restricciones Field de Pydantic o comprobaciones explícitas al principio del manejador.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Recurso MCP | La operación es una lectura pura sin efectos secundarios | El modelo necesita tomar una acción o realizar un cálculo |
| Prompt MCP | Desea estandarizar instrucciones, no exponer una acción llamable | El cliente necesita un valor de resultado de vuelta en la conversación |
| Llamada directa a función (no MCP) | La capacidad solo se usará alguna vez por una aplicación específica | Desea que la misma capacidad sea reutilizable en Claude Code, Claude Desktop y aplicaciones personalizadas |
Un manejador @mcp.server de bajo nivel (en lugar de FastMCP) | Necesita control total sobre el protocolo de enlace o el cableado de transporte personalizado | Solo necesita exponer un puñado de herramientas sencillas rápidamente |
FastMCP lee las anotaciones de tipo de la función para construir un Esquema JSON para sus parámetros.Las anotaciones de tipo simples (str, int, bool, Literal[...]) son suficientes para herramientas simples. Utilice un parámetro BaseModel de Pydantic cuando necesite restricciones a nivel de campo, estructuras anidadas o lógica de validación reutilizable en múltiples herramientas.
El SDK valida los argumentos entrantes contra el esquema generado antes de que se ejecute su manejador. Los argumentos inválidos producen un error de esquema devuelto al cliente, y el cuerpo de su función nunca se ejecuta.
Lo suficientemente específico como para que un lector que no esté familiarizado con su base de código pueda adivinar exactamente cuándo llamarla y qué significa cada argumento, incluidas las unidades, formatos o rangos válidos que no son obvios por la anotación de tipo.
Varias herramientas estrechas, en la mayoría de los casos. Una herramienta con un indicador mode o action oculta varios comportamientos detrás de un solo esquema, lo que dificulta que el modelo elija correctamente y dificulta que usted pruebe cada comportamiento de forma aislada.
@mcp.tool()
def withdraw(account_id: str, amount: float) -> str:
"""Retirar fondos de una cuenta."""
if amount <= 0:
raise ValueError("el monto debe ser positivo")
return f"Retirados {amount} de {account_id}"Elevar una excepción estándar con un mensaje claro es suficiente; el SDK la convierte en un error de herramienta que el cliente puede mostrar al modelo.
Nada impide que un manejador llame a una función Python normal que otra herramienta también llama, pero las herramientas no deben llamarse entre sí a través de la capa del protocolo MCP en sí. Comparta la lógica a través de la composición de funciones ordinarias en su lugar.
Las llamadas concurrentes pueden competir contra el estado compartido, produciendo resultados inconsistentes o datos corruptos. Está bien para una demostración o prototipo, pero las herramientas de producción deben usar un almacén de datos real con las garantías transaccionales adecuadas.
Por defecto sí, FastMCP usa el nombre de la función como nombre de la herramienta. Manténgalo estable una vez que cualquier cliente haya comenzado a depender de él, ya que un cambio de nombre cambia lo que el cliente ve durante el descubrimiento.
No hay un límite estricto, pero si una herramienta necesita muchos parámetros opcionales para cubrir diferentes casos de uso, a menudo es una señal de que debería dividirse en una o más herramientas enfocadas en su lugar.
Sí. Un parámetro como units: str = "celsius" se convierte en un campo opcional en el esquema generado, y el cliente puede omitirlo por completo para recurrir al valor predeterminado.
Versiones de Stack: Escrito contra la línea de modelos Claude actual a partir de ~junio de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (el predeterminado), y Claude Haiku 4.5 - y la especificación actual del Protocolo de Contexto del Modelo. Los nombres de los modelos, las versiones del SDK y la especificación MCP se mueven rápidamente: verifique los detalles actuales en platform.claude.com/docs y modelcontextprotocol.io antes de confiar en ellos.
Revisado por Chris St. John·Última actualización: 16 jul 2026