Написано 5 августа 2026 года. Это самая быстро устаревающая часть цикла: она про то, как устроено внутри, а внутри меняется чаще всего. Редакция спецификации от 28 июля 2026-го убрала из протокола вещи, которые год назад были в нём основой. Сверяйтесь с первоисточником.

Как это устроено изнутри

Как это устроено изнутри

Уберите словарь — и MCP-сервер окажется тремя вещами: список инструментов, описание каждого, написанное для машины, а не для человека, и один из двух транспортов, которым едут сообщения: stdio — для сервера на вашей машине, Streamable HTTP — для того, что работает не у вас. Всё остальное в Model Context Protocol — JSON-RPC, авторизация по OAuth 2.1, три ступени ошибок — навешено на эти три.

Третья часть — про устройство. Она подробнее двух первых, но по-прежнему без кода: задача не научить писать сервер, а показать, из чего он состоит и почему именно так.

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

Что вообще передаётся по проводу

Про сервер пишут все: он предлагает список действий и выполняет их по просьбе. MCP-клиента не объясняет почти никто, а клиент — это программа, внутри которой живёт модель: она ведёт диалог, держит список подключённых серверов, решает, какой инструмент позвать, и спрашивает у человека подтверждение, если вызов выглядит необратимым. Сама модель до сервера не дотягивается никогда. Она формулирует намерение, клиент превращает намерение в вызов, и отвечает сервер именно клиенту. Когда пишут «модель вызвала инструмент», вызвал клиент.

Между ними ходит JSON-RPC — формат сообщений, вторая версия которого вышла в 2010 году и не менялась с 2013-го, за полтора десятилетия до всей этой истории с искусственным интеллектом, и примерно такой же захватывающий, как почтовая наклейка. В запросе три части: имя метода, объект с аргументами и идентификатор, по которому ответы сопоставляются с вопросами, когда в полёте сразу несколько. Взять готовое было правильным решением: спорить не о чем, парсер есть в любом языке, а авторы Model Context Protocol — название стоит развернуть хотя бы раз, аббревиатура его проглотила — освободили внимание для того, что действительно было новым.

Почему вызов MCP совсем не похож на REST

На REST это не похоже совсем. В REST смысл несёт адрес: GET /articles/17 кладёт существительное в путь, глагол — в метод, и кэш посередине может что-то с этим сделать, не заглядывая в тело. В MCP любой вызов идёт по одному и тому же адресу и говорит «tools/call». Какой инструмент, с какими аргументами, что он сейчас сделает с вашими данными — всё внутри тела, и любой промежуточный узел видит одинаковые конверты. Поэтому редакция июля 2026-го и добавила заголовки, по которым посредник может маршрутизировать и кэшировать запрос, не вскрывая содержимое: протокол выкупал обратно то, что HTTP получает даром задолго до того, как этот стиль записали.

Два транспорта: stdio и Streamable HTTP

Протокол описывает, как выглядит сообщение. Про то, как байты попадают из одного процесса в другой, он не говорит ничего — это отдельная работа, и у неё есть своё название: транспорт. Труба, а не язык. Транспортов в MCP два, и выбор между ними следует из того, где живёт сервер, а не из чьих-то достоинств. Если вы когда-нибудь прописывали сервер в Claude Desktop, Cursor или VS Code, вы уже выбрали один, не заметив: эти клиенты держат список серверов в конфигурационном файле, и локальная запись в нём — просто командная строка.

    • stdio: сервер запускается соседним процессом на той же машине и общается через свои стандартные ввод и вывод — те самые две трубы, которыми программы командной строки пользуются с семидесятых. Ни портов, ни сети. Так работает большинство локальных серверов, и поэтому небрежно сделанный успевает заработать раньше, чем кто-нибудь вспомнит про аутентификацию.
    • Streamable HTTP: один сетевой адрес, на который и пишут, и с которого читают, — и он же умеет держать соединение открытым и досылать сообщения. Это транспорт для всего, что работает не на вашей машине.
    • Старый транспорт HTTP+SSE, у которого адресов было два вместо одного, объявлен устаревшим ещё в марте 2025-го. Если он встретился вам в инструкции, инструкция несвежая и в остальном тоже.
    • Что убрали в июле 2026-го: восстановление оборванного потока. Раньше клиент мог переподключиться и попросить пропущенное; теперь запрашивает заново с начала. Протокол проще, работы больше — этот размен редакция делает раз за разом.
