Skip to main content
Glama
dwsitproject-hub

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.

Компонент

Зафиксировано

Примечания

@modelcontextprotocol/sdk

1.30.0 (точно, без каретки)

Опубликован 27 июля 2026 г. Предоставляет OAuth AS, транспорт Streamable HTTP и requireBearerAuth.

Ревision протокола MCP

2025-11-25 реализован; формат провода совместим с клиентами 2025-06-18

Ревision 2026-07-28 (черновик) убрала сессии на уровне протокола, GET-поток и Last-Event-ID. Этот сервер не сохраняет состояние, поэтому он прямосовместим с этим изменением и отвечает 405 на устаревшие GET/DELETE на /mcp.

Node.js

22 LTS (node:22-slim)

TypeScript

5.9.3, strict + exactOptionalPropertyTypes

express

5.2.1

Повышено с 4.x из TSD: SDK зависит от express@^5.2.1, а монтирование роутера express-5 в приложение express-4 смешивает мажорные версии path-to-regexp.

zod

4.4.3

Повышено с 3.x из TSD: определения типов SDK нацелены на zod 4. Обратите внимание: .default() теперь предоставляет тип выходных данных.

jose

6.2.9

Токены шлюза RS256.

@node-rs/argon2

2.1.0

Замена для argon2: готовые бинарные сборки, поэтому инструментарий node-gyp в node:22-slim не нужен.

axios

1.19.0

Клиент KLIP, за защитой метода.

pg

8.23.0

PostgreSQL

16 (контейнер)

nginx

mainline от nginx.org

Конфигурация в deploy/nginx/mcp.conf.

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 (чтение данных)

svc-mcp через /api/auth/login от 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)

staging

тестовый DWS Hub

клиент шлюза в тестовом Hub

7+ (продуктовый cutover)

production

продуктовый 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 — token_endpoint_auth_methods_supported: ["none"], секрета клиента не существует

Discovery

/api/sso/.well-known/openid-configurationне путь RFC 8414 от издателя

Тело токена

JSON; форма-кодирование возвращает unsupported_grant_type

Области

только openid profile email — нет claims групп, поэтому HUB_REQUIRED_GROUP непригоден

redirect_uri

обязателен в запросе токена и должен совпадать байт-в-байт

Поэтому HUB_DISCOVERY_URL настраивается явно, HUB_CLIENT_SECRET необязателен, а HUB_TOKEN_BODY по умолчанию равен json с одноразовым откатом на форму при unsupported_grant_type (с записью в лог того, что сработало, чтобы это можно было зафиксировать).

