# MCP: что это и как работает stateless на 3 инструментах заметок в 2026

> MCP связывает ИИ-модель с внешними инструментами и данными. Разбираю переход от stateful к stateless на локальном примере с заметками.

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

[MCP](/concepts/mcp) - способ связать ИИ-модель с внешними инструментами и данными. Запрос `mcp` обычно появляется в тот момент, когда чата уже мало: нужно прочитать заметку, вызвать действие или получить данные из другой системы. Разбираю разницу между stateful- и stateless-взаимодействием на локальном примере с заметками.

## Что такое MCP и зачем он нужен?

MCP подключает модель к внешнему источнику и даёт ей конкретные инструменты вместо свободного shell-доступа. В связке `mcp ai` модель не получает полный контроль над компьютером. Она видит ограниченный набор действий, выбирает подходящее и получает результат в понятном формате. Так модель остаётся управляемой, а доступ к данным - проверяемым.

Без MCP модель остаётся советчиком. Она может написать инструкцию, предложить код или объяснить ошибку. Но сама по себе она не знает содержимое базы заметок и не умеет выполнить действие в ней.

MCP добавляет прослойку между моделью и внешней системой. Такой системой может быть локальный файл, база данных или отдельное приложение. Сервер описывает доступные инструменты. Модель получает эти описания и решает, какой инструмент нужен для текущего запроса.

Главная граница здесь простая: инструмент делает конкретное действие. Доступ ограничен отдельными операциями: «создай заметку», «добавь текст», «прочитай заметку». Такой набор возможностей проще проверять и контролировать, чем свободный shell-доступ и произвольные команды.

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

Для читателя это выглядит как один разговор. Внутри проходит несколько отдельных действий.

MCP даёт модели заранее определённые рычаги - и не более того. Чем точнее эти рычаги, тем проще понять, что именно произошло, когда результат оказался неправильным.

MCP - новый стандарт для подключения ИИ-ассистентов к системам, где живут данные: репозиториям контента, бизнес-инструментам и средам разработки.

## Кто за что отвечает в связке MCP-клиент и MCP-сервер?

в связке `mcp client server` MCP-клиент принимает запрос модели и передаёт вызовы, MCP-сервер предоставляет инструменты, а модель выбирает нужный инструмент. `mcp server` не решает задачу самостоятельно: он получает конкретный вызов, выполняет действие во внешней системе и возвращает результат клиенту.

Разложу роли без документации:

- **Модель** читает сообщение пользователя и выбирает действие. Она видит описание доступных инструментов: название, назначение и аргументы.
- **MCP-клиент** связывает модель с сервером. Он получает решение модели, оформляет вызов инструмента и отправляет его дальше. Поэтому `mcp client` - не само приложение с данными и не модель.
- **MCP-сервер** принимает вызов. Он проверяет аргументы, выполняет конкретное действие и возвращает результат.
- **Внешний источник** хранит или отдаёт данные. В локальном примере это SQLite или JSON-файл.

Путь запроса выглядит так:

1. Ты пишешь: «Добавь текст в заметку».
2. Модель выбирает `append_to_note`.
3. MCP-клиент передаёт серверу имя инструмента и аргументы.
4. MCP-сервер добавляет текст в хранилище.
5. Сервер возвращает результат клиенту.
6. Модель показывает итог в чате.

Модель не обязана знать файл и способ записи: это зона ответственности сервера. Клиент не лезет в содержимое базы, его задача - передать вызов.

Такое разделение полезно именно в [вайб-кодинге](/concepts/vajb-koding). Когда агент переписал часть приложения, проще искать поломку по границе: модель выбрала не тот инструмент, клиент передал неверные данные или сервер неправильно обработал аргументы.

## Почему модель не вызывает инструмент напрямую?

модель не вызывает инструмент напрямую, потому что она формирует намерение, а не управляет HTTP-соединением и хранилищем. MCP-клиент получает выбранный вызов, MCP-сервер выполняет конкретное действие и возвращает результат. Ограниченный список возможностей делает такой путь проверяемым и управляемым на каждом отдельном шаге.

Представь три слоя:

Модель отвечает на вопрос: «Что надо сделать?» Она выбирает инструмент и заполняет его аргументы. Например, передаёт `note_id` и текст, который нужно добавить.

Клиент отвечает на вопрос: «Куда и в каком формате отправить вызов?» Он знает, к какому MCP-серверу подключаться, и передаёт вызов дальше.

