docs/admin_docs/configuration/mcp-server.mdx
Superset includes a built-in Model Context Protocol (MCP) server that lets AI assistants -- Claude, ChatGPT, and other MCP-compatible clients -- interact with your Superset instance. Through MCP, clients can list dashboards, query datasets, execute SQL, create charts, and more.
This guide covers how to run, secure, and deploy the MCP server.
:::tip Looking for user docs? See Using AI with Superset for a guide on what AI can do with Superset and how to connect your AI client. :::
flowchart LR
A["AI Client
(Claude, ChatGPT, etc.)"] -- "MCP protocol
(HTTP + JSON-RPC)" --> B["MCP Server
(:5008/mcp)"]
B -- "Superset context
(app, db, RBAC)" --> C["Superset
(:8088)"]
C --> D[("Database
(Postgres)")]
Get the MCP server running locally and connect an AI client in three steps.
The MCP server runs as a separate process alongside Superset:
superset mcp run --host 127.0.0.1 --port 5008
| Flag | Default | Description |
|---|---|---|
--host | 127.0.0.1 | Host to bind to |
--port | 5008 | Port to bind to |
--debug | off | Enable debug logging |
The endpoint is available at http://<host>:<port>/mcp.
For local development, tell the MCP server which Superset user to impersonate (the user must already exist in your database):
# superset_config.py
MCP_DEV_USERNAME = "admin"
Point your MCP client at the server. For Claude Desktop, edit the config file:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.json{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
Restart Claude Desktop. The hammer icon in the chat bar confirms the connection.
See Connecting AI Clients for Claude Code, Claude Web, ChatGPT, and raw HTTP examples.
fastmcp package (pip install fastmcp)The MCP server supports multiple authentication methods depending on your deployment scenario.
flowchart TD
R["Incoming MCP Request"] --> F{"MCP_AUTH_FACTORY
set?"}
F -- Yes --> CF["Custom Auth Provider"]
F -- No --> AE{"MCP_AUTH_ENABLED?"}
AE -- "True" --> JWT["JWT Validation"]
AE -- "False" --> DU["Dev Mode
(MCP_DEV_USERNAME)"]
JWT --> ALG{"MCP_JWT_ALGORITHM"}
ALG -- "RS256 + JWKS" --> JWKS["Fetch keys from
MCP_JWKS_URI"]
ALG -- "RS256 + static" --> PK["Use
MCP_JWT_PUBLIC_KEY"]
ALG -- "HS256" --> SEC["Use
MCP_JWT_SECRET"]
JWKS --> V["Validate token
(exp, iss, aud, scopes)"]
PK --> V
SEC --> V
V --> UR["Resolve Superset user
from token claims"]
UR --> OK["Authenticated request"]
CF --> OK
DU --> OK
Disable authentication and use a fixed user:
# superset_config.py
MCP_AUTH_ENABLED = False
MCP_DEV_USERNAME = "admin"
All operations run as the configured user.
:::warning Never use development mode in production. Always enable authentication for any internet-facing deployment. :::
For production, enable JWT-based authentication. The MCP server validates a Bearer token on every request.
The most common setup for OAuth 2.0 / OIDC providers that publish a JWKS (JSON Web Key Set) endpoint:
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWKS_URI = "https://your-identity-provider.com/.well-known/jwks.json"
MCP_JWT_ISSUER = "https://your-identity-provider.com/"
MCP_JWT_AUDIENCE = "your-superset-instance"
Use this when you have a fixed RSA key pair (e.g., self-signed tokens):
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
Use this when both the token issuer and the MCP server share a symmetric secret:
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "HS256"
MCP_JWT_SECRET = "your-shared-secret-key"
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
:::warning
Store MCP_JWT_SECRET securely. Never commit it to version control. Use environment variables:
import os
MCP_JWT_SECRET = os.environ.get("MCP_JWT_SECRET")
:::
The MCP server validates these standard claims:
| Claim | Config Key | Description |
|---|---|---|
exp | -- | Expiration time (always validated) |
iss | MCP_JWT_ISSUER | Token issuer (optional but recommended) |
aud | MCP_JWT_AUDIENCE | Token audience (optional but recommended) |
sub | -- | Subject -- primary claim used to resolve the Superset user |
After validating the token, the MCP server resolves a Superset username from the claims. It checks these in order, using the first non-empty value:
subject -- the standard sub claim (via the access token object)client_id -- for machine-to-machine tokenspayload["sub"] -- fallback to raw payloadpayload["email"] -- email-based lookuppayload["username"] -- explicit username claimThe resolved value must match a username in the Superset ab_user table.
Require specific scopes in the JWT to limit what MCP operations a token can perform:
# superset_config.py
MCP_REQUIRED_SCOPES = ["mcp:read", "mcp:write"]
Only tokens that include all required scopes are accepted.
For advanced scenarios (e.g., a proprietary auth system), provide a factory function. This takes precedence over all built-in JWT configuration:
# superset_config.py
def my_custom_auth_factory(app):
"""Return a FastMCP auth provider instance."""
from fastmcp.server.auth.providers.jwt import JWTVerifier
return JWTVerifier(
jwks_uri="https://my-auth.example.com/.well-known/jwks.json",
issuer="https://my-auth.example.com/",
audience="superset-mcp",
)
MCP_AUTH_FACTORY = my_custom_auth_factory
Local development (no auth):
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
With JWT authentication:
{
"mcpServers": {
"superset": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"http://your-superset-host:5008/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN"
]
}
}
}
Add to your project's .mcp.json:
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp"
}
}
}
With authentication:
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
https://your-superset-host/mcp):::info Custom connectors on Claude Web require a Pro, Max, Team, or Enterprise plan. :::
:::info ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan. :::
Call the MCP server directly with any HTTP client:
curl -X POST http://localhost:5008/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
The simplest setup: run the MCP server alongside Superset on the same host.
flowchart TD
subgraph host["Host / VM"]
direction TB
S["Superset
:8088"] --> DB[("Postgres")]
M["MCP Server
:5008"] --> DB
end
C["AI Client"] -- "HTTPS" --> P["Reverse Proxy
(Nginx / Caddy)"]
U["Browser"] -- "HTTPS" --> P
P -- ":8088" --> S
P -- ":5008/mcp" --> M
superset_config.py:
MCP_SERVICE_HOST = "0.0.0.0"
MCP_SERVICE_PORT = 5008
MCP_DEV_USERNAME = "admin" # or enable JWT auth
# If behind a reverse proxy, set the public-facing URL so
# MCP-generated links (chart previews, SQL Lab URLs) resolve correctly:
MCP_SERVICE_URL = "https://superset.example.com"
Start both processes:
# Terminal 1 -- Superset web server
superset run -h 0.0.0.0 -p 8088
# Terminal 2 -- MCP server
superset mcp run --host 0.0.0.0 --port 5008
Nginx reverse proxy with TLS:
server {
listen 443 ssl;
server_name superset.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Superset web UI
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MCP endpoint
location /mcp {
proxy_pass http://127.0.0.1:5008/mcp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
}
Run Superset and the MCP server as separate containers sharing the same config:
# docker-compose.yml
services:
superset:
image: apache/superset:latest
ports:
- "8088:8088"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
mcp:
image: apache/superset:latest
command: ["superset", "mcp", "run", "--host", "0.0.0.0", "--port", "5008"]
ports:
- "5008:5008"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
depends_on:
- superset
Both containers share the same superset_config.py, so authentication settings, database connections, and feature flags stay in sync.
For high-availability deployments, configure Redis so that replicas share session state:
flowchart TD
LB["Load Balancer"] --> M1["MCP Pod 1"]
LB --> M2["MCP Pod 2"]
LB --> M3["MCP Pod 3"]
M1 --> R[("Redis
(session store)")]
M2 --> R
M3 --> R
M1 --> DB[("Postgres")]
M2 --> DB
M3 --> DB
superset_config.py:
MCP_STORE_CONFIG = {
"enabled": True,
"CACHE_REDIS_URL": "redis://redis-host:6379/0",
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
When CACHE_REDIS_URL is set, the MCP server uses a Redis-backed EventStore for session management, allowing replicas to share state. Without Redis, each pod manages its own in-memory sessions and stateful MCP interactions may fail when requests hit different replicas.
All MCP settings go in superset_config.py. Defaults are defined in superset/mcp_service/mcp_config.py.
| Setting | Default | Description |
|---|---|---|
MCP_SERVICE_HOST | "localhost" | Host the MCP server binds to |
MCP_SERVICE_PORT | 5008 | Port the MCP server binds to |
MCP_SERVICE_URL | None | Public base URL for MCP-generated links (set this when behind a reverse proxy) |
MCP_DEBUG | False | Enable debug logging |
MCP_DEV_USERNAME | -- | Superset username for development mode (no auth) |
| Setting | Default | Description |
|---|---|---|
MCP_AUTH_ENABLED | False | Enable JWT authentication |
MCP_JWT_ALGORITHM | "RS256" | JWT signing algorithm (RS256 or HS256) |
MCP_JWKS_URI | None | JWKS endpoint URL (RS256) |
MCP_JWT_PUBLIC_KEY | None | Static RSA public key string (RS256) |
MCP_JWT_SECRET | None | Shared secret string (HS256) |
MCP_JWT_ISSUER | None | Expected iss claim |
MCP_JWT_AUDIENCE | None | Expected aud claim |
MCP_REQUIRED_SCOPES | [] | Required JWT scopes |
MCP_JWT_DEBUG_ERRORS | False | Log detailed JWT errors server-side (never exposed in HTTP responses per RFC 6750) |
MCP_AUTH_FACTORY | None | Custom auth provider factory (flask_app) -> auth_provider. Takes precedence over built-in JWT |
Limits response sizes to prevent exceeding LLM context windows:
MCP_RESPONSE_SIZE_CONFIG = {
"enabled": True,
"token_limit": 25000,
"warn_threshold_pct": 80,
"excluded_tools": [
"health_check",
"get_chart_preview",
"generate_explore_link",
"open_sql_lab_with_context",
],
}
| Key | Default | Description |
|---|---|---|
enabled | True | Enable response size checking |
token_limit | 25000 | Maximum estimated token count per response |
warn_threshold_pct | 80 | Warn when response exceeds this percentage of the limit |
excluded_tools | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
Optional response caching for read-heavy workloads. Requires Redis when used with multiple replicas.
MCP_CACHE_CONFIG = {
"enabled": False,
"CACHE_KEY_PREFIX": None,
"list_tools_ttl": 300, # 5 min
"list_resources_ttl": 300,
"list_prompts_ttl": 300,
"read_resource_ttl": 3600, # 1 hour
"get_prompt_ttl": 3600,
"call_tool_ttl": 3600,
"max_item_size": 1048576, # 1 MB
"excluded_tools": [
"execute_sql",
"generate_dashboard",
"generate_chart",
"update_chart",
],
}
| Key | Default | Description |
|---|---|---|
enabled | False | Enable response caching |
CACHE_KEY_PREFIX | None | Optional prefix for cache keys (useful for shared Redis) |
list_tools_ttl | 300 | Cache TTL in seconds for tools/list |
list_resources_ttl | 300 | Cache TTL for resources/list |
list_prompts_ttl | 300 | Cache TTL for prompts/list |
read_resource_ttl | 3600 | Cache TTL for resources/read |
get_prompt_ttl | 3600 | Cache TTL for prompts/get |
call_tool_ttl | 3600 | Cache TTL for tools/call |
max_item_size | 1048576 | Maximum cached item size in bytes (1 MB) |
excluded_tools | See above | Tools that are never cached (mutating or non-deterministic) |
Enables Redis-backed session and event storage for multi-replica deployments:
MCP_STORE_CONFIG = {
"enabled": False,
"CACHE_REDIS_URL": None,
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
| Key | Default | Description |
|---|---|---|
enabled | False | Enable Redis-backed store |
CACHE_REDIS_URL | None | Redis connection URL (e.g., redis://redis-host:6379/0) |
event_store_max_events | 100 | Maximum events retained per session |
event_store_ttl | 3600 | Event TTL in seconds |
These values are flat-merged into the Flask app config used by the MCP server process:
MCP_SESSION_CONFIG = {
"SESSION_COOKIE_HTTPONLY": True,
"SESSION_COOKIE_SECURE": False,
"SESSION_COOKIE_SAMESITE": "Lax",
"SESSION_COOKIE_NAME": "superset_session",
"PERMANENT_SESSION_LIFETIME": 86400,
}
MCP_CSRF_CONFIG = {
"WTF_CSRF_ENABLED": True,
"WTF_CSRF_TIME_LIMIT": None,
}
fastmcp is installed: pip install fastmcpMCP_DEV_USERNAME is set if auth is disabled -- the server requires a user identitylsof -i :5008exp claim)MCP_JWT_ISSUER and MCP_JWT_AUDIENCE match the token's iss and aud claims exactlyBEGIN/END markersMCP_JWT_SECRETMCP_JWT_DEBUG_ERRORS = True for detailed server-side logging (errors are never leaked to the client)superset_config.py/mcp (e.g., http://localhost:5008/mcp)MCP_DEV_USERNAME maps to a user with appropriate roles (e.g., Admin)superset/security/manager.py for the specific permission tuples required by each tool domain (e.g., ("can_execute_sql_query", "SQLLab"))page_size or limit parameters, use select_columns to exclude large fields, or add filters to narrow resultstoken_limit in MCP_RESPONSE_SIZE_CONFIGMCP_RESPONSE_SIZE_CONFIG = {"enabled": False}MCP_JWT_SECRET, database credentials, and API keys in environment variables or a secrets manager, never in config files committed to version controlMCP_REQUIRED_SCOPES to limit what operations a token can perform