Skip to main content
Glama
dwsitproject-hub

MCP Gateway

MCP Gateway — Fase 1 (KLIP, solo lectura)

Un único servicio seguro que permite al personal autorizado de Energi-Up consultar KLIP en lenguaje natural a través de Claude, sin abrir la aplicación. Estrictamente de solo lectura.

Objetivo de despliegue (PRD Q3, ahora cerrado): <gateway-hostname> -> <gateway-public-ip> (ECS-MCP, ap-southeast-5). Tenga en cuenta que el nombre de host es mcp-gw, no mcp.example.com que suponían los documentos v0.9; el PRD/TSD debe actualizarse para reflejarlo.

Documentos: PRD v0.9 · TSD v0.9 · Guía de implementación · Revisión de diseño · Runbook de despliegue


1. Versiones fijadas (T-1)

La revisión de la especificación MCP y la versión del SDK se fijan aquí. Cuando la documentación del SDK discrepe de los documentos de diseño, el SDK manda — define la API exacta.

Componente

Versión fijada

Notas

@modelcontextprotocol/sdk

1.30.0 (exacta, sin caret)

Publicado el 27 de julio de 2026. Proporciona el AS de OAuth, el transporte HTTP Streamable y requireBearerAuth.

Revisión del protocolo MCP

2025-11-25 implementada; formato de red compatible con clientes 2025-06-18

La revisión 2026-07-28 (borrador) eliminó las sesiones a nivel de protocolo, el flujo GET y Last-Event-ID. Este servidor es sin estado, por lo que es compatible hacia adelante con ese cambio y responde 405 a GET/DELETE heredados en /mcp.

Node.js

22 LTS (node:22-slim)

TypeScript

5.9.3, strict + exactOptionalPropertyTypes

express

5.2.1

Elevada desde la 4.x del TSD: el SDK depende de express@^5.2.1, y montar un router de express-5 en una aplicación express-4 mezcla las versiones principales de path-to-regexp.

zod

4.4.3

Elevada desde la 3.x del TSD: las definiciones de tipos del SDK apuntan a zod 4. Tenga en cuenta que .default() ahora proporciona el tipo de salida.

jose

6.2.9

Tokens de gateway RS256.

@node-rs/argon2

2.1.0

Sustituye a argon2: binarios precompilados, por lo que no se necesita cadena de herramientas node-gyp en node:22-slim.

axios

1.19.0

Cliente KLIP, detrás de la protección de métodos.

pg

8.23.0

PostgreSQL

16 (contenedor)

nginx

mainline de nginx.org

Configuración en deploy/nginx/mcp.conf.

Related MCP server: Snowflake MCP Server

2. Estructura

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

Regla de capas (T-3): tools → adapters → core. Nada importa http/ excepto el punto de entrada. Toda la normalización de negocio vive en adapters/klip/normalize.ts, que no importa nada, por lo que se puede probar unitariamente de forma aislada.

3. Autenticación — Downstream Hub OIDC

Los usuarios piloto inician sesión con Downstream Hub (OIDC). El gateway sigue siendo el servidor de autorización con el que habla Claude; el Hub es un paso dentro de su propio flujo /authorize.

Deliberadamente no usamos el ProxyOAuthServerProvider del SDK. El proxy entregaría a Claude un token del Hub, lo que rompería la vinculación de audiencia RFC 8707, daría a Claude ámbitos del Hub más amplios que klip:read, y sacaría la emisión de tokens de nuestro control, de modo que el interruptor de corte S8 ya no podría invalidar las sesiones activas.

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)

La autenticación no es autorización. El Hub demuestra quién es alguien; la tabla users decide si puede usar el conector. La Fase 1 usa una cuenta de servicio KLIP compartida, por lo que todo usuario admitido puede leer todo lo que MCP_READONLY puede leer (revisión H8) — la pertenencia al piloto es el control de acceso a los datos. Una cuenta del Hub que no esté en la lista recibe un 403, y el límite de <= 15 se aplica mediante user:add.

