TL;DR
Function calling memberi model cara untuk meminta sebuah alat. MCP memberi aplikasi cara standar untuk menemukan dan memanggil alat pada server. Gunakan integrasi fungsi langsung untuk kumpulan alat kecil yang dimiliki aplikasi Anda. Tambahkan MCP saat Anda ingin menggunakan layanan kompatibel yang sudah ada atau mengekspos alat melalui antarmuka bersama. Di bawah ini, Anda akan menghubungkan pencarian pesanan yang sama dengan kedua cara melalui CometAPI, menggunakan satu loop agen yang lengkap.
Poin Utama
- Mulailah dengan pengiriman langsung ketika beberapa alat melayani satu aplikasi; gunakan MCP saat layanan alat yang kompatibel atau antarmuka alat bersama memudahkan perawatan integrasi.
- Kedua jalur membutuhkan loop lengkap: jelaskan alatnya, terima panggilan, eksekusi, kembalikan hasil, dan biarkan model menjawab.
- Anda bisa mempertahankan fungsi yang sama di sisi model sambil mengubah eksekusinya dari handler langsung menjadi klien MCP.
- Pertahankan kontrak alat yang sempit dan periksa hasil terhadap tugas yang ingin diselesaikan agen.
Bagaimana Pemanggilan Fungsi dan MCP Saling Melengkapi
Seorang agen dukungan dapat menjelaskan penundaan pengiriman, tetapi ia memerlukan akses ke sistem pesanan untuk memberi tahu pelanggan di mana paket mereka berada. Pemanggilan fungsi memberi model cara untuk meminta pencarian tersebut. Aplikasi Anda menjalankan pencarian dan mengembalikan hasilnya. Handler dapat menggunakan pustaka lokal, basis data, atau API jarak jauh.
Untuk memenuhi permintaan tersebut melalui MCP, aplikasi menggunakan klien untuk menemukan alat server dengan tools/list dan memanggilnya dengan tools/call. Aplikasi tetap memutuskan alat mana yang ditemukan yang akan diekspos ke modelnya.

Gambar 1. MCP menyediakan antarmuka umum antara aplikasi AI dan sistem eksternal. Sumber: pengantar resmi MCP. © 2024–2025 Anthropic, PBC dan kontributor. Direproduksi tanpa perubahan di bawah Lisensi MIT; kredit gambar lengkap dan pemberitahuan izin.
MCP vs Pemanggilan Fungsi: Perbedaan Utama
Baris terakhir adalah kunci untuk membangun dengan keduanya. Model dapat meminta get_order_status, sementara aplikasi memilih apakah akan memanggil handler miliknya sendiri atau server MCP. Definisi fungsi di sisi model dapat tetap sama.
Pilih integrasi yang dibutuhkan aplikasi Anda
Kami menyarankan memulai dengan integrasi langsung ketika Anda memiliki beberapa alat yang digunakan oleh satu aplikasi. Yang perlu dideploy lebih sedikit, dan jalur dari permintaan model ke kode bisnis Anda mudah diikuti.
MCP menjadi berguna ketika server yang kompatibel sudah menyediakan kapabilitas tersebut, atau ketika beberapa aplikasi AI memerlukan antarmuka alat yang sama. Alat pesanan yang sama dapat melayani asisten dukungan dan aplikasi operasi internal, dengan masing-masing host mengendalikan bagaimana penggunanya mengaksesnya.
Sebuah dispatcher dapat mendukung fungsi internal dan layanan MCP, merutekan setiap operasi ke implementasinya. Library dan REST API juga dapat dibagikan; pilih MCP ketika antarmuka standar untuk penemuan dan pemanggilan bermanfaat bagi aplikasi yang mengonsumsi alat tersebut.
Bangun loop alat yang lengkap melalui CometAPI
Kita akan menggunakan satu operasi read-only, get_order_status, dan satu pesanan fiksi, ORD-104. Contoh ini pertama-tama memanggil operasi tersebut secara langsung, lalu menjalankannya melalui server MCP lokal. Kedua mode menggunakan API Responses CometAPI untuk inferensi model.

