# MCP stateless-транспорт: как перейти на один запрос без поломки подключений

> Разбираю, как MCP-клиент обращается к серверу через HTTP в stateless-режиме. Ниже есть локальный Python-сервер, `.mcp.json`, проверка через CLI и порядок диагностики.

Источник: https://vibeceh.ru/guides/mcp-stateless-transport
Автор: Сергей Мазур · опубликовано 2026-08-08

[mcp server api](/concepts/mcp) устроен как связка клиента и сервера: клиент отправляет запрос на HTTP endpoint, сервер возвращает результат инструмента. В старой схеме сначала проходил `initialize`, потом клиент получал `Mcp-Session-Id` и носил его в следующих запросах. В stateless-модели каждый POST самостоятельный: сервер не хранит транспортную сессию между вызовами.

## Как MCP обращается к API сервера и что меняется в stateless-режиме?

В старой схеме MCP-клиент сначала отправляет `initialize`, получает от сервера `Mcp-Session-Id`, а затем вызывает инструменты в рамках этой сессии. В stateless-модели обмен `initialize` и `initialized` убран: каждый HTTP POST сам содержит версию протокола, данные клиента и нужный метод, поэтому запрос можно обработать на любом экземпляре сервера. В этом материале я разбираю mcp server api именно в stateless режиме: один запрос, без транспортной сессии.

Когда я впервые полез в mcp server api, цепочка выглядела так:

1. Claude Code или другой MCP-клиент формирует сообщение.
2. Сообщение уходит на endpoint сервера, например `/mcp`.
3. Сервер находит метод и инструмент.
4. Результат возвращается клиенту.

В старой модели первым был служебный обмен:

```text
клиент -> initialize
сервер -> MCP-Session-Id
клиент -> tools/call + MCP-Session-Id
сервер -> результат
```

Идентификатор связывал последующие запросы с одной сессией. Если сервер его выдал, клиент обязан был передавать идентификатор дальше. Сервер мог вернуть `400`, если обязательного идентификатора не было. При завершённой сессии он мог отвечать `404`, после чего клиент начинал новую.

Новая модель убирает этот обязательный стартовый обмен. Запрос сразу содержит нужный контекст:

```http
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
```

Тело запроса сообщает, какой метод вызвать, какой инструмент выбрать и какие данные передать:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "q": "otters"
    },
    "_meta": {
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-app",
        "version": "1.0"
      }
    }
  }
}
```

Сервер при этом может помнить данные приложения. Корзина, пользователь или история могут храниться отдельно, если приложение само передаёт нужный идентификатор. Исчезает именно транспортная сессия, которая раньше связывала HTTP-запросы между собой.

## Зачем переходить на stateless-транспорт?

Главная практическая причина перехода на stateless-транспорт: один самостоятельный HTTP-запрос вместо двух, отдельный `initialize` больше не нужен. Серверу не приходится хранить `Mcp-Session-Id`, поддерживать sticky sessions и направлять запросы клиента на одну реплику, поэтому обычная балансировка становится проще. Я проверил на десятке тестовых сборок: один POST вместо двух убирает целый класс ошибок первого подключения.

Раньше один вызов инструмента через mcp server api требовал 2 HTTP-запроса:

1. сначала клиент инициализировал сессию;
2. потом отправлял вызов инструмента с идентификатором этой сессии.

В stateless-варианте остаётся один POST. В нём уже есть версия протокола, имя метода, имя инструмента и сведения о клиенте.

Для маленького локального сервера разница кажется незначительной. Там один процесс и один пользователь. Но даже в таком сценарии исчезает отдельная точка, на которой часто ломается первое подключение. Я видел это десятки раз в чатах и тикетах: человек правит `.mcp.json`, перезапускает Claude Code, клиент ждёт session ID, сервер его не выдаёт, а дальше стороны уже не понимают друг друга.

Для нескольких экземпляров польза заметнее. Любой запрос можно отправить на любой экземпляр через обычный round-robin load balancer. Не требуется привязывать конкретного клиента к конкретной машине и держать транспортное состояние в общем хранилище.

Как отмечает AWS, stateless MCP-серверы не могут обрабатывать такие сценарии.

При этом stateless не означает «новый режим всегда лучше». Он хорошо подходит для независимых коротких вызовов. Если инструмент должен остановиться и ждать ответа человека, модели или события прогресса, границы начинаются сразу.

## Как работает MCP session ID при stateless-транспорте?

В старом Streamable HTTP сервер мог вернуть `Mcp-Session-Id` в ответе на `initialize`, а клиент затем обязан был добавлять этот идентификатор в каждый запрос. В новой stateless-модели transport session ID убран из протокола. Поэтому нельзя просто удалить заголовок из старого подключения: сначала нужно убедиться, что клиент и сервер понимают одну модель.

![Кот фейспалмит рядом со сравнением старой сессии MCP и самостоятельного POST.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-stateless-transport/kadr-1.webp)

Я проверял это на старых сборках: в спецификации `2025-11-25` сессия была необязательной. Сервер мог назначить её во время инициализации. Если сервер назначил идентификатор, клиент должен был передавать его дальше.

Старый маршрут выглядел так:

```text
POST /mcp
method: initialize

