Галоўная / Артыкулы

Перадача засоба MCP праз LiteLLM: практычныя нарады з імітаваным банкавым шлюзам

Адынстаець MCP інструментаў праз гэйтвей LitleLLM: зарэгіструйце мак-банк, які можа працаваць праз HTTP, парагляньце ціклы ручна напісаных інструментаў з аднам запітам, апраўдайце, што механізмы захавання працуюць раней за MCP.

Выдавец: REACTAPP.TOP16 хв чытання3709 слоў

Крыніцы: LiteLLM + MCP: Building and Testing a Tool-Calling Gateway

Фота адміністратара 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; рэзультат вяртаецца праз шлюз; модэль продовжае працу або зноў прасі. Запыты, якія включаюць адзін інструмент, автаматычна завершаюць гэты цікл; запыты, якія включаюць калькі інструментоў, можаць выконвацца часткова автаматычна толькі у працаванай тут версіі.
  • Захоўнікі прыўязваюцца en двух месцах. Функцыя pre_call пераглядае необработаны пакет дзяўароў пры перадачы. Функцыя pre_mcp_call пераглядае аргументы інструмента бліжэй да стороны MCP. У дыяграмах часта паказваецца адна коробка; на самай праўда перэхоплення вяршацца на разных этапах.
  • Коробка з пунктаванымі лініямі “tools” — гэта спіс у файле 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 Автаматычны
    Цыкл інструментаў Вы самі яго падтрымляеце
    Внутршняе Замена прадаўця Перапісваўчык вызоў SDK Змена рэчы model Автанація да калькі MCP-сэрвераў Індывідуальна для кожнага сэрвера Центральная настройка mcp_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 мадэлю», «выключаных інструментаў», «праблем з аднаходжэнням сервераў» чы «частковага выканання паралельных запытоў», чым павторныя ручныя запускі. Рэзервны банк даных трэба вікорыстоўваць толькі раз: вычысці і пачаць занова пасля кожных тэстаў пераказаў, южабы залішкі сум на рахунку не выдаваліся за успешныя новыя дэманстрацыі.