Перейти к содержимому
MCP-сервер

Своя инсталляция

Что настроить, чтобы подключение работало «из коробки».

Для администраторов, разворачивающих сервис у себя.

Чтобы пользователи подключались адресом и входили через браузер — без ручных токенов, — сервер должен сообщать клиенту, что доступ закрыт и как авторизоваться.

Для обнаружения входа:

  1. Обращение к /mcp без токена возвращает 401 с заголовком WWW-Authenticate: Bearer resource_metadata="…". Ответ 200 без авторизации приводит к тому, что клиент считает сервер открытым и никогда не предложит вход.
  2. Адреса /.well-known/oauth-protected-resource и /.well-known/oauth-authorization-server отдаются без авторизации. Если прокси закрывает их, клиент не прочитает описание входа.
  3. В метаданных issuer совпадает с доменом инсталляции.

Проверить:

Окно терминала
curl -i https://app.layers.md/.well-known/oauth-protected-resource
curl -i -X POST https://app.layers.md/mcp -H 'Content-Type: application/json' -d '{}'

Первая команда должна вернуть 200 с описанием, вторая — 401 с WWW-Authenticate.

Самая частая ошибка: .well-known не доходит до сервера

Заголовок раздела «Самая частая ошибка: .well-known не доходит до сервера»

Она незаметна, потому что всё остальное работает. /mcp отвечает, страница подключения открывается, сервер выглядит поднятым — а подключиться нельзя ни из одного клиента. Клиент читает наш указатель на /.well-known/oauth-protected-resource, получает там ответ приложения вместо описания входа и делает единственный вывод: сервер не умеет OAuth.

Причина в том, что этот путь лежит в корне, а не под /mcp. Правило прокси, написанное для /mcp, его не покрывает — нужна отдельная строка:

location /mcp { proxy_pass http://mcp_upstream; }
location /.well-known/ { proxy_pass http://mcp_upstream; }

В Nginx важны тип и специфичность location, а не порядок обычных префиксных блоков. Проверьте, что регулярные выражения или дополнительные правила авторизации не перехватывают .well-known и OAuth-маршруты. Не закрывайте discovery входом в приложение.

Вход должен завершаться от имени пользователя

Заголовок раздела «Вход должен завершаться от имени пользователя»

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

Для сохранности OAuth-билетов между перезапусками задайте стабильный секрет MCP_OAUTH_TICKET_SECRET через защищённую конфигурацию. При нескольких репликах он должен быть одинаковым. Не коммитьте значение и не меняйте его без плана переподключения пользователей.

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

Не включайте общий LAYERS_BOOTSTRAP_TOKEN как замену пользовательскому входу в многопользовательском SaaS. Не выключайте layers.backend.requireIncomingAuth ради успешного подключения клиента. HTTPS, пересылка заголовка Authorization и корректный внешний адрес нужны на всём пути.

Сервер проверяет это сам. Откройте /mcp в браузере — в ответе есть auth.discovery:

"discovery": { "url": "…/.well-known/oauth-protected-resource", "reachable": true }

reachable: false означает, что по этому адресу отвечает не MCP — рядом будет сказано, что именно поправить. Проверять стоит сразу после установки: иначе поломку находит первый пользователь, и находит в виде, из которого причина не следует.

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

Настройка нужна в одном случае: приложение и /mcp живут на разных адресах. Тогда укажите адрес приложения явно, иначе ссылка приведёт человека на адрес сервера, где страницы нет:

Окно терминала
LAYERS_BACKEND_FRONTENDBASEURL=https://app.your-domain.example

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

Проверьте ссылку в результате чтения существующей задачи или страницы: она должна начинаться с домена приложения. Создавать объект ради этой проверки не нужно.

Второй способ входа — токен рабочего пространства, для скриптов и серверных интеграций. Токен показывается один раз при создании; при компрометации отзывается в настройках.

Значение задаётся при запуске и не требует пересборки.

Например, mcp.tools.profile=pages. Запросом можно сузить созданный при старте каталог, но вернуть отсутствующие в процессе инструменты нельзя. После изменения конфигурации перезапустите сервер и обновите каталог в клиентах. Подробнее о наборах.

Сайт собирается один и тот же для любой инсталляции — домен и название сервиса задаются двумя переменными окружения:

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

Адрес берётся при отдаче страницы, по порядку:

  1. переменная окружения MCP_HOST контейнера — если задана, побеждает всегда;
  2. адрес, по которому страницу сейчас открыли: её отдал тот самый сервер, к которому подключаются;
  3. заглушка https://app.layers.md — если ни того, ни другого.

Задавать MCP_HOST нужно только когда сервер отвечает на несколько имён и людям надо показывать одно определённое:

MCP_HOST=https://mcp.example.com
Окно терминала
MCP_HOST=https://mcp.example.com MCP_NAME=Acme npm run build

MCP_HOST подставится в адрес на всех страницах, MCP_NAME — в заголовок сайта и в тексты. Без MCP_NAME документация говорит нейтрально: «MCP-сервер», «ваше рабочее пространство».