Construye con CometAPI¿Necesitas una sola API para varios modelos?Prueba el flujo en el Playground y luego crea una clave de API cuando el modelo y el costo encajen con tu caso de uso.
MCP vs llamadas a funciones: cómo conectar herramientas con agentes de IA
Compara MCP frente a las llamadas a funciones y construye un bucle de herramientas completo con CometAPI. Sigue el código en Python para ejecutar la misma consulta de pedido directamente y a través de MCP.
Deon GoodwinEquipo de investigación de modelos de IA y API
Las llamadas a funciones le dan al modelo una forma de solicitar una herramienta. MCP le da a una aplicación una forma estándar de descubrir y llamar herramientas en un servidor. Use integración directa de funciones para un conjunto pequeño de herramientas que su aplicación posee. Añada MCP cuando quiera consumir un servicio compatible existente o exponer herramientas a través de una interfaz compartida. A continuación, conectará la misma consulta de pedidos de ambas maneras a través de CometAPI, usando un único bucle de agente completo.
Puntos clave
Empiece con despacho directo cuando unas pocas herramientas sirvan a una sola aplicación; use MCP cuando un servicio de herramientas compatible o una interfaz compartida hagan que la integración sea más fácil de mantener.
Ambos caminos requieren un bucle completo: describir la herramienta, recibir la llamada, ejecutarla, devolver el resultado y permitir que el modelo responda.
Revisado para mayor claridad, atribución de fuentes y terminología API actual.
Puede mantener la misma función de cara al modelo mientras cambia su ejecución de un manejador directo a un cliente MCP.
Mantenga los contratos de las herramientas estrechos y verifique los resultados contra la tarea que quiere que el agente complete.
Cómo encajan Function Calling y MCP
Un agente de soporte puede explicar un retraso en el envío, pero necesita acceso al sistema de pedidos para decirle a un cliente dónde está su paquete. Las llamadas a funciones le dan al modelo una forma de solicitar esa consulta. Su aplicación realiza la consulta y devuelve el resultado. El manejador puede usar una biblioteca local, una base de datos o una API remota.
Para satisfacer esa solicitud a través de MCP, la aplicación usa un cliente para descubrir herramientas del servidor con tools/list e invocarlas con tools/call. La aplicación sigue decidiendo qué herramientas descubiertas expone a su modelo.
La acción solicitada por un modelo hacia el código de la aplicación
El cliente MCP de una aplicación hacia un servidor de herramientas
Función principal
Permitir que el modelo solicite una operación nombrada con argumentos estructurados
Estandarizar cómo las aplicaciones compatibles descubren e invocan herramientas del servidor
Definiciones de herramienta
Definiciones suministradas en el formato del API del modelo
Definiciones proporcionadas por el servidor, incluido un inputSchema
Ubicación de ejecución
El manejador al que la aplicación despacha, que puede llamar a un servicio local o remoto
La implementación de la herramienta en el servidor MCP, que puede llamar a un servicio backend
¿Servidor separado requerido?
La propia interfaz de llamadas a funciones no requiere un servidor
Se requiere un servidor MCP, pero un cliente puede lanzarlo como un subproceso local por stdio; un servicio remoto desplegado por separado es opcional
Uso típico
Unas pocas herramientas propiedad de la aplicación con despacho directo
Servicios compatibles existentes o una interfaz de herramientas reutilizada entre aplicaciones
¿Pueden trabajar juntos?
La aplicación puede enrutar la solicitud de función del modelo a través de MCP
El cliente MCP satisface esa solicitud con tools/call y devuelve el resultado a la aplicación
Esa última fila es la clave para construir con ambos. Un modelo puede solicitar get_order_status, mientras que la aplicación elige si llama a su propio manejador o a un servidor MCP. La definición de la función de cara al modelo puede permanecer igual.
Elija la integración que su aplicación necesita
Recomendamos comenzar con la integración directa cuando posea unas pocas herramientas usadas por una sola aplicación. Hay menos que desplegar, y el camino desde la solicitud del modelo hasta su código de negocio es fácil de seguir.
MCP se vuelve útil cuando un servidor compatible ya proporciona la capacidad, o cuando varias aplicaciones de IA necesitan una interfaz de herramientas común. La misma herramienta de pedidos podría servir a un asistente de soporte y a una aplicación interna de operaciones, con cada host controlando cómo acceden sus usuarios.
Un despachador puede admitir tanto funciones internas como servicios MCP, enrutando cada operación a su implementación. Las bibliotecas y las APIs REST también se pueden compartir; elija MCP cuando su interfaz estándar de descubrimiento e invocación sea útil para las aplicaciones que consumen las herramientas.
Construya un bucle de herramienta completo con CometAPI
Usaremos una operación de solo lectura, get_order_status, y un pedido ficticio, ORD-104. El ejemplo primero llama a la operación directamente, luego la ejecuta a través de un servidor MCP local. Ambos modos usan la Responses API de CometAPI para la inferencia del modelo.
Figura 2. Un bucle de agente puede admitir ejecución directa y vía MCP. Ilustración original, basada en la arquitectura MCP y el flujo de llamadas a funciones de CometAPI.
Paso 1 Configure el proyecto
Use Python 3.11 o posterior y una clave de API de CometAPI. Este recorrido se probó con Python 3.12.14 y mcp==2.3.0. Siga el inicio rápido de CometAPI para crear una clave. En una shell de macOS o Linux, cree un proyecto y active un entorno virtual:
Reemplace el marcador por su clave y manténgalo fuera del código fuente público. Este recorrido fija la versión del SDK de MCP usada por el ejemplo; la guía oficial de servidor en Python apunta a la API 2.x. Los dos comandos de agente de abajo envían solicitudes a CometAPI y consumen crédito del API.
Paso 2 Defina la consulta de pedidos
Guarde esto como orders.py. Datos fijos mantienen el recorrido autocontenido; más adelante podrá reemplazar la consulta con su propio servicio.
"""Read-only, invented order data; never connects to a real order system."""
import re
def get_order_status(order_id: str) -> dict[str, str]:
"""Look up an invented order; ORD-104 is the only available record."""
if not isinstance(order_id, str) or not re.fullmatch(r"ORD-[0-9]{3}", order_id):
raise ValueError("order_id must have the form ORD-104")
if order_id != "ORD-104":
raise ValueError("Order not found in this demo")
return {"order_id": order_id, "status": "shipped", "source": "fictional_demo"}
Llamar a get_order_status("ORD-104") devuelve este fixture:
Guarde el siguiente script completo como agent.py. Registra la función, envía la pregunta a CometAPI, ejecuta las llamadas solicitadas y devuelve cada resultado usando el call_id original.
La definición de cara al modelo usa el campo parameters de la Responses API de CometAPI. MCP proporciona un inputSchema; este ejemplo verifica el contrato descubierto contra el esquema de una sola cadena order_id antes de exponer la función revisada.
"""A deliberately narrow direct/MCP bridge, not a general MCP adapter."""
import argparse
import asyncio
import json
import os
from pathlib import Path
import sys
from urllib.request import Request, urlopen
from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client
from orders import get_order_status
NAME = "get_order_status"
SCHEMA = {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"], "additionalProperties": False}
TOOL = {"type": "function", "name": NAME, "description": "Look up fictional order ORD-104.",
"parameters": SCHEMA, "strict": True}
def validate_arguments(arguments):
# Exact validation for SCHEMA above; deliberately not a JSON Schema engine.
if (not isinstance(arguments, dict) or set(arguments) != {"order_id"}
or not isinstance(arguments["order_id"], str)):
raise ValueError("Expected exactly one string argument: order_id")
def validate_catalog(tools):
if len(tools) != 1 or tools[0].name != NAME:
raise RuntimeError("Unexpected MCP tool catalog")
schema = tools[0].input_schema
if (schema.get("type") != "object" or schema.get("required") != ["order_id"]
or set(schema.get("properties", {})) != {"order_id"}
or schema["properties"]["order_id"].get("type") != "string"):
raise RuntimeError("MCP schema does not match this reviewed demo tool")
async def dispatch(name, arguments, client=None):
if name != NAME:
raise ValueError("Tool is not allowlisted")
try:
validate_arguments(arguments)
if client is None:
return {"is_error": False, "data": get_order_status(**arguments)}
except ValueError as error:
return {"is_error": True, "error": str(error)}
result = await client.call_tool(name, arguments)
if getattr(result, "result_type", "complete") != "complete":
raise RuntimeError("Input-required MCP results are outside this demo")
if any(block.type != "text" for block in result.content):
raise RuntimeError("Non-text MCP content is outside this demo")
return {"is_error": bool(result.is_error),
"content": [block.text for block in result.content],
"structured_content": result.structured_content}
async def run_agent(send, execute, question, max_rounds=4):
history = [{"role": "user", "content": question}]
seen_call_ids = set()
for _ in range(max_rounds):
response = send({"model": "gpt-6-astra", "input": list(history),
"tools": [TOOL], "reasoning": {"effort": "low"}})
if response.get("status") != "completed" or response.get("error"):
raise RuntimeError("Model response failed or did not complete")
output = response["output"]
# CometAPI's documented manual replay: keep ALL items, omit top-level ids.
history.extend({key: value for key, value in item.items() if key != "id"}
for item in output)
blocks = [block for item in output if item["type"] == "message"
for block in item.get("content", [])]
if any(block["type"] == "refusal" for block in blocks):
raise RuntimeError("Model refused the request")
calls = [item for item in output if item["type"] == "function_call"]
if not calls:
text = "\n".join(block["text"] for block in blocks
if block["type"] == "output_text")
if not text:
raise RuntimeError("No final text returned")
return text
for call in calls:
call_id = call["call_id"]
if not isinstance(call_id, str) or not call_id or call_id in seen_call_ids:
raise ValueError("Missing or duplicate function call_id")
seen_call_ids.add(call_id)
try:
arguments = json.loads(call["arguments"])
except (ValueError, TypeError):
result = {"is_error": True, "error": "Arguments must be JSON text"}
else:
result = await execute(call["name"], arguments)
print(json.dumps({"tool": call["name"], "arguments": call["arguments"],
"result": result}), file=sys.stderr)
history.append({"type": "function_call_output", "call_id": call_id,
"output": json.dumps(result)})
raise RuntimeError("Tool loop reached its round limit")
def send_live(payload):
key = os.environ["COMETAPI_KEY"] # Only live mode reads the caller's key.
request = Request("https://api.cometapi.com/v1/responses",
data=json.dumps(payload).encode(),
headers={"Authorization": f"Bearer {key}",
"Content-Type": "application/json"})
with urlopen(request, timeout=45) as response:
return json.load(response)
def server_parameters():
return StdioServerParameters(command=sys.executable,
args=[str(Path(__file__).with_name("server.py"))])
async def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--mode", choices=["direct", "mcp"], required=True)
parser.add_argument("--question", default="Use the tool to check order ORD-104.")
args = parser.parse_args()
if args.mode == "direct":
answer = await run_agent(send_live, dispatch, args.question)
else:
async with Client(stdio_client(server_parameters())) as client:
discovered = await client.list_tools()
validate_catalog(discovered.tools)
async def execute(name, arguments):
return await dispatch(name, arguments, client)
answer = await run_agent(send_live, execute, args.question)
print(answer)
if __name__ == "__main__":
asyncio.run(main())
El bucle conserva la pregunta original y cada elemento de respuesta devuelto. Siguiendo el formato de conversación de CometAPI Responses, elimina los campos id de nivel superior de los elementos de salida antes de la repetición y añade un function_call_output para cada llamada. Preservar el resto de los elementos devueltos también preserva cualquier elemento de razonamiento necesario para el siguiente turno.
Paso 4 Ejecute la vía directa
Ejecute:
python agent.py --mode direct
La aplicación describe get_order_status al modelo. Cuando el modelo lo solicita, dispatch() verifica el nombre y los argumentos, luego llama a la función en orders.py. El resultado vuelve a la conversación, y el modelo puede convertirlo en una respuesta.
Use la traza de la herramienta impresa en su terminal para comprobar que la consulta usó ORD-104 y devolvió status: shipped. La respuesta final debería reflejar ese resultado; su redacción puede variar. Ahora tiene el bucle de llamadas a funciones completo, desde la pregunta del usuario hasta una operación ejecutada por la aplicación y una respuesta posterior del modelo.
El siguiente paso mantiene el modelo y el bucle de conversación en su lugar. Mueve la ejecución detrás de un servidor MCP y permite a la aplicación descubrir e invocar la herramienta a través de un cliente.
Paso 5 Exponga la consulta como una herramienta MCP
Guarde esto como server.py, junto a los otros dos archivos:
"""One read-only MCP tool, served over stdio. No stdout logging."""
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from orders import get_order_status as lookup_order
server = MCPServer("fictional-orders")
@server.tool(structured_output=True)
def get_order_status(order_id: str) -> dict[str, str]:
"""Look up fictional order ORD-104; this demo has no real customer data."""
try:
return lookup_order(order_id)
except ValueError as error:
raise ToolError(str(error)) from error
if __name__ == "__main__":
server.run(transport="stdio")
La rama mcp en main() inicia server.py, llama a list_tools() y verifica el contrato de la herramienta de pedidos. Cuando el modelo solicita la consulta, el mismo despachador ahora usa client.call_tool(). El servidor ejecuta la función y la aplicación devuelve su resultado al modelo.
Compare las dos ejecuciones según los mismos criterios: la herramienta correcta, ORD-104 como su argumento, un resultado exitoso que contenga shipped y una respuesta final coherente con ese resultado. La ruta de ejecución ha cambiado; la tarea y la función de cara al modelo se han mantenido iguales.
Los tres archivos fuente están incluidos arriba. El paquete de ejemplo también los contiene y las verificaciones offline para una reutilización conveniente. Esas verificaciones ejercitan un servidor MCP local real con respuestas de modelo guionadas; la salida de un modelo en vivo está separada de esa verificación local.
Adapte el ejemplo a sus propias herramientas
Reemplace los datos de pedido fijos con la operación más pequeña que su aplicación realmente necesite. Para una pregunta de envío, devuelva un ID de pedido y campos de estado útiles. Un resultado compacto le da al modelo menos material irrelevante que interpretar y hace que la respuesta sea más fácil de verificar.
Mantenga la descripción y el contrato de argumentos de la herramienta específicos. Este ejemplo acepta exactamente una cadena, y el host comprueba el nombre de la herramienta solicitada antes del despacho. La guía de seguridad de MCP exige que los servidores validen entradas e implementen controles de acceso. Para un servicio de pedidos real, derive la identidad del cliente a partir del contexto confiable de la aplicación y haga cumplir el acceso a los pedidos de ese cliente dentro del servicio. Un ID de pedido suministrado por el modelo no puede establecer la propiedad.
Una vez que la herramienta funcione, evalúe tareas completadas, tiempo de extremo a extremo y costo real. Añada MCP donde su interfaz de servicio ayude a las aplicaciones que necesitan la herramienta, y mantenga manejadores directos donde sean más simples de operar.
Preguntas frecuentes
¿MCP reemplaza las llamadas a funciones?
Atienden interfaces diferentes: MCP conecta el cliente de la aplicación con un servidor. En este ejemplo, el modelo solicita una función y la aplicación satisface esa solicitud a través de un cliente MCP. Ambos participan en el mismo bucle de agente.
¿Una herramienta MCP puede llamar a una REST API existente?
Sí. Reemplace la consulta dentro de la herramienta MCP con una llamada a su API. El servidor MCP publica el contrato de la herramienta y maneja las llamadas a herramientas; su servicio existente continúa proporcionando su lógica de negocio.
¿Cuándo debería usar un servidor MCP remoto?
Use un servicio remoto cuando los clientes necesiten alcanzar un servidor desplegado por separado en lugar de lanzar un proceso local. MCP define Streamable HTTP para esa conexión y stdio para un subproceso lanzado por el cliente. Configure la autorización del servidor remoto para sus llamadores previstos.
Conclusión
Para una sola aplicación, reemplace el registro ficticio en orders.py con una sola consulta de solo lectura de su servicio y empiece con el modo directo.
Si necesita un servicio MCP existente o una interfaz de herramientas compartida, use la vía MCP y mantenga los mismos chequeos: la operación correcta, el registro correcto y una respuesta sustentada por los datos devueltos.