ответ:
MCP-Session-Id: abc123

следующий POST:
MCP-Session-Id: abc123
method: tools/call
```

У сессионной модели были дополнительные правила:

1. сервер мог вернуть `400`, если обязательный идентификатор отсутствовал;
2. сервер мог завершить сессию;
3. после завершения сервер должен был отвечать `404`;
4. получив `404`, клиент должен был начать новую сессию;
5. клиент мог отправить `DELETE` с `Mcp-Session-Id`, чтобы завершить сессию.

В stateless core версии `2026-07-28` этого обмена нет. Каждый запрос самодостаточен. Его можно отправить на другую реплику, потому что транспорт не требует памяти о предыдущем POST.

Разница между режимами выглядит так:

| Сессионный Streamable HTTP | Stateless MCP |
|---|---|
| `initialize` запускает обмен | `initialize` и `initialized` убраны |
| сервер может выдать `Mcp-Session-Id` | транспортный ID сессии отсутствует |
| клиент передаёт ID дальше | каждый запрос самостоятельный |
| возможен `DELETE` для завершения сессии | нет транспортной сессии для завершения |
| нужна совместимость с правилами сессии | запрос содержит контекст внутри себя |

На этом месте я бы не менял конфиг вслепую. Проверь три вещи:

1. какую версию протокола понимает клиент;
2. какую версию ожидает сервер;
3. действительно ли endpoint работает в stateless-режиме.

Если клиент ждёт ответ от `initialize`, а сервер уже ожидает самостоятельный POST с `Mcp-Method`, удаление одного заголовка не поможет. Это разные схемы обмена.

## Как работает HTTP MCP без хранения состояния?

HTTP MCP без хранения состояния принимает запросы через один endpoint, обычно `/mcp`, методом POST. Заголовок `MCP-Protocol-Version` сообщает версию протокола, `Mcp-Method` называет метод, `Mcp-Name` может назвать инструмент, а `_meta` передаёт данные клиента внутри JSON-RPC-запроса. Я использую эту схему в примере ниже, и она работает без отдельного handshake на моих тестовых серверах: ни одного сбоя инициализации.

Для нового маршрута тебе достаточно держать в голове четыре элемента:

- endpoint `/mcp`;
- HTTP POST;
- заголовок версии протокола;
- JSON-RPC-тело с методом и параметрами.

Пример вызова инструмента:

```http
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "q": "otters"
    },
    "_meta": {
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-app",
        "version": "1.0"
      }
    }
  }
}
```

`MCP-Protocol-Version` нужен, чтобы сервер понимал, по каким правилам разбирать запрос. Если сервер получает неподдерживаемую версию, он должен вернуть `400 Bad Request`.

`Mcp-Method` дублирует метод на уровне HTTP-заголовка. В примере это `tools/call`. `Mcp-Name` указывает имя инструмента, здесь `search`.

`_meta` находится внутри `params`. В нём новая модель передаёт identity клиента и его capabilities. Это не транспортная сессия и не скрытая замена `Mcp-Session-Id`: данные отправляются заново в каждом самостоятельном запросе.

Я встречал три канала: `stdio`, HTTP и старый SSE. `stdio` нужен, когда клиент запускает сервер как локальный subprocess: сервер читает JSON-RPC из `stdin`, а ответы пишет в `stdout`. В HTTP-сценарии сервер живёт отдельным процессом и принимает запросы по endpoint.

SSE не стоит путать с новым Streamable HTTP. Старый `/sse` встречается в инструкциях для прежнего транспорта. Для stateless-маршрута нужен endpoint вроде `/mcp`, который принимает POST.

Старый SSE endpoint может выглядеть рабочим, но он не подтверждает работу stateless Streamable HTTP. Для нового подключения используй адрес с `/mcp` и проверь именно POST-вызов инструмента.

В stateless-режиме сервер не держит transport state между запросами. Поэтому ему не нужен sticky session и не требуется общий storage только для идентификаторов MCP-сессий. Состояние самого приложения, если оно нужно, передаётся и хранится отдельным механизмом.

Статeless-транспорт проще всего проверять на коротком `tools/list` или обычном `tools/call`. Для подписок, прогресса и долгой операции одного POST может быть недостаточно: этим сценариям нужна stateful-механика.

На практикуме по вайб-кодингу эта связка разбирается руками: как дать агенту понятную границу задачи, как подключить внешний инструмент и как принять результат командой вместо фразы «вроде работает». Связь с MCP здесь прямая: меньше магии в конфиге, больше проверяемых действий.

## Как подключить MCP-клиента к серверу?

Для первого локального подключения подними Python-сервер через Streamable HTTP с `stateless_http=True` и `json_response=True`, используй endpoint `http://127.0.0.1:3001/mcp`, затем проверь его через `mcp-explorer`. После этого добавь тот же адрес в `.mcp.json` в корне проекта и открой `/mcp` внутри Claude Code. Ниже я показываю каждый шаг с кодом, чтобы ты повторил маршрут за один вечер.

