enВойти в Senler

Подписанный контекст запросов к MCP

Для чего нужен контекст

Senler добавляет заголовок X-Senler-MCP-Context к запросам подключений из раздела «MCP-серверы» проекта: собственных серверов и серверов, установленных из шаблонов. В заголовке находится подписанный JWT. Дополнительной настройки в кабинете не требуется.

Подпись позволяет вашему MCP-серверу проверить, что указанные в контексте идентификаторы выдал Senler и их не изменили после подписания. Сам HTTP-запрос может отправлять Senler, MCP Router или AI-провайдер при прямом подключении. Подпись подтверждает происхождение контекста, а не сетевой адрес отправителя.

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

Это соглашение интеграции Senler. Оно не добавляет обязательное требование к стандарту авторизации MCP.

Контекст и режим авторизации

Заголовок передаётся при любом выбранном режиме авторизации, в том числе «Без авторизации». Он не меняет выбранный аккаунт и не заменяет токен доступа, OAuth или JWT авторизации лида.

Что проверяет серверГде находятся данные
Кто выдал контекст и к какому подключению он относитсяX-Senler-MCP-Context, тип JWT senler-mcp-context+jwt
Доступ общего аккаунта проектаТокен, OAuth или секретные заголовки выбранного способа авторизации
Внешняя идентичность лида при подписанной авторизацииНастроенный заголовок авторизации лида, по умолчанию X-Senler-Identity, тип JWT senler-lead+jwt

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

Например, запрос может содержать действующий проектный токен и lead_id в контексте. Это означает, что вызов выполняется с данными доступа проекта в диалоге указанного лида. Сам lead_id не доказывает вход этого человека во внешний аккаунт. Для персональных данных используйте авторизацию лида, проверяйте подтверждение внешней идентичности и права на конкретное действие.

Контекст не содержит external_id, user_hash, признака identity_verified, имени, email или секретов аккаунта. JWT подписан, но не зашифрован: получатель может прочитать его содержимое.

Формат заголовка

X-Senler-MCP-Context: eyJ...eyJ...signature

Значение — компактный JWT без префикса Bearer. Имя заголовка зарезервировано: его нельзя использовать в собственных заголовках авторизации или задавать в качестве заголовка JWT лида.

Заголовок JWT содержит alg: "ES256", typ: "senler-mcp-context+jwt" и kid — идентификатор открытого ключа. Используется ключ EC P-256. Тело содержит следующие поля:

ПолеЗначение
vВерсия формата: 1
issПубличный origin API Senler; для основного окружения — https://api.senler.io
audПубличный адрес целевого MCP: origin и путь без параметров запроса
iat, expВремя выпуска и окончания действия в секундах Unix; exp - iat = 300
jtiУникальный идентификатор выпущенного JWT
mcp_server_idИдентификатор подключения MCP в Senler
auth_modeproject или lead, выбранный режим авторизации
project_idИдентификатор проекта, если доступен
lead_idИдентификатор лида, если вызов выполняется с контекстом лида
agent_id, dialog_id, request_idИдентификаторы агента, диалога и запуска, когда они известны

Необязательные поля без значения отсутствуют. Например, проверка проектных данных доступа из настроек может выполняться без lead_id и dialog_id. Сервер не должен угадывать лида по предыдущему запросу.

aud для https://crm.example.com/mcp?region=eu будет https://crm.example.com/mcp. При внутренней маршрутизации Senler здесь сохраняется публичный адрес подключения. Если один URL обслуживает несколько проектов или клиентов, дополнительно проверяйте ожидаемые project_id и mcp_server_id.

Проверка подписи на MCP-сервере

Открытые ключи опубликованы в формате JWKS по адресу ключей контекста Senler. Для другого окружения используется тот же путь на его публичном API. Закрытый ключ MCP-серверу не передаётся.

  1. Задайте доверенные iss, URL JWKS и ожидаемый адрес вашего MCP в конфигурации сервера. Не выбирайте адрес загрузки ключей по непроверенным полям входящего JWT.
  2. По kid выберите ключ из доверенного JWKS и проверьте подпись, разрешая только ES256 и тип senler-mcp-context+jwt.
  3. Проверьте iss, aud, iat, exp, версию v = 1, обязательные поля и допустимый auth_mode. Допуск расхождения часов должен быть небольшим.
  4. Сопоставьте подключение и проект с разрешёнными у вас значениями. Затем отдельно проверьте авторизацию аккаунта и права на запрошенное действие.

Пример для Node.js с библиотекой jose:

import { createRemoteJWKSet, jwtVerify } from 'jose';

const issuer = 'https://api.senler.io';
const audience = 'https://crm.example.com/mcp';
const jwks = createRemoteJWKSet(
  new URL('/.well-known/senler-mcp-jwks.json', issuer),
);