Два транспорта

Два транспорта

Безопасность локального сервера

Сервер на собственной машине кажется безопасным и является самым небезопасным местом во всей картине. Он работает от вашего имени, держит ваши ключи, и его никто не укреплял — предполагалось, что до него не добраться. Добраться может одна вещь: открытая в браузере страница.

Эту часть спецификация пишет капслоком, и это не крик. Когда слово-требование в стандарте набрано заглавными — MUST, MUST NOT, SHOULD, — это термин, а не выделение. RFC 2119 от 1997 года закрепляет значения, а RFC 8174 двадцатью годами позже — правило, по которому считается только капслок: пропустив MUST, вы получаете не реализацию спецификации, а что-то другое; SHOULD можно пропустить, если понимаете, чем платите. Заглавные нужны, чтобы читающий по диагонали не принял требование за совет. В этом разделе их три. Проверять заголовок Origin. Слушать только локальный адрес. Требовать аутентификацию.

Перепривязка DNS: атака, которую закрывают эти три правила

У атаки, которую эти три строчки закрывают, неудачное имя — перепривязка DNS — и простое устройство. Вы открываете страницу; ничего необычного в ней нет. Она принадлежит имени, которым распоряжается атакующий, и когда браузер это имя разрешал, он получил адрес атакующего — ровно как и должен был. Через несколько секунд атакующий меняет ответ, и то же самое имя разрешается теперь в 127.0.0.1, то есть в вашу машину. Браузер разрешает имя заново, видит то же имя и заключает, что ничего не изменилось: вся его модель безопасности построена на именах, а не на адресах. Страница сохраняет все свои права. И вот она уже разговаривает с тем, что слушает на вашем ноутбуке, изнутри вашего ноутбука, и читает ответы.

Поэтому «слушать только локальный адрес» само по себе не спасает: запрос действительно приходит с вашей машины. Три требования работают только вместе. Origin — это браузер, честно сообщающий, с какой страницы пришёл запрос, а страница эта — не ваш клиент; аутентификация делает «дотянуться до сервера» и «иметь право им пользоваться» разными вещами.

Как описывают инструмент

Первая часть цикла сказала, что сервер может предложить три вещи, и дальше — как почти всё, что пишут про MCP, — говорила только про первую. Инструменты (tools) — это действия: модель просит, что-то происходит, приходит ответ. Ресурсы (resources) — данные, которые сервер даёт прочитать: файл, таблица, страница; у каждого есть адрес, побочных эффектов нет, и есть важное отличие — втянуть ресурс в диалог решает клиент или человек, а не модель, которая до него дотянулась. Промпты (prompts) — заготовленные запросы, которые сервер предлагает в виде меню; в клиенте они всплывают как слэш-команды. На практике инструменты доминируют до состояния монополии. Большинство серверов ничего другого и не предлагает, мой в том числе.

Из чего состоит описание инструмента

Инструмент описывают четыре вещи: имя для программы, человеческое название, описание словами и схема аргументов. Схема — это JSON Schema, скучный стандарт для описания формы данных: какие поля бывают, какие обязательны, какого типа каждое, что каждое значит. Скучность здесь достоинство: библиотека есть в любом языке, и клиент проверит аргументы модели раньше, чем выполнится первая строчка вашего кода. Есть необязательная вторая схема — для ответа, и она выглядит лишней: ответ ведь и так придёт. Она не про ответ. Она позволяет клиенту проверить пришедшее и заранее сообщает вызывающему форму будущего ответа. Рядом с текстом инструмент может вернуть структурированный результат: один регистр — чтобы читала модель, другой — чтобы действовала программа.

Статус не вычитывают из текста

Из этого различия следует правило, которое я поставил бы выше всех остальных в этой статье. Статус не вычитывают из текста.