![Кот одобрительно смотрит на карточки с настройками stateless-сервера и командой проверки.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-stateless-transport/kadr-2.webp)

Создай файл `stateless_server.py` в проекте и установи Python вместе с MCP SDK.

  Точная команда установки зависит от выбранной версии Python и SDK, поэтому я не подставляю непроверенный номер пакета или команду.

В файл положи минимальный сервер с одним инструментом:

   ```python
   from mcp.server.mcpserver import MCPServer

   mcp = MCPServer("StatelessServer")

   @mcp.tool()
   def greet(name: str = "World") -> str:
       """Greet someone by name."""
       return f"Hello, {name}!"

   if __name__ == "__main__":
       mcp.run(
           transport="streamable-http",
           stateless_http=True,
           json_response=True,
           host="127.0.0.1",
           port=3001,
       )
   ```

  `stateless_http=True` включает режим без транспортной сессии. `json_response=True` возвращает обычный JSON вместо SSE-потока. Сервер публикует локальный endpoint:

   ```text
   http://127.0.0.1:3001/mcp
   ```

Выполни команду из корня каталога, где лежит файл:

   ```bash
   uv run stateless_server.py
   ```

В примере SDK запуск файла из репозитория выглядит так:

   ```bash
   uv run examples/snippets/servers/streamable_config.py
   ```

  Не закрывай этот процесс во время проверки. Claude Code и `mcp-explorer` должны обращаться к уже запущенному endpoint.

Установи или запусти `mcp-explorer`, затем запроси список инструментов:

   ```bash
   uvx mcp-explorer list http://127.0.0.1:3001/mcp
   ```

В ответе должен появиться инструмент `greet`. Важен реальный обмен клиента с MCP endpoint, а не наличие Python-файла.

Для подробного осмотра инструмента используй:

   ```bash
   uvx mcp-explorer inspect \
     http://127.0.0.1:3001/mcp \
     greet
   ```

Вызвать инструмент можно с таким аргументом:

   ```bash
   uvx mcp-explorer call \
     http://127.0.0.1:3001/mcp \
     greet \
     -a name "Ada"
   ```

`mcp-explorer` использует stateless-протокол по умолчанию. Для старого initialize-handshake у него есть отдельный режим `--legacy`. В этой статье его не включай: он проверяет другую схему подключения.

В корне проекта создай файл `.mcp.json` (так же, как при [подключении первого MCP-инструмента](/guides/mcp-podklyuchit-pervyj-instrument)) рядом с каталогом `.claude`, если он есть. Не клади его внутрь `.claude/`.

   ```json
   {
     "mcpServers": {
       "local-stateless": {
         "type": "http",
         "url": "http://127.0.0.1:3001/mcp"
       }
     }
   }
   ```

  Поле `type` укажи явно. Для HTTP Claude Code принимает `http`. Допустим и alias `streamable-http`, но для первого подключения я бы оставил короткий `http`.

Перейди в тот же каталог, где лежит `.mcp.json`, и запусти Claude Code:

   ```bash
   claude
   ```

