MCP — для ИИ-агентов

MCP — для ИИ-агентов

Read-only доступ для LLM-агентов (Claude Code, Claude Desktop, Cursor). Два transport'а: Streamable HTTP (рекомендуется) и stdio через бинарник bcs-mcp.

:::info Что даёт MCP Шесть read-only tools: list_companies, list_bank_accounts, get_balance, list_statements, get_statement, search_operations. Никаких write-операций (платежи / заказ выписок / refresh balance) — read-only по дизайну, на 4+ уровнях защиты (admin UI / admin API / backend middleware / tool registry). :::

Два transport'а

Оба пути дают одни и те же шесть tools, одну и ту же ACL и один и тот же audit. Выбор — про ergonomics клиента, не про функциональность.

  • Streamable HTTP (рекомендуется, без бинарника): Claude Code CLI ≥ 1.0, Cursor ≥ 0.43, любой HTTP-MCP-клиент. Backend сам отвечает по MCP JSON-RPC 2.0 на POST /api/v1/mcp.
  • stdio через bcs-mcp (fallback): Claude Desktop, embedded LLM-tools без HTTP-transport. Локальный бинарник на машине оператора.

Вариант A — Streamable HTTP (рекомендуется)

Claude Code CLI

# Добавить MCP-сервер в Claude Code:
claude mcp add bsc --transport http https://bsc.example.com/api/v1/mcp \
  --header "Authorization: Bearer bcs_live_<TOKEN>"
 
# Проверить, что добавился:
claude mcp list

Файл, в который Claude Code пишет конфиг MCP-серверов: ~/.claude.json (user-scope) или .mcp.json в корне проекта (project-scope). Можно править его руками вместо claude mcp add.

Cursor

~/.cursor/mcp.json (user) или .cursor/mcp.json (project):

{
  "mcpServers": {
    "bsc": {
      "transport": "http",
      "url": "https://bsc.example.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer bcs_live_<TOKEN>"
      }
    }
  }
}

Wire-контракт

POST /api/v1/mcp — JSON-RPC 2.0. Методы: initialize, initialized (notification), ping, tools/list, tools/call. Body ≤ 16 KB.

GET /api/v1/mcp — 405 Method Not Allowed + Allow: POST, DELETE. SSE upgrade не реализован.

DELETE /api/v1/mcp — Терминирование сессии. Требует Mcp-Session-Id. 204 No Content на успех.

Заголовки

ПолеТипОбяз.Описание
AuthorizationheaderBearer bcs_live_…. Тот же MCP-токен с scope_read_statements=true.
Mcp-Session-IdheaderUUIDv4. Server-issued на initialize (см. ниже), client-echoed на всех остальных запросах.
OriginheaderДля браузерных клиентов. Если есть — проверяется по MCP_ALLOWED_ORIGINS env. Пустой = localhost-only default. Server-to-server без Origin всегда проходит.
Content-Typeheaderapplication/json.

Session lifecycle

  1. Клиент шлёт POST /api/v1/mcp с method=initialize. Backend генерит UUIDv4, сохраняет сессию в Redis (mcp:sessions:<uuid>, TTL 8 часов), возвращает header Mcp-Session-Id + результат initialize.
  2. Клиент шлёт tools/list / tools/call / ping с тем же Mcp-Session-Id. Backend валидирует сессию + token-binding + продлевает TTL.
  3. Сессия истекла → JSON-RPC error «Сессия истекла…», клиент сам должен вызвать initialize заново.
  4. Redis недоступен → 503 service_unavailable (fail-closed — без session store нельзя держать lifecycle / audit гарантии).

Manual smoke (curl)

curl --silent --show-error -i -X POST \
     -H "Authorization: Bearer bcs_live_<TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
          "params":{"protocolVersion":"2025-03-26"}}' \
     "https://bsc.example.com/api/v1/mcp"

Возьмите Mcp-Session-Id из response header и продолжайте:

curl --silent --show-error -X POST \
     -H "Authorization: Bearer bcs_live_<TOKEN>" \
     -H "Mcp-Session-Id: <uuid-from-initialize>" \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
     "https://bsc.example.com/api/v1/mcp"
curl --silent --show-error -X POST \
     -H "Authorization: Bearer bcs_live_<TOKEN>" \
     -H "Mcp-Session-Id: <uuid>" \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
          "params":{"name":"list_companies","arguments":{}}}' \
     "https://bsc.example.com/api/v1/mcp"

Вариант B — stdio через bcs-mcp (fallback)

Используйте, если ваш AI-хост не поддерживает HTTP-transport (например, старый Claude Desktop, embedded LLM-tools), либо если нужен offline-deploy без сетевого доступа к backend в момент запуска.

Сборка бинарника

cd backend
go build -o ./bcs-mcp ./cmd/bcs-mcp
install -m 0755 ./bcs-mcp ~/.local/bin/bcs-mcp

Бинарник статический (Go), ~9 MB. Если Go на машине агента не установлен — попросите администратора BCS прислать готовый под вашу ОС.

Claude Code CLI через stdio

claude mcp add bsc \
  --env BCS_API_URL=https://bsc.example.com/api/v1 \
  --env BCS_API_TOKEN=bcs_live_<TOKEN> \
  -- ~/.local/bin/bcs-mcp