Я выучил его обычным способом. Мой публикующий сервер кладёт черновики на Medium, управляя настоящим браузером, и адаптеру нужно было понимать, не сорвалось ли сохранение. Он искал в тексте страницы фразы, которые Medium показывает при сбое, — одна из них «something is wrong». И нашёл её в тексте публикуемой статьи, во фразе «if something is wrong you flip back». Успешный прогон был объявлен провалившимся, отработал повтор, появился дубль черновика. Починка состояла в том, чтобы перестать читать страницу как текст и смотреть только на те элементы, которыми страница объявляет статус. Урок обобщается далеко за пределы браузеров: если единственный способ узнать исход — искать слова в тексте, вы его не узнали, а угадали, и рано или поздно ваше ключевое слово встретится в тексте случайно.

Пометки о поведении — утверждение, а не гарантия

Инструмент может нести и пометки о собственном поведении: только чтение, разрушающий, идемпотентный, работающий с внешним миром. Тут же спецификация предупреждает: верить им нельзя, если сервер не ваш. Я скажу резче. Пометки о поведении — самая опасная часть спецификации: «только чтение» выглядит как гарантия, а является обещанием чужого кода. Гарантию обеспечивает тот, кому будет плохо, если она нарушится. Эту — тот, кому будет хорошо.

И следствие, которое стоит настоящих денег: описание инструмента — это текст, который модель читает в каждом запросе, до начала любой работы. За него платят каждый раз, он занимает место в контекстном окне, где мог бы лежать диалог, и он единственный решает, выберут ли ваш инструмент из сорока соседних. Писать описания — не работа технического писателя. Описание и есть интерфейс.

Три ступени ошибок

У большинства систем есть один способ сломаться. У MCP их два по замыслу и третий, которого замысел не видит.

Первая ступень — ошибка протокола: инструмента с таким именем нет, аргументы не подошли под схему, запрос кривой. Ничего не выполнялось. Она возвращается так, как JSON-RPC возвращает ошибки всю свою жизнь, с кодом, и говорит о разговоре, а не о ваших данных. Разбирается с ней обвязка клиента.

Вторая — ошибка выполнения: инструмент отработал, и новости плохие. Файла не было, API ответил 403, ветка уже существует. Здесь MCP делает то, что с первого взгляда выглядит недоразумением: плохая новость приезжает успешным ответом с пометкой, а сама новость написана в тексте. Транспорт сработал — он об этом и сообщает.

Это замысел, а не небрежность. В REST-сервисе ошибка адресована программисту, которого рядом нет: вернули 500, записали в лог, а код, написанный когда-то давно, справится или не справится. В MCP получатель — модель посреди задачи, и она может что-то предпринять. Узнав, что ветка уже есть, она переключится на неё; узнав, что файла нет, посмотрит каталог. Ошибка протокола спрятала бы новость от единственного участника, способного отреагировать: такие ошибки разбирает обвязка клиента, до модели они не доходят.

Третья ступень: успех, которого не было

Третья ступень — та, которую не поймает никакая спецификация. Мне она стоила двенадцати статей.

В прошлом месяце я прошёл по всем двадцати пяти записям, у которых стоял адрес публикации. Двенадцать живых постов на Medium оказались обрезаны: от исходного текста осталось 41–72%, у части пропали блоки кода. И все двенадцать были отчитаны как успех. Адаптер дожидался, пока адрес в браузере сменится на адрес редактирования черновика, считал дело сделанным и закрывал вкладку. Medium в это время досохранял тело инкрементально, несколькими фоновыми запросами, и всё, что не успело уехать на сервер, пропадало молча. Инструмент возвращал валидный URL и ничем не примечательное «ok».

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

Всё остальное доехало целиком — dev.to, Stack Overflow, Hacker News, — и двое из этих троих публикуются через тот же браузер, что и Medium. Спасло их не отсутствие браузера, а форма отправки: один POST со всем текстом, и досылать после него нечего. Не замечали неделями потому, что мониторинг спрашивал, открывается ли ссылка, — она открывалась. Обрезанная статья — совершенно здоровая веб-страница.

Вывод неприятный и неизбежный. Для всего, что имеет значение, проверка «работа сделана» должна быть отделена от инструмента, который её делал, и задавать другой вопрос: не «вернулся ли вызов», а «совпадает ли то, что сейчас лежит на сервере, с тем, что я отправлял». У меня теперь есть скрипт, который вычитывает каждую опубликованную статью обратно и сверяет с исходником. Он существует потому, что успешный ответ — это мнение.