Настройка

  1. Зарегистрируйте шлюз как OIDC-клиент в Hub с redirect URI <PUBLIC_URL>/authorize/hub/callback. Это публичный клиент — не запрашивайте секрет.

  2. Поместите HUB_ISSUER, HUB_DISCOVERY_URL и HUB_CLIENT_ID в /opt/mcp/.env.

  3. Проверьте до того, как любой пилотный пользователь попробует:

    docker compose exec -T gateway node dist/cli.js hub:check

    Это выводит redirect URI для регистрации, выполняет discovery и предупреждает, если Hub не объявляет PKCE S256. Шлюз также проверяет discovery при загрузке и сообщает о нём в /healthz как hub_oidc.

  4. Добавьте пилотных пользователей (без паролей — их аутентифицирует 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

Набор тестов

Что покрывает

normalize.spec.ts (33)

Матрица Incoterm × статус × null, кг→MT, порядок округления, отрицательный остаток, временные метки WIB

guard.spec.ts (9)

Исчерпывающая таблица метод/путь для T-6, обходы путей и выходы за пределы origin

envelope.spec.ts (12)

Конверт T-5, усечение next_step, обезвреживание инъекционной полезной нагрузки

truncation.spec.ts (4)

Ограниченная выборка публикует totals_partial, но никогда totals

integration.spec.ts (23)

Все 9 инструментов против mock KLIP; ни один не-GET запрос никогда не достигает KLIP; повторный вход после 401; AUTH_DEGRADED; типизированные ошибки; дисциплина unit-тестов

resource.spec.ts (6)

Привязка audience по RFC 8707: принимается только канонический resource этого сервера

audit.spec.ts (8)

Маскирование S5 — агрессивно в отношении строк, инертно в отношении чисел

hub.spec.ts (20)

Hub OIDC против mock-провайдера: несовпадение issuer в discovery, посторонний ключ подписи, неверный issuer/audience, истёкший токен, отсутствующий и повторно использованный nonce, claim без email

hubGroupGate.spec.ts (5)

Допуск по HUB_REQUIRED_GROUP, включая почти совпадающие имена групп

hubPairing.spec.ts (5)

Production KLIP за тестовым Hub отказывается запускаться; обычные связки — нет

hubTokenAuth.spec.ts (7)

Метод аутентификации token-endpoint, выбираемый из discovery, включая Hub, поддерживающие только POST, и Hub с публичным клиентом

hubDws.spec.ts (16)

DWS Hub смоделирован точно: discovery /api/sso, публичный клиент, тело токен-запроса только в JSON, отсутствие scope groups, плюс запасной вариант кодирования в обе стороны

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 gaps

Kill switch (S8) — целевое время: менее 5 минут:

docker compose exec -T gateway node dist/cli.js tokens:revoke-all --reason "incident 2026-xx"
docker compose stop gateway

Break-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

aud = <PUBLIC_URL>/mcp, а не голое имя хоста

Привязка аудитории по RFC 8707. В документ PRM добавлены authorization_servers + resource, в 401-вызов — scope, а также iss по RFC 9207.

B5

Без состояния транспорт; идентичность из токена на каждый запрос

Редакция от 2026-07-28 убрала сессии протокола. T-4 выполняется по построению — не существует сессии, которую можно перехватить.

B6

Аварийный выключатель запускается через docker compose exec

На хосте нет Node.js, поэтому cd /opt/mcp && node cli.js никогда не мог бы сработать.

B7

nginx — базовый уровень защиты от злоупотреблений; лимит на пользователя по ключу sub из OAuth

Весь трафик приходит из общего диапазона исходящих адресов Anthropic, поэтому ограничение по IP помещает весь пилот в один bucket. Добавлено limit_req_status 429 (по умолчанию — 503).

H1

OAuth построен на mcpAuthRouter + OAuthServerProvider из SDK

В SDK уже есть AS, включая отзыв токенов и ограничение частоты по умолчанию. Наши — только хранилище и аутентификация пользователей.

H2

Вход через downstream Hub OIDC, плюс одна локальная учётная запись break-glass

Вынесено из Фазы 2. Hub аутентифицирует; таблица пользователей остаётся пилотным allowlist. ProxyOAuthServerProvider отклонён намеренно — см. §3.

H3

Каркас allowlist в nginx для 160.79.104.0/21 от Anthropic

Anthropic публикует стабильные диапазоны исходящего трафика и рекомендует allowlist; /authorize остаётся открытым для корпоративного egress, так как выполняется в браузере пользователя. Закомментировано до подтверждения реальных исходных адресов на этапе 6.

H4

Усечённые результаты публикуют totals_partial; агрегация в целых кг; несопоставленные enum исключаются; отрицательный outstanding сохраняется

Четыре разных пути к уверенно неверному числу.

H5

Короткий TTL-кэш; страницы 2..N загружаются конкурентно; размер страницы ограничен maxLimit

Десять последовательных round-trip'ов не уложатся в P95 ≤ 5 с.

H6

Добавлен 9-й инструмент klip_reference плюс типизированный UNKNOWN_FILTER_VALUE

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

H7

environment и source берутся из KLIP_ENV

Захардкоженная строка «KLIP production» заставила бы каждый staging-ответ UAT утверждать, что это production.

H9

audit_events разбит по месяцам range-партициями; добавлен audit:export; учитывается X-Forwarded-For

Удаление партициями — единственное, что допускает триггер append-only; экспорт U5 не имел реализации; IP клиентов иначе записывались бы как 127.0.0.1.

H10

Sidecar для резервного копирования, healthcheck контейнера, детали /healthz ограничены внутренними вызывающими

Описано в TSD, но так и не было реализовано в руководстве, поэтому на момент запуска этого бы не существовало.

Неизвестные параметры инструментов отклоняются через z.strictObject

PRD 8.1 требует отклонения; обычный z.object молча игнорирует лишние параметры.

Инструменты возвращают structuredContent по схеме outputSchema

Типизированные данные вместо 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_secret Hub, подтверждёнными email и группами, затем hub:check. Отдельная регистрация для продакшн-Hub — на этапе 7.

  • Решить, задавать ли HUB_REQUIRED_GROUP и выключать ли BREAK_GLASS_ENABLED после подтверждения пути через Hub на проде.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    Enables read-only querying of the gong-nl-db Postgres database through natural language via Claude Desktop.
    9
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language queries against Snowflake Gold-layer tables through Claude Desktop, allowing users to ask business questions in plain English without SQL knowledge.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Gives Claude live access to your Observe tenant, enabling natural language queries about errors, logs, and metrics without writing OPAL pipelines.
    17
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables natural language interaction with Kintone data via Claude, allowing listing apps, field definitions, querying and modifying records.

View all related MCP servers

Related MCP Connectors

  • WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.

  • Connect Claude to Fathom meeting recordings, transcripts, and summaries

  • Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dwsitproject-hub/MCP-Gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server