关于 MCP Gateway
MCP Gateway 是面向组织的多租户平台,可将内部数据库、文件系统和自定义 MCP 工具提供给 AI 客户端。它集中管理身份验证、角色权限和审计日志,并支持 OAuth 2.1 与 Entra ID。
主要功能
- 01多租户工具访问
- 02OAuth 2.1 与 Entra ID 身份验证
- 03基于角色的访问控制
- 04支持四种数据库的 SQL 工具
- 05沙盒文件系统工具
- 06审计日志与管理界面
适用场景
- 01让 Claude 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 工具接口的工具。
查看实际效果 — 简短演示:Claude Desktop 通过 MCP Gateway 查询数据库。
目录
概述
MCP Gateway 位于 AI 助手与数据库之间。它可以:
- 通过密码登录、Microsoft Entra ID(Azure AD)或 API 密钥验证用户身份
- 执行基于角色的访问控制(viewer / analyst / admin)
- 将数据库作为 MCP 工具公开,供 AI 助手发现和调用
- 通过 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——用户只需验证一次身份,即可访问所有获准使用的内容
- Microsoft Entra ID SSO——根据 Azure AD 群组自动分配角色
- 完整的审计跟踪——记录每次工具调用、每条查询和每次登录,以及操作人员和时间
面向你的工具
- 将任何 MCP 工具接入网关,即可自动继承身份验证、RBAC 和日志记录
- 按工具覆盖角色——限制只有分析师才能执行 SQL,只有管理员才能写入文件系统
- 内置: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
管理界面
- Web 界面托管在
/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 |
| 大模型 | Anthropic SDK | 0.42.0 |
| MCP 协议 | mcp | 1.28.1 |
| SQL 验证 | sqlglot | 25.1.0 |
| 速率限制 | slowapi | 0.1.9 |
| HTTP 客户端 | httpx | 0.28.1 |
| 数据库驱动 | 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
已启动的服务:
api,端口 8000(FastAPI + 管理界面)db,端口 5432(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. 打开管理界面
访问 **http://localhost:8000/admin/**,并使用管理员凭据登录。
5. 添加数据库连接
在管理界面 → 连接 → 创建连接,或通过 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 | 用于数据库凭据的 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/ 自然语言查询端点必需 |
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 状态有效期 — 对于高延迟 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 工作进程数。大于 1 且未设置 REDIS_URL 时,限流额度会按此数值倍增 |
TRUST_PROXY_HEADERS | false | 根据 X-Forwarded-For 生成限流键。仅在受信任的代理后启用 — 否则客户端可伪造该值 |
Entra ID
| 变量 | 默认值 | 描述 |
|---|---|---|
ENTRA_AUTHORITY_URL | https://login.microsoftonline.com | Microsoft 身份平台基础 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示例使用未带版本号的路径,以便与运行中的 管理界面一致。新集成请在路径前添加/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 | 公开 | 开始管理界面的 SSO 登录(重定向到 Microsoft) |
GET | /auth/entra/callback | 公开 | 管理界面流程的 Microsoft 重定向目标 |
GET | /auth/entra/exchange | 公开 | 将一次性 SSO nonce 换取为 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/min | 执行自然语言查询 |
GET | /query/history | 管理员 | 60/min | 分页查询审计历史 |
查询:
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/min | 列出审计事件(可筛选) |
# 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 工具
对于用户可以访问的每个活动数据库连接,网关会提供两个工具:
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
以 ISO 8601 格式返回当前 UTC 时间。所有角色均可使用。
文件系统工具(仅在配置了 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 中,并包含身份验证和审计日志记录功能。
基于角色的访问控制
权限按升序排列的三个角色:viewer → analyst → admin
默认权限
| 操作 | Viewer | Analyst | Admin |
|---|---|---|---|
| 查看连接 | ✓ | ✓ | ✓ |
| 运行自然语言查询 | — | ✓ | ✓ |
| 使用文件系统工具(读取) | — | ✓ | ✓ |
| 使用文件系统工具(写入) | — | — | ✓ |
| 查看审计日志 | — | — | ✓ |
| 查看查询历史 | — | — | ✓ |
| 管理连接 | — | — | ✓ |
| 管理用户 | — | — | ✓ |
| 配置 SSO | — | — | ✓ |
| 管理 API 密钥 | ✓ | ✓ | ✓ |
| 覆盖工具角色 | — | — | ✓ |
按连接设置角色
每个连接都有一个 min_role。角色低于此级别的用户无法查看或使用该连接,也无法使用它生成的 MCP 工具。
示例:对于 min_role: admin 的敏感生产数据库,分析师和查看者完全不可见——它不会出现在 /connections/ 或 /tools/ 中,其 MCP 工具也不会列出。
按工具覆盖
管理员可以独立覆盖任何 MCP 工具的有效最低角色,不受连接的 min_role 影响:
# 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 中注册应用(应用注册 → 新注册)
- 添加重定向 URI:
http://<gateway-url>/auth/entra/callback(管理界面 SSO)http://<gateway-url>/t/<slug>/oauth/entra-callback(MCP OAuth 流程)
- 在 API 权限下添加:
- 委托(Microsoft Graph):
openid、profile、email、User.Read、GroupMember.Read.All - 应用程序(Microsoft Graph):
GroupMember.Read.All(令牌刷新期间同步角色所必需) - 对委托权限和应用程序权限中的
GroupMember.Read.All均授予管理员同意
- 委托(Microsoft Graph):
- 创建客户端密码(证书和密码 → 新建客户端密码)
- 记下 Azure 租户 ID、应用客户端 ID 和客户端密码值
在 MCP Gateway 中配置
通过管理界面:SSO 配置选项卡,或通过 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 为可选项——只配置所需的内容。属于多个已映射组的用户将获得最高角色。
登录流程
将用户引导至: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
在管理界面中将这些数据库添加为连接,以体验自然语言查询功能。
运行测试
# 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 一致。不一致会导致 OAuth 回调静默失败。
查询返回 "INVALID_QUERY"
大模型无法根据你的问题生成有效的 SELECT,或生成了非 SELECT 语句(此类语句会被阻止)。请尝试:
- 让问题更加具体
- 确保数据库中的列名和表名具有描述性
- 检查
ANTHROPIC_API_KEY是否已设置且有效
Entra 登录返回 400“Entra ID not configured for this tenant”
该租户尚未配置 Entra ID。请通过管理界面 → SSO 配置或 POST /auth/entra/config 添加配置。
Entra 回调返回 403“Not a member of any authorized group”
该 Azure AD 用户不属于为该租户配置的三个组中的任何一个。你可以:
- 将用户添加到 Azure AD 中已映射的组
- 更新网关配置中的组 ID,使其与用户的实际组相匹配(
POST /auth/entra/config)
刷新令牌被拒绝,提示“Invalid or expired”
刷新令牌只能使用一次——每次使用都会签发一对新令牌,并撤销旧令牌。如果两个请求同时尝试使用同一个刷新令牌,第二个请求会失败。请重新进行身份验证以获取一对新令牌。
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可实现共享限流。 - 如果部署在反向代理后面,请设置
TRUST_PROXY_HEADERS=true,以便根据X-Forwarded-For确定限流键。否则,所有请求看起来都来自代理, 所有客户端会共用一个限额桶。只有在可信代理确实会设置此标头时才启用该选项, 否则客户端可能伪造此标头。
安全性
静态存储的凭据
| 数据 | 存储方式 |
|---|---|
| 密码 | bcrypt(从不以明文存储) |
| JWT 签名 | SECRET_KEY(HS256) |
| 数据库连接字符串 | 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 密钥也会失效——它们使用 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 | 每次自然语言查询 |
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 查询多个数据源
- 面向业务用户的聊天界面,支持可视化——无需 SQL 知识
- 数据隐私控制,支持字段级掩码和查询级审计跟踪
- AI 供应商零锁定
- 理解您的业务语言
如果您有兴趣了解更多信息、想讨论具体用例,或只是想关注项目进展:
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。