Files
crank/docs/mcp-interface.md
bsodfather 63f8ee333f
CI / Rust Checks (push) Successful in 12m5s
CI / UI Checks (push) Successful in 9s
CI / Deployment Manifests (push) Successful in 6s
CI / Frontend E2E (push) Failing after 30s
CI / Deploy (push) Has been skipped
наблюдаемость: измерять бюджет каталога MCP
2026-07-21 01:59:09 +03:00

11 KiB
Raw Permalink Blame History

MCP-интерфейс

Crank публикует REST-операции как MCP-инструменты через Streamable HTTP.

Endpoint агента

Каждый агент имеет собственный MCP endpoint:

/mcp/v1/{workspace_slug}/{agent_slug}

Для demo seed:

/mcp/v1/default/currency-rates

Полный URL зависит от вашего домена:

https://crank.example.com/mcp/v1/default/currency-rates

Авторизация

MCP-клиент должен передавать API-ключ агента:

Authorization: Bearer <agent_api_key>

Ключ выдается в разделе API ключи. Полное значение показывается только один раз при создании.

Для операций с подтверждением человеком нужен отдельный ключ подтверждения. Его тоже выдают в разделе API ключи, но в режиме Подтверждения. Такой ключ нельзя передавать LLM или MCP-клиенту. Он нужен только вашему внешнему интерфейсу, где пользователь нажимает «Подтвердить» или «Отклонить».

Поддерживаемые методы

MCP methods:

  • initialize;
  • notifications/initialized;
  • ping;
  • tools/list;
  • tools/call.

HTTP transport:

  • POST /mcp/v1/{workspace_slug}/{agent_slug} - JSON-RPC запросы;
  • GET /mcp/v1/{workspace_slug}/{agent_slug} - server-to-client stream;
  • DELETE /mcp/v1/{workspace_slug}/{agent_slug} - закрытие сессии.

Обязательные заголовки

Для POST:

Authorization: Bearer <agent_api_key>
Content-Type: application/json
Accept: application/json, text/event-stream

Для GET:

Authorization: Bearer <agent_api_key>
Accept: text/event-stream
MCP-Session-Id: <session_id>

После initialize сервер возвращает заголовок:

MCP-Session-Id: <session_id>

Если клиент передает MCP-Protocol-Version, он должен совпадать с версией, согласованной при инициализации.

Пример initialize

curl -i https://crank.example.com/mcp/v1/default/currency-rates \
  -H 'Authorization: Bearer <agent_api_key>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": {
        "name": "curl",
        "version": "1.0.0"
      }
    }
  }'

Сохраните MCP-Session-Id из ответа.

Пример tools/list

curl https://crank.example.com/mcp/v1/default/currency-rates \
  -H 'Authorization: Bearer <agent_api_key>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'MCP-Session-Id: <session_id>' \
  --data '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
  }'

Ожидаемый результат для demo seed содержит инструмент:

frankfurter_latest_rate

Пример tools/call

curl https://crank.example.com/mcp/v1/default/currency-rates \
  -H 'Authorization: Bearer <agent_api_key>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'MCP-Session-Id: <session_id>' \
  --data '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "frankfurter_latest_rate",
      "arguments": {
        "base": "USD",
        "quote": "EUR"
      }
    }
  }'

Crank выполнит REST-запрос:

GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=EUR

Операции с подтверждением человеком

Если в мастере операции включено Подтверждение человеком, первый tools/call не выполняет REST-запрос сразу. Вместо этого Crank создает ожидающий запрос на подтверждение и возвращает MCP-клиенту структурированный результат:

{
  "status": "approval_required",
  "approval_id": "approval_...",
  "approval_url": "/v1/default/sales/approvals/approval_...",
  "approve": {
    "method": "POST",
    "url": "/v1/default/sales/approvals/approval_.../approve",
    "body": { "approve": "yes" }
  },
  "deny": {
    "method": "POST",
    "url": "/v1/default/sales/approvals/approval_.../deny",
    "body": { "approve": "no" }
  }
}

