Skip to main content

Safety-gated MCP server for Metabase API access and dashboard authoring

Project description

mcp-for-metabase

mcp-for-metabase is a safety-gated Python MCP server for Metabase. It lets MCP clients and agents inspect Metabase, run governed queries, and create or maintain dashboards, cards/questions, collections, snippets, permissions, and other Metabase assets through the Metabase REST API.

The server is Docker-first, uses the official MCP Python SDK/FastMCP, and defaults to read-only mode.

Project Status

This project is in production-readiness beta. It has broad generated Metabase API coverage, curated tools for common dashboard and content workflows, Docker-first deployment, live integration coverage, and restrictive default safety gates. Some lower-frequency API areas are available through the generic executor rather than polished high-level tools.

Current highlights:

  • MCP transports: stdio and Streamable HTTP.
  • Auth: Metabase API key via X-API-Key, with username/password session fallback.
  • Generic API coverage: 600 generated operations from the Metabase OpenAPI document.
  • Curated tools for discovery, search, metadata inspection, query execution, collections, cards, dashboards, snippets, permissions, users, API keys, public links, copies, card queries, and exports.
  • Generic API validation for required path/query/body fields, path/query parameter schemas, and OpenAPI JSON request-body schemas.
  • Safety gates: read-only by default, safe-writes for normal content writes, and all-writes plus confirm=true for destructive/admin operations.
  • dry_run=true support for write-capable tools.
  • Audit logging for mutating requests.
  • CI gates for formatting, linting, typing, unit tests, MCP stdio protocol tests, Docker smoke tests, package checks, generated-registry freshness, and live Metabase integration tests across supported Docker tags.

Detailed capability tables live in docs/CAPABILITIES.md, with generated API coverage in docs/API_COVERAGE.md. Live test instructions are in docs/INTEGRATION_TESTING.md.

5-Minute Setup

You need:

  • Python 3.12+ for local stdio setup, or Docker for HTTP setup.
  • A Metabase URL, for example https://metabase.example.com.
  • A Metabase API key. In Metabase, create one under Admin settings -> Authentication -> API keys and assign it to a least-privileged group. The key is sent to Metabase as X-API-Key.

Start in read-only mode. After the first successful connection, switch to safe-writes only if the agent should create or update Metabase content.

Option A: Local stdio

Use this when Codex or Claude Code should launch the MCP server as a local process.

Install the command:

From PyPI:

pipx install mcp-for-metabase

If the PyPI package is not available yet, install from GitHub:

pipx install git+https://github.com/DominikPollok/mcp-for-metabase.git

From source:

git clone https://github.com/DominikPollok/mcp-for-metabase.git
cd mcp-for-metabase
python -m pip install -e ".[dev]"

The package name and primary console command are both mcp-for-metabase. The legacy metabase-mcp console command is also installed as a compatibility alias. There are similarly named Metabase MCP packages on PyPI, so check the repository URL and license metadata before installing.

Connect Codex:

codex mcp add metabase \
  --env METABASE_URL=https://metabase.example.com \
  --env METABASE_API_KEY=mb_your_key \
  --env METABASE_MCP_WRITE_MODE=read-only \
  -- mcp-for-metabase serve --transport stdio
codex mcp list

Connect Claude Code:

claude mcp add --transport stdio --scope user \
  --env METABASE_URL=https://metabase.example.com \
  --env METABASE_API_KEY=mb_your_key \
  --env METABASE_MCP_WRITE_MODE=read-only \
  metabase -- mcp-for-metabase serve --transport stdio
claude mcp list

In Codex or Claude Code, ask the agent to call metabase_connection_test.

Option B: Docker HTTP

Use this when the MCP server should run as a long-lived local or remote HTTP service.

Build and run the server against an existing Metabase:

git clone https://github.com/DominikPollok/mcp-for-metabase.git
cd mcp-for-metabase
docker build -t mcp-for-metabase .
docker run --rm -p 8000:8000 \
  -e METABASE_URL=https://metabase.example.com \
  -e METABASE_API_KEY=mb_your_key \
  -e METABASE_MCP_TRANSPORT=http \
  -e METABASE_MCP_WRITE_MODE=read-only \
  -e METABASE_MCP_SQL_GUARD_MODE=strict \
  -e METABASE_MCP_SNAPSHOT_DIR=/data/snapshots \
  -v mcp-for-metabase-data:/data \
  mcp-for-metabase

