# MCP Gateway

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

- **Type:** MCP server
- **Install:** `agentstack add mcp-panossalt-mcp-gateway`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [PanosSalt](https://agentstack.voostack.com/s/panossalt)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 1.0.0
- **License:** MIT
- **Upstream author:** [PanosSalt](https://github.com/PanosSalt)
- **Source:** https://github.com/PanosSalt/MCP-Gateway

## Install

```sh
agentstack add mcp-panossalt-mcp-gateway
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

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

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
| 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_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

| 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

```bash
git clone 
cd MCP-Gateway
cp .env.example .env
```

Edit `.env`:

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

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

```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": "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:

```bash
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):

```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

| 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

```http
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:
```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](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:**
```bash
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:**
```bash
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.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v1.0.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **1.0.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-panossalt-mcp-gateway
- Seller: https://agentstack.voostack.com/s/panossalt
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
