TL;DR
Function calling, bir modelin bir aracı talep etmesini sağlar. MCP, bir uygulamanın bir sunucudaki araçları keşfetmesi ve çağırması için standart bir yol sunar. Uygulamanızın sahip olduğu küçük bir araç seti için doğrudan fonksiyon bütünleşmesini kullanın. Mevcut, uyumlu bir hizmeti tüketmek veya araçları paylaşılan bir arayüz üzerinden sunmak istediğinizde MCP ekleyin. Aşağıda, CometAPI üzerinden tek bir tamamlanmış ajan döngüsü içinde aynı sipariş sorgulamasını her iki yoldan da bağlayacaksınız.
Öne Çıkanlar
- Birkaç aracın tek bir uygulamaya hizmet ettiği durumlarda doğrudan yönlendirme ile başlayın; uyumlu bir araç hizmeti veya paylaşılan bir araç arayüzü bakımını kolaylaştırdığında MCP kullanın.
- Her iki yol da tam bir döngü gerektirir: aracı tanımlayın, çağrıyı alın, çalıştırın, sonucu döndürün ve modelin yanıt vermesine izin verin.
- Yürütmesini doğrudan bir işleyiciden bir MCP istemcisine değiştirirken aynı modele dönük fonksiyonu koruyabilirsiniz.
- Araç sözleşmelerini dar tutun ve sonuçları, ajanın tamamlamasını istediğiniz göreve göre kontrol edin.
Function Calling ve MCP Nasıl Bir Araya Gelir?
Bir destek ajanı bir gönderim gecikmesini açıklayabilir, ancak bir müşteriye paketinin nerede olduğunu söylemek için sipariş sistemine erişmesi gerekir. Function calling, modelin bu sorgulamayı talep etmesini sağlar. Uygulamanız sorgulamayı çalıştırır ve sonucu döndürür. İşleyici yerel bir kütüphane, bir veritabanı veya bir uzak API kullanabilir.
Bu talebi MCP üzerinden gerçekleştirmek için, uygulama tools/list ile sunucu araçlarını keşfetmek ve tools/call ile çağırmak için bir istemci kullanır. Uygulama hâlâ keşfedilen hangi araçların modeline açılacağına karar verir.

Şekil 1. MCP, yapay zeka uygulamaları ile harici sistemler arasında ortak bir arayüz sağlar. Kaynak: resmi MCP tanıtımı. © 2024–2025 Anthropic, PBC ve katkıda bulunanlar. MIT Lisansı kapsamında değiştirilmeden yeniden üretilmiştir; tam görsel kredileri ve izin bildirimi.
MCP ve Function Calling: Temel Farklar
Son satır, her ikisiyle birlikte inşa etmenin anahtarıdır. Bir model get_order_status talep edebilir; uygulama ise kendi işleyicisini mi yoksa bir MCP sunucusunu mu çağıracağına karar verir. Modelin fonksiyon tanımı aynı kalabilir.
Uygulamanızın ihtiyacı olan entegrasyonu seçin
Bir uygulama tarafından kullanılan birkaç araca sahip olduğunuzda doğrudan entegrasyonla başlamayı öneririz. Dağıtılacak daha az şey vardır ve bir model talebinden iş kodunuza giden yol kolay izlenir.
Uyumlu bir sunucu zaten bu kabiliyeti sağladığında veya birden fazla yapay zeka uygulaması ortak bir araç arayüzüne ihtiyaç duyduğunda MCP faydalı hale gelir. Aynı sipariş aracı bir destek asistanına ve bir iç operasyonlar uygulamasına hizmet edebilir; her bir ana uygulama kullanıcılarının buna erişimini kontrol eder.
Bir yönlendirici hem dahili fonksiyonları hem de MCP hizmetlerini destekleyerek her işlemi kendi uygulamasına yönlendirebilir. Kütüphaneler ve REST API’ler de paylaşılabilir; MCP’yi, standart keşif ve çağrı arayüzü bu araçları tüketen uygulamalar için faydalı olduğunda seçin.
CometAPI üzerinden eksiksiz bir araç döngüsü kurun
Tek bir salt-okunur işlem, get_order_status, ve bir kurgusal sipariş, ORD-104 kullanacağız. Örnek önce işlemi doğrudan çağırır, ardından yerel bir MCP sunucusu üzerinden çalıştırır. Her iki mod da model çıkarımı için CometAPI Responses API’sini kullanır.