Connect Codex:

codex mcp add metabase --url http://localhost:8000/mcp
codex mcp list

Connect Claude Code:

claude mcp add --transport http --scope user metabase http://localhost:8000/mcp
claude mcp list

For Docker on macOS or Windows connecting to a Metabase running on your host machine, use METABASE_URL=http://host.docker.internal:3000.

Local Metabase Playground

Use this when you want disposable Metabase, Postgres, and MCP containers for testing.

Create an environment file:

cp .env.example .env

Edit .env:

METABASE_URL=http://metabase:3000
METABASE_IMAGE=metabase/metabase:v0.61.2
METABASE_API_KEY=your_metabase_api_key
METABASE_MCP_TRANSPORT=http
METABASE_MCP_WRITE_MODE=read-only
METABASE_MCP_SQL_GUARD_MODE=strict
METABASE_MCP_SNAPSHOT_DIR=/data/snapshots

Start the stack:

docker compose up --build

The MCP server listens on http://localhost:8000/mcp in Streamable HTTP mode. Metabase is exposed on http://localhost:3000.

Running The MCP Server

stdio:

mcp-for-metabase serve --transport stdio

Streamable HTTP:

mcp-for-metabase serve --transport http --host 0.0.0.0 --port 8000

Core environment variables:

Variable Required Description
METABASE_URL Yes Metabase base URL.
METABASE_IMAGE Optional Docker Compose Metabase image tag for local integration work.
METABASE_API_KEY Recommended Preferred authentication method.
METABASE_USERNAME / METABASE_PASSWORD Optional Session-auth fallback.
METABASE_HTTP_HEADERS_JSON Optional JSON object of extra outbound headers for deployments behind a proxy, for example {"X-Forwarded-User":"agent"}.
METABASE_BASIC_AUTH_USERNAME / METABASE_BASIC_AUTH_PASSWORD Optional Adds an outbound Authorization: Basic ... header for reverse proxies in front of Metabase.
METABASE_MCP_TRANSPORT Optional stdio or http.
METABASE_MCP_WRITE_MODE Optional read-only, safe-writes, or all-writes.
METABASE_MCP_SQL_GUARD_MODE Optional strict by default. Use disabled only for trusted internal deployments.
METABASE_MCP_AUDIT_LOG Optional JSONL path for mutation audit events.
METABASE_MCP_SNAPSHOT_DIR Optional Directory for durable dashboard/card/collection rollback snapshots.
METABASE_MCP_TIMEOUT Optional HTTP timeout in seconds.

Safety Model

The server never bypasses Metabase permissions. API keys inherit their Metabase group permissions, so create a dedicated least-privileged group for agent work.

Write modes:

  • read-only: default. Read/query operations are allowed; mutations are blocked.
  • safe-writes: allows common create/update/content operations.
  • all-writes: allows destructive/admin operations only when the tool call also passes confirm=true.

Every mutating request is audited when METABASE_MCP_AUDIT_LOG is set. Read docs/SECURITY.md before connecting the server to production Metabase.

Native SQL guard:

  • strict is the default and applies to both curated tools and metabase_api_request.
  • Native SQL is limited to single-statement read-only queries starting with SELECT, WITH, EXPLAIN, SHOW, DESCRIBE, or DESC.
  • SQL comments, statement separators, and mutation/admin keywords such as INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, MERGE, GRANT, and CALL are blocked before any request is sent to Metabase.
  • Native SQL snippets may be fragments, but the same separator/comment/mutation keyword blocklist applies.
  • This guard is defense-in-depth; still use least-privileged Metabase API keys and database roles.

Snapshot tools persist rollback payloads under METABASE_MCP_SNAPSHOT_DIR:

  • metabase_snapshot_entity saves a dashboard, card, collection, or permissions graph snapshot and returns a snapshot_id.
  • metabase_list_saved_snapshots lists saved snapshots.
  • metabase_restore_saved_snapshot restores by snapshot_id with the same write-mode and confirmation gates as direct updates.

Treat snapshot files as sensitive because they can contain dashboard definitions, SQL, collection metadata, and permission graph data.