Сервер отвечает на вопрос: «Как выполнить действие?» Он открывает SQLite или JSON-файл, меняет данные и отдаёт результат.

Если убрать клиент и сервер, модели пришлось бы дать свободный доступ к окружению. Тогда она сама решала бы, какой файл открыть, какую команду запустить и куда отправить данные. Такой доступ сложнее ограничить и проверить.

MCP оставляет модели выбор инструмента, но не отдаёт ей весь набор системных возможностей. Для каждой функции есть отдельная граница и понятные аргументы. Правильный выбор инструмента ещё не гарантирует правильный результат, зато область возможной ошибки становится меньше.

Модель может правильно выбрать действие, а сервер всё равно может вернуть ошибку. Проверяй не только ответ в чате, но и результат операции в хранилище.

## Что изменилось при переходе от сессии к stateless?

старый MCP сначала требовал `initialize`, затем выдавал `Mcp-Session-Id`, и только после этого клиент мог вызывать инструмент. В [stateless-варианте](/guides/mcp-stateless-transport) вызов становится самостоятельным HTTP-запросом. Протокол больше не хранит транспортную сессию, но данные приложения всё ещё могут жить в базе, файле или другом хранилище.

![Кот показывает переход от stateful-сессии к самостоятельному stateless-запросу.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-ponyat-perehod-k-stateless-na-prostom-primere/kadr-1.webp)

В старой схеме клиент проходил обязательное начало соединения:

1. Отправлял `initialize`.
2. Получал `Mcp-Session-Id`.
3. Передавал этот идентификатор в следующем запросе.
4. Вызывал инструмент внутри созданной сессии.

У нового варианта другая логика. Один вызов инструмента укладывается в один HTTP-запрос. В нём передаётся версия протокола `MCP-Protocol-Version`, а имя метода и инструмента дублируется в заголовках `Mcp-Method` и `Mcp-Name`.

Серверу больше не требуется хранить ID сессии и направлять запросы на тот же backend через sticky routing. Любой запрос должен быть понятен сам по себе.

| Характеристика | Stateful (старая схема) | Stateless (новая схема) |
|---|---|---|
| Начало соединения | Обязательный `initialize` | Не требуется |
| Идентификатор сессии | `Mcp-Session-Id` | Отсутствует |
| Вызов инструмента | Внутри созданной сессии | Самостоятельный HTTP-запрос |
| Заголовки | Зависит от реализации | `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name` |
| Sticky routing | Требуется | Не требуется |
| Состояние приложения | Часто путали с сессией | Явный `note_id` или другой ID |

На этом месте я останавливаюсь на важной границе. Stateless относится к протокольному уровню MCP. Он не означает, что приложение обязано забывать все данные после каждого запроса.

Практический пример у Simon Willison показывает масштаб работы модели: один вопрос к Datasette привёл к 7 отдельным SQL-запросам. В другом примере MCP-инструмент вернул число заметок 151. За разговором пользователя стояла цепочка вызовов, несколько последовательных операций.

Новая спецификация MCP `2026-07-28` убрала обязательные `initialize`, `initialized`, `Mcp-Session-Id` и зависимость от sticky routing. Состояние приложения при этом остаётся задачей самого приложения.

MCP преобразуется из двустороннего протокола с состоянием в протокол запросов и ответов без состояния.

Переход к stateless нужен не ради нового названия. Он убирает лишнюю транспортную сессию. Состояние заметки, корзины или задачи переносится в явный идентификатор.

## Как сделать локальный MCP-вызов stateless по шагам?

собери один локальный endpoint `/mcp` и три инструмента: `create_note`, `append_to_note` и `get_note`. Храни заметки в SQLite или JSON-файле, возвращай из первого инструмента явный `note_id`, а в следующих запросах передавай его заново. Каждый HTTP-вызов должен работать без `Mcp-Session-Id` как самостоятельная операция.

![Собака одобряет схему endpoint с тремя инструментами заметок и явным идентификатором.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-ponyat-perehod-k-stateless-na-prostom-primere/kadr-2.webp)

Сделай один локальный HTTP endpoint `/mcp`. Не добавляй сразу авторизацию, десятки инструментов и сложную бизнес-логику: фактура не содержит готовой настройки конкретного клиента или команды запуска.

Важно разделить две вещи. Endpoint принимает MCP-запрос. Хранилище держит заметки. Сам endpoint не должен связывать вызовы через скрытый `Mcp-Session-Id`.