Кто кого пускает

Сначала предупреждение про слово. Во второй части, про стоимость MCP в токенах, «токен» означал единицу, в которой считают текст и выставляют счёт. Здесь это совсем другое: удостоверение, строка, доказывающая, кто вы и что вам можно. Одно слово, два несвязанных значения, и оба общеприняты в своём углу.

Авторизация в MCP тяжелее, чем в REST, по структурной причине. Ключ в REST удостоверяет одну программу, которая зовёт другую. В MCP участников трое — человек, клиент, действующий от его имени, и сервер, у которого могут быть собственные ключи к четвёртой системе, — и всё интересное происходит в зазорах между ними.

    • В REST обычно: ключ в заголовке, и на этом всё. Работает, потому что обе стороны пишет обычно одна команда или хотя бы читает одну документацию.
    • В MCP: OAuth 2.1, обязательный PKCE и отдельные стандарты на то, как клиент вообще узнаёт, у кого спрашивать разрешение. PKCE заслуживает простых слов. Ответ авторизационного потока возвращается через браузер или через операционную систему, обрабатывающую ссылки, и несёт короткоживущий код, а путь этот не приватный: подслушать может другое приложение на той же машине. Поэтому клиент в самом начале придумывает случайный секрет, отправляет только его отпечаток и обязан предъявить оригинал, когда меняет код на токен. Украденный код становится бесполезен тому, кто его украл: код у него есть, секрета нет, обмен не проходит.
    • Привязка токена к получателю: сервер обязан проверить, что токен перед ним выписан именно ему, а не просто что он действителен. Токен — не пароль, а записка, адресованная кому-то; принять записку, адресованную другому, — это и есть способ превратить сервер во вход.
    • Прямой запрет пробрасывать чужой токен дальше, и спецификация называет по имени то, что запрет предотвращает: проблема запутанного посредника. Посредник — это компонент с бо́льшими правами, чем у того, кто его зовёт: ваш сервер с админским ключом. Запутайте его в том, кто спрашивает, — и он потратит собственные права в пользу спрашивающего. Разрешения у зовущего не было, был вопрос, у которого оно есть.
    • Что изменилось в 2026-м: динамическую регистрацию клиента, когда клиент сам прописывается на авторизационном сервере на лету, объявили устаревшей. Это была самая удобная часть потока и самая злоупотребляемая.

Главный практический вопрос: обёртка

Всё выше — про протокол. Этот раздел — про то, на что у вас реально уйдёт время.

Почти всегда MCP-сервер — это переводчик, стоящий перед REST API, который у вас уже есть. Это не новая система, своих данных у неё нет, и, если она исчезнет, нижележащий сервис даже не заметит. Мой ходит в собственный админский API по HTTP с API-ключом — в те же эндпоинты, которыми пользуется админка. У API есть описание OpenAPI; MCP-сервер его не читает — описание, написанное для программиста на этапе интеграции, это не то, что нужно модели во время работы. Это норма, а не срезанный угол.

Интересно другое: что появляется в переводчике такого, чего в API никогда не было. Первым делом описания, и написаны они для читателя, который вашей документации не видел и не увидит. Дальше права — потому что зовёт теперь модель, а не ваш собственный фронтенд, которому можно было доверять хотя бы в том, что он попросит ровно то, что нарисовано на экране. Подтверждения — для всего, что не хочется получить дважды. И фильтрация ответов, которую пропускают все: эндпоинт на шестьдесят полей бесплатен, когда его читает программа, и дорог, когда его читает модель, — она заплатит за все шестьдесят, а нужно было шесть.

Не делайте инструмент на каждый эндпоинт

Дальше начинается соблазн, и он ловит почти всех. У вас двести эндпоинтов; сгенерировать из них двести инструментов — работа на вечер, и результат не просто бесполезен, а дорог и бесполезен. Каждое описание оплачивается в каждом запросе, список уже достаточно длинный, чтобы регулярно выбиралось не то, и форма неправильная на более глубоком уровне: эндпоинты устроены как база данных, а задачи — как человеческая голова. «Закрыть задачу» — одно действие для человека и три вызова для API.

