Completely vibecoded a TypeScript/Node.js MCP (Model Context Protocol) server that wraps the Metabase REST API, giving AI agents (Claude Desktop, Claude Code) full programmatic access to a Metabase v0.59.x instance. Agents can execute queries, explore schemas, and build dashboards entirely through structured tool calls — no human click-through required.
- Node.js 20 LTS or later
- Docker (for running a local Metabase instance during development)
- A Metabase instance reachable over HTTP/HTTPS with an API key
-
Install dependencies:
npm install
-
Build the server:
npm run build
Compiled output is written to
dist/. The entry point isdist/index.js. -
Configure environment variables (copy
.env.exampleand fill in values):cp .env.example .env
-
(Optional) Start a local Metabase instance for development:
docker compose up -d
Metabase is available at
http://localhost:3000once healthy (allow ~2 minutes on first boot).
The server supports two transport modes, selected via the TRANSPORT environment variable.
Used by Claude Desktop and Claude Code when running the server as a local process. The MCP protocol runs over stdin/stdout.
Runs an HTTP server implementing the MCP Streamable HTTP transport. Each client authenticates with its own Metabase API key passed as a URL query parameter — there is no shared key on the server.
Start the HTTP server:
METABASE_URL=https://your-metabase.example.com \
TRANSPORT=http \
PORT=4000 \
node dist/index.jsConnect from Claude Desktop / Claude Code (remote MCP):
The API key is passed as a Bearer token in the Authorization header — not in the URL.
{
"mcpServers": {
"metabase": {
"url": "https://your-mcp-host:4000/mcp",
"headers": {
"Authorization": "Bearer YOUR_METABASE_API_KEY"
}
}
}
}Docker (full stack):
# Build and start Metabase + MCP server
docker compose up --build
# Or publish a multi-platform image to Docker Hub
make publishThe compose stack exposes the MCP server on port 4000. Connect with Authorization: Bearer <key>.
Curl smoke-test:
curl -s -X POST http://localhost:4000/mcp \
-H "Authorization: Bearer YOUR_METABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}' \
-D -
# Response headers will include: mcp-session-id: <uuid>| Variable | Required | Description |
|---|---|---|
METABASE_URL |
Yes | Base URL of your Metabase instance, e.g. http://localhost:3000 |
METABASE_API_KEY |
Yes | API key created in Admin → Settings → Authentication → API Keys |
| Variable | Required | Description |
|---|---|---|
TRANSPORT |
Yes | Set to http to enable HTTP transport |
METABASE_URL |
Yes | Base URL of your Metabase instance (shared for all sessions) |
PORT |
No | Port to listen on (default: 4000) |
HOST |
No | Host to bind to (default: 0.0.0.0) |
METABASE_API_KEY |
— | Not used in HTTP mode. Each client passes Authorization: Bearer <key>. |
API Key Permissions: The API key must belong to a Metabase group with sufficient permissions for the operations you need. For full agent access (read schema, execute queries, create/edit cards and dashboards), the key's group should have access to all relevant databases and admin capabilities. A key with only basic viewer permissions will return 403 errors on write operations.
Add the following to your Claude Desktop MCP config file (typically ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"metabase": {
"command": "node",
"args": ["/absolute/path/to/metabase-mcp/dist/index.js"],
"env": {
"METABASE_URL": "http://localhost:3000",
"METABASE_API_KEY": "your-api-key-here"
}
}
}
}Replace /absolute/path/to/metabase-mcp with the actual path to this project.
Add the same config to your Claude Code MCP settings:
{
"mcpServers": {
"metabase": {
"command": "node",
"args": ["/absolute/path/to/metabase-mcp/dist/index.js"],
"env": {
"METABASE_URL": "http://localhost:3000",
"METABASE_API_KEY": "your-api-key-here"
}
}
}
}Once connected, ask the agent to call the server_ping tool:
"Call the server_ping tool to verify the Metabase MCP server is running."
A successful response looks like:
{"ok": true, "server": "metabase-mcp", "version": "0.1.0"}# Run tests
npm test
# Watch mode
npm run test:watch
# Lint
npm run lint
# Type check without building
npm run typecheck
# Build
npm run build
# Run server on stdio (requires env vars set)
npm start
# Run server on HTTP transport (port 4000)
npm run start:httpsrc/client.ts—MetabaseClientclass (HTTP,X-Api-Keyauth, typed errors) andMetabaseApiErrorsrc/index.ts— MCP server factory (createServer(credentials?)), all tool registrations, stdio and HTTP bootstrapsrc/types.ts— Hand-written TypeScript interfaces for Metabase API responses
All MCP server logging goes to stderr exclusively. stdout is reserved for the JSON-RPC protocol stream — any non-protocol bytes there would corrupt the MCP session.
This server targets Metabase v0.59.x. APIs introduced after this version are not used.