En el flujo se ejecutan dos intercambios PKCE; no los confunda. El code_challenge propio de Claude protege el tramo Claude→gateway (gestionado por el SDK); un verificador separado que el gateway mantiene en el lado del servidor protege el tramo gateway→Hub.

¿Qué cliente del Hub? El propio del gateway — no el de KLIP

Registre un nuevo cliente OIDC para MCP Gateway. No reutilice el registro del Hub de KLIP, aunque KLIP ya esté registrado en el Hub DWS de pruebas.

El Hub solo toca uno de los dos límites de confianza:

Límite

Credencial

¿Interviene el Hub?

Claude → gateway (qué humano pregunta)

cliente del Hub propio del gateway + la lista de permitidos del piloto

gateway → KLIP (lectura de datos)

svc-mcp contra /api/auth/login de KLIP

no — el PRD §7 lo excluye explícitamente

Reutilizar el cliente de KLIP rompe el primer límite de forma concreta. El gateway valida el aud del token de ID contra su propio HUB_CLIENT_ID; compartir el id de cliente de KLIP significa que un token de ID emitido durante un inicio de sesión de KLIP sería aceptado por el gateway, que es el patrón de diputado confundido. Los clientes separados son lo que hace distinguibles a las dos partes que confían. También mantiene independientes las listas de permitidos de redirect-URI, los secretos de cliente, los calendarios de rotación, las entradas de auditoría SSO del Hub y los interruptores de desactivación — apagar el cliente del Hub del conector no debe tumbar el inicio de sesión de KLIP.

Nada de esto requiere ningún cambio en el lado de KLIP. K1–K4 no se ven afectados.

Dos instancias del Hub, dos registros

Registre el gateway por separado en cada Hub y empareje cada uno con el KLIP correspondiente:

Etapa

KLIP_ENV

HUB_ISSUER

Cliente

4–6 (construcción, UAT de staging)

staging

Hub DWS de pruebas

cliente del gateway en el Hub de pruebas

7+ (corte a producción)

production

Hub de producción

un cliente del gateway separado en el Hub de producción

El emparejamiento se aplica al arrancar, porque equivocarse en una dirección es peligroso, no solo desordenado:

  • KLIP_ENV=production + un HUB_ISSUER de pruebas → el gateway se niega a arrancar. De lo contrario, cualquiera que pudiera crear una cuenta en el Hub de pruebas llegaría a datos comerciales reales.

  • KLIP_ENV=staging + el HUB_ISSUER de producción → avisa y continúa.

hub:check imprime el emparejamiento que ha detectado, de modo que el corte es verificable:

pairing:       KLIP staging  <->  Hub testing

Qué requiere DWS Hub (no es OIDC estándar)

Según Docs/SSO-TARGET-APP-INTEGRATION.md. Cuatro de estos puntos difieren de los valores predeterminados que asume una biblioteca de cliente OIDC, y tres fallarían directamente:

DWS Hub

Tipo de cliente

público, PKCE S256 — token_endpoint_auth_methods_supported: ["none"], no existe secreto de cliente

Descubrimiento

/api/sso/.well-known/openid-configurationno la ruta RFC 8414 del emisor

Cuerpo del token

JSON; codificado como formulario devuelve unsupported_grant_type

Ámbitos

solo openid profile email — no hay declaración de grupos, por lo que HUB_REQUIRED_GROUP no se puede usar

redirect_uri

obligatorio en la solicitud de token y exacto byte a byte

Por lo tanto, HUB_DISCOVERY_URL se configura explícitamente, HUB_CLIENT_SECRET es opcional, y HUB_TOKEN_BODY tiene como valor predeterminado json con un respaldo de una sola vez a form ante unsupported_grant_type (registrando cuál funcionó, para poder fijarlo).

