MCP Gateway

io.github.PanosSalt由 GitHub 账号 PanosSalt 发布v1.0.4更新于 Oct 24, 2026

Multi-tenant MCP platform with OAuth 2.1, Entra SSO, RBAC and audit logging.

已验证SSE可网页运行数据库文件与存储安全与监控

概览

AI 生成的概览

多租户 MCP 网关,向助手提供受角色控制的数据库查询与沙箱文件系统工具,并支持 OAuth 2.1、Entra SSO、RBAC 和审计日志。

可以做什么
数据库:列出可访问的连接、读取表结构,并执行仅限 SELECT 的 SQL(30 秒超时),以 JSON 返回结果,可选由 Claude 生成自然语言摘要。文件系统:在配置的目录内读取、列目录、查看目录树、按通配符搜索和查看文件信息(分析师及以上);写入、创建目录和移动文件(仅管理员)。查询当前 UTC 时间。平台管理:通过 API 和管理界面管理租户、用户与角色、数据库连接、按工具的角色覆盖、API 密钥、审计日志和查询历史。
适用场景
适合团队希望用一个共享的、带身份验证的 MCP 端点,让助手以只读方式查询多个数据库并获得受限的文件系统访问,同时具备租户隔离和基于角色的权限控制。也适合需要 SSO、审计记录以及集中控制各角色可用工具的组织。
运行要求
作为自托管服务运行(Docker 与 Docker Compose,或 Python 加 PostgreSQL),通过 SSE 访问;清单中列为远程端点。需要 SECRET_KEY、ENCRYPTION_KEY、POSTGRES_PASSWORD 和 DATABASE_URL;自然语言查询端点需要 ANTHROPIC_API_KEY;文件系统工具需配置 FILESYSTEM_ALLOWED_DIRS;SSO 可选,需 Entra ID 应用注册。客户端通过 OAuth 2.1 浏览器流程或 URL 中的 API 密钥认证。
安装前请注意
数据库连接字符串以 ENCRYPTION_KEY 加密存储,该密钥与 SECRET_KEY 必须保密且保持稳定。管理员角色的文件系统工具可在允许目录内创建、覆盖和移动文件。以 URL 查询参数传递的 API 密钥可能出现在日志中;原始密钥仅显示一次。设置 ANTHROPIC_API_KEY 后,自然语言查询会把数据库结构和结果发送给 Anthropic。TRUST_PROXY_HEADERS 只应在可信代理之后启用。

安装

在 SourceWeft 中

  1. 打开 控制台中的 MCP Gateway,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Web executable,通过 SSE。 远程服务在工作区中配置后即可从网页运行时运行。

交给智能体安装

把这段提示词粘贴到 Claude Code、Codex、Cursor 等智能体,或 SourceWeft 对话中。智能体会读取该服务器的安装指南,先向你展示发布者、端点和登录方式,经你同意后再把它添加到你的客户端。密钥不会写进任何命令。

阅读 https://sourceweft.com/mcp/io.github.PanosSalt%2FMCP-Gateway/install.md,并按其中说明把这个 MCP 服务器接入我的客户端。修改任何配置之前,先告诉我它的发布者、端点和登录方式,等我确认。不要把 API 密钥写进命令或我会提交的文件;问我密钥应从哪里获取。

查看智能体所依据的安装指南

其他 MCP 客户端

把它添加到你客户端的 mcpServers 配置中。

{
  "mcpServers": {
    "gateway": {
      "type": "sse",
      "url": "https://your-gateway.example.com/t/your-org/mcp/sse"
    }
  }
}

README

MCP Gateway

[Tests] [License: MIT] [Python 3.12]

The production platform for MCP tools.

Claude Desktop can connect to your internal tools — databases, filesystems, APIs, anything — through a single authenticated endpoint. You control who can use which tools, every action is logged, and no raw credentials ever leave your server.

Built-in tools: SQL query (Postgres, MySQL, SQLite, MSSQL), filesystem access. Custom tools: plug in anything that implements the MCP tool interface.

