Skip to main content
Glama
dwsitproject-hub

MCP Gateway

MCP Gateway — フェーズ 1(KLIP、読み取り専用)

認可されたEnergi-Upスタッフが、アプリケーションを開かずにClaudeを通じて自然言語でKLIPに問い合わせできるようにする、単一のセキュアなサービス。厳密に読み取り専用。

デプロイ先(PRD Q3、現在はクローズ済み): <gateway-hostname> -> <gateway-public-ip> (ECS-MCP、ap-southeast-5)。ホスト名はmcp-gwであり、v0.9ドキュメントが想定していたmcp.example.comではないことに注意。PRD/TSDはこれに合わせて更新すべきである。

ドキュメント: PRD v0.9 · TSD v0.9 · 実装ガイド · 設計レビュー · デプロイランブック


1. 固定バージョン(T-1)

MCP仕様リビジョンとSDKバージョンはここで固定する。SDK自身のドキュメントが設計ドキュメントと矛盾する場合、SDKが優先される — SDKが正確なAPIを定義するからである。

コンポーネント

固定バージョン

備考

@modelcontextprotocol/sdk

1.30.0(完全一致、キャレットなし)

2026年7月27日公開。OAuth AS、Streamable HTTPトランスポート、requireBearerAuthを提供する。

MCPプロトコルリビジョン

2025-11-25を実装。ワイヤー形状は2025-06-18クライアントと互換

リビジョン2026-07-28(ドラフト)はプロトコルレベルのセッション、GETストリーム、Last-Event-IDを削除した。このサーバーはステートレスであるため、その変更に対して前方互換であり、/mcpへのレガシーGET/DELETEには405で応答する。

Node.js

22 LTS(node:22-slim)

TypeScript

5.9.3、strict + exactOptionalPropertyTypes

express

5.2.1

TSDの4.xから引き上げ: SDKはexpress@^5.2.1に依存しており、express-5ルーターをexpress-4アプリにマウントするとpath-to-regexpのメジャーバージョンが混在する。

zod

4.4.3

TSDの3.xから引き上げ: SDKの型定義はzod 4を対象としている。.default()は現在出力型を提供することに注意。

jose

6.2.9

RS256ゲートウェイトークン。

@node-rs/argon2

2.1.0

argon2の代替: プリビルドバイナリのため、node:22-slimにnode-gypツールチェーンが不要。

axios

1.19.0

KLIPクライアント。メソッドガードの背後にある。

pg

8.23.0

PostgreSQL

16(コンテナ)

nginx

nginx.org mainline

設定は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. 認証 — 下流Hub OIDC

パイロットユーザーはDownstream Hub(OIDC)でサインインする。ゲートウェイはClaudeが通信する認可サーバーのままであり、Hubはその独自の/authorizeフロー内の1ステップである。

SDKのProxyOAuthServerProviderは意図的に使用しない。プロキシすると、ClaudeにHubトークンが渡され、RFC 8707のオーディエンスバインディングが壊れ、Claudeにklip:readより広いHubスコープが付与され、トークン発行が当社の管理外に移るため、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サービスアカウントを1つ使用するため、承認されたすべてのユーザーはMCP_READONLYが読み取れるすべてを読み取れる(レビューH8)— パイロットメンバーシップがデータアクセス制御である。リストにないHubアカウントは403を受け取り、<= 15の上限はuser:addによって強制される。

フローでは2つのPKCE交換が実行される。混同しないこと。Claude自身のcode_challengeはClaude→ゲートウェイ区間を保護し(SDKが処理)、ゲートウェイがサーバー側で保持する別のベリファイアがゲートウェイ→Hub区間を保護する。

どのHubクライアントか? ゲートウェイ自身のもの — KLIPのものではない

MCP Gateway用に新しいOIDCクライアントを登録すること。KLIPのHub登録を再利用しないこと。KLIPがテスト用DWS Hubにすでに登録されている場合でも同様である。

Hubは2つの信頼境界のうち1つにのみ関与する:

境界

資格情報

Hub関与?

Claude → ゲートウェイ(どの人間が質問しているか)

ゲートウェイ自身のHubクライアント + パイロット許可リスト

あり

ゲートウェイ → KLIP(データの読み取り)

KLIPの/api/auth/loginに対するsvc-mcp

なし — PRD §7が明示的に除外している