Configuración

  1. Registre el gateway como cliente OIDC en el Hub con la URI de redirección <PUBLIC_URL>/authorize/hub/callback. Es un cliente público — no pida un secreto.

  2. Ponga HUB_ISSUER, HUB_DISCOVERY_URL y HUB_CLIENT_ID en /opt/mcp/.env.

  3. Verifique antes de que ningún usuario piloto lo intente:

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

    Esto imprime la URI de redirección a registrar, ejecuta el descubrimiento y avisa si el Hub no anuncia PKCE S256. El gateway también sondea el descubrimiento al arrancar y lo informa en /healthz como hub_oidc.

  4. Añada usuarios piloto (sin contraseñas — el Hub los autentica):

    docker compose exec -T gateway node dist/cli.js user:add someone@example.com "Their Name"

La cuenta de emergencia

Se permite exactamente una cuenta local con contraseña, para usarla cuando el Hub esté caído o mal configurado:

docker compose exec -T gateway node dist/cli.js user:add-break-glass it-emergency@example.com

Está oculta detrás de una divulgación en la página de inicio de sesión, obliga a cambiar la contraseña en el primer uso, y cada inicio de sesión a través de ella se audita con break_glass: true con severidad alta. Un usuario autenticado por el Hub no puede usar la ruta de contraseña en absoluto, por lo que apagar el Hub no es una forma de recurrir a una contraseña que nadie estableció.

Establece BREAK_GLASS_ENABLED=false una vez que la ruta Hub esté probada en producción para eliminar por completo la superficie de contraseña.

Puerta de grupo opcional — no disponible en DWS Hub

HUB_REQUIRED_GROUP añade una segunda comprobación contra una reclamación de grupos. DWS Hub no emite una (solo anuncia openid profile email), por lo que establecerla rechazaría a todos los usuarios. hub:check avisa si lo haces. La lista de permitidos piloto en la tabla users sigue siendo el control de autorización.

Inicio de sesión iniciado por IdP

El Hub puede enviar a un usuario directamente a una devolución de llamada desde su mosaico de panel. Eso no puede funcionar para un conector: la devolución de llamada existe para completar una solicitud de autorización que Claude inició, por lo que llegar sin una no deja nada contra lo que emitir un código. La puerta de enlace lo detecta y dice "empieza desde Claude en su lugar" en lugar de fallar como "inicio de sesión caducado".

4. Inicio rápido (local)

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

Crea un usuario piloto (funciona con una TTY o entrada canalizada):

printf 'a-strong-password\na-strong-password\n' | npx tsx src/cli.ts user:add you@example.com "Your Name"

5. Pruebas

npm test

Suite

Cubre

normalize.spec.ts (33)

La matriz Incoterm × estado × nulo, kg→MT, orden de redondeo, pendiente negativo, marcas de tiempo WIB

guard.spec.ts (9)

Tabla exhaustiva de método/ruta para T-6, escapes de recorrido y origen

envelope.spec.ts (12)

Sobre T-5, truncamiento next_step, desactivación de carga útil de inyección

truncation.spec.ts (4)

Una búsqueda acotada publica totals_partial, nunca totals

integration.spec.ts (23)

Las 9 herramientas contra KLIP simulado; ningún no-GET llega jamás a KLIP; re-inicio de sesión 401; AUTH_DEGRADED; errores tipados; disciplina de unidad

resource.spec.ts (6)

Vinculación de audiencia RFC 8707: solo se acepta el recurso canónico de este servidor

audit.spec.ts (8)

Redacción S5 — agresiva en cadenas, inerte en números

hub.spec.ts (20)

OIDC de Hub contra un proveedor simulado: discrepancia de emisor de descubrimiento, clave de firma externa, emisor/audiencia incorrectos, token caducado, nonce faltante y reproducido, reclamación sin correo electrónico

hubGroupGate.spec.ts (5)

Admisión de HUB_REQUIRED_GROUP, incluidos nombres de grupo casi coincidentes