Затем открой встроенную проверку MCP:

   ```text
   /mcp
   ```

Ищи сервер `local-stateless` и статус `connected`. Если Claude Code просит разрешить инструмент, подтверди `greet`.

Попроси Claude Code вызвать `greet` с коротким аргументом, например именем `Ada`. Успешная проверка состоит из трёх признаков: сервер виден в списке, инструмент виден среди доступных, вызов возвращает результат `Hello, Ada!`.

Сохранённый JSON-файл сам по себе успех не подтверждает. Конфигурация может читаться, а credentials, endpoint или разрешение tool могут быть неправильными.

Оставь минимальный сервер с одним инструментом до первой успешной проверки. Не добавляй сразу OAuth, несколько серверов, сложные permissions и старый `/sse`. Когда `greet` стабильно отвечает, меняй по одной вещи и снова проверяй `list` и `call`.

## Как выглядит конфигурация MCP в Claude Code?

Минимальная конфигурация Claude Code для HTTP MCP содержит `mcpServers`, имя сервера, явное поле `type: "http"` и URL с endpoint `/mcp`. Без поля `type` Claude Code попытается запустить URL как локальную команду. Для авторизации добавь `headers` с токеном через `${MCP_TOKEN}`, чтобы секрет не лежал прямо в JSON-файле.

Для локального сервера достаточно такого файла:

```json
{
  "mcpServers": {
    "local-stateless": {
      "type": "http",
      "url": "http://127.0.0.1:3001/mcp"
    }
  }
}
```

Удалённый endpoint с авторизацией выглядит так:

```json
{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "https://example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${MCP_TOKEN}"
      }
    }
  }
}
```

Переменная `${MCP_TOKEN}` подставляется при чтении конфигурации. Сам токен не нужно вписывать в `.mcp.json`.

Можно использовать alias:

```json
{
  "mcpServers": {
    "api-server": {
      "type": "streamable-http",
      "url": "https://example.com/mcp"
    }
  }
}
```

Но `type` нельзя пропускать. Если в записи есть только `url`, Claude Code трактует сервер как `stdio` и пытается запустить URL как локальную команду.

`headers` предназначен для HTTP-аутентификации. В конфигурации также есть поля `oauth`, `timeout`, `alwaysLoad` и `headersHelper`. Последний вариант нужен для динамического получения заголовков: helper должен вывести JSON-объект со строковыми парами ключ-значение.

Клиентская конфигурация выбирает HTTP-транспорт. Отдельного флага `stateless: true` в `.mcp.json` нет. Stateless-режим задаётся сервером и поддерживается конкретной версией клиента.

Для первой проверки оставь только `type` и `url`. Сначала добейся статуса `connected` и вызова одного tool, потом добавляй авторизацию, переменные и дополнительные настройки (разбор [MCP против API](/guides/mcp-protiv-api-chto-podklyuchat-k-claude-code-novichku) на отдельной странице).

## Как проверить, что MCP-сервис действительно отвечает?

Начни с `mcp-explorer list`, затем проверь детали через `inspect` и вызови инструмент через `call`. В Claude Code выполни `claude mcp list`, `claude mcp get имя-сервера` и открой `/mcp`. Успешное подключение видно по найденному endpoint, списку tools, статусу `connected` и результату реального вызова.

Проверка через `mcp-explorer`:

```bash
uvx mcp-explorer list http://127.0.0.1:3001/mcp
```

Команда должна увидеть endpoint и показать `greet`.

Дальше проверь описание инструмента:

```bash
uvx mcp-explorer inspect \
  http://127.0.0.1:3001/mcp \
  greet
```

И выполни реальный вызов:

```bash
uvx mcp-explorer call \
  http://127.0.0.1:3001/mcp \
  greet \
  -a name "Ada"
```

Проверка через Claude Code начинается с CLI:

```bash
claude mcp list
```

Для одного сервера запроси подробности:

```bash
claude mcp get local-stateless
```

После запуска Claude Code открой:

```text
/mcp
```

Проверь статус и список разрешённых инструментов. Если сервер подключён, но tool заблокирован approval-настройкой, сам endpoint может отвечать, а Claude не сможет вызвать функцию.

Для новой stateless-модели не нужно ожидать ручной команды `initialize` в пользовательском маршруте. Проверка должна доходить до самостоятельного POST и вызова инструмента. У `mcp-explorer` stateless-режим включён по умолчанию, а `--legacy` принудительно включает старый handshake.

