MCP-сервер - адаптер возможностей, а не доверенный агент
Model Context Protocol описывает, как host обнаруживает и вызывает tools, получает resources и предлагает prompts. Решение о доступе, правах и выполнении остаётся за приложением. Модель формирует предложение вызова, но сервер обязан проверить identity, scope, arguments и бизнес-инварианты.
Production-готовность определяется не количеством интеграций, а тем, можно ли объяснить каждый доступ и безопасно пережить ошибочный или вредоносный вызов.
Разделите ответственность host, client и server
| Компонент | Отвечает за | Не должен |
|---|---|---|
| Host | UX, модель, consent, выбор серверов | Автоматически доверять каталогу |
| MCP client | Protocol lifecycle и transport | Расширять scopes |
| MCP server | Capabilities, authz, вызов backend | Принимать identity от модели |
| Backend API | Domain rules и source of truth | Доверять MCP без проверки |
| Authorization server | Выдача tokens и consent | Выдавать token не той audience |
Выберите транспорт по границе доверия
Stdio подходит локальному server-процессу, который запускает host. Он не использует MCP HTTP authorization flow; credentials берутся из защищённого окружения процесса. Remote Streamable HTTP подходит общему cloud-сервису и требует полноценной сетевой защиты и авторизации.
| Критерий | stdio | Streamable HTTP |
|---|---|---|
| Развёртывание | На машине пользователя | Удалённый endpoint |
| Auth | OS/process boundary | OAuth и server policy |
| Масштабирование | Процесс на client | Общий fleet |
| Local files | Естественный вариант | Нужен upload/connector |
| SaaS API | Сложнее distribution | Единый deployment |
Начните с threat model и data flow
Нарисуйте путь от user intent до side effect: какой текст видит модель, какой token получает server, какие upstream APIs вызываются, где сохраняются prompts, results и traces. Отметьте trust boundaries и data classifications.
- Вредоносный пользовательский prompt.
- Indirect injection в resource.
- Скомпрометированный MCP server.
- Подмена server URL.
- Кража или token passthrough.
- Cross-tenant object ID.
- Повтор write-вызова.
- Утечка через error и trace.
- Resource exhaustion.
- Supply-chain изменение SDK.
OAuth защищает пользователя только при правильной audience
Для удалённого HTTP-сервера следуйте актуальной MCP authorization specification. Access token должен быть выпущен именно для MCP resource; server проверяет issuer, audience/resource, signature, expiry и scopes. Передача upstream token через MCP запрещена: confused deputy и утечка полномочий становятся вероятными.
HTTPS only Discover protected resource and authorization metadata Authorization Code + PKCE for public clients Validate issuer, signature, audience/resource, expiry and scope Use short-lived access tokens Rotate refresh tokens where required Never accept tokens issued for another resource Never pass the MCP access token through to an upstream API
User delegation и machine identity - разные потоки
Если host действует от имени человека, consent и scopes привязываются к пользователю и tenant. Для server-to-server задачи применяется machine identity без имитации пользователя. Не подменяйте один режим другим ради простоты.
Delegated
User consent, actor и ограниченный scope.
Machine
Workload identity и service policy.
Runtime
Повторная авторизация каждого объекта.
Scopes должны отражать узкие операции
Scope вида all делает consent бессодержательным. Разделяйте чтение, создание proposal и выполнение: orders:read, refunds:propose, refunds:execute. Каталог capabilities для запроса формируется как пересечение scopes, tenant policy и текущего состояния.
- Scope не содержит произвольный resource ID.
- Object-level authorization выполняется после scope.
- Admin capabilities находятся в отдельном consent flow.
- Изменение scopes требует нового token.
- Denied capability не раскрывается через подробную ошибку.
Tools, resources и prompts имеют разную модель контроля
Спецификация описывает tools как model-controlled, resources как application-controlled, prompts как user-controlled. Это полезная UX-модель, но host может вводить дополнительные подтверждения. Не выдавайте write-операцию как resource только для обхода approval.
| Примитив | Назначение | Ключевой контроль |
|---|---|---|
| Tool | Действие или запрос | Schema, authz, approval |
| Resource | Контекстные данные | URI scope, access, size |
| Prompt | Пользовательский шаблон | Явный выбор и provenance |
Каталог capabilities фильтруется по текущим правам
Server может возвращать только tools, разрешённые представленным credentials. Это уменьшает поверхность выбора модели, однако invoke endpoint всё равно повторно проверяет доступ: каталог мог измениться, token - истечь, объект - перейти в другое состояние.
visible_capabilities = intersection of: server deployment allowlist ∩ token scopes ∩ tenant entitlements ∩ actor roles ∩ environment policy ∩ current risk tier Invocation repeats authorization and business validation. Visibility is an optimization, never the final security gate.
Tool contract должен быть узким и строгим
Имя и description участвуют в выборе модели. Опишите действие, условия использования и запреты. Input schema ограничивает типы, enums, длины и число элементов; output schema возвращает только нужные поля. Identity и tenant server получает из trusted context.
| Опасно | Безопаснее | Контроль |
|---|---|---|
| execute_sql | get_invoice_status | Tenant filter server-side |
| http_request | get_shipping_quote | Fixed upstream |
| manage_order | propose_order_update | Typed diff |
| send_message | send_approved_message | Recipient approval |
| delete_file | archive_selected_file | Recoverable action |
Annotations - подсказка, а не security boundary
Признак read-only помогает host выбрать approval UX, но злонамеренный или ошибочный server может разметить tool неверно. Host применяет allowlist и trust policy, а backend оценивает фактический side effect. Версия каталога фиксируется в trace.
- Проверяйте annotations при review.
- Не отключайте approval только по self-declared hint для неизвестного server.
- Разделяйте trusted internal и third-party catalogs.
- Alert на неожиданное изменение tools/list.
- Показывайте пользователю server, tool и arguments.
Write-операции проходят preview, approval и execute
Сначала tool создаёт immutable proposal с diff, последствиями и expiry. Host показывает его пользователю. Execute принимает proposal ID и одноразовое approval, а не заново сгенерированные arguments.
Preview
Точные ресурсы, diff, стоимость и риск.
Approval
Actor, hash, scope и expiry.
Execute
Idempotent side effect и audit event.
Prompt injection приходит через любой content block
Resource, tool result и error message могут содержать текст «игнорируй правила». Server возвращает это как data с provenance, а host не повышает до system instruction. Даже успешная injection не должна дать дополнительные tools или права.
- Instructions отделены от content.
- Third-party text помечен как untrusted.
- Secrets не находятся в model context.
- Tool allowlist задаёт host.
- Arguments валидирует server.
- Side effect повторно авторизуется.
- Output ограничен и sanitised для UI.
- Indirect injection входит в evals.
Upstream credentials остаются за server
MCP access token подтверждает доступ к MCP resource, а не ко всем backend APIs. Server использует собственную workload identity либо отдельный delegated token exchange, если он спроектирован для upstream. Credentials не возвращаются в tool result, logs или model-visible errors.
Защитите server от resource exhaustion
Один model loop способен генерировать много calls. Вводите per-user и per-tenant concurrency, rate limits, request/body caps, deadlines, pagination, output limits и bounded queues. Cancel распространяется в upstream.
| Лимит | Зачем | Ответ |
|---|---|---|
| Request size | Memory/parse protection | Typed invalid request |
| Concurrency | Noisy neighbor | Queue или retry-after |
| Tool deadline | Зависшие backend calls | Cancelled/timeout |
| Result size | Context и exfiltration | Pagination/artifact ref |
| Daily budget | Runaway agents | Policy denied |
Повторы безопасны только с idempotency
Transport retry, host reconnect или model retry могут повторить вызов. Для write-tool client или host передаёт idempotency key, server связывает его с actor, operation и canonical arguments и возвращает прежний результат. Ключ не переиспользуется с другими parameters.
Retry only transient failures within one deadline. Read tools: bounded retry with backoff and jitter. Write tools: retry only with idempotency key. Validation, authorization and policy errors: no retry. Unknown outcome: reconcile by operation ID before repeating. Respect cancellation and Retry-After.
Ошибки типизируются и не раскрывают лишнее
Возвращайте stable code, retryable, safe message и correlation ID. Не включайте stack trace, SQL, token, внутренний URL или существование чужого объекта. Для unauthorized и not found часто используют одинаковый безопасный ответ.
- INVALID_ARGUMENT - исправить input.
- NOT_FOUND_OR_NOT_ALLOWED - не раскрывать ownership.
- APPROVAL_REQUIRED - вернуть proposal reference.
- CONFLICT - перечитать state.
- RATE_LIMITED - Retry-After.
- TEMPORARY_UNAVAILABLE - ограниченный retry.
- INTERNAL - correlation ID и server log.
Trace связывает protocol и бизнес-действие
Сохраняйте request/trace ID, server и protocol version, client identity, actor/tenant, capability и schema version, arguments hash, policy decisions, approval, upstream call, latency, status и idempotency outcome. Payload редактируется по data class.
trace_id, mcp_request_id protocol_version, server_version, client_id actor_id, tenant_id, token_issuer, scopes tool_name, tool_schema_version, arguments_hash policy_decisions, approval_id upstream_operation_id, idempotency_key latency, result_size, error_code side_effect_status
Compatibility проверяется как матрица
Клиенты обновляют protocol и поддерживают capabilities с разной скоростью. Зафиксируйте минимальную и максимальную версии, тестируйте initialize, discovery, pagination, cancellation, auth challenges, structured content и ошибки на поддерживаемых hosts. Не включайте draft-функцию без negotiation.
| Проверка | Ожидаемый результат |
|---|---|
| Unknown capability | Graceful negotiation |
| Old client | Поддержанный fallback или clear error |
| Catalog change | Корректное notification/refresh |
| Reconnect | Нет двойного side effect |
| Expired token | Auth challenge без утечки |
| Malformed content | Protocol error, server жив |
Evals охватывают protocol, security и task success
Unit tests schema недостаточно. Запускайте реальные client sessions с benign, edge и adversarial задачами. Проверяйте не только ответ модели, но и вызванный tool, arguments, права и side effects.
- Корректные initialize и discovery.
- Scope-filtered catalog.
- Token другой audience отклоняется.
- Cross-tenant ID не раскрывается.
- Injection из resource и tool output.
- Write без approval отклоняется.
- Повтор write идемпотентен.
- Timeout и cancel доходят upstream.
- Oversized input/output ограничен.
- Errors и traces не содержат secrets.
- Старый client получает ожидаемый fallback.
- Task success не хуже baseline.
Пошаговый rollout MCP-сервера
- Зафиксируйте threat model и trust boundaries.
- Начните с одного read-only tool.
- Включите OAuth, scopes и object authz.
- Проверьте через MCP Inspector и contract tests.
- Запустите internal allowlist.
- Добавьте traces, quotas и alerts.
- Проведите adversarial evals.
- Откройте ограниченной группе клиентов.
- Добавляйте write только через proposal и approval.
- Держите kill switch на server и отдельные tools.
Production-чек-лист MCP-сервера
- Транспорт соответствует trust boundary.
- HTTP endpoints используют HTTPS.
- OAuth discovery и PKCE проверены.
- Server валидирует token audience/resource.
- Token passthrough отсутствует.
- Scopes узкие и понятны в consent.
- Capabilities фильтруются, invoke авторизуется заново.
- Tools имеют строгие input/output schemas.
- Read, propose и execute разделены.
- Write требует scoped approval и idempotency.
- Prompt injection не повышает права.
- Deadlines, quotas, caps и cancel работают.
- Errors безопасны, traces tenant-aware.
- Compatibility matrix зелёная.
- Security и task evals включены в CI.
Что такое MCP-сервер?
Когда выбирать stdio, а когда Streamable HTTP?
Нужен ли OAuth локальному stdio MCP-серверу?
Почему нельзя передавать OAuth token дальше в upstream API?
Можно ли доверять readOnlyHint у tool?
Как подтверждать опасные MCP-вызовы?
Как защитить MCP-server от prompt injection?
Какие тесты обязательны перед production?
- Model Context Protocol - Current specification
- Model Context Protocol - Authorization
- Model Context Protocol - Security best practices
- Model Context Protocol - Tools
- Model Context Protocol - Transports
- Model Context Protocol - Official TypeScript SDK
- OpenAI API - Remote MCP servers
- OWASP - MCP Security Cheat Sheet