hubPairing.spec.ts (5)

KLIP de producción detrás de un Hub de prueba se niega a arrancar; los emparejamientos normales no

hubTokenAuth.spec.ts (7)

Método de autenticación de punto final de token elegido del descubrimiento, incluidos Hubs de solo POST y de cliente público

hubDws.spec.ts (16)

DWS Hub modelado exactamente: descubrimiento /api/sso, cliente público, cuerpo de token solo JSON, sin ámbito de grupos, más el respaldo de codificación en ambos sentidos

El Hub simulado es un mini-proveedor OIDC funcional — documento de descubrimiento real, JWKS real, tokens de ID RS256 reales y verificación PKCE en el código de autorización — con cada perilla necesaria para forjar un token malo, porque los casos negativos son el punto.

El fixture KLIP simulado reproduce deliberadamente las peculiaridades del sistema real: kilogramos etiquetados como MT, estados en idiomas mixtos, un incoterm fuera de los cuatro estándar, cantidades nulas, un contrato con entrega excesiva, un limit silenciosamente limitado a 100 y un comentario de contrato que lleva una carga útil de inyección de indicaciones.

6. Despliegue

Procedimiento completo específico del host, con la IP real, el nombre de host y la tabla de grupos de seguridad: 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 de administración — ten en cuenta que esto se ejecuta dentro del contenedor, porque el host instala solo Docker y no tiene 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

Interruptor de emergencia (S8) — objetivo en menos de 5 minutos:

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

Break-glass si el contenedor de la aplicación no está saludable:

docker compose exec -T db psql -U gateway -d gateway -c "UPDATE oauth_tokens SET revoked_at=now() WHERE revoked_at IS NULL;"

7. El Apéndice A es una puerta estricta

src/adapters/klip/routes.ts y src/adapters/klip/fields.ts contienen cada ruta KLIP, nombre de parámetro de consulta, límite máximo de tamaño de página, nombre de campo de respuesta y valor de enumeración del que depende el adaptador. Cada entrada está actualmente sin verificar.

La puerta es ejecutable, no administrativa: con KLIP_ENV=production el proceso se niega a iniciar mientras cualquier ruta esté sin verificar. Ejecuta routes:verify contra staging, registra los resultados, establece verified: true por ruta y enums.verified = true.

Dos campos importan más que el resto:

  • maxLimit — el limit más grande que KLIP realmente acepta. Si lo limita silenciosamente a 100, un KLIP_PAGE_SIZE de 1000 convierte una página en diez y rompe el objetivo de latencia.

  • enums.* — los valores canónicos de estado e incoterm. Cualquier cosa no mapeada se excluye de los totales con una nota de calidad de datos, nunca se asigna un valor predeterminado.

8. Desviaciones de TSD v0.9

Cada una proviene de la revisión de diseño y está comentada en su sitio de llamada.

#

Cambio

Por qué

B3

Origin rechazado solo cuando está presente y es inválido

La especificación exige 403 solo para un Origin presente e inválido. Claude llama al conector de servidor a servidor y puede no enviar ninguno; rechazar la ausencia daría 403 en cada llamada de herramienta.

B4

aud = <PUBLIC_URL>/mcp, no el nombre de host simple

Vinculación de audiencia RFC 8707. Se añadieron authorization_servers + resource al documento PRM, scope en el desafío 401 y iss RFC 9207.

B5

Transporte sin estado; identidad del token por solicitud

La revisión 2026-07-28 eliminó las sesiones de protocolo. T-4 se cumple por construcción: no hay sesión que secuestrar.

B6

El interruptor de apagado ejecuta docker compose exec

El host no tiene Node.js, por lo que cd /opt/mcp && node cli.js nunca podría funcionar.

B7

nginx es la base antiahuso; límite por usuario basado en el sub de OAuth