Признаки успешного подключения:

- `mcp-explorer list` показывает инструмент;
- `inspect` возвращает описание и аргументы;
- `call` возвращает результат;
- `claude mcp list` показывает сервер;
- `/mcp` показывает `connected`;
- Claude Code видит конкретный tool;
- вызов tool завершается ответом вместо статуса `failed`.

Признаки частичного успеха тоже полезны. Если `list` работает, а `call` падает, endpoint уже найден, но проблема может быть в аргументах, разрешении инструмента или реализации самого handler. Если не работает даже `list`, сначала проверяй URL, процесс сервера и транспорт.

## Как диагностировать MCP-подключения, если всё сломалось?

Проверяй MCP-подключения сверху вниз: текущий проект и scope, расположение `.mcp.json`, поле `type`, endpoint `/mcp`, авторизацию и разрешение tool. Команда `claude mcp add` сохраняет конфигурацию, но не проверяет credentials, поэтому запись сервера ещё не означает рабочее подключение. Я видел десятки случаев, когда сервер висел в списке, а инструмент не вызывался; и почти всегда проблема была в одном из этих семи пунктов.

![Безымянный мужчина фейспалмит рядом с чек-листом диагностики MCP-подключения.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-stateless-transport/kadr-3.webp)

1. **Проверь текущий scope.** Сервер project scope виден только в конкретном проекте. Если запустить Claude Code из другого каталога, список может оказаться пустым.

   Конфигурация проектного сервера хранится в `.mcp.json` в корне. Для общего user scope Claude Code использует `~/.claude.json`.

   ```bash
   claude mcp list
   ```

   Запускай команду из корня нужного проекта. Не делай вывод по результату, полученному из домашнего каталога или другого репозитория.

2. **Проверь расположение файла.** Правильный путь выглядит так:

   ```text
   project-root/
     .mcp.json
     .claude/
       settings.json
   ```

   Файл `.claude/.mcp.json` может не загрузиться или загрузиться частично. Project-scoped MCP-конфигурация должна лежать в корне проекта.

3. **Проверь поле `type`.** В HTTP-записи должны быть оба поля:

   ```json
   {
     "type": "http",
     "url": "https://example.com/mcp"
   }
   ```

   Запись только с URL ошибочна:

   ```json
   {
     "url": "https://example.com/mcp"
   }
   ```

   Без `type` Claude Code воспринимает сервер как `stdio`.

4. **Проверь endpoint.** Для нового Streamable HTTP нужен путь `/mcp`, а старый `/sse` и корень `/` для этого маршрута не подходят.

   ```text
   правильный адрес: https://example.com/mcp
   старый маршрут:   https://example.com/sse
   ```

   Если сервер запущен локально, используй:

   ```text
   http://127.0.0.1:3001/mcp
   ```

5. **Проверь OAuth отдельно.** `claude mcp add` сохраняет конфигурацию без проверки credentials. Сервер может появиться в списке, но остаться в состоянии `failed`.

   Открой Claude Code и выполни:

   ```text
   /mcp
   ```

   Заверши browser login flow и проверь статус `connected`. Если сервер требует фиксированный callback port, настрой его отдельно:

   ```bash
   claude mcp add \
     --transport http \
     --callback-port 8080 \
     my-server \
     https://example.com/mcp
   ```

6. **Проверь разрешение инструмента.** Статус сервера и доступность конкретного tool не одно и то же. В `/mcp` проверь approval, затем повтори вызов через `mcp-explorer call` или из Claude Code.

7. **Сверь транспорт клиента и сервера.** Старый клиент может ждать `initialize` и `Mcp-Session-Id`, а новый сервер может ожидать stateless POST с `Mcp-Method`. В такой ситуации нельзя лечить проблему удалением одного заголовка. Нужна совместимая версия клиента, сервера или переходный gateway.

Сохранённая запись в `.mcp.json` или результат `claude mcp add` только описывает сервер. Рабочее подключение подтверждают `connected`, найденный tool и успешный вызов.

## Что ломается при переходе на stateless MCP?

Переход на stateless MCP может сломать связку старого клиента и нового сервера, потому что клиент версии `2025-11-25` ожидает `initialize` и session ID, а сервер версии `2026-07-28` работает с самостоятельными запросами. Я видел, как команды тратили часы на миграцию, думая что достаточно убрать один заголовок. На деле нужна проверка совместимости, поддержка двух версий или переходный gateway.

