The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Panos Salt MCP listing page.
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.
MCP Gateway sits between AI assistants and your databases. It:
For your organisation
For your tools
For your security team
| Database | Driver | DSN Format |
|---|---|---|
| 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 | Built-in | sqlite:///path/to/file.db |
FILESYSTEM_ALLOWED_DIRS environment variablefs_read_file, fs_list_directory, fs_directory_tree, fs_search_files, fs_get_file_infofs_write_file, fs_create_directory, fs_move_file/admin/| Layer | Technology | Version |
|---|---|---|
| API Framework | FastAPI + Starlette | 0.136.1 / 1.3.1 |
| ASGI Server | Uvicorn | 0.34.0 |
| Validation | Pydantic + pydantic-settings | 2.12.5 / 2.7.1 |
| ORM | SQLAlchemy | 2.0.30 |
| Migrations | Alembic | 1.13.1 |
| Auth / JWT | PyJWT + bcrypt | 2.14.0 / 4.0.1 |
| Encryption | cryptography (Fernet) | 50.0.0 |
| LLM | Anthropic SDK | 0.42.0 |
| MCP Protocol | mcp | 1.28.1 |
| SQL Validation | sqlglot | 25.1.0 |
| Rate Limiting | slowapi | 0.1.9 |
| HTTP Client | httpx | 0.28.1 |
| DB Drivers | psycopg2-binary / PyMySQL / pymssql | 2.9.10 / 1.1.1 / 2.3.1 |
| Frontend | React 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.
/query/ endpoint; not needed for raw MCP tool access)Edit .env:
Generate secure random values:
Services started:
api on port 8000 (FastAPI + admin UI)db on port 5432 (PostgreSQL, internal only)The slug becomes part of your MCP URL: http://localhost:8000/t/my-org/mcp/sse
Navigate to http://localhost:8000/admin/ and sign in with your admin credentials.
In the admin UI → Connections → Create connection, or via API:
Add to your Claude Desktop MCP config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
Restart Claude Desktop. It will open a browser window for OAuth login. After authenticating, Claude can use your database tools.
All configuration is via environment variables. See .env.example for a template.
| Variable | Description |
|---|---|
SECRET_KEY | JWT signing secret — use a random 64-char string |
ENCRYPTION_KEY | Fernet AES key for DB credentials — minimum 32 characters; full key consumed via BLAKE2b |
DATABASE_URL | PostgreSQL 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.
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY | — | Required for /query/ NL query endpoint |
BASE_URL | http://localhost:8000 | Public-facing URL (used in OAuth callbacks) |
CORS_ORIGINS | BASE_URL | Comma-separated allowed origins for CORS. Must be absolute URLs — wildcards (*) are rejected |
ACCESS_TOKEN_EXPIRE_MINUTES | 15 | JWT access token lifetime |
REFRESH_TOKEN_EXPIRE_DAYS | 30 | OAuth refresh token lifetime |
OAUTH_STATE_TTL_MINUTES | 10 | OAuth PKCE state validity window — increase for high-latency SSO providers |
OAUTH_CODE_TTL_MINUTES | 5 | OAuth authorization code validity window |
LLM_MODEL | claude-sonnet-4-6 | Anthropic model for SQL generation |
LLM_MAX_TOKENS_SQL | 1024 | Max tokens for SQL generation |
LLM_MAX_TOKENS_SUMMARY | 500 | Max 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_LEVEL | INFO | Root log level (DEBUG, INFO, WARNING, ERROR) |
ALGORITHM | HS256 | JWT signing algorithm |
REDIS_URL | — | Shared rate-limiter storage. When empty, limits are in-memory and therefore per worker |
WEB_CONCURRENCY | 1 | Uvicorn worker count. Above 1 without REDIS_URL, rate limits are multiplied by this number |
TRUST_PROXY_HEADERS | false | Derive the rate-limit key from X-Forwarded-For. Enable only behind a trusted proxy — otherwise clients can spoof it |
| Variable | Default | Description |
|---|---|---|
ENTRA_AUTHORITY_URL | https://login.microsoftonline.com | Microsoft identity platform base URL |
ENTRA_GRAPH_URL | https://graph.microsoft.com/v1.0 | Microsoft Graph API base URL |
Response:
Include the token in subsequent requests:
Access tokens expire after 15 minutes by default. Use the OAuth token endpoint with a refresh token to get a new pair.
Generate a key (requires authentication):
The raw_key in the response is shown once only — store it immediately:
Use via query parameter:
The gateway implements RFC 8414 OAuth discovery. MCP clients follow this flow automatically:
/t/{slug}/mcp/sse — receives 401 + WWW-Authenticate header pointing to the OAuth discovery URL/.well-known/oauth-authorization-server/t/{slug}POST /t/{slug}/oauth/register/t/{slug}/oauth/authorizePOST /t/{slug}/oauth/tokenNo manual configuration needed — just point mcp-remote at your tenant's SSE URL.
For CI/CD, scripts, or when you want to skip the browser login, pass an API key in the URL:
The SSE endpoint validates the key and establishes the session directly — no OAuth flow, no browser window. See API Keys for details.
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
curlexamples below use the unversioned paths so they match the running admin UI. Prefix them with/api/v1for new integrations.
| Method | Path | Role | Description |
|---|---|---|---|
POST | /auth/login | Public | Email + password login, returns a JWT |
| Method | Path | Role | Description |
|---|---|---|---|
POST | /tenants/ | Public | Register new tenant + admin user |
GET | /tenants/me | Any | Get your tenant details |
GET | /tenants/users | Admin | List all users in your tenant |
POST | /tenants/users | Admin | Create a local user |
PATCH | /tenants/users/{user_id} | Admin | Update user role |
DELETE | /tenants/users/{user_id} | Admin | Remove a user from the tenant |
| Method | Path | Role | Description |
|---|---|---|---|
POST | /auth/entra/config | Admin | Create or replace the tenant's Entra config |
GET | /auth/entra/config | Admin | Read the current Entra config (secret redacted) |
DELETE | /auth/entra/config | Admin | Remove the Entra config |
GET | /auth/entra/login | Public | Begin admin-UI SSO login (redirects to Microsoft) |
GET | /auth/entra/callback | Public | Microsoft redirect target for the admin-UI flow |
GET | /auth/entra/exchange | Public | Exchange the one-time SSO nonce for a JWT |
Register tenant:
Create user:
Roles: viewer (default), analyst, admin. Passwords must be at least 12 characters.
| Method | Path | Role | Description |
|---|---|---|---|
POST | /connections/ | Admin | Add a database connection |
GET | /connections/ | Viewer+ | List accessible connections |
PATCH | /connections/{id} | Admin | Update connection |
DELETE | /connections/{id} | Admin | Soft-delete connection |
Add connection:
min_role controls who can query this connection. Users below this role cannot see or use it.
| Method | Path | Role | Rate Limit | Description |
|---|---|---|---|---|
POST | /query/ | Analyst+ | 30/min | Execute NL query |
GET | /query/history | Admin | 60/min | Paginated query audit history |
Query:
Response:
The query pipeline:
SELECT statement (blocks all writes)| Method | Path | Role | Description |
|---|---|---|---|
GET | /tools/ | Any | List MCP tools with role metadata |
PATCH | /tools/{tool_name} | Admin | Set or reset role override |
List tools:
Response:
Override tool role:
| Method | Path | Description |
|---|---|---|
POST | /api-keys | Generate a new key |
GET | /api-keys | List your keys |
DELETE | /api-keys/{id} | Revoke a key |
| Method | Path | Role | Rate Limit | Description |
|---|---|---|---|---|
GET | /audit-logs/ | Admin | 60/min | List audit events (filterable) |
Query parameters: skip (offset, default 0), limit (max 200, default 50), event_prefix (comma-separated, e.g. query, login, fs).
Returns 503 if the database is unreachable. Suitable for Kubernetes liveness and readiness probes.
Install mcp-remote:
Add to claude_desktop_config.json:
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.
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):
| Tool | Role | Description |
|---|---|---|
fs_read_file | Analyst+ | Read a file as UTF-8 text |
fs_list_directory | Analyst+ | List directory contents |
fs_directory_tree | Analyst+ | Recursive directory tree (JSON) |
fs_search_files | Analyst+ | Glob pattern search |
fs_get_file_info | Analyst+ | File metadata (size, timestamps) |
fs_write_file | Admin | Create or overwrite a file |
fs_create_directory | Admin | Create a directory (with parents) |
fs_move_file | Admin | Move or rename a file |
| Endpoint | Auth | Description |
|---|---|---|
GET /t/{slug}/mcp/sse | Bearer JWT or ?api_key= | Tenant-scoped SSE (recommended) |
POST /t/{slug}/mcp/messages | Bearer JWT, ?api_key=, or session ID | Tenant-scoped message handler |
GET /mcp/sse | ?api_key= or ?token= | Legacy SSE (deprecated, sunset 2027-03-01) |
POST /mcp/messages | Bearer JWT, ?api_key=, or ?token= | Legacy message handler (deprecated) |
| Endpoint | RFC | Description |
|---|---|---|
GET /.well-known/oauth-authorization-server/t/{slug} | RFC 8414 | Authorization server metadata |
GET /.well-known/oauth-protected-resource/t/{slug}/mcp/sse | RFC 9728 | Protected 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/register | RFC 7591 | Dynamic client registration |
GET /t/{slug}/oauth/authorize | RFC 6749 | Authorization 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/token | RFC 6749 | Token endpoint (code + refresh_token) |
Any Python function becomes an authenticated, audited MCP tool:
Restart the gateway. The tool appears in Claude Desktop automatically, with auth and audit logging included.
Three roles in ascending order of permission: viewer → analyst → admin
| Action | Viewer | Analyst | Admin |
|---|---|---|---|
| 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 | — | — | ✓ |
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.
Admins can override the effective minimum role for any MCP tool independently of the connection's min_role:
http://<gateway-url>/auth/entra/callback (admin UI SSO)http://<gateway-url>/t/<slug>/oauth/entra-callback (MCP OAuth flow)openid, profile, email, User.Read, GroupMember.Read.AllGroupMember.Read.All (required for role sync during token refresh)GroupMember.Read.AllVia admin UI: SSO Config tab, or via API:
Group IDs are optional — configure only what you need. Users in multiple mapped groups get the highest role.
Direct users to: http://<gateway>/auth/entra/login?tenant_slug=<slug>
The gateway redirects to Microsoft. After authentication it:
/me)/me/transitiveMemberOf)The Vite dev server proxies all API paths to http://localhost:8000, so the frontend and API can run independently during development.
Starts pre-seeded sample databases:
sample_postgres on port 5433 → postgresql://sampleuser:samplepass@localhost:5433/sampledbsample_mysql on port 3307 → mysql+pymysql://sampleuser:samplepass@localhost:3307/sampledbAdd these as connections in the admin UI to explore the natural language query feature.
docker-compose uses ${VAR:?error message} syntax — it fails fast if these are not set. Generate them:
Add to your .env file before running docker compose up.
The database is not reachable. Check:
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.
"INVALID_QUERY"The LLM could not generate a valid SELECT for your question, or it generated a non-SELECT statement (which is blocked). Try:
ANTHROPIC_API_KEY is set and validThe tenant doesn't have an Entra ID configuration. Add one via Admin UI → SSO Config or POST /auth/entra/config.
The Azure AD user is not in any of the three groups configured for the tenant. Either:
POST /auth/entra/config)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.
| Endpoint | Limit |
|---|---|
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 (covers both authorization_code and refresh_token grants) |
POST /query/ | 30/min |
GET /audit-logs/ | 60/min |
GET /query/history | 60/min |
Wait 60 seconds for the limit window to reset.
Limits are keyed on the client IP. Two deployment caveats:
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.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.| Data | Storage |
|---|---|
| Passwords | bcrypt (never stored plain) |
| JWT signing | SECRET_KEY (HS256) |
| DB connection strings | Fernet AES-256 encrypted |
| Entra client secrets | Fernet AES-256 encrypted |
| API keys | HMAC-SHA-256 keyed with SECRET_KEY (raw key returned once, never stored) |
| Refresh tokens | SHA-256 hash |
All responses include:
X-Content-Type-Options: nosniffX-Frame-Options: DENYStrict-Transport-Security: max-age=31536000Cache-Control: no-store on auth endpointslocalhost, 127.0.0.1, and ::1 are accepted as redirect targets (per RFC 8252)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.
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 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.
All significant events are written to the audit_logs table:
| Event | When |
|---|---|
login.success / login.failure | Every login attempt |
oauth.login / oauth.entra_login | OAuth authorization |
oauth.token_issued / oauth.token_refreshed | Token exchange and refresh |
query.success / query.failure | Every NL query |
tool.execute_sql / tool.execute_sql.rejected / tool.execute_sql.error | MCP SQL tool usage |
fs.* (e.g. fs.fs_read_file, fs.fs_write_file.error) | Filesystem tool usage |
connection.created / connection.updated / connection.deleted | Connection changes |
tenant.created | Tenant registration |
user.deleted / user.role_updated | User management |
key.created / key.revoked | API key lifecycle |
Query the audit log:
| Guide | Description |
|---|---|
| Testing with Claude Desktop | End-to-end walkthrough: local users + Entra SSO |
| Deployment Guide | Railway, Render, and generic Docker/VPS deployment |
| OAuth 2.1 Flow | Full PKCE flow, endpoints, token lifecycle |
| Filesystem Tools | Sandboxed file access via MCP |
| Audit Logging | Event catalog, API, and metadata reference |
| API Keys | Key lifecycle, security model, usage |
| Tool Role Overrides | Per-tool RBAC configuration |
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:
If you're interested in learning more, have a use case you'd like to discuss, or just want to follow the progress: