MCP-сервер с лендингом на одном URL: как убрать Method Not Allowed в браузере
Вы публикуете MCP-сервер, а пользователь открывает ссылку в браузере и видит белый экран с загадочной ошибкой. «Я думал, здесь искусственный интеллект, а тут какая-то тарабарщина» — такая реакция убивает доверие к инструменту раньше, чем кто-то успевает его подключить. Эта история случилась с MCP-сервером сервиса путешествий Туту, и она обнажила простое, но важное правило: если вы делаете MCP для людей, у него должна быть человеческая посадочная страница.
Ниже — не пересказ новости, а рабочий метод, который превращает «сырой» MCP-эндпоинт в продукт, понятный и агенту, и обычному пользователю. Разбираем, как именно это делается, что положить на лендинг и какие заголовки заставить работать на маршрутизацию.
Почему пользователь видит ошибку, когда просто кликает на ссылку
MCP (Model Context Protocol) — это протокол взаимодействия между хост-приложением (Claude Desktop, Codex, Cursor и другими AI-клиентами) и сервером, который предоставляет инструменты, ресурсы и подсказки. Сервер ожидает JSON-RPC запросы, а браузер при прямом переходе отправляет обычный HTTP GET — и получает в ответ отказ, потому что сервер не умеет отвечать на «человеческий» просмотр.
Классическая картина:
{"detail": "Method Not Allowed"}
Для разработчика это очевидно: «нужно слать POST с нужным телом». Для обычного пользователя, который видит ссылку в статье или чате, это стена. Он не понимает, что делать дальше, и с высокой вероятностью просто закроет вкладку. Потерянный контакт, потерянное доверие.
Проблема усиливается тем, что MCP-серверы всё чаще публикуют в открытом доступе, ими делятся в соцсетях, их адреса копируют и рассылают. Первый клик почти всегда делает человек, а не AI‑агент. И если этот первый опыт отрицательный, агентная коммерция не взлетает даже при технически безупречном сервере.
Решение: одна конечная точка — две аудитории
Идея, мгновенно предложенная сообществом и реализованная в том самом примере Туту, проста и изящна: сервер смотрит на заголовок Accept входящего HTTP-запроса.
- Если клиент указал
Accept: text/html(или браузерное значение по умолчанию), значит, перед нами живой человек, открывший URL в браузере. - Если заголовок
Acceptсодержитapplication/jsonили специфичный MCP-профиль — это AI‑агент, который ожидает JSON-RPC.
Дальше логика ветвится: - Для браузера сервер отдаёт полноценную HTML-страницу — лендинг с инструкциями. - Для агента сервер продолжает работать в штатном режиме JSON-RPC 2.0, обслуживая MCP-сессию.
Такой подход не требует менять URL, не ломает существующих клиентов и добавляет ноль трений для пользователя. Это не «отдельный сайт», а аккуратный роутинг внутри того же самого процесса, который обслуживает протокол.
Важно: решение работает не только на FastAPI или Starlette, а в любом HTTP-стеке, где можно прочитать заголовки запроса до выбора обработчика. В Python это несколько строк, и ниже мы покажем, как именно.
Как добавить лендинг в существующий MCP-сервер: минимальная реализация
Рассмотрим практический каркас на Python и FastAPI, потому что именно он чаще всего используется в экосистеме MCP. Суть не изменится и для Node.js, и для Go.
Базовая логика:
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
from mcp.server.fastmcp import FastMCP
app = FastAPI()
mcp = FastMCP("My MCP Server")
@app.api_route("/mcp", methods=["GET", "POST"])
async def mcp_endpoint(request: Request):
accept_header = request.headers.get("accept", "")
# Если браузер запрашивает HTML — отдаём лендинг
if "text/html" in accept_header:
html_content = """
<html>
<head><title>My MCP Server</title></head>
<body>
<h1>🌐 My MCP Server</h1>
<p>Этот сервер предоставляет инструменты для AI‑агентов...</p>
...
</body>
</html>
"""
return HTMLResponse(content=html_content)
# Иначе обслуживаем MCP (POST и GET для SSE)
return await mcp.handle_request(request.scope, receive=request.receive, send=...) # упрощённо
В реальном проекте HTML стоит вынести в шаблон, но принцип именно такой. При старте сервера на одном порту и одном пути (/mcp) одновременно живут и лендинг, и RPC-транспорт.
Альтернативный вариант для тех, кто использует Starlette или bare ASGI: можно перехватывать событие lifespan и добавлять route через router.add_route, проверяя заголовок в самом представлении (view). Главное — не пытаться «угадать» агента по User‑Agent: это ненадёжно, а вот Accept определён стандартами и практически не фальсифицируется браузерами.
Что обязательно должно быть на MCP‑лендинге: чеклист содержания
Человек, попавший на страницу, скорее всего хочет понять три вещи: что это, как подключить и зачем ему это нужно. Поэтому лендинг не должен быть тизером — он должен быть мини-документацией.
Обязательные элементы:
- [ ] Название сервера и краткое описание (1-2 предложения).
- [ ] Примеры возможностей: «может искать билеты», «генерирует SQL по описанию», «создаёт задачи в трекере».
- [ ] Инструкция по подключению для популярных клиентов: Claude Desktop, Codex, Cursor, Continue.
- Пример конфигурации для
claude_desktop_config.jsonилиmcp.json. - Прямая копируемая строка команды запуска, если сервер работает через
npx,uvxилиpip. - [ ] Ссылка на полную документацию или репозиторий (если есть).
- [ ] Ограничения и требования (токен авторизации, версия Python, регион).
- [ ] Несколько живых примеров запросов и ответов — чтобы пользователь сразу видел ценность.
- [ ] Контакты или форма обратной связи для вопросов.
Эти пункты закрывают путь от первого клика до первого полезного вызова агента без внешней помощи. Для внутренних корпоративных MCP‑серверов можно добавить информацию о том, как получить доступ, но структура остаётся той же.
Таблица: как сервер различает аудиторию и что получает каждая
Для наглядности соберём логику роутинга в компактную таблицу. Она пригодится и при проектировании, и при объяснении команде.
| Клиент | Заголовок Accept |
Метод запроса | Ответ сервера | Поведение для пользователя |
|---|---|---|---|---|
| Браузер (человек) | text/html, application/xhtml+xml ... |
GET | HTML‑лендинг (200 OK) | Видит понятную страницу с инструкцией |
| curl без явного заголовка | */* |
GET | Возврат 405 или JSON-ошибка? ⚠️ | Обработать отдельно или показать подсказку |
| AI‑агент (JSON-RPC) | application/json или отсутствует |
POST | JSON-RPC 2.0 ответ | Нормальная работа MCP |
| AI‑агент (SSE‑транспорт) | text/event-stream |
GET | SSE-поток | Подписка на события |
| Инструмент мониторинга | отсутствует | GET | Можно отдать JSON со статусом сервера | healthcheck для DevOps |
Строка с */* — пограничный случай. Если человек использует curl без флагов, он увидит ошибку, что неудобно. Решение: при GET и */* либо показывать страницу (как для браузера), либо отдавать текстовую подсказку: «Для подключения используйте POST с телом JSON‑RPC. Для справки откройте этот URL в браузере». Это уже деталь реализации, но продумать её стоит заранее.
Контрольный список: перед тем как публиковать MCP-сервер
Перед тем как выложить ссылку в открытый доступ или разослать коллегам, пробегитесь по этому списку. Он сэкономит часы на поддержку и убережёт от потери первых пользователей.
Проверка человеческого входа: - [ ] Откройте URL MCP-сервера в трёх разных браузерах (Chrome, Safari, Firefox) — везде открывается страница, а не белый экран или JSON-ошибка. - [ ] Страница содержит все элементы чеклиста содержания (см. выше). - [ ] Кнопки копирования конфигураций работают (если есть). - [ ] Мобильная версия страницы хотя бы читаема (базовый viewport).
Проверка агентного входа: - [ ] Подключитесь к серверу через Claude Desktop или аналогичный клиент — инструменты отображаются корректно. - [ ] Выполните один реальный вызов инструмента и убедитесь, что ответ не содержит HTML-примесей. - [ ] Проверьте, что при POST-запросе без Accept: text/html сервер не уходит в HTML-ветку.
Проверка граничного поведения: - [ ] GET без заголовков (curl http://your-server/mcp) — выдаёт разумную подсказку, а не 405 с пустым телом. - [ ] POST с Accept: text/html (маловероятно, но лучше перестраховаться) — можно отдать HTML или вернуть ошибку с пояснением, что браузеры так не делают.
Эксплуатационные мелочи: - [ ] Лендинг не кэшируется слишком агрессивно (заголовок Cache-Control), чтобы пользователи не получали старую инструкцию после обновления. - [ ] Если сервер требует аутентификации, на лендинге объяснено, как её получить или передать.
Прохождение этого списка занимает не больше получаса, но резко повышает шанс, что MCP-сервер назовут «удобным», а не «очередной штукой для гиков».
Почему это влияет на adoption и что делать прямо сейчас
Разработчик может считать, что страница-инструкция — это косметика, а настоящая работа происходит, когда агент начинает дёргать инструменты. Но реальность такова: первое касание с продуктом формирует отношение к нему. Если это касание — непонятная ошибка, то продукт воспринимается как сырой и небезопасный.
Пример: после того как MCP-сервер Туту получил лендинг, ссылку стали пересылать в чатах путешественников, а не только в AI-каналах. Обычные пользователи начали понимать, что «это можно подключить к Клоду и он сам найдёт билеты». Дальше — сарафанное радио и рост агентной коммерции.
Если вы прямо сейчас разрабатываете MCP-сервер для своей компании или личного проекта, выделите час на создание такой страницы. Это несложно, а отдача в виде понятности и доверия — огромна.
И самое важное: когда ваша жена, муж, коллега из HR или клиент откроет этот URL, он не должен растерянно показывать телефон с белым экраном. Он должен увидеть инструкцию и сказать: «О, так я тоже могу это использовать».