Wiring MCP-Tools über LiteLLM: Schritt-für-Schritt-Anleitung für ein simuliertes Bank-Gateway
Registrieren Sie eine streambare HTTP-Simulationsbank, vergleichen Sie manuell erstellte Tool-Loops mit einer einzigen Gateway-Anfrage und beweisen Sie, dass die vor dem Aufruf geltenden Schutzmechanismen niemals den MCP erreichen.
Bild von Quilia auf Unsplash
Es wird vorausgesetzt, dass ein LiteLLM-Gateway bereits aktiv ist (Admin-UI plus Datenbank) mit gpt-5.4-mini in Azure AI Foundry registriert ist. Der Überblicksartikel behandelt diese Grundkonfiguration.
Was „agentic“ in LiteLLM bedeutet
LiteLLMs MCP-Gateway ermöglicht es einem registrierten Modell, über den Proxy auf externe Tools zuzugreifen. Das Gateway entdeckt die Tools, übersetzt die Schemata und führt den Ausführungslauf aus, sodass der Anwendungscode keine Details des MCP-Protokolls für jeden Modellanbieter berücksichtigen muss. Ihr Client kommuniziert mit einem endpoint im Stil von OpenAI; das Gateway befindet sich zwischen diesem Client, dem Modell und allen von Ihnen registrierten MCP-Servern.
Modell-IDs lassen sich leicht falsch eingeben. Je nachdem, wie der Eintrag in Azure hinzugefügt wurde, kann LiteLLM möglicherweise azure_ai/gpt-5.4-mini anstelle von einfachem gpt-5.4-mini anzeigen. Überprüfen Sie zunächst das aktuelle Register:
curl -X GET 'http://localhost:4000/v1/models' -H 'Authorization: Bearer sk-1234'
# Expected Results
{"data":[{"id":"azure_ai/gpt-5.4-mini","object":"model","created":1677610602,"owned_by":"openai"}],"object":"list"}
Verwenden Sie genau diesen String bei jeder Anfrage. Ein Mismatch zeigt sich als Invalid model name passed in model=... anstelle eines unklaren Netzwerkfehlers.
Derselbe Endpunkt – reines LLM oder mit Tools
Es gibt keinen separaten URL-Path für den „MCP-Modus“. /chat/completions und /v1/responses verhalten sich beim normalen Chat sowie beim chat mit Tools genauso. Der einzige Unterschied liegt darin, ob der JSON-Körper ein tools-Array enthält (oft mit type: „mcp“ für von dem Gateway verwaltete Tools). Wenn Sie tools weglassen, erhalten Sie eine normale Antwort, selbst wenn MCP-Server am Gateway angeschlossen sind. Wenn Sie tools hinzufügen, kann das Gateway die Tools entdecken, aufrufen und deren Ergebnisse in die Antwort einbeziehen.
Ein reiner Aufruf ohne Tools sieht so aus:
curl -X POST 'http://localhost:4000/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"model": "azure_ai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}'
# Expected Results
{"id":"chatcmpl-E778uLVTQJQLIJu0Dc2XAI1UYHuwL","created":1785364460,"model":"azure_ai/gpt-5.4-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"The capital of France is **Paris**.","role":"assistant","provider_specific_fields":{"refusal":null},"annotations":[]},"provider_specific_fields":{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}}],"usage":{"completion_tokens":13,"prompt_tokens":13,"total_tokens":26,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"latency_checkpoint":{"engine_tbt_ms":8,"engine_ttft_ms":86,"engine_ttlt_ms":185,"pre_inference_ms":70,"service_tbt_ms":10,"service_ttft_ms":244,"service_ttlt_ms":354,"total_duration_ms":306,"user_visible_ttft_ms":174}},"service_tier":"default","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}]}
Ein häufiges Problem bei Kopieren und Einfügen ist ein zusätzlicher Anführungszeichen vor dem Modellnamen (""azure_ai/gpt-5.4-mini"). Dadurch tritt der Fehler Invalid JSON payload: unexpected character auf – es handelt sich dabei um ein Syntaxproblem im Dateninhalt, nicht um eine fehlerhafte Route.
Das MCP Gateway, Schritt für Schritt (end-to-end getestet)
Simulierter Bankserver
Anstelle eines generischen „Hello-World“-MCP-Prozesses verwendet diese Anleitung einen kleinen simulierten Bankserver mit drei Tools – get_balance, list_transactions und transfer_funds – die von zwei im Speicher gespeicherten Konten unterstützt werden. Echte Kontostände und Überweisungen machen deutlich, ob der vollständige Pfad funktioniert hat. Jeder unten aufgeführte Schritt wurde ausgeführt und anhand dieser Daten überprüft.
Schritt 1 – Server starten (siehe auch die eigenständige Datei mock_bank_server.py):
mkdir mock-bank-server && cd mock-bank-server
uv init .
uv add "mcp[cli]" openai
touch server.py
server.py verwendet künstliche Daten und nicht ein echtes Buchhaltungssystem. Die vorgesehene Struktur ist: Client → LiteLLM → Guardrails → Router → entweder das Azure-Modell oder der künstliche Bank-MCP-Prozess samt seinen drei Tools.
Mehrere verhaltensbezogene Details sind wichtig, sobald man das Diagramm verlässt:
- Der interessante Ablaufweg ist ein Loop, nicht eine einzelne Pfeilspitze. Das Modell bittet um ein Tool; das Gateway leitet an MCP weiter; das Ergebnis kehrt über das Gateway zurück; das Modell setzt fort oder fragt erneut nach. Einzel-Tool-Abläufe schließen diesen Loop automatisch ab; parallele Mehr-Tool-Abläufe können in der hier getesteten Version nur teilweise automatisch ausgeführt werden.
pre_call überprüft den rohen Chat-Inhalt vor der Weiterleitung. pre_mcp_call prüft die Tool-Argumente näher am MCP-Bereich. In Diagrammen wird oft ein einziger Kasten dargestellt; die Abfangvorgänge finden jedoch in unterschiedlichen Phasen statt.server.py, nicht drei separate Microservices.Der Mock-Bank-MCP-Server
Rund neunzig Zeilen definieren den FastMCP „mock-bank“ mit:
get_balance(account_id)– Inhaber sowie Kontostandlist_transactions(account_id, limit)– Jüngste Transaktionentransfer_funds(from_account, to_account, amount)– Übertragung von Geldern mit grundlegender Validierung (unbekanntes Konto, unzureichende Mittel)
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
mcp = FastMCP(
"mock-bank",
host="127.0.0.1",
port=3001,
transport_security=TransportSecuritySettings(
allowed_hosts=["localhost:3001", "127.0.0.1:3001", "host.docker.internal:3001"],
),
)
# --- In-memory mock data (resets every restart) ---
ACCOUNTS = {
"ACC1001": {"owner": "Alice Johnson", "balance": 4250.75, "currency": "USD"},
"ACC1002": {"owner": "Bob Smith", "balance": 980.10, "currency": "USD"},
}
TRANSACTIONS = {
"ACC1001": [
{"date": "2026-07-20", "description": "Grocery Store", "amount": -84.32},
{"date": "2026-07-18", "description": "Payroll Deposit", "amount": 2500.00},
{"date": "2026-07-15", "description": "Electric Bill", "amount": -120.44},
],
"ACC1002": [
{"date": "2026-07-21", "description": "Coffee Shop", "amount": -6.75},
{"date": "2026-07-19", "description": "Freelance Payment", "amount": 450.00},
],
}
@mcp.tool()
def get_balance(account_id: str) -> dict:
"""Get the current balance and owner for a bank account."""
account = ACCOUNTS.get(account_id)
if not account:
return {"error": f"Account '{account_id}' not found"}
return {
"account_id": account_id,
"owner": account["owner"],
"balance": account["balance"],
"currency": account["currency"],
}
@mcp.tool()
def list_transactions(account_id: str, limit: int = 5) -> dict:
"""List recent transactions for a bank account, most recent first."""
if account_id not in ACCOUNTS:
return {"error": f"Account '{account_id}' not found"}
txns = TRANSACTIONS.get(account_id, [])[:limit]
return {"account_id": account_id, "transactions": txns}
@mcp.tool()
def transfer_funds(from_account: str, to_account: str, amount: float) -> dict:
"""Transfer funds between two mock bank accounts."""
if from_account not in ACCOUNTS:
return {"error": f"Source account '{from_account}' not found"}
if to_account not in ACCOUNTS:
return {"error": f"Destination account '{to_account}' not found"}
if amount <= 0:
return {"error": "Transfer amount must be positive"}
if ACCOUNTS[from_account]["balance"] < amount:
return {"error": f"Insufficient funds in '{from_account}'"}
ACCOUNTS[from_account]["balance"] -= amount
ACCOUNTS[to_account]["balance"] += amount
return {
"status": "success",
"from_account": from_account,
"to_account": to_account,
"amount": amount,
"new_balance_from": ACCOUNTS[from_account]["balance"],
"new_balance_to": ACCOUNTS[to_account]["balance"],
}
if __name__ == "__main__":
mcp.run(transport="streamable-http")
Vergleichsskripte machen klar, was durch das Gateway ersetzt wird:
test-direct-mcp.pykommuniziert ausschließlich mit dem MCP-Server – er beweist, dass die Tools funktionieren, beinhaltet aber niemals ein Modell.test-direct-model.pyruft direkt Azure und den MCP auf und orchestriert manuell die Übersetzung des Schemas, die Ausführung der Tools sowie den zweiten Schritt – etwa siebzigfünf Zeilen an Verbindungscode reduzieren das Gateway auf eintools-Array in einem einzigen HTTP-Aufruf.- Absolutes keines der Skripte verwendet Port
4000; genau diese Trennung ist der Sinn des Vorher/Nachher-Vergleichs.
Starten Sie den Server und lassen Sie ihn laufen:
uv run python server.py
# Execution results
INFO: Started server process [91854]
INFO: Waiting for application startup.
[07/26/26 21:36:05] INFO StreamableHTTP session manager started streamable_http_manager.py:131
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:3001 (Press CTRL+C to quit)
[07/26/26 21:36:26] INFO Created new transport with session ID: caf5347483ba4256977e29de8892a327
Schritt 2 – zwei Korrekturen zur Erreichbarkeit, damit LiteLLM (in Docker) mit dem Host-Prozess kommunizieren kann
Inhalb des Compose/Admin-UI-Containers ist localhost der Container selbst, nicht Ihr Laptop. Weisen Sie die MCP-URL auf Docksers Host-Forward-DNS hin:
http://host.docker.internal:3001/mcp
Auf separater Ebene kann der DNS-Neubindungsschutz des MCP SDK auf eine 421 Misdirected Request-Antwort reagieren, es sei denn, der weitergeleitete Host steht auf der Server-Allowlist:
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
mcp = FastMCP(
"mock-bank",
host="127.0.0.1",
port=3001,
transport_security=TransportSecuritySettings(
allowed_hosts=["localhost:3001", "127.0.0.1:3001", "host.docker.internal:3001"],
),
)
Schritt 3 – den Server bei LiteLLM registrieren
In der Benutzeroberfläche: MCP Servers → Add MCP Server
- Name:
mock_bank - Transport: Streamable HTTP (muss mit
mcp.run(transport="streamable-http")übereinstimmen) - URL:
http://host.docker.internal:3001/mcp - Authentifizierung: für diese lokale Demo nicht erforderlich
Oder über die Konfiguration:
mcp_servers:
mock_bank:
url: http://host.docker.internal:3001/mcp
transport: streamable_http
auth_type: none
Stellen Sie sicher, dass der **Verbindungsstatus: Verbunden** angezeigt wird und unter **Tool Configuration** alle drei Tools als aktiv aufgelistet sind.
Schritt 4 – überprüfen, welchen Modellstring das Gateway tatsächlich registriert hat
curl -X GET 'http://localhost:4000/v1/models' -H 'Authorization: Bearer sk-1234'
# Expected Result
{"data":[{"id":"azure_ai/gpt-5.4-mini","object":"model","created":1677610602,"owned_by":"openai"}],"object":"list"}
Trotz des Präfixes azure_ai/ erreichte der Traffic den Azure OpenAI-Chat-Pipeline während der Tests weiterhin – in den Antworten erschienen Felder zur Inhaltssuche. Betrachten Sie die ID als eine Namensbesonderheit, die wörtlich übernommen werden soll, und nicht als fehlerhaften Anbieter.
Schritt 5 – einzelnes Tool in einer Turn-Runde (reiner Pfad)
curl --location 'http://localhost:4000/v1/responses' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer sk-1234" \
--data '{
"model": "azure_ai/gpt-5.4-mini",
"input": [
{"role": "user", "content": "What is the balance on account ACC1001, and what were its last 3 transactions?", "type": "message"}
],
"tools": [
{
"type": "mcp",
"server_label": "mock_bank",
"server_url": "litellm_proxy",
"require_approval": "never"
}
],
"tool_choice": "required"
}'
# Expected Result
{"id":"resp_QQRbArl1kNuzmPJahCQIWJvpgxSgsw7SuuwX_pZJfuWmrGiguydiPvF7EhPhfRrojCr6UtQGUTlWEA3FSDBl4c1PSDpccSmbIZWgCMizU1Vp8gDt_jokT8ZOL9tIiYK0pUHjYVGu3PgbJjz5J0jkAJUFKshh_-9xn0zVGf08-33WD19mcLCh3qfxRM4AcMciWE4nNQa_TrS-6U6olfonnXvge9qszI4u6dA02Eo6GlLmEpGE19InxbopOLBny1QNdMDgdC4SzzZh9XWnQvFNu-IjYEe68pfoJ5u0ohZE1scbgF9ABNMAaN94vfGIuApDd9ibCBpgbHbcTsrE86EGP6_XJ_72uwG_aTHTjgZ-laFUxCIjEgEEo8S09Ojq-eupjWI8WTwSOT_ozMKVF0cyOuQKwkpARIrIeGE=","created_at":1785242019,"error":null,"incomplete_details":null,"instructions":null,"metadata":{},"model":"azure_ai/gpt-5.4-mini","object":"response","output":[{"id":"msg_0977207685db3e25006a68a1a3dc108196ad8f53ed0c3c8c73","content":[{"annotations":[],"text":"Account **ACC1001** belongs to **Alice Johnson**.\n\n- **Balance:** **$3,450.75 USD**\n\nLast 3 transactions:\n1. **2026-07-20** — Grocery Store — **-$84.32**\n2. **2026-07-18** — Payroll Deposit — **+$2,500.00**\n3. **2026-07-15** — Electric Bill — **-$120.44**","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message","phase":"final_answer"},{"type":"mcp_tools_fetched","id":"mcp_tools_cbeff22f","status":"completed","role":"system","content":[{"type":"output_text","text":"[\n \"name='mock_bank-get_balance' title=None description='Get the current balance and owner for a bank account.' inputSchema={'properties': {'account_id': {'title': 'Account Id', 'type': 'string'}}, 'required': ['account_id'], 'title': 'get_balanceArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\",\n \"name='mock_bank-list_transactions' title=None description='List recent transactions for a bank account, most recent first.' inputSchema={'properties': {'account_id': {'title': 'Account Id', 'type': 'string'}, 'limit': {'default': 5, 'title': 'Limit', 'type': 'integer'}}, 'required': ['account_id'], 'title': 'list_transactionsArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\",\n \"name='mock_bank-transfer_funds' title=None description='Transfer funds between two mock bank accounts.' inputSchema={'properties': {'from_account': {'title': 'From Account', 'type': 'string'}, 'to_account': {'title': 'To Account', 'type': 'string'}, 'amount': {'title': 'Amount', 'type': 'number'}}, 'required': ['from_account', 'to_account', 'amount'], 'title': 'transfer_fundsArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\"\n]","annotations":[]}],"phase":null},{"type":"tool_execution_results","id":"tool_results_ee9f6c7e","status":"completed","role":"system","content":[{"type":"output_text","text":"[\n {\n \"tool_call_id\": \"call_ToKnEWo532C8nDABKElHCSBs\",\n \"result\": \"{\\n \\\"account_id\\\": \\\"ACC1001\\\",\\n \\\"owner\\\": \\\"Alice Johnson\\\",\\n \\\"balance\\\": 3450.75,\\n \\\"currency\\\": \\\"USD\\\"\\n}\",\n \"name\": \"mock_bank-get_balance\"\n },\n {\n \"tool_call_id\": \"call_xZ9GNGQEPXRWI3EEaVuh6J4H\",\n \"result\": \"{\\n \\\"account_id\\\": \\\"ACC1001\\\",\\n \\\"transactions\\\": [\\n {\\n \\\"date\\\": \\\"2026-07-20\\\",\\n \\\"description\\\": \\\"Grocery Store\\\",\\n \\\"amount\\\": -84.32\\n },\\n {\\n \\\"date\\\": \\\"2026-07-18\\\",\\n \\\"description\\\": \\\"Payroll Deposit\\\",\\n \\\"amount\\\": 2500.0\\n },\\n {\\n \\\"date\\\": \\\"2026-07-15\\\",\\n \\\"description\\\": \\\"Electric Bill\\\",\\n \\\"amount\\\": -120.44\\n }\\n ]\\n}\",\n \"name\": \"mock_bank-list_transactions\"\n }\n]","annotations":[]}],"phase":null}],"parallel_tool_calls":true,"temperature":1.0,"tool_choice":"auto","tools":[{"name":"mock_bank-get_balance","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"Get the current balance and owner for a bank account."},{"name":"mock_bank-list_transactions","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"List recent transactions for a bank account, most recent first."},{"name":"mock_bank-transfer_funds","parameters":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"Transfer funds between two mock bank accounts."}],"top_p":0.98,"max_output_tokens":null,"previous_response_id":"resp_0977207685db3e25006a68a1a1f48c8196b7990872db041d14","reasoning":{"context":"current_turn","effort":"none","mode":"standard","summary":null},"status":"completed","text":{"format":{"type":"text"},"verbosity":"medium"},"truncation":"disabled","usage":{"input_tokens":473,"input_tokens_details":{"audio_tokens":null,"cached_tokens":0,"text_tokens":null},"output_tokens":97,"output_tokens_details":{"reasoning_tokens":0,"text_tokens":null},"total_tokens":570,"cost":null},"user":null,"store":true,"background":false,"completed_at":1785242020,"content_filters":[{"blocked":false,"source_type":"completion","content_filter_raw":[],"content_filter_results":{"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"hate":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"}},"content_filter_offsets":{"start_offset":0,"end_offset":255,"check_offset":0}}],"frequency_penalty":0.0,"max_tool_calls":null,"moderation":null,"presence_penalty":0.0,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","safety_identifier":null,"service_tier":"default","top_logprobs":0
Bei dem erfolgreichen Ausführungsvorgang rief das Modell die Funktionen get_balance und list_transactions auf und antwortete anschließend mit simulierten Live-Daten – Alice Johnson bei ACC1001, ein Saldo, der mit dem im Speicher gespeicherten Wert übereinstimmte, sowie die drei neuesten Transaktionen aus mock_bank_server.py. Das bestätigt den Ablauf Gateway → Entdeckung → Ausführung → Endantwort.
Schritt 6 – Aufruf mit Zustandsänderung, wenn es das einzige Tool in der Turn-Runde ist
curl -X POST 'http://localhost:4000/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"model": "azure_ai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "First, transfer $200 from ACC1001 to ACC1002. Do not call any other tools yet."}],
"tools": [{"type": "mcp", "server_label": "mock_bank", "server_url": "litellm_proxy", "require_approval": "never"}],
"tool_choice": "auto"
}'
# Expected result
{"id":"chatcmpl-E6bKV9nPurWICiuBlkgwaJzOS8vIC","created":1785242171,"model":"azure_ai/gpt-5.4-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"Done — transferred $200 from ACC1001 to ACC1002 successfully.","role":"assistant","provider_specific_fields":{"refusal":null,"mcp_list_tools":[{"type":"function","function":{"name":"mock_bank-get_balance","description":"Get the current balance and owner for a bank account.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-list_transactions","description":"List recent transactions for a bank account, most recent first.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-transfer_funds","description":"Transfer funds between two mock bank accounts.","parameters":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object","additionalProperties":false},"strict":false}}],"mcp_tool_calls":[{"function":{"arguments":"{\"from_account\":\"ACC1001\",\"to_account\":\"ACC1002\",\"amount\":200}","name":"mock_bank-transfer_funds"},"id":"call_PIN38jt2KT9M4hkoxEKXXXaD","type":"function"}],"mcp_call_results":[{"tool_call_id":"call_PIN38jt2KT9M4hkoxEKXXXaD","result":"{\n \"status\": \"success\",\n \"from_account\": \"ACC1001\",\n \"to_account\": \"ACC1002\",\n \"amount\": 200.0,\n \"new_balance_from\": 3250.75,\n \"new_balance_to\": 1980.1\n}","name":"mock_bank-transfer_funds"}]},"annotations":[]},"provider_specific_fields":{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}}],"usage":{"completion_tokens":19,"prompt_tokens":373,"total_tokens":392,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"latency_checkpoint":{"engine_tbt_ms":5,"engine_ttft_ms":30,"engine_ttlt_ms":116,"pre_inference_ms":120,"service_tbt_ms":5,"service_ttft_ms":306,"service_ttlt_ms":316,"total_duration_ms":223,"user_visible_ttft_ms":186}},"service_tier":"default","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{}}]}
Ergebnis: finish_reason: „stop“, transfer_funds wurde automatisch ausgeführt, abschließende Zusammenfassung mit den aktualisierten Kontoständen. Das Überweisen von Geld ist genau die Art von Aktion, die vor dem Verlassen einer Laptop-Demo Schutzmaßnahmen erfordert.
Lektion – parallele Toolaufrufe in einem Schritt
Komplexe Anfragen wie „Überweisen Sie 200 $ von ACC1001 auf ACC1002 und bestätigen Sie anschließend beide Kontostände“ führten dazu, dass das Modell gleichzeitig drei Toolaufrufe ausführte. Bei der getesteten LiteLLM-Version wurde die MCP-Autoausführung zuverlässig nur für einen Aufruf abgeschlossen (transfer_funds ist in mcp_tool_calls / mcp_call_results aufgeführt), während die verbleibenden get_balance-Aufrufe ungelöst im OpenAI-ähnlichen tool_calls-Array verblieben – ohne Endtext, da das Modell noch wartete. Dieses Verhalten trat sowohl bei /v1/responses als auch bei /chat/completions auf, unabhängig davon, ob tool_choice auf "required" oder "auto" gesetzt war. Es handelt sich also nicht um einen Fehler im Anfragedesign, sondern darum, wie diese Version mehrere parallele MCP-Toolaufrufe in einem einzigen Schritt handhabte.
Praktische Anleitung für diesen Aufbau: Fordern Sie jeweils nur ein Tool nach dem anderen an (wie in Schritt 6). Falls Sie tatsächlich eine parallele Ausführung mehrerer Aufrufe benötigen, führen Sie den Loop selbst aus – führen Sie die verbleibenden tool_calls aus und senden Sie die Ergebnisse mit role: „tool“, nach demselben Muster wie in den untenstehenden Direct- und Gateway-Skripten. Die Wahl des Modells ist weiterhin entscheidend für die Zuverlässigkeit der Agenten; die gestrige „perfekte“ Wahl kann bereits im nächsten Release-Zyklus veraltet erscheinen, daher sollten die Orchestrierungspfade austauschbar bleiben.
Schritt 7 – Überprüfung der Entdeckung ohne Einbeziehung des Modells
curl -X POST 'http://localhost:4000/mcp/mock_bank' \
-H 'Authorization: Bearer sk-1234' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Expected result
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"mock_bank-get_balance","description":"Get the current balance and owner for a bank account.","inputSchema":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object"}},{"name":"mock_bank-list_transactions","description":"List recent transactions for a bank account, most recent first.","inputSchema":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object"}},{"name":"mock_bank-transfer_funds","description":"Transfer funds between two mock bank accounts.","inputSchema":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object"}}]}}
Der Aufruf trennt den Fall „Die Entdeckung funktioniert nicht“ vom Fall „Das Modell weigerte sich, das Tool aufzurufen“ oder „Nur ein Teil der Ausführung wurde durchgeführt.“
Direct vs Gateway: Was der Proxy Ihnen bringt
Test 1 – reines MCP, kein Modell, keine Gateway. Überprüfen Sie den Bankserver allein:
import asyncio
from mcp.client.streamable_http import streamablehttp_client
from mcp.client.session import ClientSession
async def main():
async with streamablehttp_client("http://127.0.0.1:3001/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. Discover what tools exist
tools = await session.list_tools()
print("Available tools:", [t.name for t in tools.tools])
# 2. Call a tool directly — YOU decide which tool and args.
# There is no model here, no reasoning, no interpretation of
# natural language. You must already know exactly what to call.
result = await session.call_tool(
"get_balance", arguments={"account_id": "ACC1001"}
)
print("\nget_balance result:")
print(result.content[0].text)
result2 = await session.call_tool(
"list_transactions", arguments={"account_id": "ACC1001", "limit": 3}
)
print("\nlist_transactions result:")
print(result2.content[0].text)
if __name__ == "__main__":
asyncio.run(main())
Erforderliche Ausgabe der Liste und des Kontostands:
uv run test-direct-mcp.py
# Expected result
Available tools: ['get_balance', 'list_transactions', 'transfer_funds']
get_balance result:
{
"account_id": "ACC1001",
"owner": "Alice Johnson",
"balance": 3250.75,
"currency": "USD"
}
list_transactions result:
{
"account_id": "ACC1001",
"transactions": [
{
"date": "2026-07-20",
"description": "Grocery Store",
"amount": -84.32
},
{
"date": "2026-07-18",
"description": "Payroll Deposit",
"amount": 2500.0
},
{
"date": "2026-07-15",
"description": "Electric Bill",
"amount": -120.44
}
]
}
Test 2 – direktes Modell zusammen mit manuell erstellter Orchestrierung. MCP-Tools abrufen, Schemata in das OpenAI-Toolformat übersetzen, Azure aufrufen, Tools ausführen und die Ergebnisse zurückgeben – insgesamt etwa siebzigfünf Zeilen Code müssen in der Anwendung enthalten sein, ohne gemeinsame Schutzmechanismen oder Ausgabenverfolgung:
import asyncio
import json
import os
from openai import AzureOpenAI
from mcp.client.streamable_http import streamablehttp_client
from mcp.client.session import ClientSession
MODEL_DEPLOYMENT = "gpt-5.4-mini"
def mcp_tool_to_openai_format(mcp_tool):
"""Manual translation step #2 — the gateway normally does this for you."""
return {
"type": "function",
"function": {
"name": mcp_tool.name,
"description": mcp_tool.description or "",
"parameters": mcp_tool.inputSchema,
},
}
async def main():
client = AzureOpenAI(
api_key=os.environ["AZURE_API_KEY"],
api_version="2024-10-21",
azure_endpoint=os.environ["AZURE_API_BASE"],
)
async with streamablehttp_client("http://127.0.0.1:3001/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# Step 1: discover tools yourself
mcp_tools = (await session.list_tools()).tools
openai_tools = [mcp_tool_to_openai_format(t) for t in mcp_tools]
messages = [
{
"role": "user",
"content": "What is the balance on account ACC1001, and "
"what were its last 3 transactions?",
}
]
# Step 3: call the model directly
response = client.chat.completions.create(
model=MODEL_DEPLOYMENT,
messages=messages,
tools=openai_tools,
tool_choice="required",
)
msg = response.choices[0].message
messages.append(msg.model_dump(exclude_none=True))
# Step 4: manually execute any tool calls the model requested
for tool_call in msg.tool_calls or []:
args = json.loads(tool_call.function.arguments)
result = await session.call_tool(tool_call.function.name, arguments=args)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": result.content[0].text,
}
)
# Second turn: give the model the tool results, get final answer
final = client.chat.completions.create(
model=MODEL_DEPLOYMENT,
messages=messages,
)
print(final.choices[0].message.content)
if __name__ == "__main__":
asyncio.run(main())
Beispiel für eine erfolgreiche narrative Antwort:
uv run test-direct-model.py
# Expected Result
Account **ACC1001** (Alice Johnson) has a balance of **USD 3,250.75**.
Last 3 transactions:
1. **2026-07-20** — Grocery Store — **-84.32**
2. **2026-07-18** — Payroll Deposit — **+2,500.00**
3. **2026-07-15** — Electric Bill — **-120.44**
Test 3 – dieselbe Absicht über LiteLLM. Einer HTTP-Anfrage; Entdeckung, Übersetzung, Ausführung sowie die anschließende Antwort finden direkt innerhalb des Gateways statt:
curl --location 'http://localhost:4000/v1/responses' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer sk-1234" \
--data '{
"model": "azure_ai/gpt-5.4-mini",
"input": [{"role": "user", "content": "What is the balance on account ACC1001, and what were its last 3 transactions?", "type": "message"}],
"tools": [{"type": "mcp", "server_label": "mock_bank", "server_url": "litellm_proxy", "require_approval": "never"}],
"tool_choice": "required"
}'
# Expected Result
{"id":"resp_DCjguhF87Zw3ekO8Jw3782IkYnZQfG_avgYD_kkI-NmeDU9bNSDRFM3FTGJl31tMzutajvYJ6x_S8siU06zyXozMCPC1pSQhXraDtpREE_RRXq23BZDJR37fxmPqzVsdhlk-o2IEF3C8Z3251nJPIecUJM75GFAYdXygfMfoqmM1Jdaxc3VQfcZKMaW1IOG7528zrRqms58F5xwpR0vvFScwclLhOYrch5WQRY6KBBi-60wMBuylfQNfqT8cWwvbeG7Xbe6OKEiBXFzWOx7QkaGhckDFat-DZ3RFW8Iv8SP0a21rA_9h19QUijB-sPBaD9BNIKk4I2dRqz2B1vMtyut3GpX8mO5D5dinKruIMy2nrMatBun8uJC5Mt76hWgFc6YzH3h_ihsZjCRzWz1j85VowrVdnOYGGhg=","created_at":1785242960,"error":null,"incomplete_details":null,"instructions":null,"metadata":{},"model":"azure_ai/gpt-5.4-mini","object":"response","output":[{"id":"msg_0f2c0a8554062b57006a68a550d828819085dd064764201c32","content":[{"annotations":[],"text":"Account **ACC1001** is owned by **Alice Johnson**.\n\n- **Balance:** **$3,250.75 USD**\n\n**Last 3 transactions:**\n1. **2026-07-20** — Grocery Store — **-$84.32**\n2. **2026-07-18** — Payroll Deposit — **+$2,500.00**\n3. **2026-07-15** — Electric Bill — **-$120.44**","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message","phase":"final_answer"},{"type":"mcp_tools_fetched","id":"mcp_tools_9d9b1cdc","status":"completed","role":"system","content":[{"type":"output_text","text":"[\n \"name='mock_bank-get_balance' title=None description='Get the current balance and owner for a bank account.' inputSchema={'properties': {'account_id': {'title': 'Account Id', 'type': 'string'}}, 'required': ['account_id'], 'title': 'get_balanceArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\",\n \"name='mock_bank-list_transactions' title=None description='List recent transactions for a bank account, most recent first.' inputSchema={'properties': {'account_id': {'title': 'Account Id', 'type': 'string'}, 'limit': {'default': 5, 'title': 'Limit', 'type': 'integer'}}, 'required': ['account_id'], 'title': 'list_transactionsArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\",\n \"name='mock_bank-transfer_funds' title=None description='Transfer funds between two mock bank accounts.' inputSchema={'properties': {'from_account': {'title': 'From Account', 'type': 'string'}, 'to_account': {'title': 'To Account', 'type': 'string'}, 'amount': {'title': 'Amount', 'type': 'number'}}, 'required': ['from_account', 'to_account', 'amount'], 'title': 'transfer_fundsArguments', 'type': 'object'} outputSchema=None icons=None annotations=None meta=None execution=None\"\n]","annotations":[]}],"phase":null},{"type":"tool_execution_results","id":"tool_results_56298345","status":"completed","role":"system","content":[{"type":"output_text","text":"[\n {\n \"tool_call_id\": \"call_t6y64QgQjDT4jGMLOEyFxc5u\",\n \"result\": \"{\\n \\\"account_id\\\": \\\"ACC1001\\\",\\n \\\"owner\\\": \\\"Alice Johnson\\\",\\n \\\"balance\\\": 3250.75,\\n \\\"currency\\\": \\\"USD\\\"\\n}\",\n \"name\": \"mock_bank-get_balance\"\n },\n {\n \"tool_call_id\": \"call_nZZbT734wXIuElJTdSWOORwL\",\n \"result\": \"{\\n \\\"account_id\\\": \\\"ACC1001\\\",\\n \\\"transactions\\\": [\\n {\\n \\\"date\\\": \\\"2026-07-20\\\",\\n \\\"description\\\": \\\"Grocery Store\\\",\\n \\\"amount\\\": -84.32\\n },\\n {\\n \\\"date\\\": \\\"2026-07-18\\\",\\n \\\"description\\\": \\\"Payroll Deposit\\\",\\n \\\"amount\\\": 2500.0\\n },\\n {\\n \\\"date\\\": \\\"2026-07-15\\\",\\n \\\"description\\\": \\\"Electric Bill\\\",\\n \\\"amount\\\": -120.44\\n }\\n ]\\n}\",\n \"name\": \"mock_bank-list_transactions\"\n }\n]","annotations":[]}],"phase":null}],"parallel_tool_calls":true,"temperature":1.0,"tool_choice":"auto","tools":[{"name":"mock_bank-get_balance","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"Get the current balance and owner for a bank account."},{"name":"mock_bank-list_transactions","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"List recent transactions for a bank account, most recent first."},{"name":"mock_bank-transfer_funds","parameters":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object","additionalProperties":false},"strict":false,"type":"function","defer_loading":null,"description":"Transfer funds between two mock bank accounts."}],"top_p":0.98,"max_output_tokens":null,"previous_response_id":"resp_0f2c0a8554062b57006a68a54eeaa0819097e6e8dcb9255a38","reasoning":{"context":"current_turn","effort":"none","mode":"standard","summary":null},"status":"completed","text":{"format":{"type":"text"},"verbosity":"medium"},"truncation":"disabled","usage":{"input_tokens":473,"input_tokens_details":{"audio_tokens":null,"cached_tokens":0,"text_tokens":null},"output_tokens":100,"output_tokens_details":{"reasoning_tokens":0,"text_tokens":null},"total_tokens":573,"cost":null},"user":null,"store":true,"background":false,"completed_at":1785242961,"content_filters":[{"blocked":false,"source_type":"completion","content_filter_raw":[],"content_filter_results":{"protected_material_text":{"detected":false,"filtered":false},"protected_material_code":{"detected":false,"filtered":false},"hate":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"}},"content_filter_offsets":{"start_offset":0,"end_offset":260,"check_offset":0}}],"frequency_penalty":0.0,"max_tool_calls":null,"moderation":null,"presence_penalty":0.0,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","safety_identifier":null,"service_tier":"default","top_logprobs":0}
| Aspekt | Direkt (Test 2) | Gateway (Test 3) |
|---|---|---|
| Anwendungscode | ~75 Zeilen pro Anwendung | Eine HTTP-Anfrage |
| Schema-Übersetzung | Manuell pro MCP-Server | Automatisch |
| Tool-Loop | Dies wird von Ihnen verwaltet |
model-Zeichenfolge ändernmcp_serversSchutzmaßnahmen wie E-Mail-Redaktion, Blockierung von Kartennummern und Schlüsselwörtern laufen in pre_call – also **vor** der Entdeckung oder Ausführung durch MCP. Eine blockierte Anfrage sollte niemals den simulierten Banksystem erreichen. Genau diese Eigenschaft muss nachgewiesen werden, nicht nur dass der Text in der Antwort maskiert erscheint.
curl -X POST 'http://localhost:4000/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"model": "azure_ai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Check the balance on ACC1001 and email the results to john.doe@example.com"}],
"tools": [{"type": "mcp", "server_label": "mock_bank", "server_url": "litellm_proxy", "require_approval": "never"}],
"tool_choice": "auto",
"guardrails": ["basic-content-filter"]
}'
# Expected result
{"id":"chatcmpl-E6xkAAMouHdsDuJR038UZH4Thej9z","created":1785328330,"model":"azure_ai/gpt-5.4-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"I checked the balance for ACC1001:\n\n- Owner: Alice Johnson\n- Balance: USD 3,250.75\n\nI can’t send emails directly from here, but you can forward this result to [EMAIL_REDACTED].","role":"assistant","provider_specific_fields":{"refusal":null,"mcp_list_tools":[{"type":"function","function":{"name":"mock_bank-get_balance","description":"Get the current balance and owner for a bank account.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-list_transactions","description":"List recent transactions for a bank account, most recent first.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-transfer_funds","description":"Transfer funds between two mock bank accounts.","parameters":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object","additionalProperties":false},"strict":false}}],"mcp_tool_calls":[{"function":{"arguments":"{\"account_id\":\"ACC1001\"}","name":"mock_bank-get_balance"},"id":"call_4jiRhEZhMUSY8GLWJHfDWulI","type":"function"}],"mcp_call_results":[{"tool_call_id":"call_4jiRhEZhMUSY8GLWJHfDWulI","result":"{\n \"account_id\": \"ACC1001\",\n \"owner\": \"Alice Johnson\",\n \"balance\": 3250.75,\n \"currency\": \"USD\"\n}","name":"mock_bank-get_balance"}]},"annotations":[]},"provider_specific_fields":{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}}],"usage":{"completion_tokens":53,"prompt_tokens":332,"total_tokens":385,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"latency_checkpoint":{"engine_tbt_ms":4,"engine_ttft_ms":36,"engine_ttlt_ms":260,"pre_inference_ms":125,"service_tbt_ms":4,"service_ttft_ms":375,"service_ttlt_ms":594,"total_duration_ms":478,"user_visible_ttft_ms":250}},"service_tier":"default","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{}}]}
Wiederholen Sie den Inhalt, ohne die Schutzmaßnahmen im Benutzertext zu nennen:
curl -X POST 'http://localhost:4000/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"model": "azure_ai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Check the balance on ACC1001 and email the results to john.doe@example.com"}],
"tools": [{"type": "mcp", "server_label": "mock_bank", "server_url": "litellm_proxy", "require_approval": "never"}],
"tool_choice": "auto"
}'
# Expected result
{"id":"chatcmpl-E6xlbLbaUP1awks92ZGSrsHCB8sl0","created":1785328419,"model":"azure_ai/gpt-5.4-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"I checked ACC1001:\n\n- Owner: Alice Johnson\n- Balance: $3,250.75 USD\n\nI can’t send emails directly from here, but you can forward this result to [EMAIL_REDACTED].","role":"assistant","provider_specific_fields":{"refusal":null,"mcp_list_tools":[{"type":"function","function":{"name":"mock_bank-get_balance","description":"Get the current balance and owner for a bank account.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"}},"required":["account_id"],"title":"get_balanceArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-list_transactions","description":"List recent transactions for a bank account, most recent first.","parameters":{"properties":{"account_id":{"title":"Account Id","type":"string"},"limit":{"default":5,"title":"Limit","type":"integer"}},"required":["account_id"],"title":"list_transactionsArguments","type":"object","additionalProperties":false},"strict":false}},{"type":"function","function":{"name":"mock_bank-transfer_funds","description":"Transfer funds between two mock bank accounts.","parameters":{"properties":{"from_account":{"title":"From Account","type":"string"},"to_account":{"title":"To Account","type":"string"},"amount":{"title":"Amount","type":"number"}},"required":["from_account","to_account","amount"],"title":"transfer_fundsArguments","type":"object","additionalProperties":false},"strict":false}}],"mcp_tool_calls":[{"function":{"arguments":"{\"account_id\":\"ACC1001\"}","name":"mock_bank-get_balance"},"id":"call_59tE386FwTOGOROXJNJPQZlB","type":"function"}],"mcp_call_results":[{"tool_call_id":"call_59tE386FwTOGOROXJNJPQZlB","result":"{\n \"account_id\": \"ACC1001\",\n \"owner\": \"Alice Johnson\",\n \"balance\": 3250.75,\n \"currency\": \"USD\"\n}","name":"mock_bank-get_balance"}]},"annotations":[]},"provider_specific_fields":{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}}],"usage":{"completion_tokens":50,"prompt_tokens":332,"total_tokens":382,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"latency_checkpoint":{"engine_tbt_ms":5,"engine_ttft_ms":39,"engine_ttlt_ms":272,"pre_inference_ms":154,"service_tbt_ms":5,"service_ttft_ms":434,"service_ttlt_ms":646,"total_duration_ms":507,"user_visible_ttft_ms":281}},"service_tier":"default","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{}}]}
Sowohl Fälle sollten bei standardmäßig aktivierten Schutzmaßnahmen E-Mails als REDACTED anzeigen. Falls diese deaktiviert sind, aktivieren Sie sie in der Admin-Oberfläche.
Praktische Erklärungen
1. Muss jede Anfrage tools enthalten?
Ja, immer dann, wenn dieser Schritt MCP verwenden sollte. Die Gateway-Funktion fügt Tools nicht stillschweigend zu Anfragen hinzu, die sie weglassen. Wenn Sie tools weglassen, erhalten Sie eine einfache LLM-Antwort – auch dann, wenn mock_bank verbunden ist.
2. tool_choice: „required“ gegenüber „auto“
Verwenden Sie „required“ für einen einfachen Test, bei dem eine Tool-Aufruf gesichert erfolgen soll. Wählen Sie „auto“ für konversationelle oder mehrstufige Abläufe, da „required“ strukturell nicht in der Lage ist, allein eine endgültige Antwort in natürlicher Sprache zu liefern.
3. Sicherere, klarere Tool-Auswahl
Kunden müssen sonst die genauen Tool-Names und -Schemata kennen, und nichts hindert ein riskantes Tool wie transfer_funds daran, genauso leicht ausgeführt zu werden wie get_balance. LiteLLM bietet bereits Lösungsansätze:
a) Veröffentlichen Sie den Katalog – verhindern Sie, dass Integratoren die Aufrufe der Modell-Tools durch Reverse Engineering entschlüsseln müssen:
curl -X POST 'http://localhost:4000/mcp/mock_bank' \
-H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' \
-d '{"method":"tools/list"}'
Stellen Sie diesen (oder /v1/mcp/tools) in den Entwicklerdokumenten oder einer Onboarding-Oberfläche zur Verfügung, sodass Namen, Beschreibungen sowie Argument-Schemata bereits sichtbar sind, bevor jemand Client-Code schreibt.
b) Wählen Sie statt allow_all_keys MCP-Berechtigungen pro Schlüssel bzw. Team. Begrenzen Sie den Zugriff auf mock_bank über Guardrails/MCP → Permission Management, damit jeder Schlüssel nur die Tools sieht, die ihm zustehen.
c) Nach Risiko aufteilen. Markieren Sie transfer_funds als schreibendes/risikoreiches Tool und setzen Sie require_approval: „always“, damit eine Person die Überweisung bestätigt. Lassen Sie Lesetools bei „never“, falls dies zu Ihrem Bedrohungsmodell passt.
d) Durch die serverseitige Tool-Permission-Guardrail durchsetzen, nicht durch die clientseitige tool_choice. Die Aufrufer steuern tool_choice; dies stellt keine Sicherheitsgrenze dar. Eine Gateway-Guardrail kann von einer Kundenanwendung nicht umgangen werden:
guardrails:
- guardrail_name: "mcp-tool-permissions"
litellm_params:
guardrail: tool_permission
mode: "pre_mcp_call"
rules:
- rule_id: "block-transfers"
tool_name: "^mock_bank-transfer_fundsquot;
decision: "deny"
default_action: "allow"
e) Argumente einschränken, nicht nur Tool-Allowlists. allowed_params kann die amount-Bereiche begrenzen oder festlegen, welche account_id-Werte eine Schlüsselverwendung zulassen – präziser als ein einfaches Alles-oder-Nichts-Verbot.
Betrachten Sie den Tool-Katalog wie eine Produkt-API: Veröffentlichen Sie ihn, begrenzen Sie seinen Zugriff pro Kunde und definieren Sie Risikostufen für Lese- und Schreibzugriffe mithilfe von Server-seitigen Richtlinien, anstatt jedem Client zu vertrauen.
Nächste Schritte
Sobald grundlegende Sicherheitsmaßnahmen sowie MCP über LiteLLM vorhanden sind, folgen logischerweise Limits für die Anfragenhäufigkeit, maximale gleichzeitige Anfragen, spezifische Eingabekontrollen für MCP sowie strengere Richtlinien zur Auswahl von Tools. Bis diese umgesetzt sind, halten Sie die Demos lokal, behalten Sie risikoreiche Tools hinter einer Genehmigung und überlegen Sie sorgfältig, welche Funktionen jede API-Schlüssel aufrufen darf.
Beim Debuggen instabiler agenter Abläufe sollten Sie die Gateway-Anfrage-ID, den registrierten Modellstring, die angehängten MCP-Servernamen sowie festhalten, ob tool_choice auf auto oder required gesetzt war. Diese vier Informationen helfen in der Regel schneller, zwischen „falscher Modell-ID“, „ausgelassenen Tools“, „fehlender Entdeckungsfunktion“ und „teilweiser Ausführung paralleler Aufrufe“ zu unterscheiden, als manuell wiederholt CURL-Befehle auszuführen. Halten Sie die Simulationsbank temporär: Löschen und neu starten Sie sie zwischen den Übertragungstests, damit verbleibende Kontostände nicht als erfolgreiche neue Tests ausgegeben werden.