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 на успех.
Заголовки
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
Authorization | header | ✓ | Bearer bcs_live_…. Тот же MCP-токен с scope_read_statements=true. |
Mcp-Session-Id | header | UUIDv4. Server-issued на initialize (см. ниже), client-echoed на всех остальных запросах. | |
Origin | header | Для браузерных клиентов. Если есть — проверяется по MCP_ALLOWED_ORIGINS env. Пустой = localhost-only default. Server-to-server без Origin всегда проходит. | |
Content-Type | header | ✓ | application/json. |
Session lifecycle
- Клиент шлёт
POST /api/v1/mcpсmethod=initialize. Backend генерит UUIDv4, сохраняет сессию в Redis (mcp:sessions:<uuid>, TTL 8 часов), возвращает headerMcp-Session-Id+ результат initialize. - Клиент шлёт
tools/list/tools/call/pingс тем жеMcp-Session-Id. Backend валидирует сессию + token-binding + продлевает TTL. - Сессия истекла → JSON-RPC error «Сессия истекла…», клиент сам должен вызвать
initializeзаново. - 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-mcpClaude 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"
}
}
}
}ВАЖНО: два разных адреса
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
command | string | ✓ | Локальный путь к бинарнику на машине AI-хоста (/Users/<you>/.local/bin/bcs-mcp и т.п.). Хост запускает его как подпроцесс и общается через stdio. |
env.BCS_API_URL | string | ✓ | Сетевой endpoint BCS — бинарник сам туда ходит по HTTPS. Должен заканчиваться на /api/v1. |
env.BCS_API_TOKEN | string | ✓ | Полное значение MCP-токена (bcs_live_…). |
Шесть tools
| Tool | Описание |
|---|---|
list_companies | Список компаний токена (всегда одна). |
list_bank_accounts | Список РС, доступных токену. |
get_balance | Cached остаток на РС. Банк не вызывается; возвращается последнее закэшированное значение + 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'а
| HTTP | code | Когда возникает |
|---|---|---|
| 401 | invalid_token | Токен невалиден / истёк / отозван. |
| 403 | forbidden_purpose | Использован integration-токен. |
| 403 | forbidden_scope | У токена нет scope_read_statements=true. |
| 403 | forbidden_origin | Browser-клиент с Origin вне MCP_ALLOWED_ORIGINS. |
| 405 | method_not_allowed | GET / другие методы кроме POST + DELETE. |
| 413 | payload_too_large | Body > 16 KB. |
| 429 | rate_limited | Превышен per-token лимит (default 300/min для MCP). |
| 503 | service_unavailable | Redis session store недоступен (fail-closed). |
JSON-RPC-уровень (HTTP 200, ошибка внутри body)
| code | name | Когда возникает |
|---|---|---|
| -32700 | Parse error | Невалидный JSON в body. |
| -32600 | Invalid Request | Нет/неверный jsonrpc, нет Mcp-Session-Id, сессия истекла, сессия другого токена. |
| -32601 | Method not found | Unknown JSON-RPC method или unknown tool name (включая попытку вызвать send_payment и др. write-tools). |
| -32602 | Invalid params | Bad params в tools/call. |
| -32603 | Internal error | Внутренняя ошибка сервера. |
Troubleshooting
| Симптом | Где | Что проверить |
|---|---|---|
| Claude Code: MCP server failed to start | HTTP | Проверьте URL: curl -I <URL>/healthz → 200. Проверьте, что в claude mcp list правильный --transport http. Смотрите audit mcp.initialize — если строки нет, запрос даже не дошёл (вероятно, network / firewall / DNS). |
| Claude не видит MCP-сервер | stdio | Exit-code bcs-mcp из логов хоста; проверьте env в config; self-check log в BCS_MCP_LOG_DIR. |
| JSON-RPC: "Сессия истекла" | HTTP | TTL 8 часов истёк, или Redis перезапустили. Хост должен сам перевызвать initialize. Если хост этого не делает — это баг хоста. |
| HTTP 403 forbidden_origin | HTTP | Browser-клиент с Origin вне allowlist. Либо добавить origin в MCP_ALLOWED_ORIGINS env backend'а, либо использовать CLI / server-to-server (без Origin header). |
JSON-RPC: tool вернул {"isError":true, ...rate_limited} | both | Per-token rate-limit (default 300/min для MCP). Подождите, потом повторите. Если систематически — поднимите rate_limit_per_minute у токена. |
| get_statement returned not_ready | both | Выписка ещё 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.