# Как тестировать MCP-сервер: contracts, failures и CI

> Тестирование MCP-сервера beyond Inspector: schemas, protocol versions, auth, timeouts, cancellation, retries, failure injection, conformance и agent behavior.
> Author: Roman Belov · Published: 2026-08-21 · Source: https://futurecraft.pro/ru/blog/mcp-server-testing/

MCP-сервер может пройти демо и сломаться на первом реальном workflow. Tool виден в
`tools/list`, один happy path вернул JSON, Inspector показывает зелёный результат. Это
ничего не говорит о wrong-audience token, зависшем upstream, повторе write после
disconnect или выборе неверного tool агентом.

Тестируйте сервер одновременно как:

1. обычное приложение с business logic;
2. реализацию versioned protocol;
3. поверхность capabilities для вероятностного caller.

Здесь предполагается знание
[архитектуры MCP](/ru/blog/mcp-servers-explained/). Deployment и recovery patterns
описаны в [production-гайде](/ru/blog/mcp-production-custom-servers/), а access control
и adversarial cases — в [гайде по безопасности MCP](/ru/blog/mcp-security-guide/).

## Нужен test stack, а не один инструмент

Каждый слой находит свой класс ошибок:

| Слой | Что запускается | Какие ошибки находит |
| --- | --- | --- |
| Handler unit test | Tool function и fake dependencies | Validation, policy, mapping, business rules |
| Contract test | Настоящие MCP client и server | Advertised schemas, error shapes, serialization |
| Transport test | Spawned stdio process или HTTP endpoint | Framing, auth, cancellation, lifecycle, cleanup |
| Conformance test | Официальный harness | Нарушения конкретной MCP specification |
| Agent scenario | Реальный host/model с controlled fixtures | Tool selection, sequence, recovery, task completion |

Не заменяйте первые четыре слоя model evaluation. Детерминированная ошибка требует
детерминированного теста. Но unit test не покажет, что два похожих descriptions
заставляют модель выбирать destructive tool.

## Начните с contract inventory

До написания cases перечислите всю поверхность:

- tools, resources, prompts и advertised capabilities;
- transports и protocol revisions;
- authentication modes и scopes;
- external APIs, databases, filesystems, queues и secret stores;
- state handles, subscriptions и multi-round-trip input;
- limits: request size, result size, concurrency, rate, deadline и retention.

Соберите из списка небольшую matrix. У сервера с двумя transports, двумя protocol eras
и тремя identity roles уже двенадцать осмысленных boundary combinations. Не каждый
business case нужен во всех комбинациях, но smoke path и version-specific behavior
нужны для каждой заявленной комбинации.

## `tools/list` — публичный API

Tool metadata — не декор. Это machine-readable contract и часть routing context модели.
Небрежное изменение description способно поменять выбор tool без изменений handler.

На каждом release проверяйте:

- tool names и детерминированный порядок;
- descriptions с действием, constraints и side effects;
- required и optional arguments;
- поведение `additionalProperties`;
- formats, enums, bounds и maximum lengths;
- input schema dialect и корректный `$ref` resolution;
- output schema и shape structured result;
- annotations с учётом того, что это недоверенные hints;
- tool set для каждого authorization level.

Перед snapshot нормализуйте response: удалите timestamps, generated IDs и другую
volatile metadata. Semantic changes должны проходить human review:

```json
{
  "name": "close_issue",
  "description": "Close one issue after explicit user confirmation.",
  "inputSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "issue_id": { "type": "string", "pattern": "^iss_[a-z0-9]+$" },
      "reason": { "type": "string", "minLength": 3, "maxLength": 500 }
    },
    "required": ["issue_id", "reason"]
  }
}
```

Затем убедитесь, что implementation исполняет тот же contract. Красивая schema при
handler, который принимает лишние fields, остаётся документацией, а не validation.