approval_url в ответе является путем на MCP-сервере. Если вы публикуете MCP через префикс /mcp, внешний URL будет начинаться с /mcp/v1/....

Внешний интерфейс подтверждения работает отдельным ключом подтверждения:

curl https://crank.example.com/mcp/v1/default/sales/approvals \
  -H 'Authorization: Bearer <approval_api_key>'

Статус конкретного запроса:

curl https://crank.example.com/mcp/v1/default/sales/approvals/<approval_id> \
  -H 'Authorization: Bearer <approval_api_key>'

Подтверждение:

curl https://crank.example.com/mcp/v1/default/sales/approvals/<approval_id>/approve \
  -H 'Authorization: Bearer <approval_api_key>' \
  -H 'Content-Type: application/json' \
  --data '{ "approve": "yes", "note": "Пользователь подтвердил действие" }'

После подтверждения Crank выполняет исходный REST-запрос с тем payload, который был сохранен при первом tools/call. Если запрос прошел успешно, заявка получает статус completed, а результат сохраняется в response_payload. Если upstream вернул ошибку, заявка получает статус failed, а в response_payload сохраняется код и текст ошибки.

Перед обращением к upstream заявка атомарно переходит в статус executing. Если mcp-server завершился во время выполнения, другой рабочий цикл повторно захватит заявку после истечения аренды. Для изменяющих операций рекомендуется настроить execution_config.idempotency и передавать поддерживаемый upstream заголовок идемпотентности: универсальный HTTP-клиент не может гарантировать ровно одно внешнее побочное действие при падении процесса между ответом upstream и записью результата.

Повторный tools/call с теми же агентом, операцией, версией и JSON-аргументами возвращает уже существующую активную заявку вместо создания дубликата.

Отклонение:

curl https://crank.example.com/mcp/v1/default/sales/approvals/<approval_id>/deny \
  -H 'Authorization: Bearer <approval_api_key>' \
  -H 'Content-Type: application/json' \
  --data '{ "approve": "no", "note": "Пользователь отклонил действие" }'

Ключ MCP-клиента не подходит для этих endpoints. Ключ подтверждения, наоборот, не подходит для initialize, tools/list и tools/call.

Текущая реализация возвращает approval_required сразу и не держит исходный tools/call открытым до решения пользователя. Поэтому внешний интерфейс может восстановить результат через GET /approvals/<approval_id>. Долгое ожидание через Streamable HTTP/SSE запланировано отдельно.

Как формируется каталог инструментов

MCP-клиент видит только опубликованные операции, которые привязаны к опубликованному агенту.

Черновики операций не попадают в MCP-каталог. Если два пользователя работают в одном workspace, один может редактировать черновик, а второй публиковать агента. В опубликованный каталог попадут только опубликованные версии операций.

При каждом обновлении каталога Crank анализирует фактические определения tools/list и записывает в структурированный журнал:

  • число инструментов;
  • размер компактного JSON в байтах;
  • оценочный объём контекста в токенах;
  • размер крупнейшего инструмента;
  • рекомендуемый предел и признак его превышения;
  • число предупреждений качества каталога.

Оценка токенов равна округлённому вверх отношению размера UTF-8 к трём. Она нужна для стабильного сравнения ревизий каталога и не заменяет точный токенизатор конкретной модели.

Обновление каталога

mcp-server периодически обновляет опубликованный каталог. Интервал задается:

CRANK_MCP_REFRESH_MS=5000

После публикации операции или агента подождите один интервал обновления или перезапустите mcp-server.

Ошибки

Частые причины ошибок:

  • 401 Unauthorized - отсутствует или неверен API-ключ агента.
  • 403 Forbidden - ключ не имеет нужного доступа.
  • 404 Not Found - workspace, агент или инструмент не опубликованы.
  • 400 Bad Request - неверный MCP-заголовок, session id или JSON-RPC payload.
  • 429 Too Many Requests - сработал rate limit.

Runtime-ошибки инструмента возвращаются как структурированный MCP tool error с кодом, сообщением и request_id.