See it in action — short demo of Claude Desktop querying a database through MCP Gateway.

Table of Contents


Overview

MCP Gateway sits between AI assistants and your databases. It:

  1. Authenticates users via password login, Microsoft Entra ID (Azure AD), or API keys
  2. Enforces role-based access control (viewer / analyst / admin)
  3. Exposes databases as MCP tools that AI assistants can discover and call
  4. Translates natural language questions into SQL via Claude, executes queries, and summarizes results
  5. Logs all activity to a structured audit trail
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      │                    └────────────────────────────────────  ┘

What you get out of the box

For your organisation

  • One URL for Claude Desktop — users authenticate once, access everything they're allowed
  • Microsoft Entra ID SSO — roles assigned automatically from Azure AD groups
  • Full audit trail — every tool call, every query, every login, who did what and when

For your tools

  • Drop any MCP tool into the gateway and it inherits auth, RBAC, and logging automatically
  • Per-tool role overrides — restrict SQL execution to analysts, filesystem writes to admins
  • Bundled: SQL tools (4 databases), filesystem tools (read, write, search, tree)

For your security team

  • No credentials on employee machines
  • Tenant isolation — org A cannot see org B's tools or data
  • API keys for CI/CD, OAuth 2.1 + PKCE for human users

Supported Databases

DatabaseDriverDSN Format
PostgreSQLpsycopg2postgresql://user:pass@host/db
MySQL / MariaDBPyMySQLmysql+pymysql://user:pass@host/db
Microsoft SQL Serverpymssqlmssql+pymssql://user:pass@host/db
SQLiteBuilt-insqlite:///path/to/file.db

Filesystem Tools

  • Sandboxed file read/write/search exposed as MCP tools
  • Enabled via FILESYSTEM_ALLOWED_DIRS environment variable
  • Read operations (analyst+): fs_read_file, fs_list_directory, fs_directory_tree, fs_search_files, fs_get_file_info
  • Write operations (admin): fs_write_file, fs_create_directory, fs_move_file

Admin UI

  • Web interface served at /admin/
  • Manage connections, users, SSO config, API keys, and tool roles
  • View audit logs, generated SQL, and query results

Architecture

Technology Stack

LayerTechnologyVersion
API FrameworkFastAPI + Starlette0.136.1 / 1.3.1
ASGI ServerUvicorn0.34.0
ValidationPydantic + pydantic-settings2.12.5 / 2.7.1
ORMSQLAlchemy2.0.30
MigrationsAlembic1.13.1
Auth / JWTPyJWT + bcrypt2.14.0 / 4.0.1
Encryptioncryptography (Fernet)50.0.0
LLMAnthropic SDK0.42.0
MCP Protocolmcp1.28.1
SQL Validationsqlglot25.1.0
Rate Limitingslowapi0.1.9
HTTP Clienthttpx0.28.1
DB Driverspsycopg2-binary / PyMySQL / pymssql2.9.10 / 1.1.1 / 2.3.1
FrontendReact 18 + TypeScript + Vite—

Dev tooling (requirements-dev.txt): pytest, pytest-asyncio, ruff, mypy. The pinned versions above are generated from requirements.txt — update both together.

Project Structure

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 migrationsdocs/                     # Guides (OAuth flow, deployment, audit logging, …)tests/                    # pytest suite (SQLite in-memory, no services needed)

Database Schema

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)

Quick Start

Prerequisites

  • Docker and Docker Compose
  • An Anthropic API key (for the /query/ endpoint; not needed for raw MCP tool access)

1. Clone and configure

bash
git clone <repo-url>cd MCP-Gatewaycp .env.example .env

Edit .env:

bash
# Required — generate unique valuesSECRET_KEY=<random 64-char string>ENCRYPTION_KEY=<random string, min 32 chars — longer is better>POSTGRES_PASSWORD=<strong password>
# Required for natural language queryANTHROPIC_API_KEY=sk-ant-...
# Update to your server's public URL in productionBASE_URL=http://localhost:8000