KLIPのクライアントを再利用すると、最初の境界が具体的な形で壊れる。ゲートウェイはIDトークンのaudを自身のHUB_CLIENT_IDに対して検証する。KLIPのクライアントIDを共有すると、KLIPログイン中に発行されたIDトークンがゲートウェイに受け入れられることになり、これは混乱した代理人(confused deputy)の形である。クライアントを分離することで、2つの証明書利用者(relying party)が区別可能になる。また、リダイレクトURI許可リスト、クライアントシークレット、ローテーションスケジュール、Hub SSO監査エントリ、無効化スイッチも独立して維持される — コネクタのHubクライアントをオフにしてもKLIPログインを停止させてはならない。

これにはKLIP側の変更は一切不要である。K1〜K4は影響を受けない。

2つのHubインスタンス、2つの登録

ゲートウェイを各Hubに個別に登録し、対応するKLIPとペアリングする:

ステージ

KLIP_ENV

HUB_ISSUER

クライアント

4〜6(ビルド、ステージングUAT)

staging

テスト用DWS Hub

テスト用Hubのゲートウェイクライアント

7以降(本番カットオーバー)

production

本番Hub

本番Hubの別のゲートウェイクライアント

ペアリングは起動時に強制される。間違った方向で設定すると、単なる不手際ではなく危険だからである:

  • KLIP_ENV=production + テスト用HUB_ISSUER → ゲートウェイは起動を拒否する。 テストHubアカウントを作成できる者は誰でも、実際の商用データに到達できてしまう。

  • KLIP_ENV=staging + 本番HUB_ISSUER → 警告して続行する。

hub:checkは検出したペアリングを出力するため、カットオーバーは検証可能である:

pairing:       KLIP staging  <->  Hub testing

DWS Hubが要求するもの(バニラOIDCではない)

Docs/SSO-TARGET-APP-INTEGRATION.mdによる。これらのうち4つはOIDCクライアントライブラリが想定するデフォルトと異なり、3つは完全に失敗する:

DWS Hub

クライアントタイプ

パブリック、PKCE S256 — token_endpoint_auth_methods_supported: ["none"]、クライアントシークレットは存在しない

ディスカバリ

/api/sso/.well-known/openid-configuration — イシュアーからのRFC 8414パスではない

トークンボディ

JSON。フォームエンコードはunsupported_grant_typeを返す

スコープ

openid profile emailのみ — グループクレームがないため、HUB_REQUIRED_GROUPは使用不可

redirect_uri

トークンリクエストで必須であり、バイト単位で完全一致である

したがって、HUB_DISCOVERY_URLは明示的に設定され、HUB_CLIENT_SECRETはオプションであり、HUB_TOKEN_BODYはデフォルトでjsonとなり、unsupported_grant_typeの場合にフォームへのワンショットフォールバックがある(どちらが機能したかをログに記録し、固定できるようにする)。

セットアップ手順

  1. リダイレクトURI <PUBLIC_URL>/authorize/hub/callbackでゲートウェイをHubのOIDCクライアントとして登録する。これはパブリッククライアントである — シークレットを要求しないこと。

  2. HUB_ISSUER、HUB_DISCOVERY_URL、HUB_CLIENT_IDを/opt/mcp/.envに配置する。

  3. パイロットユーザーが試す前に検証する:

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

    これにより、登録するリダイレクトURIが出力され、ディスカバリが実行され、HubがS256 PKCEを宣伝していない場合に警告する。ゲートウェイは起動時にもディスカバリをプローブし、/healthzでhub_oidcとして報告する。

  4. パイロットユーザーを追加する(パスワードなし — Hubが認証する):

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

非常用(ブレークグラス)アカウント

Hubがダウンしているか誤設定されている場合に使用するため、ローカルパスワードアカウントは正確に1つだけ許可される:

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

これはログインページの開示の背後に隠されており、初回使用時にパスワード変更を強制し、それを通じたすべてのサインインは高重大度でbreak_glass: trueとして監査される。Hub認証ユーザーはパスワードパスをまったく使用できないため、Hubをオフにしても、誰も設定していないパスワードにフォールバックする方法にはならない。

Hub パスが本番で実証されたら、BREAK_GLASS_ENABLED=false に設定して、パスワードの攻撃面を完全に削除してください。

オプションのグループゲート — DWS Hub では利用不可

HUB_REQUIRED_GROUP は、groups クレームに対する2番目のチェックを追加します。DWS Hub は groups クレームを発行しません(openid profile email のみをアドバタイズします)。そのため、これを設定するとすべてのユーザーを拒否することになります。設定した場合、hub:check が警告します。users テーブルのパイロット許可リストが、引き続き認可の制御となります。