export async function verifySenlerContext(token, expectedProjectId, expectedServerId) {
  if (!token || !expectedProjectId || !expectedServerId) {
    throw new Error('MCP context and expected connection are required');
  }
  const { payload } = await jwtVerify(token, jwks, {
    issuer,
    audience,
    algorithms: ['ES256'],
    typ: 'senler-mcp-context+jwt',
    requiredClaims: ['v', 'iat', 'exp', 'jti', 'mcp_server_id', 'auth_mode'],
    maxTokenAge: '5m',
    clockTolerance: 30,
  });
  if (
    payload.v !== 1 ||
    !['project', 'lead'].includes(payload.auth_mode) ||
    typeof payload.jti !== 'string' || !payload.jti ||
    typeof payload.iat !== 'number' || typeof payload.exp !== 'number' ||
    payload.exp <= payload.iat || payload.exp - payload.iat > 300 ||
    payload.project_id !== expectedProjectId ||
    payload.mcp_server_id !== expectedServerId
  ) {
    throw new Error('Unexpected MCP context');
  }
  return payload;
}

Передавайте в функцию значение HTTP-заголовка. Ожидаемые проект и подключение берите из настроек вашего сервера. Простого декодирования JWT без jwtVerify недостаточно.

Создавайте клиент JWKS один раз на процесс: библиотека кеширует ключи и ограничивает их повторную загрузку. При неизвестном kid она может обновить JWKS. Если ключ не найден или подпись не прошла проверку, не переходите к доверию неподписанным данным.

Общий JWKS относится только к senler-mcp-context+jwt. Для senler-lead+jwt используется отдельный ключ конкретного подключения из его настроек.

Срок действия и совместимость

JWT действует пять минут. Это срок действия предъявляемого контекста, а не время жизни диалога или входа пользователя. Senler подписывает данные автоматически; лид не вводит токен и не авторизуется заново каждые пять минут.

  • Работа через Senler или MCP Router: новый контекст создаётся для каждого исходящего HTTP-запроса к MCP, включая получение методов, вызов инструмента и служебные запросы транспорта. Авторизация аккаунта передаётся своим способом.
  • Прямое подключение AI-провайдера к MCP: Senler передаёт JWT в заголовках запуска. Провайдер может использовать его для нескольких запросов, а обновить заголовок внутри такого запуска Senler не может. При запросе позже exp сервер, проверяющий контекст, должен его отклонить. Для длительных запусков с обязательной проверкой выбирайте работу через Senler после проверки совместимости инструментов с этим режимом.

Например, контекст, выпущенный в 12:00, действует до 12:05. При работе через Senler вызов в 12:06 получает новый контекст до 12:11. В прямом запуске провайдера старый контекст в 12:06 уже недействителен. Исключение — свои серверы с авторизацией лида через JWT: Senler всегда выполняет их вызовы сам, даже при прямом режиме агента, и передаёт свежие JWT авторизации и служебного контекста. Для остальных подключений действует выбранный режим.

Проверяйте срок при приёме запроса. Истечение JWT во время уже принятой операции само по себе не требует её отмены. Подпись не содержит хеш тела HTTP-запроса и не гарантирует однократное выполнение действия. jti обозначает JWT, а не бизнес-операцию; в прямом режиме один JWT может сопровождать несколько вызовов. Для защиты от повторной записи используйте отдельный ключ идемпотентности операции.

Серверы, игнорирующие дополнительный заголовок, продолжают использовать выбранную авторизацию. Если перед MCP стоит прокси с перечнем разрешённых заголовков, добавьте в него X-Senler-MCP-Context. Не включайте обязательную проверку до настройки доверенного JWKS, ожидаемых значений и подходящего режима вызовов.

Настройка ключей в собственном окружении Senler

Этот раздел нужен администратору окружения Senler. Разработчику принимающего MCP достаточно открытого JWKS.

API и MCP Router используют один выделенный ключ MCP_CONTEXT_SIGNING_PRIVATE_KEY_BASE64: закрытый ключ EC P-256 в формате PKCS#8 PEM, закодированный в base64. В обоих сервисах задайте одинаковый API_PUBLIC_URL: HTTPS-origin без пути. Для локальной разработки допускается HTTP на loopback. При отсутствии или неверном ключе сервис не запускается с неподписанными запросами.

Ключ создаётся один раз при подготовке окружения и хранится как секрет. Его не нужно создавать при каждом запросе или обычном перезапуске. Открытая часть автоматически попадает в JWKS API. Генератор окружения remote-dev сохраняет существующий ключ и передаёт его Router.

Для плановой ротации MCP_CONTEXT_VERIFICATION_JWKS может содержать JSON-документ JWKS с дополнительными открытыми ключами, не более четырёх. Сначала опубликуйте новый открытый ключ рядом с действующим и дайте кешам обновиться. Затем замените ключ подписи во всех экземплярах API и Router, сохраняя старый открытый ключ в JWKS. Удаляйте старый ключ после завершения выдачи старых JWT и истечения их срока с учётом кеширования и расхождения часов. JWKS API отдаётся с Cache-Control: public, max-age=60; учитывайте также кеш проверяющей библиотеки.