Capability Overview

The server exposes the complete generated operation catalog through metabase_api_request, with curated high-level tools for the workflows agents need most often.

Area MCP support
API discovery Native MCP tools/list, resources/list, prompts/list, metabase://api/coverage, metabase://api/operations, and operation search/get tools.
Dashboards Curated create/get/update/archive/delete/copy, parameters, public links, item/layout management, snapshots, and saved snapshot restore.
Cards/questions Curated create/get/update/archive/delete/copy, query, export, public links, series lookup, and idempotent create-or-update.
Collections Curated tree, create, create-or-update, update, archive, and snapshot/restore.
Metadata/querying Curated database/table metadata reads and governed dataset/card query execution.
Snippets Curated list/get/create/update/create-or-update native SQL snippets.
Admin/security Curated permissions graph, permission groups, users, settings, API keys, and database sync/rescan helpers with admin gates.
Notifications Curated pulse list/get/create/update/delete subscription helpers.
Content management Curated bookmarks, revisions, timelines, timeline events, segments, documents, and cache operations.
Remaining Metabase API Available through metabase_api_request with registry discovery, schema validation, safety classification, dry-run support, and audit logging for mutations.

Connecting Agents

Choose exactly one connection style:

  • stdio: the client starts mcp-for-metabase on demand. This is simplest for one developer machine.
  • http: the MCP server runs separately, usually in Docker, and clients connect to http://localhost:8000/mcp or your hosted MCP URL.

Codex

Codex CLI and the Codex IDE extension read MCP servers from ~/.codex/config.toml. The codex mcp add command writes that file for you.

Local stdio:

codex mcp add metabase \
  --env METABASE_URL=https://metabase.example.com \
  --env METABASE_API_KEY=mb_your_key \
  --env METABASE_MCP_WRITE_MODE=read-only \
  -- mcp-for-metabase serve --transport stdio
codex mcp list

Equivalent ~/.codex/config.toml:

[mcp_servers.metabase]
command = "mcp-for-metabase"
args = ["serve", "--transport", "stdio"]
enabled = true

[mcp_servers.metabase.env]
METABASE_URL = "https://metabase.example.com"
METABASE_API_KEY = "mb_your_key"
METABASE_MCP_WRITE_MODE = "read-only"

Docker or hosted HTTP:

codex mcp add metabase --url http://localhost:8000/mcp
codex mcp list

Equivalent ~/.codex/config.toml:

[mcp_servers.metabase]
url = "http://localhost:8000/mcp"
enabled = true

Claude Code

Claude Code can add MCP servers with claude mcp add. Put all Claude Code options before the server name, then put the server command after --.

Local stdio:

claude mcp add --transport stdio --scope user \
  --env METABASE_URL=https://metabase.example.com \
  --env METABASE_API_KEY=mb_your_key \
  --env METABASE_MCP_WRITE_MODE=read-only \
  metabase -- mcp-for-metabase serve --transport stdio
claude mcp list

Project-level .mcp.json alternative:

{
  "mcpServers": {
    "metabase": {
      "type": "stdio",
      "command": "mcp-for-metabase",
      "args": ["serve", "--transport", "stdio"],
      "env": {
        "METABASE_URL": "https://metabase.example.com",
        "METABASE_API_KEY": "mb_your_key",
        "METABASE_MCP_WRITE_MODE": "read-only"
      }
    }
  }
}

Docker or hosted HTTP:

claude mcp add --transport http --scope user metabase http://localhost:8000/mcp
claude mcp list

Inside Claude Code, run /mcp and confirm the metabase server is connected.

Claude Desktop

Add the server to your Claude Desktop config at ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "metabase": {
      "type": "stdio",
      "command": "mcp-for-metabase",
      "args": ["serve", "--transport", "stdio"],
      "env": {
        "METABASE_URL": "https://metabase.example.com",
        "METABASE_API_KEY": "mb_your_key",
        "METABASE_MCP_WRITE_MODE": "read-only"
      }
    }
  }
}

Restart Claude Desktop after editing the config. If you already configured Claude Desktop and want the same server in Claude Code, run claude mcp add-from-claude-desktop.