IdP 開始ログイン

Hub はダッシュボードのタイルからユーザーをコールバックに直接送り込むことができます。これはコネクタでは機能しません。コールバックは 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)

インコターム × ステータス × null のマトリクス、kg→MT、丸め順序、マイナスの未達、WIB タイムスタンプ

guard.spec.ts (9)

T-6 の網羅的なメソッド/パステーブル、トラバーサルとオリジンエスケープ

envelope.spec.ts (12)

T-5 エンベロープ、next_step のトランケーション、インジェクションペイロードの無害化

truncation.spec.ts (4)

境界付きフェッチは totals_partial を公開し、totals を公開することはありません

integration.spec.ts (23)

モック KLIP に対する全9ツール、非 GET が KLIP に到達することは決してありません、401 再ログイン、AUTH_DEGRADED、型付きエラー、ユニット規律

resource.spec.ts (6)

RFC 8707 オーディエンスバインディング:このサーバーの正規リソースのみが受け入れられます

audit.spec.ts (8)

S5 リダクション — 文字列には積極的、数値には作用しません

hub.spec.ts (20)

モックプロバイダーに対する Hub OIDC:ディスカバリーの発行者不一致、外部署名キー、誤った issuer/audience、期限切れトークン、nonce の欠落とリプレイ、email クレームなし

hubGroupGate.spec.ts (5)

HUB_REQUIRED_GROUP の許可判定(ニアミスのグループ名を含む)

hubPairing.spec.ts (5)

テスト用 Hub の背後にある本番 KLIP は起動を拒否します。通常のペアリングは起動を拒否しません

hubTokenAuth.spec.ts (7)

ディスカバリーから選択されるトークンエンドポイントの認証方式(post-only およびパブリッククライアントの Hub を含む)

hubDws.spec.ts (16)

DWS Hub を正確にモデル化:/api/sso ディスカバリー、パブリッククライアント、JSON のみのトークンボディ、groups スコープなし、さらに双方向のエンコーディングフォールバック

モック Hub は、実際のディスカバリードキュメント、実際の JWKS、実際の RS256 ID トークン、認可コードに対する PKCE 検証を備えた動作するミニ OIDC プロバイダーであり、不正なトークンを偽造するために必要なあらゆる設定項目を備えています。ネガティブケースこそが重要なのです。

モック KLIP フィクスチャは、実システムの癖を意図的に再現しています:MT とラベル付けされたキログラム、複数言語のステータス、標準の4つ以外のインコターム、null 数量、過剰納品された契約、暗黙に 100 にクランプされる limit、そしてプロンプトインジェクションペイロードを含む契約備考です。

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

キルスイッチ(S8) — 目標は5分未満:

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

アプリコンテナが異常な場合のブレークグラス:

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 パス、クエリパラメータ名、ページサイズ上限、レスポンスフィールド名、enum 値が保持されています。現在、すべてのエントリは未検証です。

このゲートは事務的なものではなく、実行時に強制されます。KLIP_ENV=production の場合、いずれかのルートが未検証の間、プロセスは起動を拒否します。ステージングに対して routes:verify を実行し、結果を記録し、ルートごとに verified: true を設定し、enums.verified = true を設定します。

他のどのフィールドよりも重要な2つのフィールド:

  • maxLimit — KLIP が実際に受け入れる最大の limit。暗黙に 100 にクランプされる場合、KLIP_PAGE_SIZE が 1000 だと1ページが10ページになり、レイテンシ目標を破ります。

  • enums.* — 正規のステータス値とインコターム値。マッピングされていないものは、デフォルト値にされることはなく、データ品質メモ付きで合計から除外されます。

8. TSD v0.9 からの逸脱

それぞれは設計レビューに由来し、その呼び出し箇所でコメントされています。

#

変更内容

理由

B3

Origin は存在するが無効な場合のみ拒否

仕様では、存在するが無効な Origin に対してのみ 403 が要求される。Claude はコネクタをサーバー間で呼び出し、Origin を送信しない場合がある。存在しない場合を拒否すると、すべてのツール呼び出しが 403 になる。

B4

aud = <PUBLIC_URL>/mcp(ベアのホスト名ではない)

RFC 8707 のオーディエンスバインディング。PRM ドキュメントに authorization_servers と resource を追加し、401 チャレンジに scope を追加し、RFC 9207 の iss を追加した。

B5

ステートレスなトランスポート。ID はリクエストごとにトークンから取得

リビジョン 2026-07-28 でプロトコルセッションが削除された。T-4 は構造上満たされる。乗っ取るセッションが存在しないため。

