Install
$ agentstack add mcp-panossalt-mcp-gateway ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v1.0.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v1.0.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
MCP Gateway
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](docs/media/local_demo.mp4) — short demo of Claude Desktop querying a database through MCP Gateway.
Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Authentication](#authentication)
- [API Reference](#api-reference)
- [MCP Integration](#mcp-integration)
- [Role-Based Access Control](#role-based-access-control)
- [Entra ID / SSO](#entra-id--sso)
- [Development](#development)
- [Troubleshooting](#troubleshooting)
- [Security](#security)
- [Additional Documentation](#additional-documentation)
Overview
MCP Gateway sits between AI assistants and your databases. It:
- Authenticates users via password login, Microsoft Entra ID (Azure AD), or API keys
- Enforces role-based access control (viewer / analyst / admin)
- Exposes databases as MCP tools that AI assistants can discover and call
- Translates natural language questions into SQL via Claude, executes queries, and summarizes results
- 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
| 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 Tools
- Sandboxed file read/write/search exposed as MCP tools
- Enabled via
FILESYSTEM_ALLOWED_DIRSenvironment 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
| Layer | Technology | Version | |-------|-----------|---------| | API Framework | FastAPI | 0.131.0 | | ASGI Server | Uvicorn | 0.34.0 | | ORM | SQLAlchemy | 2.0.30 | | Migrations | Alembic | 1.13.1 | | Auth / JWT | PyJWT + bcrypt | 2.12.0 / 4.0.1 | | Encryption | cryptography (Fernet) | 46.0.5 | | LLM | Anthropic SDK | 0.42.0 | | MCP Protocol | mcp | 1.23.0 | | SQL Validation | sqlglot | 25.1.0 | | Rate Limiting | slowapi | 0.1.9 | | Frontend | React 18 + TypeScript + Vite | — |
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
frontend/src/
├── 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)
└── components/
├── Login.tsx # Sign-in form
├── Setup.tsx # Tenant registration
├── Dashboard.tsx # Tenant info + role display
├── Connections.tsx # DB connection management
├── Query.tsx # Natural language query UI
├── Users.tsx # User management (admin)
├── SsoConfig.tsx # Entra ID configuration (admin)
├── Tools.tsx # Tool browser + role overrides
├── ApiKeys.tsx # API key management
└── AuditLog.tsx # Filterable audit log viewer (admin)
Database Schema
Tenants ─┬─► Users ──────► APIKeys
├─► DBConnections
├─► TenantEntraConfig
├─► AuditLogs
├─► OAuthStates
├─► OAuthAuthorizationCodes
├─► OAuthRefreshTokens
└─► ToolRoleOverrides
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
git clone
cd MCP-Gateway
cp .env.example .env
Edit .env:
# Required — generate unique values
SECRET_KEY=
ENCRYPTION_KEY=
POSTGRES_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
Generate secure random values:
# 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. Start the stack
docker compose up -d
Services started:
apion port 8000 (FastAPI + admin UI)dbon port 5432 (PostgreSQL, internal only)
3. Register your first tenant
curl -s -X POST http://localhost:8000/tenants/ \
-H "Content-Type: application/json" \
-d '{
"name": "My Organization",
"slug": "my-org",
"admin_email": "admin@example.com",
"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:
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","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):
{
"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
| 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 | | POSTGRES_PASSWORD | PostgreSQL password — used by docker-compose for both the db service and DATABASE_URL | | DATABASE_URL | PostgreSQL DSN — set automatically by docker-compose; only needed for local (non-Docker) dev |
Optional
| 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. When empty, no filesystem tools are exposed |
Entra ID
| 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 |
Authentication
Local login
POST /auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SuperSecret123!",
"tenant_slug": "my-org" // optional, disambiguates if same email is in multiple tenants
}
Response:
{
"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):
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:
{
"id": "...",
"name": "CI pipeline",
"prefix": "mgw_abcd1234",
"raw_key": "mgw_abcd1234...",
"created_at": "..."
}
Use via query parameter:
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:
- Client connects to
/t/{slug}/mcp/sse— receives 401 +WWW-Authenticateheader pointing to the OAuth discovery URL - Client fetches
/.well-known/oauth-authorization-server/t/{slug} - Client registers dynamically via
POST /t/{slug}/oauth/register - Client opens browser → user logs in at
/t/{slug}/oauth/authorize - Client exchanges code + PKCE verifier for tokens via
POST /t/{slug}/oauth/token - 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:
{
"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](docs/api-keys.md) 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.
Tenants & Users
| 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/{id} | Admin | Update user role |
Register tenant:
curl -X POST http://localhost:8000/tenants/ \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"slug": "acme",
"admin_email": "admin@acme.com",
"admin_password": "SuperSecret123!"
}'
Create user:
curl -X POST http://localhost:8000/tenants/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "analyst@acme.com",
"password": "AnotherSecret456!",
"role": "analyst"
}'
`
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [PanosSalt](https://github.com/PanosSalt)
- **Source:** [PanosSalt/MCP-Gateway](https://github.com/PanosSalt/MCP-Gateway)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v1.0.0 Imported from the upstream source.