Claude Desktop / Cursor через stdio

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) или %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "bsc": {
      "command": "/Users/<you>/.local/bin/bcs-mcp",
      "env": {
        "BCS_API_URL": "https://bsc.example.com/api/v1",
        "BCS_API_TOKEN": "bcs_live_<TOKEN>",
        "BCS_MCP_LOG_DIR": "~/Library/Logs/bcs-mcp"
      }
    }
  }
}

ВАЖНО: два разных адреса

ПолеТипОбяз.Описание
commandstringЛокальный путь к бинарнику на машине AI-хоста (/Users/<you>/.local/bin/bcs-mcp и т.п.). Хост запускает его как подпроцесс и общается через stdio.
env.BCS_API_URLstringСетевой endpoint BCS — бинарник сам туда ходит по HTTPS. Должен заканчиваться на /api/v1.
env.BCS_API_TOKENstringПолное значение MCP-токена (bcs_live_…).

Шесть tools

ToolОписание
list_companiesСписок компаний токена (всегда одна).
list_bank_accountsСписок РС, доступных токену.
get_balanceCached остаток на РС. Банк не вызывается; возвращается последнее закэшированное значение + stale=true/false.
list_statementsИстория заявок на выписки по РС. Видит все выписки на РС (заказанные UI, integration-токеном или сиблинг MCP-токеном), на которые у токена есть ACL.
get_statementКонкретная выписка + операции. Если операций > 500 — возвращает summary + hint про search_operations.
search_operationsПоиск операций по периоду (макс 90 дней): фильтры amount_min/max, ИНН контрагента, free-text. Default лимит 50, макс 100.

У каждого tool — annotation readOnlyHint=true. Каждый успешный response содержит _meta.notice — guardrail против prompt-injection из ответов банка.

Audit и observability

Каждый вызов любого tool пишется в /admin/audit-log с action mcp.<tool_name>, привязкой к api_token_id и mcp_session_id. Каждое action: mcp.initialize, mcp.tools_list, mcp.tools_call, mcp.<tool_name>, mcp.ping, mcp.session.terminate, mcp.notification.<method>.

Ошибки HTTP-transport'а

HTTPcodeКогда возникает
401invalid_tokenТокен невалиден / истёк / отозван.
403forbidden_purposeИспользован integration-токен.
403forbidden_scopeУ токена нет scope_read_statements=true.
403forbidden_originBrowser-клиент с Origin вне MCP_ALLOWED_ORIGINS.
405method_not_allowedGET / другие методы кроме POST + DELETE.
413payload_too_largeBody > 16 KB.
429rate_limitedПревышен per-token лимит (default 300/min для MCP).
503service_unavailableRedis session store недоступен (fail-closed).

JSON-RPC-уровень (HTTP 200, ошибка внутри body)

codenameКогда возникает
-32700Parse errorНевалидный JSON в body.
-32600Invalid RequestНет/неверный jsonrpc, нет Mcp-Session-Id, сессия истекла, сессия другого токена.
-32601Method not foundUnknown JSON-RPC method или unknown tool name (включая попытку вызвать send_payment и др. write-tools).
-32602Invalid paramsBad params в tools/call.
-32603Internal errorВнутренняя ошибка сервера.

Troubleshooting

СимптомГдеЧто проверить
Claude Code: MCP server failed to startHTTPПроверьте URL: curl -I <URL>/healthz → 200. Проверьте, что в claude mcp list правильный --transport http. Смотрите audit mcp.initialize — если строки нет, запрос даже не дошёл (вероятно, network / firewall / DNS).
Claude не видит MCP-серверstdioExit-code bcs-mcp из логов хоста; проверьте env в config; self-check log в BCS_MCP_LOG_DIR.
JSON-RPC: "Сессия истекла"HTTPTTL 8 часов истёк, или Redis перезапустили. Хост должен сам перевызвать initialize. Если хост этого не делает — это баг хоста.
HTTP 403 forbidden_originHTTPBrowser-клиент с Origin вне allowlist. Либо добавить origin в MCP_ALLOWED_ORIGINS env backend'а, либо использовать CLI / server-to-server (без Origin header).
JSON-RPC: tool вернул {"isError":true, ...rate_limited}bothPer-token rate-limit (default 300/min для MCP). Подождите, потом повторите. Если систематически — поднимите rate_limit_per_minute у токена.
get_statement returned not_readybothВыписка ещё processing — банк не вернул данные. Дождитесь (worker опрашивает банк каждые 30s) или закажите вручную через integration-токен и UI.

MCP НЕ может (по дизайну)

  • Отправить платёж (send_payment) — никаких write tools в registry.
  • Заказать новую выписку (create_statement) — используйте REST integration-токен.
  • Обновить баланс из банка (refresh_balance) — get_balance возвращает только cached значение.
  • Изменить любой другой data — read-only по архитектуре.

Если вам нужна write-операция в MCP-агенте — выпустите отдельный integration-токен и попросите агента ходить в REST endpoint'ы (POST /integration/…) не через MCP, а через обычный HTTP-tool.