B6

キルスイッチは docker compose exec を実行

ホストに Node.js がないため、cd /opt/mcp && node cli.js は決して動作しない。

B7

nginx が不正利用対策の最低限の防御。OAuth の sub をキーにしたユーザー単位の制限

すべてのトラフィックは Anthropic の共有 egress レンジから到着するため、IP をキーにした制限ではパイロット全体が 1 つのバケットに入ってしまう。limit_req_status 429 を追加した(デフォルトは 503)。

H1

SDK の mcpAuthRouter + OAuthServerProvider 上に構築された OAuth

SDK には AS(認可サーバー)が同梱されており、失効とデフォルトのレート制限も含まれる。ストレージとユーザー認証のみが当社の実装である。

H2

下流の Hub OIDC がログインパスであり、緊急用のローカルアカウントが 1 つある

Phase 2 から前倒しされた。Hub が認証を行い、users テーブルはパイロットの許可リストのままである。ProxyOAuthServerProvider は意図的に却下された — §3 を参照。

H3

Anthropic の 160.79.104.0/21 に対する nginx 許可リストの雛形

Anthropic は安定した egress レンジを公開し、許可リスト化を推奨している。/authorize はユーザーのブラウザで実行されるため、企業 egress に対して開かれたままである。Stage 6 で実際の送信元アドレスが確認されるまでコメントアウトされている。

H4

切り詰められた結果は totals_partial を公開。集計は整数 kg で実施。未マッピングの enum は除外。マイナスの outstanding は保持

自信を持って誤った数値に至る 4 つの異なる経路。

H5

短い TTL のキャッシュ。2..N ページ目は並行して取得。ページサイズは maxLimit にクランプ

10 回のシーケンシャルなラウンドトリップでは P95 ≤ 5 秒を満たせない。

H6

9 番目のツール klip_reference を追加し、型付きの UNKNOWN_FILTER_VALUE も追加

これがないと、誤入力されたプラント名が空のセットを返し、「outstanding は何もない」と読めてしまう。

H7

environment と source は KLIP_ENV から導出

指定されたハードコードの「KLIP production」文字列では、ステージングの UAT のすべての応答が本番であると主張することになっただろう。

H9

audit_events は月次のレンジパーティション。audit:export を追加。X-Forwarded-For を尊重

パーティション削除による保持管理は、追記専用トリガーが許可する唯一の方法である。U5 のエクスポートには実装がなかった。クライアント IP はすべて 127.0.0.1 として記録されていただろう。

H10

バックアップサイドカー、コンテナのヘルスチェック、/healthz の詳細は内部呼び出し元に限定

TSD では指定されていたがガイドでは実装されておらず、本番稼働時には存在しなかっただろう。

—

未知のツールパラメータは z.strictObject で拒否

PRD 8.1 は拒否を要求している。通常の z.object は余分なパラメータを黙って無視する。

—

ツールは outputSchema に対して structuredContent を返す

散文に埋め込まれた JSON ではなく型付きデータ。転記エラーが減り、それが M1 が測定するものである。

9. 未解決事項

  • 付録 A の整合性確認(P1)— 本番稼働をブロックし、起動時に強制される。

  • KLIP 側の K1–K4 — MCP_READONLY ロール、svc-mcp アカウント、セキュリティグループルール。

  • ホスト外へのバックアップ同期とテスト済みのリストア — サイドカーはローカルのみに書き込む。

  • deploy/nginx/mcp.conf の実際の企業 egress CIDR。その後、コメントアウトされた 2 つの return 403 行を有効にする。

  • 30 同時ユーザーのキャパシティ NFR での負荷テスト。

  • Hub クライアント登録 — ゲートウェイ自身のものを、まずテスト用 DWS Hub に登録:HUB_ISSUER、クライアント ID とシークレット、リダイレクト URI <PUBLIC_URL>/authorize/hub/callback を Hub 側に登録する。Hub の email と groups クレーム名を確認してから hub:check を実行する。Stage 7 では、本番 Hub への 2 つ目の個別登録が必要である。

  • HUB_REQUIRED_GROUP を設定するかどうか、および Hub パスが本番で実証された後に BREAK_GLASS_ENABLED=false にするかどうかを決定する。

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables read-only querying of the gong-nl-db Postgres database through natural language via Claude Desktop.
    9
    Academic Free v1.1
  • 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
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language interaction with Kintone data via Claude, allowing listing apps, field definitions, querying and modifying records.
    -