How does it work について
How does it work は、Pomerium とコンテキストアクセスポリシーを用いて、リモート MCP サーバーとクライアントを保護する方法を示すデモチャットアプリケーションです。内部の MCP サーバーを安全に公開したい開発者を想定しています。OpenAI を利用したチャット画面を備え、認証には OAuth 2.1 フローを使用します。
主な機能
- 01Pomerium によるMCP サーバーのプロキシ保護
- 02OAuth 2.1 認証と上流トークンの管理
- 03ユーザー ID に基づくアクセスポリシー
- 04MCP ツール呼び出しの監査ログ設定
- 05OpenAI を利用したチャット画面
活用シーン
- 01社内データベースの MCP サーバーを外部クライアントに安全に共有
- 02社内アプリケーションから保護された MCP サーバーを LLM 経由で呼び出し
- 03上流 OAuth を必要とする MCP サーバーへのサインインの統一
MCP 対応
Pomerium の背後にリモート MCP サーバーを配置し、クライアントがそれらに接続して一覧取得やツール利用を行う流れを示します。ツール呼び出しは mcp-* フィールドで記録されます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Pomerium Chat へようこそ。これは、Pomerium で保護されたリモートの Model Context Protocol サーバーを紹介する、シンプルなチャットアプリケーションです。
https://github.com/user-attachments/assets/50a17106-a471-4f64-be81-982c09d4689e
前提条件
- Linux または MacOS のホスト
- Docker と Docker Compose
- お使いのマシンでは、LetsEncrypt から TLS 証明書を取得し、OpenAI が MCP サーバーのエンドポイントを呼び出せるように、ポート 443 をインターネットに公開する必要があります。
- OpenAI API キー
クイックスタート
環境変数
ルートディレクトリに .env-mcp-app-demo ファイルを作成し、次の環境変数を追加します。
OPENAI_API_KEY=your_api_key_here
Pomerium の設定
pomerium-config.yaml を更新し、YOUR-DOMAIN をご自身が管理するサブドメインに置き換えます。該当するホスト(または *.YOUR-DOMAIN)の A レコードを DNS に作成します。
デフォルトでは、アクセス ポリシーにより、メールアドレスが YOUR-DOMAIN に属するユーザーにアクセスが制限されます。調整が必要な場合は、ポリシー言語のリファレンスをご覧ください。
Docker Compose
このリポジトリの docker-compose.yaml ファイルをご覧ください。
docker compose up -d
これにはデモ用 SQLite サーバーが含まれており、デモデータベースが必要です。たとえば https://github.com/jpwhite3/northwind-SQLite3 を sqlite.db として保存してください。
テスト
これで https://mcp-app-demo.YOUR-DOMAIN/ にアクセスできるはずです。
サインインページが開きます。サインインすると、アプリケーションにリダイレクトされます。
デモ用データベースサーバー(Database)が利用可能で、「接続済み」の状態になっているはずです。会話で使用するには、これをクリックします。
「年ごとの売上はいくらでしたか?」などの質問をして、OpenAI の LLM 推論が、お使いのコンピューターで実行中の MCP データベースサーバーとどのように連携して回答を取得するかを確認できます。
仕組み
詳しくは、Model Context Protocol の機能概要をご覧ください。
トークンの用語
-
外部トークン(TE): Pomerium が発行する、ユーザーのセッションを表す外部向けトークンです。このトークンは、外部クライアント(Claude.ai、OpenAI、自作アプリなど)が Pomerium で保護された MCP サーバーへのリクエストを認証するために使用します。 例:LLM API またはエージェント型フレームワークに渡し、MCP サーバーを呼び出せるようにするトークンです。
-
内部トークン(TI): Pomerium がユーザーに代わってアップストリームの OAuth2 プロバイダー(Notion、Google Drive、GitHub など)から取得する内部認証トークンです。このトークンが外部クライアントに公開されることはありません。Pomerium は、MCP サーバーへのリクエストをプロキシする際に、このトークンを使ってアップストリームサービスへのリクエストを認証します。
Pomerium は、Model Context Protocol(MCP)のクライアントとサーバーの間で安全なゲートウェイとして機能します。OAuth 2.1 フローを使用して、ローカル HTTP MCP サーバーの認証と認可を提供します。この構成は、MCP サーバーから OAuth トークンを必要とするアップストリーム API(Notion、Google Drive、GitHub など)にアクセスする場合に特に役立ちます。
また、このリポジトリで紹介しているように、エージェント型フレームワークや、MCP サーバーを呼び出せる LLM API を利用する内部アプリケーションを構築できます。
この構成を理解するために、Pomerium で保護された MCP サーバーと MCP クライアントがどのように通信するかを見ていきましょう。
1. 内部 MCP サーバーをリモートクライアントに公開する
https://github.com/user-attachments/assets/c7a809ad-6f0a-4ea3-94a2-6cf22ea6d27b
Claude.ai などの外部 MCP クライアントが内部 MCP サーバーにアクセスできるようにしつつ、安全性を保ちたい場合を考えてみましょう。Pomerium をサーバーの前段に配置し、すべての受信リクエストの認証と認可を管理します。
これにより、データベースなどの内部リソースをインターネットに直接公開せずに、外部クライアントと安全に共有できます。
通常どおり Pomerium Route を設定し、追加で mcp プロパティを指定します。これにより、このルートが Model Context Protocol サーバーのルートであることを示します。
routes:
- from: https://my-mcp-server.your-domain.com
to: http://my-mcp-server.int:8080/mcp
name: My MCP Server
mcp:
server: {}
sequenceDiagram
actor U as User
participant C as MCP Client
participant P as Pomerium
participant S as MCP Server
U ->> C: Adds a server URL
C ->> P: Registers client, initiates auth
P ->> C: Sign-in URL
C ->> U: Redirect to sign-in URL
U ->> P: Sign-in
P ->> C: Redirect to client
C ->> P: Obtain Token
C ->> P: GET https://mcp-server Authorization: Bearer Token
P ->> S: Proxy request to MCP Server
2. MCP サーバーがアップストリーム OAuth を必要とする場合
MCP サーバーが GitHub や Google Drive など、OAuth を必要とするアップストリームサービスにアクセスする必要がある場合、Pomerium が OAuth フローを処理できます。プロセスは次のとおりです。
- ユーザーがクライアント(例:Claude.ai)に MCP サーバーの URL を追加します。
- クライアントが Pomerium に登録し、認証を開始します。
- Pomerium がクライアントにサインイン URL を渡し、ユーザーに表示します。
- ユーザーが Pomerium にサインインすると、アップストリームの OAuth プロバイダーにリダイレクトされます。
- ユーザーがアップストリームプロバイダーで認証します。プロバイダーは Pomerium に 内部トークン(TI) を返します。
- Pomerium がサインインを完了し、ユーザーをクライアントにリダイレクトします。
- クライアントは Pomerium から 外部トークン(TE) を受け取ります。
- クライアントは TE を使用して MCP サーバーにリクエストを送信します。
- Pomerium は必要に応じてアップストリームトークン(TI)を更新し、
Authorizationヘッダーに TI を含めてリクエストを MCP サーバーにプロキシします。
主な利点:
- Claude.ai などの外部クライアントにアップストリーム OAuth トークンが見えることはありません。
- MCP サーバーは常に有効なアップストリームトークンを受け取ります。
- MCP サーバーはステートレスなまま運用でき、OAuth フローやトークンを管理する必要がありません。
ルート設定:
routes:
- from: https://github.your-domain
to: http://github-mcp.int:8080/mcp
name: GitHub
mcp:
server:
upstream_oauth2:
client_id: xxxxxxxxxxxx
client_secret: yyyyyyyyy
scopes: ['read:user', 'user:email']
endpoint:
auth_url: 'https://github.com/login/oauth/authorize'
token_url: 'https://github.com/login/oauth/access_token'
sequenceDiagram
actor U as User
participant C as MCP Client
participant O as Upstream OAuth
participant P as Pomerium
participant S as MCP Server
U ->> C: Adds a server URL
C ->> P: Registers client, initiates auth
P ->> C: Sign-in URL
C ->> U: Redirect to sign-in URL
U ->> P: Sign-in
P ->> U: Redirect to upstream OAuth
U ->> O: Authenticate with upstream OAuth
O ->> P: Return Internal Token (TI)
P ->> C: Redirect to client
C ->> P: Obtain External Token (TE)
C ->> P: GET https://mcp-server Authorization: Bearer (TE)
P ->> O: Refresh (TI) if necessary
P ->> S: Proxy request to MCP Server, Bearer (TI)
3. アプリから内部 MCP サーバーを呼び出す
OpenAI API や Claude API など、一部の推論 API は MCP サーバーの直接呼び出しに対応しています。この傾向は今後さらに広がると予想され、多くのエージェント型フレームワークが MCP サーバー呼び出しに対応しつつあります。また、LLM の関数呼び出し機能を使って、アプリ内で MCP ツール呼び出しを手動で実装することもできます。いずれの方法でも、Pomerium を経由してリクエストを安全にルーティングするために、MCP サーバー用の Authorization: Bearer 外部トークン(TE) を提供する必要があります。
独自の内部アプリケーションを構築しており、そのようなトークンを取得する必要がある場合、Pomerium ではルートに対して クライアント MCP モード を利用できます。mcp.pass_upstream_access_token オプションを設定すると、Pomerium は現在のユーザーセッションを表す Authorization: Bearer 外部トークン(TE) をアップストリームアプリケーションに渡します。このトークンを外部 LLM やエージェント型フレームワークに渡すことで、認可ポリシーに従って Pomerium 配下の MCP サーバーにアクセスさせることができます。
次のフローは、ユーザーがすでに Pomerium で認証されている場合のプロセスを示しています。
sequenceDiagram
actor U as User
participant P as Pomerium
participant C as Your App Backend
participant S as MCP Server
participant I as LLM API
U ->> P: GET https://mcp-app-demo.your-domain.com
P ->> C: GET http://mcp-app-demo:3000 Authorization: Bearer (TE)
C ->> I: call tool https://mcp-server.your-domain Authorization: Bearer (TE)
I ->> P: GET https://mcp-server.your-domain Authorization: Bearer (TE)
P ->> S: GET https://mcp-server
ルート設定の例:
routes:
- from: https://mcp-app-demo.your-domain.com
to: http://mcp-app-demo:3000
mcp:
client: {}
policy:
domain:
is: your-domain.com
- from: https://mcp-server.your-domain.com
to: http://mcp-server.int:8080/mcp
name: My MCP Server
mcp:
server: {}
policy:
domain:
is: your-domain.com
4. アプリから利用可能な MCP サーバーを一覧表示する
アプリケーションと同じ Pomerium インスタンスで保護されている MCP サーバーの動的な一覧を、ユーザーに提供できます。そのためには、バックエンドが受け取ったものと同じ Authorization: Bearer トークンを使って、アプリのバックエンドに HTTP リクエストを送信します。レスポンスには、この Pomerium クラスターで利用可能な各上流 MCP サーバーの一覧と接続状態が含まれます。
connected プロパティは、現在のユーザーが上流 OAuth に必要なすべての内部トークンを持っているかどうか(必要な場合)を示します。
- true – ユーザーは、上流 OAuth プロバイダーから必要なすべての内部トークンを取得済みか、このサーバーではトークンが不要です。
- false – この MCP サーバーにアクセスする前に、ユーザーは上流 OAuth プロバイダーで認証する必要があります。
後述のセクションでは、ユーザーが必要なすべての内部トークンを確実に取得する方法を説明します。
GET https://mcp-demo-app.yourdomain.com/.pomerium/mcp/routes HTTP/1.1
Accept: application/json
Authorization: Bearer (TE)
Content-Type: application/json
{
"servers": [
{
"name": "DB",
"url": "https://db-mcp.your-domain.com",
"connected": true
},
{
"name": "GitHub",
"url": "https://github-mcp.your-domain.com",
"connected": false
}
]
}
5. 現在のユーザーが上流 OAuth2 プロバイダーで認証済みであることを確認する
対象の MCP サーバーに connected: false と表示されている場合、ユーザーは必要な上流 OAuth2 プロバイダーで認証する必要があります。
そのためには、ユーザーのブラウザーを MCP サーバーのルート上にある特別な /.pomerium/mcp/connect パスにリダイレクトします(例:https://db-mcp.your-domain.com/.pomerium/mcp/connect)。
アプリケーションのページを指す redirect_url クエリパラメーターを指定します。認証後にユーザーが戻るページであり、MCP サーバーの一覧と接続状態を再読み込みするページです。
注: セキュリティ上、redirect_url のホストは、いずれかの MCP クライアントルートと一致する必要があります。
ユーザーが認証を完了すると、MCP サーバーの connected ステータスは true になります。
6. ユーザー詳細の取得
認証済みユーザーの ID とクレームにアクセスするには、MCP クライアントアプリケーションと MCP サーバーの両方で、X-Pomerium-Assertion HTTP ヘッダーを読み取ります。
このヘッダーにはユーザー情報を含む署名済み JWT が格納されています。これをデコードして検証すると、ユーザーのメールアドレスや名前、その他のクレームなどの詳細を取得できます。
MCP ツール呼び出しの監視と監査
Pomerium は、すべての MCP ツール呼び出しに対して包括的な監査ログを提供し、外部クライアントが MCP サーバーとどのようにやり取りしているかを監視、分析できます。
監査ログの表示
次のコマンドを使って、MCP ツール呼び出しをリアルタイムで監視できます。
生ログ形式
フィルターされていない監査ログを発生順に表示します。
docker compose logs -f pomerium \
| grep --line-buffered "authorize" \
| grep --line-buffered "mcp-"
整形済み JSON 形式
構文強調表示付きで整形された監査ログを表示します。
docker compose logs -f pomerium \
| grep --line-buffered "authorize" \
| grep --line-buffered "mcp-" \
| sed -u 's/^[^{]*//' \
| jq -C
注:JSON の整形には jq をインストールしておく必要があります。
監査ログエントリーの例
MCP ツール呼び出しの監査ログエントリーの例を示します。
{
"level": "info",
"server-name": "all",
"service": "authorize",
"request-id": "b9375fd5-e220-9c1d-8f27-9b42249c2de5",
"user": "google-oauth2|110679203791094235151",
"email": "[email protected]",
"mcp-method": "tools/call",
"mcp-tool": "read_query",
"mcp-tool-parameters": {
"query": "SELECT SupplierID, CompanyName, Country FROM Suppliers WHERE Country IN ('Austria', 'Belgium', 'Denmark', 'Finland', 'France', 'Germany', 'Ireland', 'Italy', 'Netherlands', 'Norway', 'Portugal', 'Spain', 'Sweden', 'Switzerland', 'UK');"
},
"host": "northwind.mcp.pomerium.com",
"allow": true,
"allow-why-true": ["domain-ok"],
"deny": false,
"deny-why-false": [],
"time": "2025-06-27T20:56:44Z",
"message": "authorize check"
}
MCP 監査ログの設定
包括的な MCP 監査ログを有効にするには、次のグローバルセクションを Pomerium の設定に追加します。mcp-* フィールドは、MCP をサポートするために特別に設計された新しい authorize_log_fields です。
# for MCP observability
authorize_log_fields:
- request-id
- user
- email
- mcp-method
- mcp-tool
- mcp-tool-parameters
- host
この設定により、MCP 固有の情報がすべて監査ログに含まれ、ツール呼び出しとそのパラメーターを完全に把握できます。
開発
このアプリケーションを開発モードで実行するには、次のコマンドを使います。
npm install
npm run dev
これにより、ホットリロードを有効にした開発サーバーが起動します。
本番環境
このアプリケーションを本番環境向けにビルドして実行するには、次のコマンドを使います。
npm run build
npm run start
機能
- OpenAI を使用した AI 搭載チャットインターフェース
- shadcn/ui によるモダンな UI コンポーネント
- TanStack Router による型安全なルーティング
- TanStack Query によるデータ取得
テスト
このプロジェクトでは、テストに Vitest を使用しています。次のコマンドでテストを実行できます。
npm run test
スタイリング
このプロジェクトでは、スタイリングにTailwind CSS、事前構築済みのコンポーネントにshadcn/uiを使用しています。UI コンポーネントは Radix UI プリミティブの上に構築されています。
新しいコンポーネントの追加
次のコマンドで shadcn コンポーネントを追加します。
npx shadcn@latest add [component-name]
リントとフォーマット
このプロジェクトでは、リントとフォーマットにeslintとprettierを使用しています。Eslint はtanstack/eslint-configを使って設定されています。次のスクリプトを利用できます。
npm run lint
npm run format
npm run check
Shadcn
Shadcnの最新バージョンを使ってコンポーネントを追加します。
pnpx shadcn@latest add button
ルーティング
このプロジェクトではTanStack Routerを使用しています。初期設定ではファイルベースのルーターを採用しています。つまり、ルートはsrc/routes内のファイルとして管理されます。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。