Şekil 2. Tek bir ajan döngüsü, doğrudan ve MCP yürütmesini destekleyebilir. MCP mimarisi ve CometAPI fonksiyon çağırma akışına dayalı orijinal görselleştirme.
Adım 1 Projeyi kurun
Python 3.11 veya üstü ve bir CometAPI API anahtarı kullanın. Bu adım adım anlatım Python 3.12.14 ve mcp==2.3.0 ile test edilmiştir. Bir anahtar oluşturmak için CometAPI hızlı başlangıç kılavuzunu izleyin. macOS veya Linux kabuğunda bir proje oluşturun ve sanal ortamı etkinleştirin:
mkdir mcp-agent-demo
cd mcp-agent-demo
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "mcp==2.3.0"
export COMETAPI_KEY="YOUR_COMETAPI_KEY"
Yer tutucuyu anahtarınızla değiştirin ve herkese açık kaynak kodunun dışında tutun. Bu yürüyüş örnekte kullanılan MCP SDK’sını sabitler; resmi Python sunucu rehberi 2.x API’sini hedefler. Aşağıdaki iki ajan komutu CometAPI’ye istek gönderir ve API kredisi tüketir.
Adım 2 Sipariş sorgulamasını tanımlayın
Bunu orders.py olarak kaydedin. Sabit veri, anlatımı kendi içinde tutar; daha sonra sorgulamayı kendi servisinizle değiştirebilirsiniz.
"""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"}
get_order_status("ORD-104") çağrısı şu sabit veriyi döndürür:
{"order_id": "ORD-104", "status": "shipped", "source": "fictional_demo"}
Adım 3 Ajan döngüsünü ekleyin
Aşağıdaki tam betiği agent.py olarak kaydedin. Fonksiyonu kaydeder, soruyu CometAPI’ye gönderir, talep edilen çağrıları yürütür ve her sonucu orijinal call_id ile geri döndürür.
Modele dönük tanım, CometAPI Responses API’sinin parameters alanını kullanır. MCP bir inputSchema sağlar; bu örnek, keşfedilen sözleşmeyi tek dizelik order_id şemasına karşı kontrol eder ve gözden geçirilmiş fonksiyonu açmadan önce bunu doğrular.
"""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())
Döngü, orijinal soruyu ve döndürülen her yanıt öğesini tutar. CometAPI Responses konuşma formatını takip ederek, yeniden oynatmadan önce çıktı öğelerinin üst düzey id alanlarını kaldırır ve her çağrı için bir function_call_output ekler. Döndürülen diğer öğeleri korumak, bir sonraki tur için gereken tüm muhakeme öğelerini de korur.
Adım 4 Doğrudan yolu çalıştırın
Şunu çalıştırın:
python agent.py --mode direct
Uygulama, modele get_order_status tanımını verir. Model bunu talep ettiğinde dispatch() adı ve argümanları kontrol eder, ardından orders.py içindeki fonksiyonu çağırır. Sonuç konuşmaya geri gider ve model bunu bir yanıta dönüştürebilir.
Terminalinize yazdırılan araç izini, sorgulamanın ORD-104 kullandığını ve status: shipped döndürdüğünü kontrol etmek için kullanın. Nihai yanıt bu sonucu yansıtmalıdır; ifadesi değişebilir. Artık kullanıcının sorusundan uygulama tarafından yürütülen bir işleme ve ardından model yanıtına giden eksiksiz bir function calling döngüsüne sahipsiniz.
Bir sonraki adım, modeli ve konuşma döngüsünü yerinde tutar. Yürütmeyi bir MCP sunucusunun arkasına taşır ve uygulamanın aracı bir istemci aracılığıyla keşfetmesine ve çağırmasına olanak verir.
Adım 5 Sorgulamayı bir MCP aracı olarak sunun
Bunu diğer iki dosyanın yanına server.py olarak kaydedin:
"""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")
SDK araç tanımını fonksiyon imzası ve docstring’ten oluşturur. Sunucu stdio kullanır; burada istemci, onu bir alt süreç olarak başlatır ve protokol mesajlarını standart akışlar üzerinden değiş tokuş eder. Uygulama günlüklerini sunucunun standart çıktısında tutmayın.
Adım 6 MCP yolunu çalıştırın
Şunu çalıştırın:
python agent.py --mode mcp
main() içindeki mcp dalı server.pyyi başlatır, list_tools() çağrısı yapar ve sipariş aracının sözleşmesini kontrol eder. Model sorgulamayı talep ettiğinde, aynı yönlendirici artık client.call_tool() kullanır. Sunucu fonksiyonu çalıştırır ve uygulama sonucunu modele döndürür.
İki çalıştırmayı aynı ölçütlere göre karşılaştırın: doğru araç, argüman olarak ORD-104, shipped içeren başarılı bir sonuç ve bu sonuca tutarlı bir nihai yanıt. Yürütme yolu değişti; görev ve modele dönük fonksiyon aynı kaldı.
Yukarıda üç kaynak dosyanın tamamı yer alır. Örnek paket ayrıca bunları ve yeniden kullanım kolaylığı için çevrimdışı kontrolleri içerir. Bu kontroller, betiklenmiş model yanıtlarıyla gerçek yerel bir MCP sunucusunu dener; canlı model çıktısı bu yerel doğrulamadan ayrıdır.
Örneği kendi araçlarınıza uyarlayın
Sabit sipariş verilerini, uygulamanızın gerçekten ihtiyaç duyduğu en küçük işlemle değiştirin. Bir gönderim sorusu için, bir sipariş kimliği ve yararlı durum alanları döndürün. Kompakt bir sonuç, modele yorumlaması için daha az ilgisiz malzeme verir ve yanıtı kontrol etmeyi kolaylaştırır.
Aracın açıklamasını ve argüman sözleşmesini net tutun. Bu örnek tam olarak tek bir dize kabul eder ve ana uygulama, yönlendirmeden önce talep edilen araç adını kontrol eder. MCP güvenlik rehberi, sunucuların girdileri doğrulamasını ve erişim kontrollerini uygulamasını önerir. Gerçek bir sipariş hizmeti için, müşterinin kimliğini güvenilir uygulama bağlamından türetin ve hizmet içinde o müşterinin siparişine erişimi zorlayın. Modelin sağladığı bir sipariş kimliği mülkiyeti kanıtlayamaz.
Araç çalıştıktan sonra, tamamlanan görevleri, uçtan uca süreyi ve gerçek maliyeti değerlendirin. Aracın servis arayüzü ona ihtiyaç duyan uygulamalara yardımcı olduğunda MCP ekleyin ve işletimi daha kolay olan yerlerde doğrudan işleyicileri koruyun.
SSS
MCP, function calling’in yerini alır mı?
Farklı arayüzleri ele alırlar: MCP, uygulamanın istemcisini bir sunucuya bağlar. Bu örnekte, model bir fonksiyon talep eder ve uygulama o talebi bir MCP istemcisi üzerinden yerine getirir. Her ikisi de aynı ajan döngüsüne katılır.
Bir MCP aracı mevcut bir REST API’yi çağırabilir mi?
Evet. MCP aracının içindeki sorgulamayı API’nize yapılan bir çağrı ile değiştirin. MCP sunucusu araç sözleşmesini yayınlar ve araç çağrılarını işler; mevcut servisiniz iş davranışını sağlamaya devam eder.
Uzak bir MCP sunucusunu ne zaman kullanmalıyım?
İstemcilerin yerel bir süreci başlatmak yerine ayrı dağıtılmış bir sunucuya ulaşması gerektiğinde uzak bir hizmet kullanın. MCP bu bağlantı için Streamable HTTP’yi ve istemci tarafından başlatılan bir alt süreç için stdio’yu tanımlar. Uzak sunucunun yetkilendirmesini amaçlanan arayanları için yapılandırın.
Sonuç
Tek bir uygulama için, orders.py içindeki kurgusal kaydı servisinizden tek bir salt-okunur sorguyla değiştirin ve doğrudan modla başlayın.
Mevcut bir MCP hizmetine veya paylaşılan bir araç arayüzüne ihtiyacınız varsa, MCP yolunu kullanın ve aynı kontrolleri koruyun: doğru işlem, doğru kayıt ve döndürülen verilerle desteklenen bir yanıt.
