MCP Gateway について
MCP Gatewayは、組織内のデータベース、ファイルシステム、カスタムMCPツールをAIクライアントに公開するマルチテナント基盤です。認証、ロール権限、監査ログを一元管理し、OAuth 2.1とEntra IDに対応します。
主な機能
- 01マルチテナントのツールアクセス
- 02OAuth 2.1とEntra ID認証
- 03ロールベースのアクセス制御
- 044種類のデータベース向けSQLツール
- 05サンドボックス化されたファイルシステムツール
- 06監査ログと管理画面
活用シーン
- 01Claude Desktopに許可済みのデータベースツールを提供
- 02ロール別にSQLとファイル操作を制限
- 03組織ごとのMCPツールとユーザーを管理
MCP 対応
テナント専用のSSEエンドポイントを通じて、データベース、ファイルシステム、カスタムMCPツールをMCPクライアントに公開します。ユーザー向けのOAuth 2.1 PKCEと、非対話型クライアント向けのAPIキー認証に対応します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://your-gateway.example.com/t/your-org/mcp/sseclaude mcp add --transport sse panossalt-mcp-gateway https://your-gateway.example.com/t/your-org/mcp/sse{"mcpServers":{"panossalt-mcp-gateway":{"type":"sse","url":"https://your-gateway.example.com/t/your-org/mcp/sse"}}}Cursor で開く{"servers":{"panossalt-mcp-gateway":{"type":"sse","url":"https://your-gateway.example.com/t/your-org/mcp/sse"}}}VS Code で開く{"mcpServers":{"panossalt-mcp-gateway":{"type":"sse","url":"https://your-gateway.example.com/t/your-org/mcp/sse"}}}{"mcpServers":{"panossalt-mcp-gateway":{"serverUrl":"https://your-gateway.example.com/t/your-org/mcp/sse"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
MCP Gateway
MCP ツール向けの本番環境用プラットフォームです。
Claude Desktop は、単一の認証済みエンドポイントを介して、データベース、ファイルシステム、API など、あらゆる内部ツールに接続できます。どのユーザーがどのツールを使用できるかを制御でき、すべての操作がログに記録されます。また、生の認証情報がサーバーの外部に出ることはありません。
組み込みツール:SQL クエリ(Postgres、MySQL、SQLite、MSSQL)、ファイルシステムアクセス。 カスタムツール:MCP ツールインターフェースを実装するものなら、どんなツールでも接続できます。
動作を見る — MCP Gateway を介して Claude Desktop がデータベースにクエリを実行する短いデモです。
目次
- 概要
- 機能
- アーキテクチャ
- クイックスタート
- 設定
- 認証
- API リファレンス
- MCP 連携
- ロールベースアクセス制御
- Entra ID / SSO
- 開発
- トラブルシューティング
- セキュリティ
- その他のドキュメント
概要
MCP Gateway は AI アシスタントとデータベースの間に位置します。次の機能を備えています。
- パスワードログイン、Microsoft Entra ID(Azure AD)、または API キーを使用してユーザーを認証します
- ロールベースアクセス制御(viewer / analyst / admin)を適用します
- データベースを、AI アシスタントが検出して呼び出せる MCP ツールとして公開します
- Claude を介して自然言語の質問を SQL に変換し、クエリを実行して、結果を要約します
- すべてのアクティビティを構造化された監査証跡に記録します
Claude Desktop / mcp-remote
│
│ MCP over SSE (OAuth 2.1 + PKCE)
▼
┌─────────────────────────────────────────────────────────┐
│ MCP Gateway │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────────┐ │
│ │ Auth / │ │ Admin │ │ MCP SSE Endpoint │ │
│ │ OAuth │ │ UI │ │ /t/{slug}/mcp/sse │ │
│ └──────────┘ └──────────┘ └───────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────┐│ │
│ │ Tool Providers ││ │
│ │ sql.py → get_schema / execute_sql ││ │
│ └──────────────────────────────────────┘│ │
└─────────────────────────────────────────┼───────────────┘
│ Decrypted DSN
┌─────────────────────┼────────────────┐
│ Your Databases │ │
│ Postgres MySQL MSSQL SQLite │
└──────────────────────────────────── ┘
すぐに利用できる機能
組織向け
- Claude Desktop 用の URL は 1 つだけです。ユーザーは一度認証すれば、許可されたすべてのものにアクセスできます
- Microsoft Entra ID SSO — Azure AD グループからロールが自動的に割り当てられます
- 完全な監査証跡 — すべてのツール呼び出し、すべてのクエリ、すべてのログインについて、誰が何をいつ実行したかを記録します
ツール向け
- MCP ツールを Gateway に追加するだけで、認証、RBAC、ログ記録が自動的に適用されます
- ツールごとのロール上書き — SQL 実行を analyst に、ファイルシステムへの書き込みを admin に制限できます
- 同梱ツール:SQL ツール(4 種類のデータベース)、ファイルシステムツール(読み取り、書き込み、検索、ツリー)
セキュリティチーム向け
- 従業員のマシンに認証情報を置く必要はありません
- テナント分離 — 組織 A が組織 B のツールやデータを見ることはできません
- CI/CD 向けの API キー、人間のユーザー向けの OAuth 2.1 + PKCE
対応データベース
| データベース | ドライバー | DSN 形式 |
|---|---|---|
| PostgreSQL | psycopg2 | postgresql://user:pass@host/db |
| MySQL / MariaDB | PyMySQL | mysql+pymysql://user:pass@host/db |
| Microsoft SQL Server | pymssql | mssql+pymssql://user:pass@host/db |
| SQLite | 組み込み | sqlite:///path/to/file.db |
ファイルシステムツール
- MCP ツールとして公開される、サンドボックス化されたファイルの読み取り/書き込み/検索
FILESYSTEM_ALLOWED_DIRS環境変数で有効化します- 読み取り操作(analyst 以上):
fs_read_file、fs_list_directory、fs_directory_tree、fs_search_files、fs_get_file_info - 書き込み操作(admin):
fs_write_file、fs_create_directory、fs_move_file
管理 UI
/admin/で提供されるウェブインターフェース- 接続、ユーザー、SSO 設定、API キー、ツールのロールを管理します
- 監査ログ、生成された SQL、クエリ結果を表示します
アーキテクチャ
テクノロジースタック
| レイヤー | テクノロジー | バージョン |
|---|---|---|
| API フレームワーク | FastAPI + Starlette | 0.136.1 / 1.3.1 |
| ASGI サーバー | Uvicorn | 0.34.0 |
| バリデーション | Pydantic + pydantic-settings | 2.12.5 / 2.7.1 |
| ORM | SQLAlchemy | 2.0.30 |
| マイグレーション | Alembic | 1.13.1 |
| 認証 / JWT | PyJWT + bcrypt | 2.14.0 / 4.0.1 |
| 暗号化 | cryptography (Fernet) | 50.0.0 |
| LLM | Anthropic SDK | 0.42.0 |
| MCP プロトコル | mcp | 1.28.1 |
| SQL バリデーション | sqlglot | 25.1.0 |
| レート制限 | slowapi | 0.1.9 |
| HTTP クライアント | httpx | 0.28.1 |
| DB ドライバー | psycopg2-binary / PyMySQL / pymssql | 2.9.10 / 1.1.1 / 2.3.1 |
| フロントエンド | React 18 + TypeScript + Vite | — |
開発ツール(requirements-dev.txt):pytest、pytest-asyncio、ruff、mypy。
上記の固定バージョンは requirements.txt から生成されています。両方を合わせて更新してください。
プロジェクト構成
app/
├── main.py # FastAPI app setup, middleware, routing
├── config.py # Environment config (Pydantic Settings)
├── database.py # SQLAlchemy engine + session factory
├── api/
│ ├── auth.py # POST /auth/login
│ ├── auth_entra.py # Entra SSO (legacy admin UI paths)
│ ├── oauth.py # OAuth 2.1 endpoints (/t/{slug}/oauth/*)
│ ├── connections.py # DB connection CRUD
│ ├── query.py # Natural language query endpoint
│ ├── tenants.py # Tenant + user management
│ ├── tools.py # Tool listing + role overrides
│ ├── mcp_sse.py # MCP SSE transport
│ ├── api_keys.py # API key management
│ └── audit_logs.py # GET /audit-logs/ (admin)
├── core/
│ ├── auth.py # JWT creation/validation, password hashing
│ ├── dependencies.py # FastAPI dependency injection
│ ├── rbac.py # Role hierarchy helpers
│ ├── security.py # Fernet encrypt/decrypt
│ ├── api_keys.py # API key generation + hashing
│ ├── limiter.py # slowapi rate limiter setup
│ └── log_filter.py # Health-check log noise filter
├── constants.py # Non-tunable application-wide constants (pagination caps, etc.)
├── models/__init__.py # All SQLAlchemy ORM models
├── schemas/__init__.py # All Pydantic request/response schemas
├── services/
│ ├── entra.py # Microsoft Graph API client
│ ├── llm.py # Anthropic API (SQL gen + summarization)
│ ├── mcp_client.py # Direct SQLAlchemy schema introspection + query execution
│ └── audit.py # Audit log writer
└── tools/
├── __init__.py # Tool provider framework + registry
├── sql.py # DB schema + execute_sql tools
├── example.py # Example custom tools
└── filesystem.py # Sandboxed file read/write/search tools
app/static/ # Built admin UI, served at /admin/ (generated by the
# frontend build; not edited by hand)
frontend/src/
├── main.tsx # Vite entry point
├── App.tsx # Root component, auth context, tab routing
├── api.ts # API client, token management
├── types.ts # TypeScript types (mirrors Pydantic schemas)
├── constants.ts # Frontend constants (timeouts, retry config)
├── app.css # Global styles
├── hooks/
│ └── useForm.ts # Shared form state helper
└── components/
├── Login.tsx # Sign-in form
├── Setup.tsx # Tenant registration
├── Dashboard.tsx # Tenant info + role display
├── Connections.tsx # DB connection management
├── ConnectionCreateForm.tsx # Add-connection form
├── ConnectionEditRow.tsx # Inline connection editor
├── Query.tsx # Natural language query UI
├── Users.tsx # User management (admin)
├── SsoConfig.tsx # Entra ID configuration (admin)
├── Tools.tsx # Tool browser + role overrides
├── ToolGroup.tsx # Grouped tool listing
├── ToolRoleOverride.tsx # Per-tool role control
├── ApiKeys.tsx # API key management
├── AuditLog.tsx # Filterable audit log viewer (admin)
├── AuditLogFilters.tsx # Audit log filter controls
├── AuditLogDetail.tsx # Single audit entry detail
├── ConfirmModal.tsx # Reusable confirmation dialog
└── ErrorBoundary.tsx # Top-level error boundary
alembic/versions/ # Database migrations
docs/ # Guides (OAuth flow, deployment, audit logging, …)
tests/ # pytest suite (SQLite in-memory, no services needed)
データベーススキーマ
Tenants ─┬─► Users ──────► APIKeys
├─► APIKeys (also a direct tenant_id FK, not only via Users)
├─► DBConnections
├─► TenantEntraConfig
├─► AuditLogs (tenant_id and user_id both nullable)
├─► OAuthAuthorizationCodes
├─► OAuthRefreshTokens
└─► ToolRoleOverrides
OAuthStates (no tenant_id FK — holds a plain
tenant_slug string, since the row is
created before the tenant is resolved)
クイックスタート
前提条件
- Docker と Docker Compose
- Anthropic API キー(
/query/エンドポイント用。MCP ツールへの直接アクセスには不要です)
1. クローンして設定する
git clone <repo-url>
cd MCP-Gateway
cp .env.example .env
.env を編集します:
# Required — generate unique values
SECRET_KEY=<random 64-char string>
ENCRYPTION_KEY=<random string, min 32 chars — longer is better>
POSTGRES_PASSWORD=<strong password>
# Required for natural language query
ANTHROPIC_API_KEY=sk-ant-...
# Update to your server's public URL in production
BASE_URL=http://localhost:8000
安全なランダム値を生成します:
# SECRET_KEY
python3 -c "import secrets; print(secrets.token_hex(32))"
# ENCRYPTION_KEY (min 32 chars; full key consumed via BLAKE2b derivation)
python3 -c "import secrets; print(secrets.token_hex(32))"
2. スタックを起動する
docker compose up -d
起動するサービス:
- ポート 8000 の
api(FastAPI + 管理 UI) - ポート 5432 の
db(PostgreSQL、内部のみ)
3. 最初のテナントを登録する
curl -s -X POST http://localhost:8000/tenants/ \
-H "Content-Type: application/json" \
-d '{
"name": "My Organization",
"slug": "my-org",
"admin_email": "[email protected]",
"admin_password": "SuperSecret123!"
}' | jq
slug は MCP URL の一部になります:http://localhost:8000/t/my-org/mcp/sse
4. 管理 UI を開く
http://localhost:8000/admin/ にアクセスし、管理者の認証情報でサインインします。
5. データベース接続を追加する
管理 UI → 接続 → 接続を作成、または API 経由で追加します:
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"SuperSecret123!"}' \
| jq -r .access_token)
curl -s -X POST http://localhost:8000/connections/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production DB",
"db_type": "postgres",
"connection_string": "postgresql://user:pass@host/mydb",
"min_role": "viewer"
}' | jq
6. Claude Desktop に接続する
Claude Desktop の MCP 設定(macOS の場合は ~/Library/Application Support/Claude/claude_desktop_config.json)に追加します:
{
"mcpServers": {
"my-org-gateway": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:8000/t/my-org/mcp/sse"
]
}
}
}
Claude Desktop を再起動します。OAuth ログイン用のブラウザーウィンドウが開きます。認証後、Claude はデータベースツールを使用できるようになります。
設定
すべての設定は環境変数で行います。テンプレートについては .env.example を参照してください。
必須
| 変数 | 説明 |
|---|---|
SECRET_KEY | JWT 署名用シークレット — ランダムな 64 文字の文字列を使用してください |
ENCRYPTION_KEY | DB 認証情報用の Fernet AES キー — 32 文字以上。キー全体が BLAKE2b で使用されます |
DATABASE_URL | PostgreSQL DSN — docker-compose によって自動設定されます。ローカル(Docker を使用しない)開発でのみ必要です。デフォルト値はありません。設定しない場合、アプリは起動しません |
POSTGRES_PASSWORD も必要ですが、必要とするのはアプリケーションではなく docker-compose です。この値は db サービスの初期設定に使われ、DATABASE_URL に埋め込まれます。POSTGRES_PASSWORD、SECRET_KEY、ENCRYPTION_KEY のいずれかが未設定の場合、Compose は即座に失敗します。
オプション
| 変数 | デフォルト | 説明 |
|---|---|---|
ANTHROPIC_API_KEY | — | /query/ NL クエリエンドポイントに必要です |
BASE_URL | http://localhost:8000 | 公開 URL(OAuth コールバックで使用) |
CORS_ORIGINS | BASE_URL | CORS で許可するオリジンをカンマ区切りで指定します。絶対 URL を指定する必要があります。ワイルドカード(*)は拒否されます |
ACCESS_TOKEN_EXPIRE_MINUTES | 15 | JWT アクセストークンの有効期間 |
REFRESH_TOKEN_EXPIRE_DAYS | 30 | OAuth リフレッシュトークンの有効期間 |
OAUTH_STATE_TTL_MINUTES | 10 | OAuth PKCE state の有効期間。レイテンシーの高い SSO プロバイダーを使用する場合は、この値を大きくしてください |
OAUTH_CODE_TTL_MINUTES | 5 | OAuth 認可コードの有効期間 |
LLM_MODEL | claude-sonnet-4-6 | SQL 生成に使用する Anthropic モデル |
LLM_MAX_TOKENS_SQL | 1024 | SQL 生成の最大トークン数 |
LLM_MAX_TOKENS_SUMMARY | 500 | 結果の要約に使用する最大トークン数 |
FILESYSTEM_ALLOWED_DIRS | — | MCP ファイルシステムツールがアクセスできるディレクトリをカンマ区切りで指定します。エントリは絶対パスである必要があり、.. を含めることはできません。そうでない場合、アプリは起動を拒否します。空の場合、ファイルシステムツールは公開されません |
LOG_LEVEL | INFO | ルートログレベル(DEBUG、INFO、WARNING、ERROR) |
ALGORITHM | HS256 | JWT 署名アルゴリズム |
REDIS_URL | — | レート制限ストレージを共有します。空の場合、制限はメモリ内に保存されるため、ワーカーごとに適用されます |
WEB_CONCURRENCY | 1 | Uvicorn ワーカー数。REDIS_URL が設定されていない状態で 1 を超えると、レート制限はこの数だけ乗算されます |
TRUST_PROXY_HEADERS | false | X-Forwarded-For からレート制限キーを導出します。信頼できるプロキシの背後にある場合にのみ有効にしてください。そうでない場合、クライアントが値を偽装できます |
Entra ID
| 変数 | デフォルト | 説明 |
|---|---|---|
ENTRA_AUTHORITY_URL | https://login.microsoftonline.com | Microsoft ID プラットフォームのベース URL |
ENTRA_GRAPH_URL | https://graph.microsoft.com/v1.0 | Microsoft Graph API のベース URL |
認証
ローカルログイン
POST /auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "SuperSecret123!",
"tenant_slug": "my-org" // optional, disambiguates if same email is in multiple tenants
}
レスポンス:
{
"access_token": "eyJ...",
"token_type": "bearer"
}
以降のリクエストにはトークンを含めます:
Authorization: Bearer eyJ...
アクセストークンはデフォルトで 15 分後に有効期限が切れます。OAuth トークンエンドポイントでリフレッシュトークンを使用すると、新しいトークンのペアを取得できます。
API キー
キーを生成します(認証が必要です):
curl -s -X POST http://localhost:8000/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "CI pipeline"}' | jq
レスポンスの raw_key は一度だけ表示されるため、すぐに保存してください:
{
"id": "...",
"name": "CI pipeline",
"prefix": "mgw_abcd1234",
"raw_key": "mgw_abcd1234...",
"created_at": "..."
}
クエリパラメーターで使用します:
curl "http://localhost:8000/connections/?api_key=mgw_abcd1234..."
OAuth 2.1(MCP ブラウザークライアント)
ゲートウェイは RFC 8414 OAuth ディスカバリーを実装しています。MCP クライアントは次のフローを自動的に実行します:
- クライアントが
/t/{slug}/mcp/sseに接続します。401 と、OAuth ディスカバリー URL を示すWWW-Authenticateヘッダーを受け取ります - クライアントが
/.well-known/oauth-authorization-server/t/{slug}を取得します - クライアントが
POST /t/{slug}/oauth/registerで動的に登録します - クライアントがブラウザーを開き、ユーザーが
/t/{slug}/oauth/authorizeでログインします - クライアントが
POST /t/{slug}/oauth/tokenでコードと PKCE 検証子をトークンに交換します - クライアントが Bearer トークンを使って再接続します
手動での設定は不要です。mcp-remote にテナントの SSE URL を指定するだけです。
API キー認証(MCP 非対話型クライアント)
CI/CD、スクリプト、またはブラウザーログインを省略したい場合は、URL に API キーを指定します:
{
"mcpServers": {
"gateway": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8000/t/my-org/mcp/sse?api_key=mgw_..."]
}
}
}
SSE エンドポイントがキーを検証し、セッションを直接確立します。OAuth フローもブラウザーウィンドウも必要ありません。詳しくはAPI キーを参照してください。
API リファレンス
すべての管理エンドポイントは、標準パス(例:/tenants/)とバージョン付きプレフィックス /api/v1/(例:/api/v1/tenants/)の両方で利用できます。バージョンなしのパスは、現在のフロントエンドとの後方互換性のために維持されています。新しい統合では /api/v1/ を使用してください。プロトコルで定義されたルート(OAuth /t/{slug}/…、MCP /t/{slug}/…、/.well-known/)とインフラストラクチャルート(/health、/admin)には、意図的にバージョンが付いていません。
以下の
curlの例では、稼働中の管理 UI と一致するよう、バージョンなしのパスを使用しています。 新しい統合では、先頭に/api/v1を付けてください。
認証
| メソッド | パス | ロール | 説明 |
|---|---|---|---|
POST | /auth/login | 公開 | メールアドレスとパスワードでログインし、JWT を返します |
テナントとユーザー
| メソッド | パス | ロール | 説明 |
|---|---|---|---|
POST | /tenants/ | 公開 | 新しいテナントと管理者ユーザーを登録します |
GET | /tenants/me | 全員 | 自分のテナントの詳細を取得します |
GET | /tenants/users | 管理者 | テナント内のすべてのユーザーを一覧表示します |
POST | /tenants/users | 管理者 | ローカルユーザーを作成します |
PATCH | /tenants/users/{user_id} | 管理者 | ユーザーのロールを更新します |
DELETE | /tenants/users/{user_id} | 管理者 | テナントからユーザーを削除します |
Entra ID / SSO
| メソッド | パス | ロール | 説明 |
|---|---|---|---|
POST | /auth/entra/config | 管理者 | テナントの Entra 設定を作成または置き換えます |
GET | /auth/entra/config | 管理者 | 現在の Entra 設定を読み取ります(シークレットは伏せられます) |
DELETE | /auth/entra/config | 管理者 | Entra 設定を削除します |
GET | /auth/entra/login | 公開 | 管理 UI の SSO ログインを開始します(Microsoft にリダイレクトします) |
GET | /auth/entra/callback | 公開 | 管理 UI フローにおける Microsoft からのリダイレクト先です |
GET | /auth/entra/exchange | 公開 | 1 回限りの SSO ノンスを JWT と交換します |
テナントの登録:
curl -X POST http://localhost:8000/tenants/ \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"slug": "acme",
"admin_email": "[email protected]",
"admin_password": "SuperSecret123!"
}'
ユーザーの作成:
curl -X POST http://localhost:8000/tenants/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "AnotherSecret456!",
"role": "analyst"
}'
ロールは viewer(デフォルト)、analyst、admin です。パスワードは 12 文字以上である必要があります。
接続
| メソッド | パス | ロール | 説明 |
|---|---|---|---|
POST | /connections/ | 管理者 | データベース接続を追加します |
GET | /connections/ | Viewer 以上 | アクセス可能な接続を一覧表示します |
PATCH | /connections/{id} | 管理者 | 接続を更新します |
DELETE | /connections/{id} | 管理者 | 接続を論理削除します |
接続の追加:
curl -X POST http://localhost:8000/connections/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Sales DB",
"db_type": "postgres",
"connection_string": "postgresql://user:pass@db-host/sales",
"description": "Production sales database",
"min_role": "analyst"
}'
min_role は、この接続にクエリできるユーザーを指定します。このロールより下位のユーザーは、接続を表示したり使用したりできません。
自然言語クエリ
| メソッド | パス | ロール | レート制限 | 説明 |
|---|---|---|---|---|
POST | /query/ | Analyst 以上 | 30/分 | 自然言語クエリを実行します |
GET | /query/history | 管理者 | 60/分 | ページ分割されたクエリ監査履歴を取得します |
クエリ:
curl -X POST http://localhost:8000/query/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "...",
"question": "What are the top 5 customers by total revenue this quarter?"
}'
レスポンス:
{
"sql_generated": "SELECT customer_name, SUM(amount) AS total FROM orders ...",
"result": [
{"customer_name": "Acme Corp", "total": 125000}
],
"summary": "The top customer this quarter is Acme Corp with $125,000 in revenue."
}
クエリパイプライン:
- データベースからスキーマを取得します
- スキーマと質問を Claude に送信し、SQL を生成します
- SQL が
SELECTステートメントであることを検証します(すべての書き込みをブロックします) - SQL を実行します(タイムアウトは 30 秒です)
- 質問と結果を Claude に送信し、要約を生成します
ツール
| メソッド | パス | ロール | 説明 |
|---|---|---|---|
GET | /tools/ | 全員 | ロールのメタデータ付きで MCP ツールを一覧表示します |
PATCH | /tools/{tool_name} | 管理者 | ロールの上書きを設定またはリセットします |
ツールの一覧表示:
curl http://localhost:8000/tools/ \
-H "Authorization: Bearer $TOKEN"
レスポンス:
[
{
"tool_name": "execute_sql_sales-db_abcd1234",
"description": "Execute SQL on Sales DB",
"connection_id": "...",
"default_min_role": "analyst",
"effective_min_role": "admin",
"accessible": false
}
]
ツールロールの上書き:
# Restrict to admin only
curl -X PATCH "http://localhost:8000/tools/execute_sql_sales-db_abcd1234" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"min_role": "admin"}'
# Reset to connection default
curl -X PATCH "http://localhost:8000/tools/execute_sql_sales-db_abcd1234" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"min_role": null}'
API キー
| メソッド | パス | 説明 |
|---|---|---|
POST | /api-keys | 新しいキーを生成します |
GET | /api-keys | 自分のキーを一覧表示します |
DELETE | /api-keys/{id} | キーを無効化します |
# Generate (expires_at is optional)
curl -X POST http://localhost:8000/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "CI pipeline", "expires_at": "2027-01-01T00:00:00Z"}'
# List
curl http://localhost:8000/api-keys \
-H "Authorization: Bearer $TOKEN"
# Revoke
curl -X DELETE "http://localhost:8000/api-keys/{id}" \
-H "Authorization: Bearer $TOKEN"
監査ログ
| メソッド | パス | ロール | レート制限 | 説明 |
|---|---|---|---|---|
GET | /audit-logs/ | 管理者 | 60/分 | 監査イベントを一覧表示します(フィルタリング可能) |
# All events (paginated)
curl "http://localhost:8000/audit-logs/?limit=50" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Filter by event type (comma-separated prefixes)
curl "http://localhost:8000/audit-logs/?event_prefix=query,tool&limit=100" \
-H "Authorization: Bearer $ADMIN_TOKEN"
クエリパラメーター:skip(オフセット、デフォルト 0)、limit(最大 200、デフォルト 50)、event_prefix(カンマ区切り。例:query、login、fs)。
ヘルスチェック
curl http://localhost:8000/health
# {"status": "ok"}
データベースに接続できない場合は 503 を返します。Kubernetes のライブネスプローブおよびレディネスプローブに適しています。
MCP の統合
Claude Desktop への接続
mcp-remote をインストールします:
npm install -g mcp-remote
claude_desktop_config.json に追加します:
{
"mcpServers": {
"gateway": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8000/t/my-org/mcp/sse"]
}
}
}
初回接続時に、OAuth ログイン用のブラウザーウィンドウが開きます。認証後、mcp-remote はトークンをキャッシュし、自動的に再接続します。トークンはバックグラウンドでサイレントに更新されます。
利用可能な MCP ツール
ユーザーがアクセスできるアクティブなデータベース接続ごとに、ゲートウェイは 2 つのツールを公開します。
get_schema_{connection-name}_{id}
データベーススキーマ全体(テーブル、カラム、型、制約、インデックス)を返します。Claude は SQL を生成する前にデータ構造を把握するため、最初にこのツールを呼び出します。
execute_sql_{connection-name}_{id}
SELECT 文を実行し、行を JSON として返します。SELECT 以外の文(INSERT、UPDATE、DELETE、DROP など)はすべて拒否されます。実行タイムアウト:30 秒。
list_connections
ユーザーがアクセスできるすべてのデータベース接続と、その名前および種類を返します。
get_current_time
現在の UTC 時刻を ISO 8601 形式で返します。すべてのロールで利用できます。
ファイルシステムツール(FILESYSTEM_ALLOWED_DIRS が設定されている場合のみ):
| ツール | ロール | 説明 |
|---|---|---|
fs_read_file | Analyst+ | ファイルを UTF-8 テキストとして読み取ります |
fs_list_directory | Analyst+ | ディレクトリの内容を一覧表示します |
fs_directory_tree | Analyst+ | 再帰的なディレクトリツリー(JSON) |
fs_search_files | Analyst+ | Glob パターンで検索します |
fs_get_file_info | Analyst+ | ファイルのメタデータ(サイズ、タイムスタンプ) |
fs_write_file | Admin | ファイルを作成または上書きします |
fs_create_directory | Admin | ディレクトリを作成します(親ディレクトリも作成) |
fs_move_file | Admin | ファイルを移動または名前変更します |
SSE エンドポイント
| エンドポイント | 認証 | 説明 |
|---|---|---|
GET /t/{slug}/mcp/sse | Bearer JWT または ?api_key= | テナント単位の SSE(推奨) |
POST /t/{slug}/mcp/messages | Bearer JWT、?api_key=、またはセッション ID | テナント単位のメッセージハンドラー |
GET /mcp/sse | ?api_key= または ?token= | レガシー SSE(非推奨、2027-03-01 に廃止) |
POST /mcp/messages | Bearer JWT、?api_key=、または ?token= | レガシーのメッセージハンドラー(非推奨) |
OAuth 検出エンドポイント
| エンドポイント | RFC | 説明 |
|---|---|---|
GET /.well-known/oauth-authorization-server/t/{slug} | RFC 8414 | 認可サーバーのメタデータ |
GET /.well-known/oauth-protected-resource/t/{slug}/mcp/sse | RFC 9728 | 保護されたリソースのメタデータ |
GET /t/{slug}/.well-known/oauth-authorization-server | — | 同じメタデータ、テナントプレフィックス付きパス |
GET /t/{slug}/.well-known/oauth-protected-resource | — | 同じメタデータ、テナントプレフィックス付きパス |
POST /t/{slug}/oauth/register | RFC 7591 | 動的クライアント登録 |
GET /t/{slug}/oauth/authorize | RFC 6749 | 認可エンドポイント(PKCE S256) |
POST /t/{slug}/oauth/login | — | ローカルログインフォームの送信(SSO を使用しないテナント) |
GET /t/{slug}/oauth/entra-callback | — | MCP OAuth フローにおける Microsoft のリダイレクト先 |
POST /t/{slug}/oauth/token | RFC 6749 | トークンエンドポイント(code + refresh_token) |
独自のツールを作成する
Python 関数はすべて、認証と監査に対応した MCP ツールになります。
# app/tools/my_tool.py
from app.tools import register_tool, ToolContext
from mcp.types import TextContent
@register_tool(name="my_custom_tool", min_role="analyst")
async def my_tool(arguments: dict, ctx: ToolContext) -> list[TextContent]:
# ctx.user gives you the authenticated user + their role
# ctx.db gives you the database session
result = do_something(arguments["input"])
return [TextContent(type="text", text=result)]
ゲートウェイを再起動します。認証と監査ログが含まれたツールが、自動的に Claude Desktop に表示されます。
ロールベースのアクセス制御
権限の低い順に 3 つのロールがあります:viewer → analyst → admin
デフォルトの権限
| アクション | Viewer | Analyst | Admin |
|---|---|---|---|
| 接続を表示 | ✓ | ✓ | ✓ |
| 自然言語クエリを実行 | — | ✓ | ✓ |
| ファイルシステムツールを使用(読み取り) | — | ✓ | ✓ |
| ファイルシステムツールを使用(書き込み) | — | — | ✓ |
| 監査ログを表示 | — | — | ✓ |
| クエリ履歴を表示 | — | — | ✓ |
| 接続を管理 | — | — | ✓ |
| ユーザーを管理 | — | — | ✓ |
| SSO を設定 | — | — | ✓ |
| API キーを管理 | ✓ | ✓ | ✓ |
| ツールのロールを上書き | — | — | ✓ |
接続ごとのロール
各接続には min_role があります。このロールに満たないユーザーは、その接続や、そこから生成される MCP ツールを表示または使用できません。
例:min_role: admin に設定された機密性の高い本番データベースは、アナリストや閲覧者には一切表示されません。/connections/ や /tools/ に表示されず、その MCP ツールも一覧に表示されません。
ツールごとの上書き
管理者は、接続の min_role とは独立して、任意の MCP ツールに適用される最小ロールを上書きできます:
# Lock down SQL execution on prod, but keep schema browsing open
PATCH /tools/execute_sql_prod-db_abcd1234 {"min_role": "admin"}
PATCH /tools/get_schema_prod-db_abcd1234 {"min_role": "analyst"}
# Reset to connection default
PATCH /tools/execute_sql_prod-db_abcd1234 {"min_role": null}
Entra ID / SSO
Azure AD でのセットアップ
- Azure Active Directory でアプリケーションを登録します(App registrations → New registration)
- リダイレクト URI を追加します:
http://<gateway-url>/auth/entra/callback(管理 UI の SSO)http://<gateway-url>/t/<slug>/oauth/entra-callback(MCP OAuth フロー)
- API permissions で以下を追加します:
- Delegated(Microsoft Graph):
openid、profile、email、User.Read、GroupMember.Read.All - Application(Microsoft Graph):
GroupMember.Read.All(トークン更新時のロール同期に必要) - 委任された
GroupMember.Read.AllとアプリケーションのGroupMember.Read.Allの両方について、admin consent を付与します
- Delegated(Microsoft Graph):
- Client secret を作成します(Certificates & secrets → New client secret)
- Azure テナント ID、アプリのクライアント ID、クライアントシークレットの値を控えます
MCP Gateway での設定
管理 UI の SSO Config タブ、または API から設定します:
curl -X POST http://localhost:8000/auth/entra/config \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entra_tenant_id": "your-azure-tenant-uuid",
"client_id": "your-app-client-id",
"client_secret": "your-client-secret",
"admin_group_id": "azure-group-uuid-for-admins",
"analyst_group_id": "azure-group-uuid-for-analysts",
"viewer_group_id": "azure-group-uuid-for-viewers"
}'
グループ ID は任意です。必要なものだけ設定してください。複数のマッピング済みグループに所属するユーザーには、最も高いロールが割り当てられます。
ログインフロー
ユーザーを次の URL に誘導します:http://<gateway>/auth/entra/login?tenant_slug=<slug>
ゲートウェイは Microsoft にリダイレクトします。認証後、次の処理を行います:
- Microsoft Graph からユーザーのプロフィールを取得します(
/me) - 推移的なグループメンバーシップを取得します(
/me/transitiveMemberOf) - グループをロールにマッピングします(最も高いロールを適用:admin > analyst > viewer)
- ユーザーが存在しない場合は作成します(ジャストインタイムプロビジョニング)
- JWT を返します
開発
ローカルでのセットアップ(Docker を使用しない場合)
# Python environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt # includes requirements.txt plus pytest/ruff/mypy
# Configure
cp .env.example .env
# Edit .env: set DATABASE_URL to a local Postgres instance (or use SQLite for quick testing)
# Run migrations
alembic upgrade head
# Start API (with auto-reload)
uvicorn app.main:app --reload --port 8000
フロントエンド開発
cd frontend
npm install
npm run dev # Vite dev server on port 5173 with API proxy
Vite 開発サーバーはすべての API パスを http://localhost:8000 にプロキシするため、開発中はフロントエンドと API を個別に実行できます。
サンプルデータベース
docker compose --profile dev up -d
データが事前投入されたサンプルデータベースを起動します:
sample_postgres(ポート 5433)→postgresql://sampleuser:samplepass@localhost:5433/sampledbsample_mysql(ポート 3307)→mysql+pymysql://sampleuser:samplepass@localhost:3307/sampledb
自然言語クエリ機能を試すには、管理 UI でこれらを接続として追加します。
テストの実行
# Full suite (no external services required — uses SQLite in-memory)
.venv/bin/python -m pytest
# Verbose output
.venv/bin/python -m pytest -v
# Single test file
.venv/bin/python -m pytest tests/test_connections_api.py -v
# Single test
.venv/bin/python -m pytest tests/test_oauth.py::test_token_exchange_valid_code_returns_tokens -v
データベースのマイグレーション
# Apply all pending migrations
alembic upgrade head
# Create a new migration after changing models/__init__.py
alembic revision --autogenerate -m "describe your change"
# Roll back one step
alembic downgrade -1
# Show history
alembic history --verbose
本番環境向けビルド
# Build frontend static files
cd frontend && npm run build && cd ..
# The Dockerfile builds both in a multi-stage build:
docker build -t mcp-gateway .
docker compose up -d
トラブルシューティング
「SECRET_KEY must be set」/「ENCRYPTION_KEY must be set」
docker-compose では ${VAR:?error message} 構文を使用しており、これらが設定されていない場合はすぐにエラーになります。次のコマンドで生成します:
python3 -c "import secrets; print(secrets.token_hex(32))" # SECRET_KEY
python3 -c "import secrets; print(secrets.token_hex(32))" # ENCRYPTION_KEY
docker compose up を実行する前に、.env ファイルに追加してください。
ヘルスチェックで API が 503 を返す
データベースに接続できません。次を確認してください:
docker compose ps # are all containers running?
docker compose logs db # any Postgres startup errors?
docker compose restart api # restart API if db was slow to start
ログイン時に Claude Desktop でブラウザーが開かない
mcp-remote がインストールされていることを確認します:npm install -g mcp-remote。.env の BASE_URL が claude_desktop_config.json に記載した URL と一致していることを確認してください。URL が一致しないと、OAuth コールバックはエラーを表示せずに失敗します。
クエリが "INVALID_QUERY" を返す
LLM が質問に対する有効な SELECT を生成できなかったか、SELECT 以外のステートメントを生成したため(ブロックされます)、次を試してください:
- 質問をより具体的にする
- データベースの列名とテーブル名が内容を表すものになっていることを確認する
ANTHROPIC_API_KEYが設定され、有効であることを確認する
Entra ログインで 400「Entra ID not configured for this tenant」が返る
このテナントには Entra ID の設定がありません。Admin UI → SSO Config または POST /auth/entra/config から追加してください。
Entra コールバックで 403「Not a member of any authorized group」が返る
Azure AD ユーザーが、テナントに設定された 3 つのグループのいずれにも所属していません。次のいずれかを行ってください:
- Azure AD でユーザーをマッピング済みグループのいずれかに追加する
- ゲートウェイ設定のグループ ID を、ユーザーが実際に所属しているグループと一致するように更新する(
POST /auth/entra/config)
更新トークンが「Invalid or expired」として拒否される
更新トークンは一度だけ使用できます。使用するたびに新しいペアが発行され、古いトークンは無効になります。同じ更新トークンを 2 つのリクエストが同時に使用しようとすると、2 つ目のリクエストは失敗します。再認証して新しいペアを取得してください。
レート制限による429レスポンス
| エンドポイント | 制限 |
|---|---|
POST /tenants/ | 5/min |
POST /auth/login | 10/min |
POST /t/{slug}/oauth/login | 10/min |
POST /api-keys | 10/min |
POST /t/{slug}/oauth/register | 10/min |
GET /auth/entra/login | 20/min |
GET /auth/entra/exchange | 20/min |
GET /t/{slug}/oauth/entra-callback | 20/min |
GET /t/{slug}/oauth/authorize | 30/min |
POST /t/{slug}/oauth/token | 30/min(authorization_code と refresh_token の両方のグラントに適用) |
POST /query/ | 30/min |
GET /audit-logs/ | 60/min |
GET /query/history | 60/min |
制限ウィンドウがリセットされるまで60秒待ってください。
制限はクライアントのIPアドレスに基づきます。デプロイ時には、次の点に注意してください。
REDIS_URLがない場合、リミッターはカウンターをプロセス内メモリに保存するため、各uvicornワーカーが個別に制限を適用します。WEB_CONCURRENCY=4の場合、実質的な制限は上記の値のおよそ4倍になります。制限を共有するにはREDIS_URLを設定してください。- リバースプロキシの背後で運用する場合は、キーの取得元が
X-Forwarded-ForになるようにTRUST_PROXY_HEADERS=trueを設定してください。設定しないと、すべてのリクエストがプロキシから送信されたように見え、すべてのクライアントで同じバケットが共有されます。信頼できるプロキシが実際にこのヘッダーを設定している場合を除き、有効にしないでください。有効にすると、クライアントがヘッダーを偽装できる可能性があります。
セキュリティ
保存時の認証情報
| データ | 保存方法 |
|---|---|
| パスワード | bcrypt(平文では保存しません) |
| JWT署名 | SECRET_KEY(HS256) |
| DB接続文字列 | Fernet AES-256で暗号化 |
| Entraクライアントシークレット | Fernet AES-256で暗号化 |
| APIキー | SECRET_KEY を鍵とするHMAC-SHA-256(生のキーは一度だけ返され、保存されません) |
| リフレッシュトークン | SHA-256ハッシュ |
HTTPセキュリティヘッダー
すべてのレスポンスに次の内容が含まれます。
X-Content-Type-Options: nosniffX-Frame-Options: DENYStrict-Transport-Security: max-age=31536000- 認証エンドポイントの
Cache-Control: no-store
OAuthの保護
- PKCE S256 — 認可コードの傍受攻撃を防ぎます
- 一度限りの認可コード — コードは5分後に期限切れとなり、初回使用時に削除されます
- ローテーションするリフレッシュトークン — 更新のたびに前のトークンが無効化されます(リプレイを防止)
- ループバック限定のリダイレクトURI — リダイレクト先として受け付けられるのは
localhost、127.0.0.1、::1のみです(RFC 8252に準拠)
SQLの安全性
execute_sql MCPツールは、クエリがデータベースに到達する前にsqlglotのAST解析を行い、SELECT以外のすべての文を拒否します。INSERT、UPDATE、DELETE、DROP、CREATE、ALTER、TRUNCATE、EXECは、形式にかかわらずすべてブロックされます。
テナントの分離
すべてのデータベースクエリは current_user.tenant_id の範囲に限定されます。外部キー制約によりスキーマレベルで分離が強制されるため、あるテナントのデータが別のテナントのレスポンスに現れるコードパスはありません。
シークレットのローテーション
SECRET_KEY のローテーション: 既存のJWTはすべてただちに無効になります。ユーザーは再認証が必要です。別途ハッシュ化されたリフレッシュトークンも無効になります。APIキーも無効になります — APIキーは SECRET_KEY を鍵とするHMACで生成されるため、ローテーション後に既存のキーを失効させ、新たに発行する必要があります。
ENCRYPTION_KEY のローテーション: 古いキーを削除する前に、保存されているすべての接続文字列とEntraクライアントシークレットを新しいキーで再暗号化する必要があります。メンテナンス時間を確保してください。ローテーション中、ゲートウェイは接続を提供できません。
監査ログ
すべての重要なイベントは audit_logs テーブルに記録されます。
| イベント | 発生時 |
|---|---|
login.success / login.failure | ログイン試行のたび |
oauth.login / oauth.entra_login | OAuth 認可時 |
oauth.token_issued / oauth.token_refreshed | トークンの交換および更新時 |
query.success / query.failure | NL クエリのたび |
tool.execute_sql / tool.execute_sql.rejected / tool.execute_sql.error | MCP SQL ツールの使用時 |
fs.*(例:fs.fs_read_file、fs.fs_write_file.error) | ファイルシステムツールの使用時 |
connection.created / connection.updated / connection.deleted | 接続の変更時 |
tenant.created | テナントの登録時 |
user.deleted / user.role_updated | ユーザー管理時 |
key.created / key.revoked | API キーのライフサイクル |
監査ログをクエリします。
curl "http://localhost:8000/audit-logs/?limit=100" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
その他のドキュメント
| ガイド | 説明 |
|---|---|
| Claude Desktop でのテスト | エンドツーエンドの手順:ローカルユーザー + Entra SSO |
| デプロイガイド | Railway、Render、および一般的な Docker/VPS へのデプロイ |
| OAuth 2.1 フロー | PKCE フロー全体、エンドポイント、トークンのライフサイクル |
| ファイルシステムツール | MCP によるサンドボックス化されたファイルアクセス |
| 監査ログ | イベント一覧、API、メタデータのリファレンス |
| API キー | キーのライフサイクル、セキュリティモデル、使用方法 |
| ツールのロールオーバーライド | ツールごとの RBAC 設定 |
今後の予定 — SaltMine AI
MCP Gateway はオープンソースの基盤です。SaltMine AI というマネージドプラットフォームを現在開発中です。このプロジェクトを基盤とし、同じセキュリティ原則に従って構築されており、インフラストラクチャを管理せずにデータをクエリしたいビジネスチームを対象としています。
予定されている機能は次のとおりです。
- データベース、データレイク、API を横断する複数のデータソースへのクエリを、1 つの質問で実行
- ビジネスユーザー向けの、ビジュアライゼーション機能を備えたチャットインターフェイス — SQL の知識は不要
- フィールドレベルのマスキングとクエリレベルの監査証跡によるデータプライバシー管理
- AI におけるベンダーロックインの排除
- ビジネスで使われる言葉を理解
詳細について知りたい方、ご相談したいユースケースがある方、または進捗を追いたい方は、以下をご覧ください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。