For proxy deployments, add METABASE_BASIC_AUTH_USERNAME and METABASE_BASIC_AUTH_PASSWORD, or set METABASE_HTTP_HEADERS_JSON.

Generic MCP Clients

Read docs/CONNECTING_AGENTS.md for Docker HTTP, pip/pipx installation, and additional configuration options.

Example stdio client config:

{
  "mcpServers": {
    "metabase": {
      "command": "mcp-for-metabase",
      "args": ["serve", "--transport", "stdio"],
      "env": {
        "METABASE_URL": "https://metabase.example.com",
        "METABASE_API_KEY": "mb_your_key",
        "METABASE_BASIC_AUTH_USERNAME": "proxy_user",
        "METABASE_BASIC_AUTH_PASSWORD": "proxy_password",
        "METABASE_MCP_WRITE_MODE": "read-only"
      }
    }
  }
}

Agents should start with native MCP discovery:

  1. Call tools/list.
  2. Call resources/list.
  3. Call resourceTemplates/list.
  4. Call prompts/list.
  5. Read metabase://agent/connection-guide.
  6. Read metabase://api/coverage or call metabase_discover_operations.

Local Development

Recommended:

uv sync --all-extras
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest

Run the disposable live integration path:

docker compose up -d postgres metabase
uv run python scripts/bootstrap_metabase_test_instance.py --output-env .metabase-test.env
set -a && . ./.metabase-test.env && set +a
uv run pytest tests/integration -q --no-cov
docker compose down -v

Fallback without uv:

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
ruff format --check .
ruff check .
mypy src
pytest

Build and inspect distributions:

python -m build
twine check dist/*

API Registry

Refresh the registry from the official public docs:

python scripts/fetch_openapi.py --output docs/openapi.json
python scripts/build_api_registry.py --openapi docs/openapi.json

Refresh from a live instance:

python scripts/fetch_openapi.py \
  --base-url "$METABASE_URL" \
  --api-key "$METABASE_API_KEY" \
  --output docs/openapi.json
python scripts/build_api_registry.py --openapi docs/openapi.json

Compare two generated registry snapshots before upgrading Metabase docs:

python scripts/diff_api_registry.py \
  --old src/mcp_for_metabase/api_registry.json \
  --new /tmp/new_api_registry.json \
  --format markdown \
  --fail-on-removal

Generated outputs:

  • src/mcp_for_metabase/api_registry.json
  • src/mcp_for_metabase/openapi.json
  • docs/API_COVERAGE.md

docs/openapi.json is intentionally ignored because it is a temporary fetch target. The runtime package includes the normalized OpenAPI snapshot under src/mcp_for_metabase/openapi.json.

Documentation

Contributing

Contributions are welcome. Read CONTRIBUTING.md, keep changes small, and preserve the safety gates around Metabase mutations.

The .codex/skills directory is intentionally included as optional agent workflow guidance for contributors who use Codex or compatible agent tooling.

License

mcp-for-metabase is licensed under GPL-3.0-or-later. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_for_metabase-0.3.0.tar.gz (313.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_for_metabase-0.3.0-py3-none-any.whl (209.4 kB view details)

Uploaded Python 3

File details

Details for the file mcp_for_metabase-0.3.0.tar.gz.

File metadata

  • Download URL: mcp_for_metabase-0.3.0.tar.gz
  • Upload date:
  • Size: 313.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcp_for_metabase-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b0712ef2faba50f95a9c30b13f424ff9486f4c5d082f2aa9a01c594fa498fb47
MD5 b27208efd40d1b9bd3fd7039038da6ef
BLAKE2b-256 85688144144e728dcd8bc13a3d8c3888cc739aa222b274a072c266c52bb38dfc

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_for_metabase-0.3.0.tar.gz:

Publisher: release.yml on DominikPollok/mcp-for-metabase

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcp_for_metabase-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_for_metabase-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8594cecdc648fc9bd03c740acc4a54c6c62d2c8d57ec83c9d3a3a0f30aa7343c
MD5 bef2ad047217c59af58e9d81058aebdc
BLAKE2b-256 c86051a18b50a7464b5a9b91cb087db1d0a3d67bda5cabe6e1a124dbc8ae6d8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_for_metabase-0.3.0-py3-none-any.whl:

Publisher: release.yml on DominikPollok/mcp-for-metabase

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page