Construa com CometAPIPrecisa de uma API para vários modelos?Teste o fluxo no Playground e depois crie uma chave da API quando o modelo e o custo atenderem ao seu caso de uso.
MCP vs chamadas de função: como conectar ferramentas a agentes de IA
Compare MCP e chamadas de função e construa um loop de ferramentas completo com a CometAPI. Siga o código em Python para executar a mesma consulta de pedido diretamente e por meio do MCP.
Deon GoodwinEquipe de pesquisa de modelos e API de IA
Function calling dá a um modelo uma forma de solicitar uma ferramenta. MCP dá a uma aplicação uma maneira padronizada de descobrir e chamar ferramentas em um servidor. Use integração direta de funções quando seu aplicativo possuir um conjunto pequeno de ferramentas. Adicione MCP quando quiser consumir um serviço compatível existente ou expor ferramentas por meio de uma interface compartilhada. Abaixo, você conectará a mesma consulta de pedido de ambas as formas pela CometAPI, usando um loop de agente completo.
Principais pontos
Comece com despacho direto quando poucas ferramentas atendem a uma aplicação; use MCP quando um serviço de ferramentas compatível ou uma interface compartilhada facilitar a manutenção da integração.
Ambos os caminhos precisam de um loop completo: descreva a ferramenta, receba a chamada, execute-a, retorne o resultado e deixe o modelo responder.
Revisado para maior clareza, atribuição de fontes e terminologia de API atual.
Você pode manter a mesma função exposta ao modelo enquanto altera sua execução de um manipulador direto para um cliente MCP.
Mantenha contratos de ferramenta estreitos e verifique os resultados em relação à tarefa que você deseja que o agente conclua.
Como Function calling e MCP se integram
Um agente de suporte pode explicar um atraso no envio, mas precisa acessar o sistema de pedidos para dizer ao cliente onde está o pacote. Function calling dá ao modelo uma forma de solicitar essa consulta. Sua aplicação executa a consulta e retorna o resultado. O manipulador pode usar uma biblioteca local, um banco de dados ou uma API remota.
Para atender a essa solicitação via MCP, a aplicação usa um cliente para descobrir as ferramentas do servidor com tools/list e invocá-las com tools/call. A aplicação ainda decide quais ferramentas descobertas expor ao seu modelo.
Ação solicitada pelo modelo ao código da aplicação
Cliente MCP da aplicação para um servidor de ferramentas
Papel principal
Permitir que o modelo solicite uma operação nomeada com argumentos estruturados
Padronizar como aplicações compatíveis descobrem e invocam ferramentas do servidor
Definições de ferramenta
Definições fornecidas no formato da API do modelo
Definições fornecidas pelo servidor, incluindo um inputSchema
Local de execução
O manipulador para o qual a aplicação faz o despacho, que pode chamar um serviço
A implementação da ferramenta no servidor MCP, que pode chamar um serviço de backend
Servidor separado exigido?
A interface de function calling em si não exige servidor
Um servidor MCP é necessário, mas um cliente pode iniciá-lo como subprocesso stdio; um serviço remoto separado é opcional
Uso típico
Poucas ferramentas de propriedade da aplicação com despacho direto
Serviços compatíveis existentes ou uma interface de ferramentas reutilizada entre aplicações
Podem funcionar juntos?
A aplicação pode rotear uma solicitação de função do modelo por meio do MCP
O cliente MCP atende essa solicitação com tools/call e retorna o resultado à aplicação
A última linha é a chave para construir com ambos. Um modelo pode solicitar get_order_status, enquanto a aplicação escolhe se chama seu próprio manipulador ou um servidor MCP. A definição de função voltada ao modelo pode permanecer a mesma.
Escolha a integração de que sua aplicação precisa
Recomendamos começar com integração direta quando você possui algumas ferramentas usadas por uma única aplicação. Há menos para implantar, e o caminho de uma solicitação do modelo até seu código de negócios é fácil de acompanhar.
MCP se torna útil quando um servidor compatível já fornece a capacidade, ou quando várias aplicações de IA precisam de uma interface de ferramenta comum. A mesma ferramenta de pedidos pode atender um assistente de suporte e uma aplicação interna de operações, com cada host controlando como seus usuários a acessam.
Um despachante pode dar suporte tanto a funções internas quanto a serviços MCP, roteando cada operação para sua implementação. Bibliotecas e APIs REST também podem ser compartilhadas; escolha MCP quando sua interface padronizada de descoberta e invocação for útil para as aplicações que consomem as ferramentas.
Construa um loop de ferramenta completo pela CometAPI
Usaremos uma operação somente leitura, get_order_status, e um pedido fictício, ORD-104. O exemplo primeiro chama a operação diretamente, depois a executa por meio de um servidor MCP local. Ambos os modos usam a Responses API da CometAPI para inferência do modelo.
Figura 2. Um único loop de agente pode dar suporte à execução direta e via MCP. Ilustração original, baseada na arquitetura do MCP e no fluxo de function calling da CometAPI.
Etapa 1 Configure o projeto
Use Python 3.11 ou posterior e uma chave da CometAPI. Este passo a passo foi testado com Python 3.12.14 e mcp==2.3.0. Siga o quickstart da CometAPI para criar uma chave. Em um shell macOS ou Linux, crie um projeto e ative um ambiente virtual:
Substitua o placeholder pela sua chave e mantenha-o fora de código-fonte público. Este passo a passo fixa a versão do SDK MCP usada no exemplo; o guia oficial do servidor Python mira a API 2.x. Os dois comandos de agente abaixo enviam solicitações à CometAPI e consomem créditos da API.
Etapa 2 Defina a consulta de pedidos
Salve como orders.py. Dados fixos mantêm o passo a passo autocontido; depois você pode substituir a consulta pelo seu próprio serviço.
"""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"}
Chamar get_order_status("ORD-104") retorna este fixture:
Salve o script completo a seguir como agent.py. Ele registra a função, envia a pergunta para a CometAPI, executa chamadas solicitadas e retorna cada resultado usando o call_id original.
A definição voltada ao modelo usa o campo parameters da Responses API da CometAPI. MCP fornece um inputSchema; este exemplo confere o contrato descoberto contra o schema de uma string única order_id antes de expor a função 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())
O loop mantém a pergunta original e cada item de resposta retornado. Seguindo o formato de conversa da CometAPI Responses, ele remove os campos id de nível superior dos itens de saída antes de reproduzir e acrescenta um function_call_output para cada chamada. Preservar o restante dos itens retornados também preserva quaisquer itens de raciocínio necessários para a próxima rodada.
Etapa 4 Execute o caminho direto
Execute:
python agent.py --mode direct
A aplicação descreve get_order_status ao modelo. Quando o modelo a solicita, dispatch() confere o nome e os argumentos, e então chama a função em orders.py. O resultado volta para a conversa, e o modelo pode transformá-lo em uma resposta.
Use o rastreamento de ferramenta impresso no seu terminal para verificar que a consulta usou ORD-104 e retornou status: shipped. A resposta final deve refletir esse resultado; sua redação pode variar. Agora você tem o loop completo de function calling, da pergunta do usuário a uma operação executada pela aplicação e uma resposta subsequente do modelo.
A próxima etapa mantém o modelo e o loop de conversa no lugar. Ela move a execução para trás de um servidor MCP e permite que a aplicação descubra e invoque a ferramenta por meio de um cliente.
Etapa 5 Exponha a consulta como uma ferramenta MCP
Salve como server.py, ao lado dos outros dois arquivos:
"""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")
O ramo mcp em main() inicia server.py, chama list_tools() e confere o contrato da ferramenta de pedidos. Quando o modelo solicita a consulta, o mesmo despachante agora usa client.call_tool(). O servidor executa a função e a aplicação retorna seu resultado ao modelo.
Compare as duas execuções pelos mesmos critérios: a ferramenta correta, ORD-104 como argumento, um resultado bem-sucedido contendo shipped e uma resposta final consistente com esse resultado. O caminho de execução mudou; a tarefa e a função voltada ao modelo permaneceram as mesmas.
Todos os três arquivos-fonte estão incluídos acima. O pacote de exemplo também os contém e as verificações offline para reutilização conveniente. Essas verificações exercitam um servidor MCP local real com respostas de modelo roteirizadas; a saída de modelo ao vivo é separada dessa verificação local.
Adapte o exemplo às suas próprias ferramentas
Substitua os dados de pedido fixos pela menor operação de que sua aplicação realmente precisa. Para uma pergunta de envio, retorne um ID de pedido e campos de status úteis. Um resultado compacto dá ao modelo menos material irrelevante para interpretar e torna a resposta mais fácil de verificar.
Mantenha a descrição da ferramenta e o contrato de argumentos específicos. Este exemplo aceita exatamente uma string, e o host confere o nome da ferramenta solicitada antes do despacho. A orientação de segurança do MCP pede que servidores validem entradas e implementem controles de acesso. Para um serviço de pedidos real, derive a identidade do cliente a partir do contexto confiável da aplicação e imponha o acesso ao pedido desse cliente dentro do serviço. Um ID de pedido fornecido pelo modelo não pode estabelecer propriedade.
Quando a ferramenta funcionar, avalie tarefas concluídas, tempo de ponta a ponta e custo real. Adicione MCP quando sua interface de serviço ajudar as aplicações que precisam da ferramenta, e mantenha manipuladores diretos quando eles forem mais simples de operar.
Perguntas frequentes
MCP substitui function calling?
Eles lidam com interfaces diferentes: MCP conecta o cliente da aplicação a um servidor. Neste exemplo, o modelo solicita uma função e a aplicação atende essa solicitação por meio de um cliente MCP. Ambos participam do mesmo loop de agente.
Uma ferramenta MCP pode chamar uma API REST existente?
Sim. Substitua a consulta dentro da ferramenta MCP por uma chamada à sua API. O servidor MCP publica o contrato da ferramenta e lida com as chamadas; seu serviço existente continua fornecendo o comportamento de negócio.
Quando devo usar um servidor MCP remoto?
Use um serviço remoto quando clientes precisarem alcançar um servidor implantado separadamente em vez de iniciar um processo local. MCP define Streamable HTTP para essa conexão e stdio para um subprocesso iniciado pelo cliente. Configure a autorização do servidor remoto para seus chamadores pretendidos.
Conclusão
Para uma única aplicação, substitua o registro fictício em orders.py por uma consulta somente leitura do seu serviço e comece pelo modo direto.
Se você precisar de um serviço MCP existente ou de uma interface de ferramentas compartilhada, use o caminho MCP e mantenha as mesmas verificações: a operação correta, o registro correto e uma resposta sustentada pelos dados retornados.