Gambar 2. Satu loop agen dapat mendukung eksekusi langsung dan melalui MCP. Ilustrasi asli, berdasarkan arsitektur MCP dan alur pemanggilan fungsi CometAPI.
Langkah 1 Siapkan proyek
Gunakan Python 3.11 atau yang lebih baru dan kunci API CometAPI. Panduan ini diuji dengan Python 3.12.14 dan mcp==2.3.0. Ikuti CometAPI quickstart untuk membuat kunci. Di shell macOS atau Linux, buat proyek dan aktifkan lingkungan virtual:
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"
Ganti placeholder dengan kunci Anda dan jangan mempublikasikannya ke kode sumber. Panduan ini mengepin SDK MCP yang digunakan oleh contoh; panduan server Python resmi menargetkan API 2.x. Dua perintah agen di bawah ini mengirim permintaan ke CometAPI dan mengonsumsi kredit API.
Langkah 2 Definisikan pencarian pesanan
Simpan ini sebagai orders.py. Data tetap membuat panduan ini berdiri sendiri; nanti Anda dapat mengganti pencarian dengan layanan Anda sendiri.
"""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"}
Memanggil get_order_status("ORD-104") mengembalikan fixture berikut:
{"order_id": "ORD-104", "status": "shipped", "source": "fictional_demo"}
Langkah 3 Tambahkan loop agen
Simpan skrip lengkap berikut sebagai agent.py. Ini mendaftarkan fungsi, mengirim pertanyaan ke CometAPI, mengeksekusi panggilan yang diminta, dan mengembalikan setiap hasil menggunakan call_id asli.
Definisi yang dihadapkan ke model menggunakan field parameters API Responses CometAPI. MCP menyuplai sebuah inputSchema; contoh ini memeriksa kontrak yang ditemukan terhadap skema order_id berupa satu string sebelum mengekspos fungsi yang telah ditinjau.
"""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())
Loop mempertahankan pertanyaan asli dan setiap item respons yang dikembalikan. Mengikuti format percakapan CometAPI Responses, loop menghapus field id tingkat atas dari item output sebelum pemutaran ulang dan menambahkan function_call_output untuk setiap panggilan. Mempertahankan item lain yang dikembalikan juga mempertahankan item penalaran apa pun yang dibutuhkan untuk giliran berikutnya.
Langkah 4 Jalankan jalur langsung
Jalankan:
python agent.py --mode direct
Aplikasi menjelaskan get_order_status kepada model. Ketika model memintanya, dispatch() memeriksa nama dan argumen, lalu memanggil fungsi di orders.py. Hasilnya dimasukkan kembali ke percakapan, dan model dapat mengubahnya menjadi jawaban.
Gunakan jejak alat yang dicetak ke terminal Anda untuk memeriksa bahwa pencarian menggunakan ORD-104 dan mengembalikan status: shipped. Jawaban akhir harus mencerminkan hasil tersebut; susunan katanya dapat bervariasi. Sekarang Anda memiliki loop pemanggilan fungsi lengkap, dari pertanyaan pengguna hingga operasi yang dijalankan aplikasi dan respons lanjutan dari model.
Langkah berikut mempertahankan model dan loop percakapan. Langkah ini memindahkan eksekusi ke belakang server MCP dan membiarkan aplikasi menemukan serta memanggil alat melalui klien.
Langkah 5 Ekspos pencarian sebagai alat MCP
Simpan ini sebagai server.py, di sebelah dua berkas lainnya:
"""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 membangun definisi alat dari tanda tangan fungsi dan docstring. Server menggunakan stdio, di mana klien meluncurkannya sebagai subprocess dan bertukar pesan protokol melalui stream standarnya. Jauhkan log aplikasi dari standard output server.
Langkah 6 Jalankan jalur MCP
Jalankan:
python agent.py --mode mcp
Cabang mcp di main() memulai server.py, memanggil list_tools() dan memeriksa kontrak alat pesanan. Ketika model meminta pencarian, dispatcher yang sama kini menggunakan client.call_tool(). Server menjalankan fungsi, dan aplikasi mengembalikan hasilnya ke model.
Bandingkan kedua jalur terhadap kriteria yang sama: alat yang benar, ORD-104 sebagai argumennya, hasil sukses yang berisi shipped, dan jawaban akhir yang konsisten dengan hasil tersebut. Jalur eksekusi berubah; tugas dan fungsi yang dihadapkan ke model tetap sama.
Ketiga berkas sumber disertakan di atas. Paket contoh juga memuatnya serta pemeriksaan offline untuk penggunaan ulang yang mudah. Pemeriksaan tersebut menjalankan server MCP lokal nyata dengan respons model terskrip; output model live terpisah dari verifikasi lokal itu.
Sesuaikan contoh dengan alat Anda sendiri
Ganti data pesanan tetap dengan operasi terkecil yang benar-benar dibutuhkan aplikasi Anda. Untuk pertanyaan pengiriman, kembalikan ID pesanan dan field status yang berguna. Hasil yang ringkas memberi model lebih sedikit materi yang tidak relevan untuk ditafsirkan dan membuat jawaban lebih mudah diperiksa.
Pertahankan deskripsi alat dan kontrak argumennya secara spesifik. Contoh ini menerima tepat satu string, dan host memeriksa nama alat yang diminta sebelum dispatch. Panduan keamanan MCP menyerukan agar server memvalidasi input dan menerapkan kontrol akses. Untuk layanan pesanan nyata, turunkan identitas pelanggan dari konteks aplikasi tepercaya dan tegakkan akses ke pesanan pelanggan tersebut di dalam layanan. ID pesanan yang disuplai model tidak dapat menetapkan kepemilikan.
Setelah alat bekerja, evaluasi tugas yang selesai, waktu end-to-end, dan biaya aktual. Tambahkan MCP ketika antarmuka layanannya membantu aplikasi yang membutuhkan alat tersebut, dan pertahankan handler langsung ketika lebih sederhana untuk dioperasikan.
FAQ
Apakah MCP menggantikan pemanggilan fungsi?
Keduanya menangani antarmuka yang berbeda: MCP menghubungkan klien aplikasi ke server. Dalam contoh ini, model meminta sebuah fungsi dan aplikasi memenuhi permintaan tersebut melalui klien MCP. Keduanya berpartisipasi dalam loop agen yang sama.
Bisakah alat MCP memanggil REST API yang sudah ada?
Ya. Ganti pencarian di dalam alat MCP dengan panggilan ke API Anda. Server MCP memublikasikan kontrak alat dan menangani panggilan alat; layanan Anda yang ada tetap menyediakan perilaku bisnisnya.
Kapan saya harus menggunakan server MCP jarak jauh?
Gunakan layanan jarak jauh ketika klien perlu mengakses server yang dideploy terpisah alih-alih meluncurkan proses lokal. MCP mendefinisikan Streamable HTTP untuk koneksi tersebut dan stdio untuk subprocess yang diluncurkan klien. Konfigurasikan otorisasi server jarak jauh untuk pemanggil yang dituju.
Kesimpulan
Untuk satu aplikasi, ganti catatan fiksi di orders.py dengan satu kueri read-only dari layanan Anda dan mulai dengan mode langsung.
Jika Anda membutuhkan layanan MCP yang sudah ada atau antarmuka alat bersama, gunakan jalur MCP dan pertahankan pemeriksaan yang sama: operasi yang benar, rekaman yang benar, dan jawaban yang didukung oleh data yang dikembalikan.