Актуальная [tools specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
требует valid JSON Schema, разделяет protocol errors и tool execution errors и
рекомендует client валидировать results до передачи в LLM.

## Сначала тестируйте handler без protocol

Tool handler должен вызываться с injected dependencies: repository, upstream client,
clock, ID generator, authorization context и audit sink. Тогда самые быстрые tests
остаются обычными application tests.

Для write tool нужны как минимум:

```text
valid request
missing or malformed field
unknown field
object not found
object belongs to another tenant
caller lacks permission
invalid state transition
upstream timeout
upstream rate limit
duplicate idempotency key
audit sink unavailable
```

Проверяйте side effects, а не только returned text. Получил ли repository ровно один
write? Tenant выведен из verified identity или взят из model argument? Upstream request
действительно aborted? Блокирует ли отказ audit sink high-risk mutation или система
безопасно деградирует согласно policy?

Используйте seeded clocks и IDs. Не делайте `sleep` в unit tests: fake clock мгновенно
пересекает deadline или expiry.

## Разделяйте protocol errors и tool errors

**Protocol errors** описывают malformed JSON-RPC, unknown method или request, который
не соответствует protocol shape. Это JSON-RPC errors.

**Tool execution errors** описывают invalid business input, API failure или forbidden
state transition, который модель может исправить. Это tool result с `isError: true` и
полезным, ограниченным feedback.

Проверяйте оба варианта. Ошибка не должна раскрывать stack trace, SQL, internal URLs,
tokens или полный upstream body. Полезный error сообщает, что caller может изменить,
не раскрывая внутренности системы. Не превращайте каждый failure в successful text
вроде «Something went wrong»: host теряет signal для retry и recovery.

## Запускайте настоящий transport

In-process tests пропускают границу, где живёт много MCP-багов.

### stdio

Запускайте собранный artifact как child process и проверяйте:

- stdout содержит только framed MCP messages, diagnostics идут в stderr;
- split и back-to-back messages корректно парсятся;
- malformed input не портит следующий request;
- закрытие stdin завершает process в shutdown budget;
- SIGTERM отменяет или дренирует in-flight work согласно policy;
- process не наследует посторонние secrets.

Хотя бы один test запускайте из directory с пробелами и с минимальным environment. Так
обнаруживаются path и implicit-shell assumptions с машины разработчика.

### Streamable HTTP

Тестируйте deployed HTTP handler, а не только tool function:

- method и content-type handling;
- authorization на каждом request;
- token issuer, audience, expiry и scope failures;
- origin и host validation, если этого требует deployment;
- body и response size limits;
- concurrent calls разных principals;
- request-scoped SSE response и cleanup после disconnect;
- reverse-proxy timeouts, forwarded metadata и CORS.

В текущей transport model каждое HTTP message отправляется POST-запросом, а response
приходит JSON-объектом или request-scoped SSE stream. Этот stream участвует в
cancellation, поэтому buffering proxy или потерянный disconnect меняют поведение
сервера.

## Проверяйте каждую заявленную protocol era

После revision июля 2026 года это обязательно. Два действующих behavior families
серьёзно отличаются:

| Behavior | До 2025-11-25 включительно | 2026-07-28 |
| --- | --- | --- |
| Connection start | `initialize` handshake | `server/discover`, без initialize |
| Client metadata | Session-scoped | `_meta` в каждом request |
| Streamable HTTP cancellation | `notifications/cancelled` | Закрытие request stream |
| Change events | Unsolicited notifications | `subscriptions/listen` |
| Liveness `ping` | Определён | Не определён |

Официальный TypeScript SDK называет их legacy и modern eras в
[гайде по protocol versions](https://ts.sdk.modelcontextprotocol.io/v2/protocol-versions).
Нельзя прогнать один wire version и объявить compatibility с обоими.

Для каждой revision проверяйте negotiation, required metadata, cancellation, errors и
version-specific features. Нужен и refusal path: client с pinned неподдерживаемой
revision должен получить явную ошибку, а не перейти в частично рабочий режим.

## Timeouts и cancellation должны быть наблюдаемыми

Сделайте fake upstream с управляемым поведением:

```text
respond immediately
delay before headers
stream one chunk, then hang
return malformed JSON
close mid-response
ignore cancellation
return 429, 500, or 503
```

Для каждого slow path проверяйте четыре вещи:

1. caller получает bounded result до deadline;
2. cancellation доходит до handler через механизм нужных transport и version;
3. upstream request и занятые ресурсы освобождаются;
4. telemetry различает timeout, cancellation и upstream failure.

Progress notification не должна бесконечно продлевать hard deadline. Отдельно задайте
budgets на queueing, upstream work, serialization и total task time. Тестируйте boundary
values, а не только заведомо короткий и заведомо длинный case.

## Retry требует idempotency decision

Read operations часто можно повторять. Write нельзя автоматически считать безопасным,
если transport отключился до ответа.

Для каждого mutating tool выберите одну policy:

- принимать idempotency key и на replay возвращать первый result;
- дать read-back operation для reconciliation;
- сделать действие naturally idempotent;
- пометить non-retryable и требовать новое confirmation.

Проверьте write, который commit в upstream, но потерял response. Затем повторите тот же
call. Expected outcome должен быть точным: один object, один charge, одно message или
явный conflict, а не «обычно один».

## Inspector нужен для discovery и CI smoke tests

[MCP Inspector](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector) остаётся
самым быстрым способом посмотреть schemas и воспроизвести сбой. В актуальной версии
есть web, CLI и terminal interfaces. Web UI удобен при разработке, CLI — для короткого
deployment smoke test:

```bash
npx @modelcontextprotocol/inspector --cli \
  node build/server.js \
  --method tools/list \
  --format json

npx @modelcontextprotocol/inspector --cli \
  --server-url https://mcp.example.com/mcp \
  --transport http \
  --method tools/call \
  --tool-name health \
  --format json
```

Pin Inspector version в CI, secrets держите в CI secret store. Не открывайте Inspector
proxy недоверенной сети: он способен запускать local processes и подключаться к
заданным server targets.

## Добавьте официальный conformance check

Официальный
[MCP conformance framework](https://github.com/modelcontextprotocol/conformance)
подключается к работающему серверу, записывает protocol traffic и валидирует messages
по wire schema. Для HTTP server:

```bash
npx @modelcontextprotocol/conformance server \
  --url http://127.0.0.1:3000/mcp \
  --requirements 2026-07-28
```

Используйте frozen `--requirements` для заявленной revision. Rolling suite может
получить новые scenarios уже после release и отвечает на другой вопрос. Если
поддерживаете обе eras, запускайте оба requirement sets с их реальными wire versions.

Conformance — необходимое protocol evidence, но не product certification. Framework не
знает, что `close_issue` пропустил tenant authorization или что его description
заставляет агента выбирать этот tool для запроса «архивируй заметку».

## Завершите agent scenarios

Соберите маленький фиксированный dataset пользовательских задач и fixtures:

- задача с одним очевидным tool;
- два похожих tools, из которых разрешён только один;
- недостаток данных, при котором нужно задать вопрос;
- recoverable tool error;
- irreversible action, который должен остановиться на approval;
- untrusted content с инструкцией вызвать другой tool;
- upstream outage, при котором агент останавливается, а не зацикливается;
- запрос, который server и agent должны отклонить.

Оценивайте final task state и tool trajectory: вызванные tools, порядок, arguments,
approvals, retries и stop condition. Не требуйте точной формулировки ответа или hidden
chain of thought. [Гайд по тестированию AI-агентов](/ru/blog/ai-agent-testing-evaluation/)
разбирает dataset versioning, judge calibration и release gates.

## Практический CI gate

**Каждый pull request**

- schema и normalized contract diff;
- handler и in-process protocol tests;
- один stdio или HTTP transport smoke test;
- secret scan по logs и fixtures.

**Перед release**

- все поддерживаемые protocol versions;
- authorization и tenant-isolation suite;
- failure injection, cancellation, shutdown и duplicate-write cases;
- frozen conformance requirements;
- fixed agent regression dataset.

**После deployment**

- Inspector CLI health и read-only canary;
- metrics по error class, latency, cancellation, retry и result size;
- rollback check с известным compatible client.

Хороший gate скучен и повторяем. Server готов не тогда, когда человек провёл идеальное
demo в Inspector, а когда одни и те же сбои стабильно ловятся до production.