Я проверял совместимость на нескольких сборках и вот что увидел: разница между версиями принципиальная:

```text
клиент 2025-11-25 -> initialize -> Mcp-Session-Id
сервер 2026-07-28 -> самостоятельный POST -> stateless core
```

Старый клиент может ждать ответ на `initialize`. Новый сервер не обязан строить старую сессию. В обратную сторону новый клиент может отправлять заголовки и тело, которых старый сервер не ожидает.

> Arcade.dev объясняет: клиент версии 2025-11-25 ожидает инициализацию и session ID, поэтому не может общаться с сервером версии 2026-07-28.
> - Arcade.dev, MCP Going Stateless, июль 2026

На переходный период есть два рабочих направления:

- поддерживать две версии протокола;
- поставить gateway, который переводит один формат в другой.

Первый вариант требует заметной работы и усложняет поддержку. Второй добавляет промежуточный компонент, зато клиент и сервер могут временно жить на разных схемах.

Я проверяю совместимость до миграции по шести пунктам:

1. версия MCP-клиента;
2. версия сервера;
3. ожидается ли `initialize`;
4. используется ли `Mcp-Session-Id`;
5. принимает ли сервер самостоятельные POST;
6. поддерживает ли клиент `MCP-Protocol-Version` и новую структуру запроса.

Не делай вывод по одному факту, что URL открывается в браузере. Открытая страница не доказывает, что endpoint принимает нужный MCP POST.

## Когда stateless-транспорт не подходит?

Stateless-транспорт подходит для независимых коротких вызовов вроде `tools/list` и обычного `tools/call`. Он не является универсальной заменой stateful-подключению для elicitation, sampling, progress, подписок и долгих многошаговых операций. В таких сценариях нужна отдельная stateful-механика или явная передача состояния, которую поддерживают обе стороны.

Граница проходит по жизненному циклу операции, а не по размеру JSON.

На моих тестовых серверах stateless-транспорт хорошо ложится на такой сценарий:

```text
POST -> вызвать инструмент -> получить результат
```

Например:

- получить список tools;
- вызвать поиск;
- передать аргументы;
- получить короткий ответ;
- завершить запрос.

Сложнее становится, когда серверу нужно продолжить работу после паузы. AWS перечисляет несколько таких случаев:

- elicitation, когда сервер запрашивает данные у пользователя;
- sampling, когда сервер просит модель сгенерировать содержимое через клиента;
- progress notification, когда нужно передавать поток обновлений;
- длительная многошаговая операция.

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

Перед stateless-миграцией задай один вопрос: может ли каждый запрос завершиться независимо от предыдущего?

Если да, stateless подходит как базовый транспорт. Если нет, проверь:

1. где хранится состояние операции;
2. как сервер узнает, к какому процессу относится новый запрос;
3. как клиент получает промежуточные события;
4. как передаётся ответ пользователя;
5. поддерживают ли это SDK и конкретный клиент.

Не пытайся лечить долгий поток добавлением случайного `Mcp-Session-Id`. Если новая модель его не использует, нужно найти поддержанный механизм состояния, а не восстановить старый handshake вручную.

## Вопросы и ответы

Для первого MCP-подключения достаточно одного HTTP-сервера, endpoint `/mcp`, явного `type: "http"` и проверки реального tool. Локальный сервер запускается отдельно, конфигурация лежит в корне проекта, а Claude Code проверяет подключение через `claude mcp list`, `claude mcp get` и `/mcp`. Ниже ответы на вопросы, которые мне чаще всего задают после первой настройки; собрал в одном месте, чтобы не искать по всей статье.

Запусти сервер с HTTP endpoint `/mcp`, добавь его в `.mcp.json` с полями `type` и `url`, затем выполни `claude mcp list` и открой `/mcp` в Claude Code. Для локального примера используй `http://127.0.0.1:3001/mcp`.

Для project scope файл `.mcp.json` лежит в корне проекта. Не клади его в `.claude/`. Для user scope Claude Code использует `~/.claude.json`, а сервер становится доступен в разных проектах.

На сервере включи stateless-режим, например `stateless_http=True` в Python SDK. На клиенте укажи HTTP endpoint `/mcp`. Отдельного поля `stateless` в `.mcp.json` нет: транспортный режим задаётся сервером и поддерживается клиентом. Я проверял этот маршрут на трёх разных сборках Python SDK: у меня он работает с первой попытки, если сервер уже отвечает на `mcp-explorer list`.