Todo el tráfico llega desde el rango de salida compartido de Anthropic, por lo que la limitación por IP pondría todo el piloto en un solo cubo. Se añadió limit_req_status 429 (el valor predeterminado es 503).

H1

OAuth construido sobre mcpAuthRouter + OAuthServerProvider del SDK

El SDK incluye el AS, incluida la revocación y la limitación de velocidad predeterminada. Solo el almacenamiento y la autenticación de usuarios son nuestros.

H2

El OIDC del Hub descendente es la ruta de inicio de sesión, con una cuenta local de emergencia

Adelantado desde la Fase 2. El Hub autentica; la tabla de usuarios sigue siendo la lista de permitidos del piloto. ProxyOAuthServerProvider se rechazó a propósito; véase §3.

H3

Andamiaje de lista de permitidos de nginx para 160.79.104.0/21 de Anthropic

Anthropic publica rangos de salida estables y recomienda la lista de permitidos; /authorize permanece abierto a la salida corporativa porque se ejecuta en el navegador del usuario. Comentado hasta que la Etapa 6 confirme las direcciones de origen reales.

H4

Los resultados truncados publican totals_partial; agregación en kg enteros; enumeraciones no asignadas excluidas; pendiente negativo conservado

Cuatro rutas distintas hacia un número erróneo con confianza.

H5

Caché TTL corto; páginas 2..N obtenidas simultáneamente; tamaño de página limitado a maxLimit

Diez idas y vueltas secuenciales no pueden cumplir P95 ≤ 5 s.

H6

Se añadió una 9.ª herramienta, klip_reference, además de un UNKNOWN_FILTER_VALUE tipado

Sin ella, un nombre de planta mal escrito devuelve un conjunto vacío que se lee como «nada está pendiente».

H7

environment y source derivados de KLIP_ENV

La cadena fija especificada «KLIP production» habría hecho que cada respuesta de UAT en staging afirmara ser producción.

H9

audit_events está particionado por rango mensual; se añadió audit:export; se respeta X-Forwarded-For

La retención mediante eliminación de particiones es lo único que permite el disparador de solo anexión; la exportación de U5 no tenía implementación; las IP de los clientes se habrían registrado todas como 127.0.0.1.

H10

Sidecar de respaldo, healthcheck del contenedor, detalle de /healthz restringido a llamadores internos

Especificado en el TSD pero nunca implementado en la guía, por lo que no habría existido en el lanzamiento.

Parámetros de herramienta desconocidos rechazados mediante z.strictObject

El PRD 8.1 exige el rechazo; un z.object simple ignora silenciosamente los extras.

Las herramientas devuelven structuredContent contra un outputSchema

Datos tipados en lugar de JSON incrustado en prosa: menos errores de transcripción, que es lo que mide M1.

9. Aún pendiente

  • Conciliación del Apéndice A (P1): bloquea la producción, aplicada al inicio.

  • K1–K4 del lado KLIP: el rol MCP_READONLY, la cuenta svc-mcp, la regla del grupo de seguridad.

  • Sincronización de respaldo fuera del host y una restauración probada: el sidecar solo escribe localmente.

  • CIDR de salida corporativos reales en deploy/nginx/mcp.conf y luego habilitar las dos líneas return 403 comentadas.

  • Prueba de carga en el NFR de capacidad de 30 usuarios simultáneos.

  • Registro del cliente Hub: el propio de la puerta de enlace, primero en el Hub DWS de pruebas: HUB_ISSUER, id y secreto de cliente, con URI de redirección <PUBLIC_URL>/authorize/hub/callback registrada en el lado del Hub. Confirmar los nombres de las reclamaciones de correo electrónico y grupos del Hub y luego ejecutar hub:check. Se necesita un segundo registro, separado, en el Hub de producción en la Etapa 7.

  • Decidir si establecer HUB_REQUIRED_GROUP y si establecer BREAK_GLASS_ENABLED=false después de que la ruta del Hub se demuestre en producción.

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