Минимальная схема выглядит так:

   ```text
   /mcp
   ├── create_note() -> note_id
   ├── append_to_note(note_id, text)
   └── get_note(note_id)
   ```

Инструмент `create_note` создаёт запись и возвращает `note_id`. Этот ID становится явной связью между будущими вызовами.

Сервер может хранить запись в SQLite или JSON-файле. Точная схема хранения, конкурентный доступ и обработка ошибок в фактуре не описаны, поэтому я не буду выдавать выдуманный код за готовую реализацию.

Правило для инструмента простое: связь должна быть видна снаружи. Верни идентификатор наружу.

   ```json
   {
     "tool": "create_note",
     "result": {
       "note_id": "созданный сервером идентификатор"
     }
   }
   ```

Инструмент `append_to_note` получает `note_id` и `text`. Сервер находит запись по этому ID и добавляет текст в SQLite или JSON-файл.

Следующий вызов не должен рассчитывать на память предыдущего HTTP-запроса. Клиент передаёт `note_id` явно.

   ```json
   {
     "tool": "append_to_note",
     "arguments": {
       "note_id": "идентификатор из предыдущего результата",
       "text": "Первый пункт заметки"
     }
   }
   ```

Инструмент `get_note` получает тот же `note_id` и возвращает сохранённый текст. Так проверяется состояние приложения поверх stateless-протокола.

   ```json
   {
     "tool": "get_note",
     "arguments": {
       "note_id": "тот же идентификатор"
     }
   }
   ```

Сначала отправь вызов создания заметки. Получи `note_id`. Затем отдельным запросом вызови добавление текста или чтение заметки.

В обоих запросах используй endpoint `/mcp`. Не передавай `Mcp-Session-Id`. Для нового stateless-вызова фактура фиксирует заголовки `MCP-Protocol-Version`, `Mcp-Method` и `Mcp-Name`.

   ```http
   POST /mcp
   MCP-Protocol-Version: 2026-07-28
   Mcp-Method: tools/call
   Mcp-Name: create_note
   ```

   ```http
   POST /mcp
   MCP-Protocol-Version: 2026-07-28
   Mcp-Method: tools/call
   Mcp-Name: get_note
   ```

Это схема проверки, а не законченный JSON-RPC payload: фактура не содержит полного формата тела и всех обязательных заголовков. Я не подставляю недостающие поля из головы.

Передай [ИИ-агенту](/concepts/agent) короткое требование. Оно помогает не смешать транспортную сессию и состояние заметки.

   

После вызова `append_to_note` вызови `get_note` с тем же `note_id`. Текст должен прийти из SQLite или JSON-файла, а не из памяти протокольной сессии.

Если сервер перезапустился, память одного процесса не должна считаться надёжным хранилищем. Для локального примера фактура разрешает SQLite или JSON-файл, но не задаёт точную реализацию восстановления и конкурентной записи. Это место надо проверять отдельно.

## Что делать, если приложению всё ещё нужно состояние?

я разделяю состояние транспорта и состояние приложения. `Mcp-Session-Id` больше не должен скрыто связывать запросы. Вместо него сервер возвращает явный `note_id`, `basket_id` или `job_id`, а клиент передаёт этот идентификатор в следующем вызове инструмента. Так приложение сохраняет память о данных без транспортной сессии.

Старая сессия смешивала несколько смыслов. Она могла означать соединение, пользователя, чат или последовательность вызовов. Из-за этого один и тот же ID не всегда совпадал с тем, что человек считал одной рабочей сессией.

В stateless-схеме смысл задаёт приложение:

- `note_id` указывает на заметку;
- `basket_id` указывает на корзину;
- `job_id` указывает на длительную задачу.

Сервер сам создаёт такой идентификатор и возвращает его в результате. Клиент сохраняет значение и передаёт его дальше. Инструменты получают состояние явно и не достают его из памяти MCP-соединения.

Например, `create_note` возвращает `note_id`. Затем `append_to_note(note_id, text)` меняет конкретную заметку. `get_note(note_id)` читает её. Протокол при этом остаётся stateless, а поведение приложения остаётся stateful.

Такой подход требует аккуратнее проектировать аргументы инструментов. Зато из вызова видно, с какой сущностью идёт работа. Это проще проверять в логах и при повторном запросе.

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

после перехода ломаются предположения о постоянной сессии. Старый клиент может ждать `initialize`, server-to-client-функции могут зависеть от сессии, счётчики и корреляция ошибок теряют ключ, авторизацию приходится проверять в каждом запросе, а память одного процесса больше нельзя считать надёжным хранилищем. Разберу пять типичных поломок.