Generate secure random values:

bash
# SECRET_KEYpython3 -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. Start the stack

bash
docker compose up -d

Services started:

  • api on port 8000 (FastAPI + admin UI)
  • db on port 5432 (PostgreSQL, internal only)

3. Register your first tenant

bash
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

The slug becomes part of your MCP URL: http://localhost:8000/t/my-org/mcp/sse

4. Open the admin UI

Navigate to http://localhost:8000/admin/ and sign in with your admin credentials.

5. Add a database connection

In the admin UI → Connections → Create connection, or via API:

bash
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. Connect Claude Desktop

Add to your Claude Desktop MCP config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

json
{  "mcpServers": {    "my-org-gateway": {      "command": "npx",      "args": [        "-y",        "mcp-remote",        "http://localhost:8000/t/my-org/mcp/sse"      ]    }  }}

Restart Claude Desktop. It will open a browser window for OAuth login. After authenticating, Claude can use your database tools.


Configuration

All configuration is via environment variables. See .env.example for a template.

Required

VariableDescription
SECRET_KEYJWT signing secret — use a random 64-char string
ENCRYPTION_KEYFernet AES key for DB credentials — minimum 32 characters; full key consumed via BLAKE2b
DATABASE_URLPostgreSQL DSN — set automatically by docker-compose; only needed for local (non-Docker) dev. No default: the app will not start without it

POSTGRES_PASSWORD is also required, but by docker-compose, not by the application — it seeds the db service and is interpolated into DATABASE_URL. Compose fails fast if it, SECRET_KEY or ENCRYPTION_KEY are unset.

Optional

VariableDefaultDescription
ANTHROPIC_API_KEY—Required for /query/ NL query endpoint
BASE_URLhttp://localhost:8000Public-facing URL (used in OAuth callbacks)
CORS_ORIGINSBASE_URLComma-separated allowed origins for CORS. Must be absolute URLs — wildcards (*) are rejected
ACCESS_TOKEN_EXPIRE_MINUTES15JWT access token lifetime
REFRESH_TOKEN_EXPIRE_DAYS30OAuth refresh token lifetime
OAUTH_STATE_TTL_MINUTES10OAuth PKCE state validity window — increase for high-latency SSO providers
OAUTH_CODE_TTL_MINUTES5OAuth authorization code validity window
LLM_MODELclaude-sonnet-4-6Anthropic model for SQL generation
LLM_MAX_TOKENS_SQL1024Max tokens for SQL generation
LLM_MAX_TOKENS_SUMMARY500Max tokens for result summarization
FILESYSTEM_ALLOWED_DIRS—Comma-separated directories the MCP filesystem tools may access. Entries must be absolute and must not contain .., or the app refuses to start. When empty, no filesystem tools are exposed
LOG_LEVELINFORoot log level (DEBUG, INFO, WARNING, ERROR)
ALGORITHMHS256JWT signing algorithm
REDIS_URL—Shared rate-limiter storage. When empty, limits are in-memory and therefore per worker
WEB_CONCURRENCY1Uvicorn worker count. Above 1 without REDIS_URL, rate limits are multiplied by this number
TRUST_PROXY_HEADERSfalseDerive the rate-limit key from X-Forwarded-For. Enable only behind a trusted proxy — otherwise clients can spoof it

Entra ID

VariableDefaultDescription
ENTRA_AUTHORITY_URLhttps://login.microsoftonline.comMicrosoft identity platform base URL
ENTRA_GRAPH_URLhttps://graph.microsoft.com/v1.0Microsoft Graph API base URL

Authentication

Local login

