Перадача засоба MCP через LiteLLM: практычныя нарады з імітаваным банкавым шлюзам
Зарэгіструйце мак-банк на базе HTTP, які можна стрімаваць, парабяльце цыклы ручнае створаных інструментоў з адным запытам гейтвэю, і падтвердзіце, што меры захавання пры прыеме запытам ніколі не доходзяць да MCP.
Фота адміністратара Quilia на Unsplash
Працюе за прыязду, што гэты LiteLLM-гейтвей ўжо запусцаны (адмінскі інтэрфейс плюс база данных), пры чым gpt-5.4-mini зарэгістраваны ў Azure AI Foundry. У частцы з аптаварамі описваецца гэта базова наладка.
Што значыць «агентны» ў LiteLLM
MCP-гейтвей LiteLLM дазваляе зарэгістраванаму моделю выкарыстоўваць зовнішніе інструменты через проксі. Гейтвей адкрывае інструменты, перакладае схемы та керуе циклам выконання, так што код прыемніка не павінен сам старацца прыладнаць деталі протаколу MCP да кожнага прадавця моделей. Ваш прыемнік ведае розмову толькі з адным канцэнтрам у формате OpenAI; гейтвей знаходзіцца межы прыемнікам, моделлю та будзь-якімі зарэгістраванымі серверамі MCP.
Ідэнтыфікаторы моделей лёгкая памыліць. Залежна ад таго, як была дадзена інфармацыя пра Azure, LiteLLM можа прадставіць azure_ai/gpt-5.4-mini заместо простага gpt-5.4-mini. Адначы спачатку пераканайцеся ў актуальным рэжыстре:
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"}
На кожным запите неабяжна выкарыстоўваць гэты самы рэчык. Неспадзянака будзе праказана як Invalid model name passed in model=..., а не як некалькісны сетевы бяда.
Той самы канцэнтр, звычны LLM або з інструментамі
Не існуе окольныя URL для “рэжыму MCP”. /chat/completions і /v1/responses працуюць аднакова як для звычнага чату, так і для чату з викорыстанням інструментаў. Едыны факт, які мае значэнне, — це тое, чы рэчык JSON містіць масэвую tools (часта з type: "mcp" для інструментаў, карыстоўваных гэйтвейем). Якшто прыміті tools, то будзе атрыбутавана звычная рэсультат, нават якшо на гэйтвейе падключены серверы MCP. Якшо включыць tools, гэйтвей можа знайсці, вызваць і адразу пракласты рэсультаты інструментаў у наступны крок чату.
Звычны запит без інструментаў выглядае так:
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"}}}]}
Частай прычынай нявернага копіювання-выклейвання є дадзенне зайвых апошніх кавычакоў пры імені модэлі (""azure_ai/gpt-5.4-mini"). Чыраўна гэта вызывае памерку Invalid JSON payload: unexpected character — гэта проблема сынтаксісу тэла, а не зламаная стазя.
MCP Gateway: па кроках (прабавана ад початку да канца)
Макет сервера банка
У працэзе MCP замест стандартнага прыкладу “hello-world” у гэтым кераванні викорыстоўваецца маленькі макет банка з трыма інструментамі — get_balance, list_transactions і transfer_funds — якія падтрымваны двума счэтыкамі ў памяці. Рэальныя балансы і пераказы дапамагаюць з’ясавіць, чы робілася работа па цэлай стазі. Кожны з наведзеных нижэй крокаў быў запусканы і перакананы на адпаведнасць гэтым дадзенням.
Крок 1 — запуск сервера (таксама паглядзіце самастоятельны файл mock_bank_server.py):
mkdir mock-bank-server && cd mock-bank-server
uv init .
uv add "mcp[cli]" openai
touch server.py
server.py выкарыстоўвае сімуляваныя даны, а не рэальны ўрачыстковы журнал. Намечаная структура ўтвараецца так: кліент → LiteLLM → захоўнік правілаў → маршрутызатор → або модэль Azure, або сімуляваны процес MCP банка з яго трыма інструментамі.
Калі выйдзеце за межы дыяграмы, важныя будуць калькі працы інструментам:
- Інтэресны шлях — це цікл, а не адна стрэлка. Модэль просі інструмент; шлюз направляе запыт да MCP; рэзультат вяртаецца через шлюз; модэль продовжае роботу або зноў прасі. Запыты, якія включаюць адзін інструмент, автаматычна завершаюць гэты цікл; запыты, якія включаюць калькі інструментоў, можаць выконвацца часткова автаматычна толькі у працаванай тут версіі.
pre_call пераглядае необработаны пакет дзяўароў пры перадачы. Функцыя pre_mcp_call пераглядае аргументы інструмента ближэй да стороны MCP. У дыяграмах часта паказваецца адна коробка; на самай праўда перехоплення вяршацца на разных этапах.server.py, а не тры околачыстыя мікросервісы.Макет сервера MCP для банку
Прыблізна дзевяносто ліній задаюць FastMCP "mock-bank" з:
get_balance(account_id)— власнік і залишок сумыlist_transactions(account_id, limit)— апошнія записыtransfer_funds(from_account, to_account, amount)— пераказ сумы з базовай перапрацоўкай (невядомы рахунак, недастатковыя сумы)
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")
Скрыпты для парабярання чыстаюць, што саме заменяе гейтвэй:
test-direct-mcp.pyсустрэчаецца толькі з серверам MCP — гэта падтверджае, што інструменты працуюць, і ў гэтым процесе не берае участі жадныя моделі.test-direct-model.pyбезпосередньа вызывае Azure і MCP, а таксама ручная коордынацыя перакладу схемы, запуску інструментоў і другага крока — прыблізна сяродзесят пяць ліній коду дазволяюць гэту структуру ператворыць у масэвую зместtoolsза адні HTTP-запыт.- Жадны з скрыптаў не сустрэчаецца з портам
4000; гэта і є метай параверкі ранейшага і пазнейшага стану.
Запусціце сервер і заставьте яго працаваць:
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
Шаг 2 — два спосабу выправлення проблем з доступнасцю, каб LiteLLM (у Docker) могла сустрэчацца з главным процесам
У контэйнеры Compose/Admin UI localhost — це сам контэйнер, а не ваш ноутбук. Направьце URL MCP на DNS-адрэс, які Docker выдае для прыемніка:
http://host.docker.internal:3001/mcp
Дзеячы аднарава, захад прыбірання DNS у MCP SDK можа адпаведаць на 421 Misdirected Request, якщо толькі адреса Host, яка перадаецца, не ўключаная у список дозволеных на серверы:
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"],
),
)
Шаг 3 — зарэўнаваць сервер у LiteLLM
У інтэрфейсе: MCP Servers → Add MCP Server
- Назва:
mock_bank - Транспорт: Streamable HTTP (павінна падходзіць да
mcp.run(transport="streamable-http")) - URL:
http://host.docker.internal:3001/mcp - Автанацыя: няма для гэтага локальнага дэмана
Альбо через настройкі:
mcp_servers:
mock_bank:
url: http://host.docker.internal:3001/mcp
transport: streamable_http
auth_type: none
Паказаецца Статус з’яўлення: З’явлены, і ў роздзеле Настройкі адзычнага пераканальваецца, што увесь трохі адзычнага ўвімкнуты.
Шаг 4 — паказаць строку модэлю, якую на самай працы зарэўнавала гейтвей
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"}
Незважаючы на прыфікс azure_ai/, трафік усё рава потрапляў у пайплайн чату Azure OpenAI пад час тэставання (у адпаведзях з’являліся поля фільтрацыі контэнту). Спрыяйце гэтаму ідентыфікатору як даўнему спосабу называння, каб скопіюваць яго без змян, а не як памылку прадаўцы.
Шаг 5 — адзін інструмент у раунде (чысты шлях)
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
Пад час успешнага запуску модэль вызвала функцыі get_balance і list_transactions, а пасля даўаў адпаведзь на базе сімуляваных дадзенняў — Эліс Джонсан на ACC1001, баланс, які падтрымліваўся внутршнім датасэтом, і тры няўзабавныя транзакцыі з mock_bank_server.py. Гэта паўнастаіць, што выконваецца роад-гейтвей → адкрыцця → выконанне → фінальная адпаведзь.
Шаг 6 — вызов, які зменяе стан, калі гэты інструмент ёсць ежым у раунде
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":{}}]}
Рэзультат: finish_reason: „stop“, transfer_funds аўтаматычна выканана, фінальны падсумак з обменаванымі балансамі. Пераказ грошаў — гэта саме тая дзеянне, якое патрабуе захоўніка пры выкананні яго ў дэмаверсіі на ноутбуку.
Урок — паралельныя вызовы інструментаў за адну партію
Складзеныя запросы, такія як «пераказаць $200 з ACC1001 на ACC1002, а пасля паўністая пераканаліцца ў балансах», вызвалі тое, што модель адразу выпанавала тры запыты да інструментаў. У тэставанай версіі LiteLLM автаматычныя запускі MCP надзеямо завершаліся толькі адним запытам (transfer_funds, які паказаны ў mcp_tool_calls / mcp_call_results), тады как засталія запыты get_balance заставаліся нерашанымі ў масэ tool_calls у формате OpenAI — без жаднага фінальнага тексту, адтолькі ўсё яшчэ чакала модель. Такая працэздатнасць спостылася як у роздзеле /v1/responses, так і ў /chat/completions, пры чым параметр tool_choice быў заданы як "required", так і "auto". Таму гэта не ўскладненне, вызванае форматам запыту; гэта спосаб, якім тая версія справлялася з некалькама паралельнымі запытамі да інструментаў MCP за адну партыю.
Практычныя нарады па гэтам стварэнню: запрашоўваць аднае засоба за раз (як у Кроку 6). Якщо вам сапраўды трэбая паралельнасць калькаў, завершыце цікл самі — выконайце застаўшыяся tool_calls і аправіце рэзультаты з role: "tool", такі ж патэрн, як у скрыптах Direct vs Gateway нижчэй. Выбор моделі яшчэ ўплывае на надзяйнасць агента; “ідеальны” выбор вчора можа стаць застарэлым пасля наступнага циклу выпуска, таму трэба, каб шлях оркестрацыі быў можлівы да змены.
Крок 7 — пераканацца ў аднараджэнні без участі моделі
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"}}]}}
Гэты кальк разлічае “аднараджэнне не працуе” ад “модель адмовілася вызваць засоб” чы “выканана толькі частка крока”.
Direct vs Gateway: што дае вам проксі
Тэст 1 — чысты MCP, без моделі, без гейтвэю. Падтвердзіце, што сам сервер банку працуе:
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())
Апэксываны вылік і показнік залишку:
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
}
]
}
Тэст 2 — прымітны модель плюс ручная організацыя працы з інструментамі. З’ядзейце з інструментамі MCP, перакладзіце схемы у формат інструментаў OpenAI, вызначыце Azure, запрацавайце інструменты, перадаўце рэзультаты назад — ваша аплікацыя павінна складацца з прыблізна семдзяць пяці ліній коду, без якіх-леба спяльных правілаў чы тоўстароўкі витрачэння:
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())
Прыклад успешнай адпаведзі:
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**
Тэст 3 — тая ж мета за дапамогою LiteLLM. Одна HTTP-запрос; адкрыцце, пераклад, запрацоўкі і наступныы крок выкааняюцца внутры шлюза:
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}
| Проблема | Прыватны спосаб (Тэст 2) | Шлюз (Тэст 3) |
|---|---|---|
| Код аплікацыі | ~75 ліній на аплікацыю | Одна HTTP-запрос |
| Пераклад схемы | Ручны для кожнага сервера MCP | Автаматычны |
| Цыкл інструментаў | Вы самі яго падтрымляеце |
modelmcp_serversМеры абароны, такія як маскаванне электронных пашт, блакіранне номераў картоць і ключоўых слоў, выканаюцца ў функцыі pre_call — **до** адкрыцья чы выканання MCP. Заблакаваны запит ніколі не должен трапіць да «фальшывага» банку. Саме гэтая якосць трэба падтвердзіць, а не толькі тое, што текст у адпаведзі выглядае замаскаваным.
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":{}}]}
Паўтарыце без называння мераў абароны ў тексте для пользователя:
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":{}}]}
Калі меры абароны ўвёсканыя за замовчаннем, у обох варыянтах пашты должны выглядаць як REDACTED. Якщо ўвёсканыя няма, увімкніце іх у адмінистрацыйным інтерфейсе.
Практычныя адказы
1. Чы гэтыя запиты ўсе павінны праходзіць через tools?
Так, калі толькі трэба выкарыстоўваць MCP. Гейтвей не таямна дадае інструменты да запытоў, у якіх яны не паказваны. Якшто не паказаць tools, то будзе простая адпаведь LLM, нават якщо падключаны mock_bank.
2. tool_choice: "required" проты "auto"
Выкорыстоўваеце "required" для простаг тэста вызову інструменту. Кращэ выбіраць "auto" для дыялогавых або багатаэтапных процесаў, таму што "required" структурна не можа сама выдаты заканчальную адпаведь на нейкі запыт.
3. Бяспечнейшы, яснейшы выбар інструменту
Кліянтам таксама неабходна інформацыя праўільных назв і схем абходзов, і нічто не заважае рызыкаванаму абходу, такаму як transfer_funds, працаваць так жа легка, як і get_balance. LiteLLM вялікай меры ўжо праставляе способы для рашэння гэтых проблем:
a) Публікуйце каталог — не дазволяйце інтеграторам аналізаваць код абходоў модэля:
curl -X POST 'http://localhost:4000/mcp/mock_bank' \
-H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' \
-d '{"method":"tools/list"}'
Апублікуйце гэты каталог (או /v1/mcp/tools) у дакументацыі для разработчыкаў або ў інтерфейсе аднарабаткі, каб назвы, апісанні і схемы параметраў былі видны ўсё раней, чым хтось запішае код кліента.
b) Вярніцеся да праваў MCP па ключу/командзе заместо allow_all_keys. Рэгулюйце доступ да mock_bank через Guardrails/MCP → Permission Management, каб кожны ключ бачыў толькі тыя абходы, якія яму неабходны.
c) Разбіленне за рызыкам. Пазначыце transfer_funds як інструмент для запісу/з высокым рызыкам і задаце require_approval: "always", ўпэўнівшыся, што чалавек паставіць падтверджэнне пры пераказе грошаў. Для інструментаў для чытання залейце значэнне "never", якщо гэта адпавядае вашай модэлі абаранення.
d) Забезпечыце абараненне за дапамою сервернага механізма Tool Permission Guardrail, а не кліентскага параметра tool_choice. Корыстувачы керуюць tool_choice; гэта не ёсць межа безпекі. Механізм абаранення гейтвэю не можа быць прыгнуты кліентскай аплікацыяй:
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) Обмежыце аргументы, а не толькі спісы дазволеных інструментаў. Параметр allowed_params можа задаць межы для значэння amount або обмежыць, якія значэння account_id можа выкарыстоўваць той чы іншы інструмент — гэта працоўнае рашэнне, калі не можна проста забарыць выкарыстоўванне інструмента цэлком.
Спрыткуйце каталог інструментаў як API да продукту: публікуйце яго, налаштавайце його дыячнасць па кожнам кліенте, а таксавайце рызыкі чытання і запісу за дапамою правіл на серверае, а не паверяйцеся кожным кліентам.
Што далей
Колі ўжо є базовыя правілы захавання і MCP через LiteLLM, наступныя крокі — це ліміты частоты, максымальная колькасць одночасных запитоў, захаванні вхідных дадзеных спецыяльна для MCP і стрэлкавейшыя правілы выбору інструментаў. Пакуль гэта не будзе налажана, трывайце дэманы локальна, трывайце рызыкаванныя інструменты за затверджэнням і будзьце абераглівы ў тым, што можа вызваць кожны API-клуч.
Калі дыбагаваце нестабільныя роботы агента, запісвайце ID запиту гейтвэю, строку з іменем зарэгістраванага моделю, назвы сервераў MCP, якія былі выкарыстоўваны, а таксама чы гэты параметр tool_choice быў у значэнні auto чы required. Гэтыя чатыры пункты зазвычай дапамагаюць быстрэй выявіць прычыны на кшталт «некоректнага ID моделю», «выключаных інструментаў», «праблем з аднаходжэнням сервераў» чы «частковага выканання паралельных запытоў», чым павторныя ручныя запускі. Рэзервны банк данных трэба викорыстоўваць толькі раз: вычысці і пачаць занова пасля кожных тэстаў пераказаў, южабы залишкі сум на рахунку не выдаваліся за успешныя новыя дэманстрацыі.