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

MCP сервер

API B1 поставляется с сервером MCP (Model Context Protocol) на /mcp. Любой MCP-совместимый клиент — Claude Code, Claude Desktop, OpenAI Agents SDK, Cursor или ваш собственный — может подключиться к нему и вызвать базовый REST API от имени аутентифицированного пользователя церкви. Это тонкая, универсальная обертка: три универсальных инструмента открывают всю поверхность API динамически, а не ручное моделирование каждой конечной точки, плюс один инструмент руководства для конструктора веб-сайтов.

Перед началом

  • B1 API ключ (cak_…) с областями, которые должен иметь клиент
  • Доступный хост B1 API — https://api.churchapps.org для размещенных церквей или ваше собственное развертывание
  • Клиент MCP. Смотрите Claude и ChatGPT для настройки пользователя

Конечная точка

POST /mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer cak_<prefix>.<secret>
АспектЗначение
Путь/mcp (относительно хоста API)
МетодPOST только — запрос/ответ и потоковая SSE происходят на одной и той же конечной точке
ТранспортMCP Streamable HTTP
Модель сессииБез состояния. Свежий экземпляр MCP сервера создается на запрос — нет session id, нет возобновления
AuthBearer токен. Работают оба cak_… API ключи и B1 JWTs; разрешение то же самое, что и любой другой аутентифицированной конечной точки

Запрос, чей заголовок Authorization отсутствует или недействителен, возвращает:

{ "error": "Unauthorized — MCP требует допустимый bearer токен (cak_* API ключ или JWT)." }

с HTTP 401.

Инструменты

Три универсальных инструмента плюс один справочник. Модель использует list_endpoints для обнаружения, describe_endpoint для изучения формы полезной нагрузки, api_call для фактического вызова API и describe_page_builder когда задача включает содержимое веб-сайта.

list_endpoints

Возвращает полный инвентарь зарегистрированных REST маршрутов, отфильтрованный по необязательной подстроке и/или HTTP глаголу. Каждая запись включает имя контроллера и области API ключа, которые наиболее вероятны.

Ввод:

ПолеТипОписание
filterstring (optional)Игнорирующая регистр подстрока, сопоставленная с путем, например "people"
methodenum (optional)GET / POST / PUT / DELETE / PATCH

Вывод: документ JSON формы

{
"total": 24,
"endpoints": [
{
"method": "GET",
"path": "/membership/people",
"controller": "PersonController.getAll",
"likelyScopes": ["people:read", "people:write"]
}
]
}

Инвентарь создается один раз при запуске API из живой таблицы маршрутов — все, что вы можете hit с curl, появляется здесь.

describe_endpoint

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

Ввод:

ПолеТипОписание
methodstringHTTP глагол
pathstringПолный путь, как возвращено list_endpoints

Вывод: для отобранных конечных точек пример с summary, requestBody и responseSample. Для неотобранных конечных точек резервное сообщение, инструктирующее модель вызвать GET первым, чтобы увидеть форму. Примерно дюжина высокотрафик маршрутов (люди, группы, пожертвования, посещаемость, фонды) отобраны.

api_call

Вызывает выбранную REST конечную точку, в процессе, через ту же стек Express middleware, что и обычный HTTP запрос — аутентификация, парсинг тела, аудит логирования и per-church scoping все применяются.

Ввод:

ПолеТипОписание
methodenumGET / POST / PUT / DELETE / PATCH
pathstringПуть, включая любой префикс модуля, например /membership/people
queryobject (optional)Плоский объект параметров строки запроса
bodyany (optional)Тело JSON запроса — обычно массив объектов модели для POST

Вывод:

{
"status": 200,
"truncated": false,
"body": [ /* ответ JSON контроллера */ ]
}

Результат инструмента помечен isError: true для любого ответа со статусом ≥ 400.

describe_page_builder