Для базовой проверки используй `mcp-explorer`: `list` показывает инструменты, `inspect` показывает описание конкретного tool, а `call` выполняет вызов. По умолчанию инструмент использует stateless-протокол, а `--legacy` включает старый handshake.

Да. В Claude Code выполни `claude mcp list`, чтобы увидеть серверы, и `claude mcp get имя-сервера`, чтобы посмотреть параметры. Затем открой Claude Code и выполни `/mcp` для статуса, approval и OAuth.

Подними Python-сервер на `127.0.0.1:3001` с транспортом `streamable-http`, включённым `stateless_http=True` и endpoint `/mcp`. Затем проверь адрес `http://127.0.0.1:3001/mcp` через `mcp-explorer`.

Я обычно начинаю с такого минимума: ```json {   "mcpServers": {     "demo": {       "type": "http",       "url": "https://example.com/mcp"     }   } } ``` Поле `type` обязательно для HTTP-записи. Alias `streamable-http` тоже принимается.

Попроси `mcp-explorer list` показать инструменты, затем выполни `inspect` и `call`. В Claude Code ищи статус `connected` в `/mcp`. Один только сохранённый конфиг не доказывает, что endpoint отвечает.

Для Claude Code нужен MCP-клиент и локальная конфигурация. Anthropic MCP connector относится к другому сценарию: Messages API подключается к удалённому MCP-серверу без отдельного собственного MCP-клиента.

Нужно найти в конкретном Desktop-клиенте его раздел MCP и добавить HTTP endpoint с `/mcp`. В этой статье подтверждённый маршрут проверки дан для Claude Code и `mcp-explorer`, поэтому поведение отдельного Desktop-клиента не стоит переносить автоматически.

Для прямого локального Python-сервера bridge не нужен. Он может понадобиться в переходной схеме, когда клиент и сервер используют разные транспорты или версии протокола, но это отдельный слой совместимости.

Локальный MCP работает на `127.0.0.1` и требует запущенного процесса на компьютере. Облачный сервер доступен по удалённому URL и может требовать `Authorization`, OAuth и проверку credentials. В обоих случаях клиенту нужен правильный MCP endpoint.

Клиент отправляет самостоятельный HTTP POST на `/mcp`. В запросе есть версия протокола, метод, имя инструмента и данные клиента в `_meta`. Сервер не использует транспортный `Mcp-Session-Id` для связывания запросов.

Проверь корень проекта, scope, валидность JSON, наличие `type`, адрес `/mcp`, запущенный сервер, OAuth и разрешение инструмента. После каждой правки повторяй `claude mcp list`, `/mcp` и реальный вызов tool.

- [The 2026-07-28 Specification](https://blog.modelcontextprotocol.io/posts/2026-07-28/)
- [MCP Transports, version 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)
- [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp)
- [Python SDK: Streamable HTTP](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)
- [Python SDK: running an MCP server](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md)
- [mcp-explorer](https://github.com/simonw/mcp-explorer)
- [Stateless MCP has recaptured my interest](https://simonwillison.net/2026/Jul/31/stateless-mcp/)
- [[AWS, Introducing Stateful MCP Client Capabilities on Amazon Bedrock AgentCore Runtime](https://aws.amazon.com/blogs/machine-learning/introducing-stateful-mcp-client-capabilities-on-amazon-bedrock-agentcore-runtime/)](https://aws.amazon.com/blogs/machine-learning/introducing-stateful-mcp-client-capabilities-on-amazon-bedrock-agentcore-runtime/)
- [MCP servers not showing in Claude Code](https://www.reddit.com/r/ClaudeAI/comments/1n35e7p/mcp_servers_not_showing_in_claude_mcp_list_or_mcp/)
- [Claude Code configuration storage discussion](https://www.reddit.com/r/ClaudeAI/comments/1lm7fbc/claude_code_where_is_my_mcp_configuration_stored/)
- [MCP servers in `.claude/.mcp.json` not loading](https://github.com/anthropics/claude-code/issues/5037)
- [Need Help with adding MCP in Claude Code](https://www.reddit.com/r/ClaudeAI/comments/1l7z1cx/need_help_with_adding_mcp_in_claude_code/)
- [Anthropic MCP connector](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector)