Выборка из пятнадцати работает лучше полного списка, и вся работа — именно в отборе. Самый наглядный пример в моём сервере — набор инструментов, которым в API не соответствует ничего: три проверяльщика. Двое смотрят, переживёт ли статья площадку, до того как её туда положат: никаких markdown-таблиц там, где в редакторе нет блока таблицы; никакого заголовка, дублирующего название; никаких незакрытых блоков кода. А третий вычитывает опубликованную страницу обратно и жалуется. Такого эндпоинта в API нет и не нужно. Человеку он тоже не понадобился бы. Они существуют потому, что зовёт модель, а мне нужен был ограничитель, который она не сможет обойти.

Раз уж я раздаю советы, назову собственную цифру. В моём сервере календаря публикаций 20 инструментов. Это на пять больше, чем я рекомендую, и я хорошо знаю, как так вышло: каждый в день добавления был очевидно оправдан. Никто не решает завести двадцать инструментов. У вас одиннадцать, потом появляется сокращение, экономящее шаг, потом парный листинг к уже имеющемуся, и в одно утро список перестаёт помещаться на экран. Отбор — не решение, принимаемое однажды. Это то, что приходится делать постоянно и против самого себя.

Обёртка

Обёртка

Переезд на редакцию 2026-07-28

У самой большой переработки в истории протокола одна организующая мысль, и весь список изменений читается как одно движение: убрано всё, что предполагало, будто стороны помнят друг друга. Про год отсрочки и про то, почему в день выхода ничего не сломалось, — в первой части; здесь важно, что именно придётся сделать вам.

    • Сессий больше нет: состояние переезжает в аргументы, которые сервер сам же и выдаёт. Клиент возвращает то, что ему дали, и ответить может любой экземпляр сервера.
    • Рукопожатия больше нет: никаких стартовых переговоров, версия и возможности едут в каждом запросе. Запрос стал самодостаточным — протокол стал stateless, в этом весь смысл.
    • Появился отдельный вызов, чтобы спросить сервер, что он умеет: раньше это было побочным эффектом подключения, теперь — вопрос, который задают, когда нужен ответ.
    • Сервер, которому не хватает данных, отвечает «нужны данные», а клиент повторяет запрос, приложив ответ. Пауза больше не живёт внутри открытого соединения.
    • Три возможности устарели, и у каждой есть замена: доступ к папкам — обычные аргументы; обращение сервера к модели — прямой вызов её собственного API, где ему и место; логирование на уровне протокола — поток ошибок или ваша телеметрия, где место ему.

С чего начать, если решились

Начните с одного инструмента. Не с представительной выборки и не с подмножества «только чтение» — с одного, того самого, о чём вам больше всего хочется попросить. Выкатите, поживите с ним пару недель и посмотрите, зовёт ли его модель тогда, когда должна. На этот вопрос отвечает описание, а не код, и переписывать описание вы будете чаще, чем рассчитывали.

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

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

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

Последний совет ничего не стоит, поэтому скажу его прямо. Большинству команд MCP-сервер сейчас не нужен. Правильный первый шаг — не написать его, а посчитать, во сколько токенов обойдутся описания инструментов, в каждом запросе, на той нагрузке, которая у вас есть на самом деле. У половины на этом всё и закончится, и вечер будет потрачен отлично. Сервер окупается там, где набор действий действительно нельзя знать заранее: когда он зависит от того, что подключил пользователь, куда повернёт задача, что решили в разговоре. Если список вызовов можно выписать заранее — выпишите список. Это было верно в 2000 году, и редакция 2026-го тихо сделала это ещё вернее.

Это последняя статья из трёх. Первая — откуда взялись MCP и REST и почему июльская редакция 2026-го развернула MCP обратно к REST; вторая — сколько MCP стоит в токенах.

Источники

Спецификация MCP публикуется на modelcontextprotocol.io; здесь использованы редакции 2025-06-18 и 2026-07-28: разделы о транспортах, инструментах и авторизации; официальный список изменений последней редакции; предложения SEP-2567, SEP-2575, SEP-2322, SEP-2549. Стандарты OAuth 2.1, RFC 8707, RFC 9728, RFC 7591, а также RFC 2119 и RFC 8174 — про капслок. Описанные выше инциденты — из моего собственного публикующего сервера, разбор от 25 июля 2026 года.