http
POST /auth/loginContent-Type: application/json
{  "email": "[email protected]",  "password": "SuperSecret123!",  "tenant_slug": "my-org"   // optional, disambiguates if same email is in multiple tenants}

Response:

json
{  "access_token": "eyJ...",  "token_type": "bearer"}

Include the token in subsequent requests:

Authorization: Bearer eyJ...

Access tokens expire after 15 minutes by default. Use the OAuth token endpoint with a refresh token to get a new pair.

API keys

Generate a key (requires authentication):

bash
curl -s -X POST http://localhost:8000/api-keys \  -H "Authorization: Bearer $TOKEN" \  -H "Content-Type: application/json" \  -d '{"name": "CI pipeline"}' | jq

The raw_key in the response is shown once only — store it immediately:

json
{  "id": "...",  "name": "CI pipeline",  "prefix": "mgw_abcd1234",  "raw_key": "mgw_abcd1234...",  "created_at": "..."}

Use via query parameter:

bash
curl "http://localhost:8000/connections/?api_key=mgw_abcd1234..."

OAuth 2.1 (MCP browser clients)

The gateway implements RFC 8414 OAuth discovery. MCP clients follow this flow automatically:

  1. Client connects to /t/{slug}/mcp/sse — receives 401 + WWW-Authenticate header pointing to the OAuth discovery URL
  2. Client fetches /.well-known/oauth-authorization-server/t/{slug}
  3. Client registers dynamically via POST /t/{slug}/oauth/register
  4. Client opens browser → user logs in at /t/{slug}/oauth/authorize
  5. Client exchanges code + PKCE verifier for tokens via POST /t/{slug}/oauth/token
  6. Client reconnects with Bearer token

No manual configuration needed — just point mcp-remote at your tenant's SSE URL.

API key auth (MCP non-interactive clients)

For CI/CD, scripts, or when you want to skip the browser login, pass an API key in the URL:

json
{  "mcpServers": {    "gateway": {      "command": "npx",      "args": ["-y", "mcp-remote", "http://localhost:8000/t/my-org/mcp/sse?api_key=mgw_..."]    }  }}

The SSE endpoint validates the key and establishes the session directly — no OAuth flow, no browser window. See API Keys for details.


API Reference

All management endpoints are available at both their canonical paths (e.g. /tenants/) and the versioned prefix /api/v1/ (e.g. /api/v1/tenants/). The unversioned paths are kept for backward compatibility with the current frontend; new integrations should use /api/v1/. Protocol-defined routes (OAuth /t/{slug}/…, MCP /t/{slug}/…, /.well-known/) and infrastructure routes (/health, /admin) are intentionally unversioned.

The curl examples below use the unversioned paths so they match the running admin UI. Prefix them with /api/v1 for new integrations.

Authentication

MethodPathRoleDescription
POST/auth/loginPublicEmail + password login, returns a JWT

Tenants & Users

MethodPathRoleDescription
POST/tenants/PublicRegister new tenant + admin user
GET/tenants/meAnyGet your tenant details
GET/tenants/usersAdminList all users in your tenant
POST/tenants/usersAdminCreate a local user
PATCH/tenants/users/{user_id}AdminUpdate user role
DELETE/tenants/users/{user_id}AdminRemove a user from the tenant

Entra ID / SSO

MethodPathRoleDescription
POST/auth/entra/configAdminCreate or replace the tenant's Entra config
GET/auth/entra/configAdminRead the current Entra config (secret redacted)
DELETE/auth/entra/configAdminRemove the Entra config
GET/auth/entra/loginPublicBegin admin-UI SSO login (redirects to Microsoft)
GET/auth/entra/callbackPublicMicrosoft redirect target for the admin-UI flow
GET/auth/entra/exchangePublicExchange the one-time SSO nonce for a JWT

Register tenant:

bash
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!"  }'

Create user:

bash
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"  }'

Roles: viewer (default), analyst, admin. Passwords must be at least 12 characters.


Connections

MethodPathRoleDescription
POST/connections/AdminAdd a database connection
GET/connections/Viewer+List accessible connections
PATCH/connections/{id}AdminUpdate connection
DELETE/connections/{id}AdminSoft-delete connection

Add connection:

bash
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 controls who can query this connection. Users below this role cannot see or use it.


Natural Language Query

MethodPathRoleRate LimitDescription
POST/query/Analyst+30/minExecute NL query
GET/query/historyAdmin60/minPaginated query audit history

Query:

bash
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?"  }'

Response:

json
{  "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."}

The query pipeline:

  1. Fetches schema from the database
  2. Sends schema + question to Claude → generates SQL
  3. Validates SQL is a SELECT statement (blocks all writes)
  4. Executes SQL (30-second timeout)
  5. Sends question + results to Claude → generates summary

Tools

MethodPathRoleDescription
GET/tools/AnyList MCP tools with role metadata
PATCH/tools/{tool_name}AdminSet or reset role override

List tools:

bash
curl http://localhost:8000/tools/ \  -H "Authorization: Bearer $TOKEN"

Response:

json
[  {    "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  }]

Override tool role:

bash
# Restrict to admin onlycurl -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 defaultcurl -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 Keys

MethodPathDescription
POST/api-keysGenerate a new key
GET/api-keysList your keys
DELETE/api-keys/{id}Revoke a key
bash
# 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"}'
# Listcurl http://localhost:8000/api-keys \  -H "Authorization: Bearer $TOKEN"
# Revokecurl -X DELETE "http://localhost:8000/api-keys/{id}" \  -H "Authorization: Bearer $TOKEN"

Audit Logs

MethodPathRoleRate LimitDescription
GET/audit-logs/Admin60/minList audit events (filterable)
bash
# 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"

Query parameters: skip (offset, default 0), limit (max 200, default 50), event_prefix (comma-separated, e.g. query, login, fs).


Health Check

bash
curl http://localhost:8000/health# {"status": "ok"}

Returns 503 if the database is unreachable. Suitable for Kubernetes liveness and readiness probes.


MCP Integration

Connecting Claude Desktop

Install mcp-remote:

bash
npm install -g mcp-remote

Add to claude_desktop_config.json:

json
{  "mcpServers": {    "gateway": {      "command": "npx",      "args": ["mcp-remote", "http://localhost:8000/t/my-org/mcp/sse"]    }  }}

On first connection, a browser window opens for OAuth login. After authenticating, mcp-remote caches the tokens and reconnects automatically. Tokens refresh silently in the background.

Available MCP Tools

For each active database connection the user can access, the gateway exposes two tools:

get_schema_{connection-name}_{id} Returns the full database schema (tables, columns, types, constraints, indexes). Claude calls this first to understand the data structure before generating SQL.

execute_sql_{connection-name}_{id} Executes a SELECT statement and returns rows as JSON. Any non-SELECT statement is rejected (INSERT, UPDATE, DELETE, DROP, etc.). Execution timeout: 30 seconds.

list_connections Returns all database connections the user can access with their names and types.

get_current_time Returns the current UTC time in ISO 8601 format. Available to all roles.

Filesystem tools (only when FILESYSTEM_ALLOWED_DIRS is configured):

ToolRoleDescription
fs_read_fileAnalyst+Read a file as UTF-8 text
fs_list_directoryAnalyst+List directory contents
fs_directory_treeAnalyst+Recursive directory tree (JSON)
fs_search_filesAnalyst+Glob pattern search
fs_get_file_infoAnalyst+File metadata (size, timestamps)
fs_write_fileAdminCreate or overwrite a file
fs_create_directoryAdminCreate a directory (with parents)
fs_move_fileAdminMove or rename a file

SSE Endpoints

EndpointAuthDescription
GET /t/{slug}/mcp/sseBearer JWT or ?api_key=Tenant-scoped SSE (recommended)
POST /t/{slug}/mcp/messagesBearer JWT, ?api_key=, or session IDTenant-scoped message handler
GET /mcp/sse?api_key= or ?token=Legacy SSE (deprecated, sunset 2027-03-01)
POST /mcp/messagesBearer JWT, ?api_key=, or ?token=Legacy message handler (deprecated)

OAuth Discovery Endpoints

EndpointRFCDescription
GET /.well-known/oauth-authorization-server/t/{slug}RFC 8414Authorization server metadata
GET /.well-known/oauth-protected-resource/t/{slug}/mcp/sseRFC 9728Protected resource metadata
GET /t/{slug}/.well-known/oauth-authorization-server—Same metadata, tenant-prefixed path
GET /t/{slug}/.well-known/oauth-protected-resource—Same metadata, tenant-prefixed path
POST /t/{slug}/oauth/registerRFC 7591Dynamic client registration
GET /t/{slug}/oauth/authorizeRFC 6749Authorization endpoint (PKCE S256)
POST /t/{slug}/oauth/login—Local login form submission (non-SSO tenants)
GET /t/{slug}/oauth/entra-callback—Microsoft redirect target for the MCP OAuth flow
POST /t/{slug}/oauth/tokenRFC 6749Token endpoint (code + refresh_token)

Build your own tools

Any Python function becomes an authenticated, audited MCP tool:

python
# app/tools/my_tool.pyfrom app.tools import register_tool, ToolContextfrom 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)]

Restart the gateway. The tool appears in Claude Desktop automatically, with auth and audit logging included.


Role-Based Access Control

Three roles in ascending order of permission: viewer → analyst → admin

Default permissions

ActionViewerAnalystAdmin
View connections✓✓✓
Run NL queries—✓✓
Use filesystem tools (read)—✓✓
Use filesystem tools (write)——✓
View audit logs——✓
View query history——✓
Manage connections——✓
Manage users——✓
Configure SSO——✓
Manage API keys✓✓✓
Override tool roles——✓

Per-connection roles

Each connection has a min_role. Users below this role cannot see or use that connection, or the MCP tools it generates.

Example: A sensitive production database with min_role: admin is invisible to analysts and viewers entirely — it won't appear in /connections/ or /tools/, and its MCP tools won't be listed.

Per-tool overrides

Admins can override the effective minimum role for any MCP tool independently of the connection's min_role:

bash
# Lock down SQL execution on prod, but keep schema browsing openPATCH /tools/execute_sql_prod-db_abcd1234  {"min_role": "admin"}PATCH /tools/get_schema_prod-db_abcd1234   {"min_role": "analyst"}
# Reset to connection defaultPATCH /tools/execute_sql_prod-db_abcd1234  {"min_role": null}

Entra ID / SSO

Setup in Azure AD

  1. Register an application in Azure Active Directory (App registrations → New registration)
  2. Add redirect URIs:
    • http://<gateway-url>/auth/entra/callback (admin UI SSO)
    • http://<gateway-url>/t/<slug>/oauth/entra-callback (MCP OAuth flow)
  3. Under API permissions, add:
    • Delegated (Microsoft Graph): openid, profile, email, User.Read, GroupMember.Read.All
    • Application (Microsoft Graph): GroupMember.Read.All (required for role sync during token refresh)
    • Grant admin consent for both the delegated and the application GroupMember.Read.All
  4. Create a Client secret (Certificates & secrets → New client secret)
  5. Note your Azure tenant ID, app client ID, and the client secret value

Configure in MCP Gateway

Via admin UI: SSO Config tab, or via API:

bash
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"  }'

Group IDs are optional — configure only what you need. Users in multiple mapped groups get the highest role.

Login flow

Direct users to: http://<gateway>/auth/entra/login?tenant_slug=<slug>

The gateway redirects to Microsoft. After authentication it:

  1. Fetches the user's profile from Microsoft Graph (/me)
  2. Fetches transitive group memberships (/me/transitiveMemberOf)
  3. Maps groups to roles (highest wins: admin > analyst > viewer)
  4. Creates the user if they don't exist (just-in-time provisioning)
  5. Returns a JWT

Development

Local setup (without Docker)

bash
# Python environmentpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements-dev.txt   # includes requirements.txt plus pytest/ruff/mypy
# Configurecp .env.example .env# Edit .env: set DATABASE_URL to a local Postgres instance (or use SQLite for quick testing)
# Run migrationsalembic upgrade head
# Start API (with auto-reload)uvicorn app.main:app --reload --port 8000

Frontend development

bash
cd frontendnpm installnpm run dev   # Vite dev server on port 5173 with API proxy

The Vite dev server proxies all API paths to http://localhost:8000, so the frontend and API can run independently during development.

Sample databases

bash
docker compose --profile dev up -d

Starts pre-seeded sample databases:

  • sample_postgres on port 5433 → postgresql://sampleuser:samplepass@localhost:5433/sampledb
  • sample_mysql on port 3307 → mysql+pymysql://sampleuser:samplepass@localhost:3307/sampledb

Add these as connections in the admin UI to explore the natural language query feature.

Running tests

bash
# 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

Database migrations

bash
# Apply all pending migrationsalembic upgrade head
# Create a new migration after changing models/__init__.pyalembic revision --autogenerate -m "describe your change"
# Roll back one stepalembic downgrade -1
# Show historyalembic history --verbose

Building for production

bash
# Build frontend static filescd frontend && npm run build && cd ..
# The Dockerfile builds both in a multi-stage build:docker build -t mcp-gateway .docker compose up -d

Troubleshooting

"SECRET_KEY must be set" / "ENCRYPTION_KEY must be set"

docker-compose uses ${VAR:?error message} syntax — it fails fast if these are not set. Generate them:

bash
python3 -c "import secrets; print(secrets.token_hex(32))"           # SECRET_KEYpython3 -c "import secrets; print(secrets.token_hex(32))"           # ENCRYPTION_KEY

Add to your .env file before running docker compose up.

API returns 503 on health check

The database is not reachable. Check:

bash
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 doesn't open a browser for login

Ensure mcp-remote is installed: npm install -g mcp-remote. Check that BASE_URL in .env matches the URL you put in claude_desktop_config.json. A mismatch causes the OAuth callback to fail silently.

Query returns "INVALID_QUERY"

The LLM could not generate a valid SELECT for your question, or it generated a non-SELECT statement (which is blocked). Try:

  • Be more specific in your question
  • Ensure your database has descriptive column and table names
  • Check that ANTHROPIC_API_KEY is set and valid

Entra login returns 400 "Entra ID not configured for this tenant"

The tenant doesn't have an Entra ID configuration. Add one via Admin UI → SSO Config or POST /auth/entra/config.

Entra callback returns 403 "Not a member of any authorized group"

The Azure AD user is not in any of the three groups configured for the tenant. Either:

  • Add the user to one of the mapped groups in Azure AD
  • Update the group IDs in the gateway config to match the user's actual groups (POST /auth/entra/config)

Refresh token rejected as "Invalid or expired"

Refresh tokens are single-use — each use issues a new pair and revokes the old one. If two requests attempt to use the same refresh token simultaneously, the second fails. Re-authenticate to get a fresh pair.

Rate limit 429 responses

EndpointLimit
POST /tenants/5/min
POST /auth/login10/min
POST /t/{slug}/oauth/login10/min
POST /api-keys10/min
POST /t/{slug}/oauth/register10/min
GET /auth/entra/login20/min
GET /auth/entra/exchange20/min
GET /t/{slug}/oauth/entra-callback20/min
GET /t/{slug}/oauth/authorize30/min
POST /t/{slug}/oauth/token30/min (covers both authorization_code and refresh_token grants)
POST /query/30/min
GET /audit-logs/60/min
GET /query/history60/min

Wait 60 seconds for the limit window to reset.

Limits are keyed on the client IP. Two deployment caveats:

  • Without REDIS_URL the limiter stores counters in process memory, so each uvicorn worker enforces its own copy. With WEB_CONCURRENCY=4 the effective limit is roughly four times the value above. Set REDIS_URL for shared enforcement.
  • Behind a reverse proxy, set TRUST_PROXY_HEADERS=true so the key comes from X-Forwarded-For. Without it every request appears to originate from the proxy and all clients share a single bucket. Do not enable it unless a trusted proxy actually sets the header — clients can otherwise spoof it.

Security

Credentials at rest

DataStorage
Passwordsbcrypt (never stored plain)
JWT signingSECRET_KEY (HS256)
DB connection stringsFernet AES-256 encrypted
Entra client secretsFernet AES-256 encrypted
API keysHMAC-SHA-256 keyed with SECRET_KEY (raw key returned once, never stored)
Refresh tokensSHA-256 hash

HTTP security headers

All responses include:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security: max-age=31536000
  • Cache-Control: no-store on auth endpoints

OAuth protections

  • PKCE S256 — prevents authorization code interception attacks
  • Single-use authorization codes — codes expire after 5 minutes and are deleted on first use
  • Rotating refresh tokens — each refresh revokes the previous token (prevents replay)
  • Loopback-only redirect URIs — only localhost, 127.0.0.1, and ::1 are accepted as redirect targets (per RFC 8252)

SQL safety

The execute_sql MCP tool rejects all non-SELECT statements via sqlglot AST parsing before any query reaches the database. INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, and EXEC are all blocked regardless of how they are formatted.

Tenant isolation

All database queries are scoped to current_user.tenant_id. Foreign key constraints enforce isolation at the schema level — there is no code path that allows data from one tenant to appear in another tenant's responses.

Rotating secrets

Rotating SECRET_KEY: All existing JWTs immediately become invalid. Users must re-authenticate. Refresh tokens (hashed separately) are also invalidated. API keys are also invalidated — they are HMAC-keyed with SECRET_KEY, so existing keys must be revoked and re-issued after rotation.

Rotating ENCRYPTION_KEY: Requires re-encrypting all stored connection strings and Entra client secrets with the new key before the old key is removed. Plan this as a maintenance window — the gateway cannot serve connections during the rotation.

Audit log

All significant events are written to the audit_logs table:

EventWhen
login.success / login.failureEvery login attempt
oauth.login / oauth.entra_loginOAuth authorization
oauth.token_issued / oauth.token_refreshedToken exchange and refresh
query.success / query.failureEvery NL query
tool.execute_sql / tool.execute_sql.rejected / tool.execute_sql.errorMCP SQL tool usage
fs.* (e.g. fs.fs_read_file, fs.fs_write_file.error)Filesystem tool usage
connection.created / connection.updated / connection.deletedConnection changes
tenant.createdTenant registration
user.deleted / user.role_updatedUser management
key.created / key.revokedAPI key lifecycle

Query the audit log:

bash
curl "http://localhost:8000/audit-logs/?limit=100" \  -H "Authorization: Bearer $ADMIN_TOKEN" | jq

Additional Documentation

GuideDescription
Testing with Claude DesktopEnd-to-end walkthrough: local users + Entra SSO
Deployment GuideRailway, Render, and generic Docker/VPS deployment
OAuth 2.1 FlowFull PKCE flow, endpoints, token lifecycle
Filesystem ToolsSandboxed file access via MCP
Audit LoggingEvent catalog, API, and metadata reference
API KeysKey lifecycle, security model, usage
Tool Role OverridesPer-tool RBAC configuration

What's Coming — SaltMine AI

MCP Gateway is the open source foundation. A managed platform called SaltMine AI is currently in development, built on top of this project, following the same security principles and aimed at business teams who want to query their data without any infrastructure to manage.

Planned features include:

  • Multi-datasource queries across databases, data lakes, and APIs in a single question
  • Business-user chat interface with visualisations — no SQL knowledge required
  • Data privacy controls with field-level masking and query-level audit trails
  • Zero Vendor Lock-in on AI
  • Understands Your Business Language

If you're interested in learning more, have a use case you'd like to discuss, or just want to follow the progress:

来源:README.md,提交 257be6a

工具

0
SourceWeft 尚未读取此服务器的工具。

版本历史

4
  1. v1.0.4最新Sep 27, 2026
  2. v1.0.3Sep 19, 2026
  3. v1.0.2Sep 18, 2026
  4. v1.0.0Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通必需25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式必需10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分