MCP-сервер с лендингом: как убрать 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, он не должен растерянно показывать телефон с белым экраном. Он должен увидеть инструкцию и сказать: «О, так я тоже могу это использовать».

Источники