MCP Gateway
MCP Gateway — Этап 1 (KLIP, только чтение)
Единый защищённый сервис, который позволяет авторизованным сотрудникам Energi-Up запрашивать KLIP на естественном языке через Claude, не открывая приложение. Строго только чтение.
Целевое развёртывание (PRD Q3, теперь закрыто): <gateway-hostname> -> <gateway-public-ip>
(ECS-MCP, ap-southeast-5). Обратите внимание: имя хоста — mcp-gw, а не mcp.example.com,
как предполагалось в документах v0.9; PRD/TSD следует обновить соответствующим образом.
Документы: PRD v0.9 · TSD v0.9 · Руководство по внедрению · Ревью дизайна · Runbook развёртывания
1. Зафиксированные версии (T-1)
Ревision спецификации MCP и версия SDK зафиксированы здесь. Если собственная документация SDK расходится с проектными документами, решающим является SDK — он определяет точный API.
Компонент | Зафиксировано | Примечания |
| 1.30.0 (точно, без каретки) | Опубликован 27 июля 2026 г. Предоставляет OAuth AS, транспорт Streamable HTTP и |
Ревision протокола MCP | 2025-11-25 реализован; формат провода совместим с клиентами | Ревision |
Node.js | 22 LTS ( | |
TypeScript | 5.9.3, | |
express | 5.2.1 | Повышено с 4.x из TSD: SDK зависит от |
zod | 4.4.3 | Повышено с 3.x из TSD: определения типов SDK нацелены на zod 4. Обратите внимание: |
jose | 6.2.9 | Токены шлюза RS256. |
| 2.1.0 | Замена для |
axios | 1.19.0 | Клиент KLIP, за защитой метода. |
pg | 8.23.0 | |
PostgreSQL | 16 (контейнер) | |
nginx | mainline от nginx.org | Конфигурация в |
Related MCP server: Snowflake MCP Server
2. Структура
src/
core/ config, logger, db, audit, cache, rateLimit, semaphore, migrate, errors
adapters/klip/ routes(APPENDIX A) · fields(APPENDIX A) · client(guard) · session · paginate · normalize
tools/klip/ 9 tool definitions + shared parameter plumbing
mcp/ server, envelope, runner
auth/ keys, hub(OIDC RP), users, clients, tokens, provider, loginPage
http/ app, consent(Hub + break-glass), health, origin, clientIp
migrations/ idempotent SQL (001 schema, 002 Hub OIDC)
deploy/ nginx config, backup sidecar
test/ 120 tests + mock KLIP and mock Hub fixturesПравило уровней (T-3): tools → adapters → core. Ничто не импортирует http/, кроме
точки входа. Вся бизнес-нормализация находится в adapters/klip/normalize.ts, который
ничего не импортирует, поэтому его можно юнит-тестировать изолированно.
3. Аутентификация — Downstream Hub OIDC
Пилотные пользователи входят через Downstream Hub (OIDC). Шлюз остаётся
сервером авторизации, с которым общается Claude; Hub — один шаг внутри его собственного
потока /authorize.
Мы сознательно не используем ProxyOAuthServerProvider из SDK. Проксирование
передало бы Claude токен Hub, что нарушило бы привязку аудитории по RFC 8707, дало бы
Claude более широкие области Hub, чем klip:read, и вывело бы выпуск токенов из-под
нашего контроля, так что аварийный рубильник S8 больше не смог бы аннулировать активные сессии.
Claude ──/authorize──▶ gateway ──302──▶ Downstream Hub ──302──▶ /authorize/hub/callback
│ │
│ validate id_token (sig/iss/aud/nonce) │
│ check the pilot ALLOWLIST │
◀──────────────────────────────────────────────┘
└──302 code──▶ Claude ──/token──▶ gateway token (klip:read)Аутентификация — это не авторизация. Hub доказывает, кто этот человек; таблица
users решает, может ли он пользоваться коннектором. На этапе 1 используется один общий
сервисный аккаунт KLIP, поэтому каждый допущенный пользователь может читать всё, что может
прочитать MCP_READONLY (ревью H8) — членство в пилоте и есть контроль доступа к данным.
Аккаунт Hub, которого нет в списке, получает 403, а ограничение <= 15 обеспечивается
командой user:add.
В потоке выполняются два обмена PKCE; не смешивайте их. Собственный
code_challenge от Claude защищает участок Claude→шлюз (обрабатывается SDK); отдельный
верификатор, который шлюз хранит на своей стороне, защищает участок шлюз→Hub.
Какой клиент Hub? Собственный шлюза — не KLIP
Зарегистрируйте новый OIDC-клиент для MCP Gateway. Не используйте повторно регистрацию KLIP в Hub, даже если KLIP уже зарегистрирован в тестовом DWS Hub.
Hub касается только одной из двух границ доверия:
Граница | Учётные данные | Участвует ли Hub? |
Claude → шлюз (какой человек спрашивает) | собственный клиент Hub шлюза + пилотный список допуска | да |
шлюз → KLIP (чтение данных) |
| нет — PRD §7 исключает это явно |
Повторное использование клиента KLIP конкретным образом ломает первую границу. Шлюз
проверяет aud ID-токена по своему собственному HUB_CLIENT_ID; совместное использование
идентификатора клиента KLIP означает, что ID-токен, выпущенный при входе в KLIP, был бы
принят шлюзом — это форма confused-deputy. Отдельные клиенты — это то, что делает две
полагающиеся стороны различимыми. Это также сохраняет независимость списков разрешённых
redirect-URI, секретов клиентов, графиков ротации, записей аудита Hub SSO и переключателей
отключения — отключение клиента Hub коннектора не должно останавливать вход в KLIP.
Ни одно из этих изменений не требует изменений на стороне KLIP. K1–K4 не затрагиваются.
Два экземпляра Hub, две регистрации
Зарегистрируйте шлюз отдельно в каждом Hub и сопоставьте их с соответствующим KLIP:
Этап | KLIP_ENV | HUB_ISSUER | Клиент |
4–6 (сборка, staging UAT) |
| тестовый DWS Hub | клиент шлюза в тестовом Hub |
7+ (продуктовый cutover) |
| продуктовый Hub | отдельный клиент шлюза в продуктовом Hub |
Сопоставление проверяется при загрузке, потому что ошибка в одну сторону опасна, а не просто неаккуратна:
KLIP_ENV=production+ тестовыйHUB_ISSUER→ шлюз отказывается запускаться. Иначе любой, кто может создать аккаунт в тестовом Hub, получил бы доступ к реальным коммерческим данным.KLIP_ENV=staging+ продуктовыйHUB_ISSUER→ предупреждение и продолжение работы.
hub:check выводит обнаруженное сопоставление, так что cutover можно проверить:
pairing: KLIP staging <-> Hub testingЧто требует DWS Hub (это не ванильный OIDC)
Согласно Docs/SSO-TARGET-APP-INTEGRATION.md. Четыре пункта отличаются от значений по
умолчанию, которые предполагает библиотека OIDC-клиента, и три привели бы к полному отказу:
DWS Hub | |
Тип клиента | публичный, PKCE S256 — |
Discovery |
|
Тело токена | JSON; форма-кодирование возвращает |
Области | только |
| обязателен в запросе токена и должен совпадать байт-в-байт |
Поэтому HUB_DISCOVERY_URL настраивается явно, HUB_CLIENT_SECRET необязателен,
а HUB_TOKEN_BODY по умолчанию равен json с одноразовым откатом на форму при
unsupported_grant_type (с записью в лог того, что сработало, чтобы это можно было зафиксировать).
Настройка
Зарегистрируйте шлюз как OIDC-клиент в Hub с redirect URI
<PUBLIC_URL>/authorize/hub/callback. Это публичный клиент — не запрашивайте секрет.Поместите
HUB_ISSUER,HUB_DISCOVERY_URLиHUB_CLIENT_IDв/opt/mcp/.env.Проверьте до того, как любой пилотный пользователь попробует:
docker compose exec -T gateway node dist/cli.js hub:checkЭто выводит redirect URI для регистрации, выполняет discovery и предупреждает, если Hub не объявляет PKCE S256. Шлюз также проверяет discovery при загрузке и сообщает о нём в
/healthzкакhub_oidc.Добавьте пилотных пользователей (без паролей — их аутентифицирует Hub):
docker compose exec -T gateway node dist/cli.js user:add someone@example.com "Their Name"
Аварийный аккаунт
Разрешён ровно один локальный аккаунт с паролем, для случаев, когда Hub недоступен или неправильно настроен:
docker compose exec -T gateway node dist/cli.js user:add-break-glass it-emergency@example.comОн скрыт за раскрытием на странице входа, принуждает к смене пароля при первом
использовании, и каждый вход через него аудируется с break_glass: true с высоким
уровнем серьёзности. Пользователь, аутентифицированный через Hub, вообще не может
использовать путь с паролем, так что отключение Hub — это не способ откатиться к паролю,
который никто не задавал.
Установите BREAK_GLASS_ENABLED=false, как только путь через Hub будет подтверждён в production, чтобы полностью исключить пароль как поверхность атаки.
Опциональный групповой контроль — недоступен на DWS Hub
HUB_REQUIRED_GROUP добавляет вторую проверку по claim groups. DWS Hub не выдаёт такой claim (он объявляет только openid profile email), поэтому его установка приведёт к отказу для каждого пользователя. hub:check предупредит об этом. Разрешительный список пилота в таблице users остаётся механизмом контроля авторизации.
Вход, инициированный IdP
Hub может направить пользователя сразу на callback из плитки своей панели управления. Для коннектора так работать не может: callback существует, чтобы завершить запрос авторизации, начатый Claude, поэтому при обращении без такого запроса не остаётся ничего, для чего можно было бы выдать код. Шлюз обнаруживает это и сообщает «начните с Claude», а не завершается ошибкой «вход истёк».
4. Быстрый старт (локально)
npm ci
docker run -d --name mcpgw-devdb -e POSTGRES_DB=gateway -e POSTGRES_USER=gateway \
-e POSTGRES_PASSWORD=devpassword -p 127.0.0.1:55432:5432 postgres:16-alpine
cp .env.example .env.dev # then edit: PUBLIC_URL=http://localhost:8787, DATABASE_URL=...55432...
npx tsx test/fixtures/mockKlip.ts 5099 & # mock KLIP, behaves like the real one
set -a; . ./.env.dev; set +a
npm run migrate
npx tsx src/index.tsСоздайте пилотного пользователя (работает с TTY или вводом через пайп):
printf 'a-strong-password\na-strong-password\n' | npx tsx src/cli.ts user:add you@example.com "Your Name"5. Тесты
npm testНабор тестов | Что покрывает |
| Матрица Incoterm × статус × null, кг→MT, порядок округления, отрицательный остаток, временные метки WIB |
| Исчерпывающая таблица метод/путь для T-6, обходы путей и выходы за пределы origin |
| Конверт T-5, усечение |
| Ограниченная выборка публикует |
| Все 9 инструментов против mock KLIP; ни один не-GET запрос никогда не достигает KLIP; повторный вход после 401; |
| Привязка audience по RFC 8707: принимается только канонический resource этого сервера |
| Маскирование S5 — агрессивно в отношении строк, инертно в отношении чисел |
| Hub OIDC против mock-провайдера: несовпадение issuer в discovery, посторонний ключ подписи, неверный issuer/audience, истёкший токен, отсутствующий и повторно использованный nonce, claim без email |
| Допуск по |
| Production KLIP за тестовым Hub отказывается запускаться; обычные связки — нет |
| Метод аутентификации token-endpoint, выбираемый из discovery, включая Hub, поддерживающие только POST, и Hub с публичным клиентом |
| DWS Hub смоделирован точно: discovery |
Mock Hub — это работающий мини-OIDC-провайдер — настоящий discovery-документ, настоящие JWKS, настоящие RS256 ID-токены и проверка PKCE для кода авторизации — со всеми настройками, необходимыми для подделки плохого токена, потому что негативные сценарии и есть суть.
Фикстура mock KLIP намеренно воспроизводит особенности реальной системы: килограммы, помеченные как MT, статусы на разных языках, incoterm за пределами стандартной четвёрки, количества со значением null, контракт с перепоставкой, limit молча усекаемый до 100, и примечание к контракту, содержащее полезную нагрузку для prompt-инъекции.
6. Развёртывание
Полная процедура для конкретного хоста, с реальным IP, именем хоста и таблицей групп безопасности: deploy/RUNBOOK.md.
# on ECS-MCP
cd /opt/mcp && git pull
docker compose build gateway && docker compose up -d
curl -fsS http://127.0.0.1:8787/healthzАдминистративный CLI — обратите внимание, он запускается внутри контейнера, поскольку на хосте установлен только Docker и нет Node.js:
docker compose exec -T gateway node dist/cli.js user:list
docker compose exec -T gateway node dist/cli.js audit:summary --days 7
docker compose exec -T gateway node dist/cli.js audit:export --from 2026-08-01 --to 2026-09-01 --out /tmp/audit.csv
docker compose exec -T gateway node dist/cli.js routes:verify # probes KLIP, reports Appendix A gapsKill switch (S8) — целевое время: менее 5 минут:
docker compose exec -T gateway node dist/cli.js tokens:revoke-all --reason "incident 2026-xx"
docker compose stop gatewayBreak-glass, если контейнер приложения нездоров:
docker compose exec -T db psql -U gateway -d gateway -c "UPDATE oauth_tokens SET revoked_at=now() WHERE revoked_at IS NULL;"7. Приложение A — это жёсткий барьер
src/adapters/klip/routes.ts и src/adapters/klip/fields.ts содержат каждый путь KLIP, имя query-параметра, максимальный размер страницы, имя поля ответа и значение enum, от которых зависит адаптер. Каждая запись на данный момент не проверена.
Этот барьер исполняемый, а не формальный: при KLIP_ENV=production процесс отказывается запускаться, пока какой-либо маршрут не проверен. Запустите routes:verify на staging, зафиксируйте результаты, установите verified: true для каждого маршрута и enums.verified = true.
Два поля важнее остальных:
`
# | Изменение | Почему |
B3 | Origin отклоняется только при наличии и недействительности | Спецификация требует 403 только для присутствующего и недействительного Origin. Claude обращается к коннектору server-to-server и может не отправлять Origin вовсе; отклонение отсутствующего Origin давало бы 403 на каждый вызов инструмента. |
B4 |
| Привязка аудитории по RFC 8707. В документ PRM добавлены |
B5 | Без состояния транспорт; идентичность из токена на каждый запрос | Редакция от 2026-07-28 убрала сессии протокола. T-4 выполняется по построению — не существует сессии, которую можно перехватить. |
B6 | Аварийный выключатель запускается через | На хосте нет Node.js, поэтому |
B7 | nginx — базовый уровень защиты от злоупотреблений; лимит на пользователя по ключу | Весь трафик приходит из общего диапазона исходящих адресов Anthropic, поэтому ограничение по IP помещает весь пилот в один bucket. Добавлено |
H1 | OAuth построен на | В SDK уже есть AS, включая отзыв токенов и ограничение частоты по умолчанию. Наши — только хранилище и аутентификация пользователей. |
H2 | Вход через downstream Hub OIDC, плюс одна локальная учётная запись break-glass | Вынесено из Фазы 2. Hub аутентифицирует; таблица пользователей остаётся пилотным allowlist. |
H3 | Каркас allowlist в nginx для | Anthropic публикует стабильные диапазоны исходящего трафика и рекомендует allowlist; |
H4 | Усечённые результаты публикуют | Четыре разных пути к уверенно неверному числу. |
H5 | Короткий TTL-кэш; страницы 2..N загружаются конкурентно; размер страницы ограничен | Десять последовательных round-trip'ов не уложатся в P95 ≤ 5 с. |
H6 | Добавлен 9-й инструмент | Без него опечатка в имени завода возвращает пустой набор, который читается как «ничего не осталось». |
H7 |
| Захардкоженная строка «KLIP production» заставила бы каждый staging-ответ UAT утверждать, что это production. |
H9 |
| Удаление партициями — единственное, что допускает триггер append-only; экспорт U5 не имел реализации; IP клиентов иначе записывались бы как 127.0.0.1. |
H10 | Sidecar для резервного копирования, healthcheck контейнера, детали | Описано в TSD, но так и не было реализовано в руководстве, поэтому на момент запуска этого бы не существовало. |
— | Неизвестные параметры инструментов отклоняются через | PRD 8.1 требует отклонения; обычный |
— | Инструменты возвращают | Типизированные данные вместо JSON, встроенного в текст, — меньше ошибок транскрибации, а именно это измеряет M1. |
9. Открытые вопросы
K1–K4 на стороне Hub — роль
MCP_READONLY, учётная записьsvc-mcp, правило security-group.Внешняя синхронизация бэкапов и протестированное восстановление — sidecar пишет только локально.
Реальные корпоративные egress-диапазоны в
deploy/nginx/mcp.confи включение двух закомментированных строкreturn 403.Нагрузочное тестирование на ёмкости 30 одновременных подключений.
Регистрация клиента в тестовом Hub — собственная регистрация коннектора, с URI редиректа
<PUBLIC_URL>/authorize/callback, ключамиHUB_ISSUER,client_id/client_secretHub, подтверждёнными email и группами, затемhub:check. Отдельная регистрация для продакшн-Hub — на этапе 7.Решить, задавать ли
HUB_REQUIRED_GROUPи выключать лиBREAK_GLASS_ENABLEDпосле подтверждения пути через Hub на проде.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Give Claude only the Google Drive files you choose. Every action logged.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables read-only querying of the gong-nl-db Postgres database through natural language via Claude Desktop.9Academic Free v1.1
- AlicenseNot gradedqualityDmaintenanceEnables natural language queries against Snowflake Gold-layer tables through Claude Desktop, allowing users to ask business questions in plain English without SQL knowledge.MIT
- FlicenseNot gradedqualityCmaintenanceEnables natural language interaction with Kintone data via Claude, allowing listing apps, field definitions, querying and modifying records.-
- FlicenseAqualityCmaintenanceEnables querying AgroClimate company data via natural language through ChatGPT or Claude.7-