![Мужчина закрывает лицо лапой рядом с перечёркнутыми проблемами stateless-перехода.](https://s3.regru.cloud/crossmark/statejnik/images/guides/mcp-ponyat-perehod-k-stateless-na-prostom-primere/kadr-3.webp)

Крайние случаи начинаются с совместимости. Клиент или middleware может по-прежнему отправлять `initialize` и ожидать `Mcp-Session-Id`. Новый сервер такой сценарий не обязан поддерживать.

Дальше идут функции, которым нужно обратиться к клиенту после вызова. В stateless-режиме отключается часть сценариев, связанных с unsolicited notifications и server-to-client requests. Client sampling, elicitation и roots тоже могут быть недоступны в legacy stateless-режиме.

Ломается и внутренняя диагностика. Раньше счётчик можно было привязать к `sessionId`. После удаления ID несколько клиентов с одинаковой ошибкой могут выглядеть как один поток событий. Для корреляции нужен другой ключ: явный идентификатор операции, failure fingerprint или внешняя телеметрия.

Авторизация тоже не исчезает вместе с сессией. Каждый запрос должен сам нести или получать из middleware credential, identity, права доступа и контекст аудита. Использовать старый ID как доказательство личности больше нельзя.

Есть и инфраструктурная ловушка. Любой запрос может попасть на другой экземпляр сервера. Поэтому память одного процесса не подходит для надёжного хранения счётчиков, задач и состояния. Данные надо вынести в SQLite, JSON-файл или другое внешнее хранилище с учётом ограничений конкретной реализации.

Поддержка новой схемы пока неоднородна: правильная по новой спецификации схема может не заработать в старой обвязке.

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

MCP-сервер предоставляет модели конкретные инструменты и выполняет их вызовы. Я вижу его как связку между действием и внешним источником данных: локальным файлом, SQLite или другой системой.

В старой схеме клиент сначала отправлял `initialize`, получал `Mcp-Session-Id`, а затем вызывал инструменты. В новом stateless-варианте вызов оформляется самостоятельным HTTP-запросом.

Финальная спецификация перехода называется MCP `2026-07-28`. Она переводит базовый протокольный слой к stateless-взаимодействию.

JSON передаёт имя инструмента, аргументы и результат в структурированном виде. Так клиент и сервер обмениваются данными без привязки к человеческому тексту.

Фактура не содержит отдельной инструкции по MCP CLI. Из неё можно утверждать только схему HTTP-вызова к endpoint `/mcp`, но не конкретную команду запуска через терминал.

Клиент получает вызов от модели и передаёт его серверу. Сервер предоставляет инструменты, выполняет действие и возвращает результат.

Модель получает описание доступных инструментов, выбирает нужный инструмент и формирует его вызов. Само действие выполняет MCP-сервер.

Пользователь отправляет сообщение, модель выбирает инструмент, клиент передаёт вызов серверу, сервер работает с данными и возвращает результат модели. Я проверял эту цепочку на локальном примере с заметками.

Я показываю минимальный пример с заметками: `create_note` возвращает `note_id`, `append_to_note` добавляет текст, а `get_note` читает запись.

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

Собери endpoint `/mcp`, добавь инструменты заметок, выбери SQLite или JSON-файл для хранения и передавай `note_id` в каждом последующем вызове.

Минимальная схема состоит из одного endpoint и трёх инструментов: `create_note`, `append_to_note` и `get_note`. Первый инструмент создаёт ID, два других используют его явно.

MCP-серверы реализуют конкретные действия и доступ к данным. Со стороны клиента модель соединяется с этими серверами, а клиент передаёт выбранные моделью вызовы.

- [Introducing the Model Context Protocol](https://www.anthropic.com/news/model-context-protocol)
- [The 2026-07-28 MCP Specification Release](https://blog.modelcontextprotocol.io/posts/2026-07-28/)
- [Stateless MCP has recaptured my interest](https://simonwillison.net/2026/Jul/31/stateless-mcp/)
- [Make MCP Stateless](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2575-stateless-mcp.md)
- [Sessionless MCP via Explicit State Handles](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2567-sessionless-mcp.md)
- [Stateless and stateful mode](https://csharp.sdk.modelcontextprotocol.io/v2/concepts/stateless/stateless.html)
- [MCP 2026-07-28 Update: What Stateless Means for Security](https://www.oasis.security/blog/stateless-mcp-spec-update)