Единственный неуниверсальный инструмент: статичный, самодостаточный справочник по созданию веб-страниц через конечные точки /content/* — модель данных Page → Section → Element, рабочий процесс создания, каждый elementType с его формой answersJSON, параметры уровня раздела, такие как форма разделителей dividerTop/dividerBottom, и проработанный сквозной пример. Он не требует входных данных и отражает каталог элементов, поддерживаемый в редакторе B1Admin (см. Архитектура конструктора веб-сайтов). Ожидается, что агенты будут вызывать его один раз перед созданием или редактированием содержимого страницы, а затем действовать через api_call.

Модель аутентификации

Сам MCP запрос проходит через CustomAuthProvider.getUser() — тот же путь, который использует каждая аутентифицированная B1 конечная точка. Носитель cak_… разрешается в Principal, чьи разрешения — это текущие RBAC выдавшего человека, пересекаемые с областями, предоставленными ключом. Это пересечение пересчитывается на каждом запросе, поэтому:

  • Удаление области из ключа (путем удаления и пересоздания) отключает доступ на следующий вызов.
  • Удаление разрешения из базового человека в B1Admin отключает доступ на следующий вызов, даже если ключ все еще существует.

Для вложенных вызовов api_call, исходный заголовок Authorization копируется на синтетический запрос, поэтому CustomAuthProvider запускается снова и пересечение области переприменяется для каждого вызова. Нет кэширования токена.

Список блокировки пути

Небольшой набор маршрутов недоступен через api_call, даже с действительным ключом:

ПаттернПочему
/giving/donate/webhook/*Конечные точки вебхука провайдера ожидают исходные, проверенные подписью тела от Stripe/PayPal — не от других вызывающих
/membership/oauth/clients*Регистрация клиента OAuth только для оператора
/membership/people/apiEmailsЗаблокировано оператором jwtSecret, не разрешения пользователя
Любой маршрут, ожидающий multipart/form-dataЗагрузки файлов несовместимы с JSON-RPC

Заблокированный путь возвращает результат инструмента isError: true с описательным сообщением; базовый маршрут никогда не вызывается.

Лимит размера ответа

Каждый ответ api_call ограничен 64 KB захватываемого вывода. Если запрос превышает лимит, ответ содержит "truncated": true и модель ожидается повторить с более узкими параметрами запроса. Это предотвращает один результат инструмента от переполнения контекстного окна клиента.

Ограничение скорости

На /mcp нет ограничения скорости уровня приложения. Дросселирование отложено на API Gateway / Lambda параллелизм в production и на любые ограничения, которые применяет ваш обратный прокси при self-hosted развертываниях.

Обнаружение OAuth

MCP сервер не рекламирует метаданные OAuth 2.1 (/.well-known/oauth-authorization-server, динамическую регистрацию клиента, поток PKCE). Клиенты, которые требуют обнаруженные MCP серверы — в частности, пользовательский интерфейс "Add custom connector" Claude.ai и функция "Connectors" ChatGPT — не могут подключиться без этой поверхности.

Клиенты, которые принимают статический bearer токен в своей конфигурации — Claude Code, Claude Desktop, OpenAI Agents SDK, Cursor, пользовательский код — работают сегодня. Существующий OAuthController уже выпускает токены через authorization-code + PKCE для приложений третьих сторон; слой обнаружения, совместимый с MCP-spec, на его вершине закрыл бы пропасть.

Локальная разработка

Конечная точка MCP монтируется наряду со всем остальным, когда API запускается локально:

cd Api
npm run dev
# Server listening on http://localhost:8084

При запуске логирование строки 📡 MCP server ready at /mcp — N routes in inventory подтверждает, что инвентарь был создан.

Зондируйте его с помощью MCP Inspector:

npx @modelcontextprotocol/inspector

В пользовательском интерфейсе Inspector укажите на http://localhost:8084/mcp и установите заголовок Authorization на Bearer cak_<prefix>.<secret>. Сначала вызовите list_endpoints; вы должны увидеть полный список маршрутов. Затем api_call({ method: "GET", path: "/membership/people" }) должен возвращать ваших локальных семян людей.

Структура кода

MCP сервер находится в src/modules/mcp/ в репозитории Api. Основные файлы:

ФайлНазначение
McpController.ts@controller("/mcp"); подключает StreamableHTTPServerTransport для каждого запроса
McpServer.tsСоздает MCP Server, регистрирует четыре инструмента
RouteInventory.tsПроходит по метаданным inversify-express-utils при запуске для перечисления маршрутов
internalDispatch.tsСинтетический req/res, который переходит в Express приложение в процессе
tools/listEndpoints.ts, describeEndpoint.ts, apiCall.ts, describePageBuilder.ts
examples.tsОтобранные образцы запроса/ответа для высокотрафик конечных точек

Связанные материалы