Content
# metricflow-mcp
A deterministic metric-serving layer over open-source [MetricFlow](https://github.com/dbt-labs/metricflow), exposed over both HTTP (FastAPI) and MCP (FastMCP).
## Overview
`metricflow-mcp` puts a single, framework-agnostic core service in front of MetricFlow's engine and fronts it with two thin transports:
- **FastAPI** — a plain HTTP API for services and dashboards.
- **FastMCP** — MCP tools for LLM-driven clients.
The point is determinism: clients reference metrics, dimensions, and entities **by name** and let MetricFlow compile the SQL, instead of writing SQL themselves. Both transports share one core (`MetricFlowService`), so query semantics are identical regardless of how you call it.
## Functionalities
Both transports expose the same read/query operations over your dbt semantic manifest:
- List metrics (with their queryable dimensions)
- List dimensions for one or more metrics
- List entities (join keys) for one or more metrics
- List the distinct values of a dimension
- List saved queries defined in the manifest
- Run a metric query — or **dry-run** it to get the compiled SQL without hitting the warehouse
### HTTP endpoints (FastAPI)
| Method | Path | Description |
| ------ | ----------------------------- | ------------------------------------------------------- |
| GET | `/metrics` | List metrics and their queryable dimensions |
| GET | `/metrics/{name}/dimensions` | Dimensions available for a metric |
| GET | `/metrics/{name}/entities` | Entities (join keys) for a metric |
| POST | `/dimension-values` | Distinct values of a dimension (`DimensionValuesSpec`) |
| GET | `/saved-queries` | Saved queries defined in the manifest |
| POST | `/query?execute=true\|false` | Run (or dry-run) a query (`QuerySpec`) |
### MCP tools (FastMCP)
| Tool | Description |
| --------------------------- | ---------------------------------------------------- |
| `mf_list_metrics` | List metrics + their queryable dimensions |
| `mf_list_dimensions` | Dimensions for the given metrics |
| `mf_list_entities` | Entities (join keys) for the given metrics |
| `mf_list_dimension_values` | Distinct values of a single dimension |
| `mf_list_saved_queries` | Saved queries defined in the manifest |
| `mf_query` | Run a metric query (or dry-run with `execute=False`) |
## Requirements
- Python 3.12+
- [uv](https://docs.astral.sh/uv/) for dependency and environment management
- A dbt project with a semantic manifest. The bundled `example/mf_tutorial_project` uses dbt + DuckDB (via `dbt-metricflow[dbt-duckdb]`) and is ready to point at.
## Configuration
Configuration is read from environment variables at process startup:
| Variable | Required | Description |
| ----------------- | -------- | ------------------------------------------------------------------- |
| `MF_PROJECT_DIR` | yes | Directory holding `dbt_project.yml` |
| `MF_PROFILES_DIR` | no | Directory holding `profiles.yml`; defaults to dbt's own resolution |
| `HOST` | no | API bind host (default `127.0.0.1`) |
| `PORT` | no | API bind port (default `8000`) |
| `MCP_TRANSPORT` | no | MCP transport: `http` (default) or `stdio` |
| `MCP_HOST` | no | MCP HTTP bind host (default `127.0.0.1`) |
| `MCP_PORT` | no | MCP HTTP bind port (default `8000`) |
## Development
### Install
```bash
uv sync
```
This installs runtime dependencies plus the dev group (ruff, mypy, pytest, httpx).
### Point at a dbt project
Use the bundled example project:
```bash
export MF_PROJECT_DIR="$(pwd)/example/mf_tutorial_project"
export MF_PROFILES_DIR="$(pwd)/example/mf_tutorial_project"
```
> **Note:** `example/mf_tutorial_project/profiles.yml` references an absolute DuckDB path. Adjust that path (or your `MF_PROFILES_DIR`) so it points to a valid location on your machine before querying.
### Run the HTTP API
```bash
uv run metricflow-mcp-api
# or, with autoreload:
uv run uvicorn "metricflow_mcp.api.app:create_app" --factory --reload
```
Then call it:
```bash
curl http://127.0.0.1:8000/metrics
curl -X POST "http://127.0.0.1:8000/query?execute=false" \
-H "Content-Type: application/json" \
-d '{"metrics": ["transactions"], "group_by": ["metric_time__day"]}'
```
Set `execute=false` to get the compiled SQL back without running it against the warehouse.
### Run the MCP server
By default the server speaks streamable HTTP, with the MCP endpoint at `/mcp/` and a
health check at `/health`:
```bash
uv run metricflow-mcp
# or:
uv run python -m metricflow_mcp.mcp
curl http://127.0.0.1:8000/health
# {"status": "ok", "engine_bootstrapped": true}
```
For stdio clients (e.g. a Claude Desktop server entry), set `MCP_TRANSPORT=stdio`:
```bash
MCP_TRANSPORT=stdio uv run metricflow-mcp
```
Tool failures surface as MCP tool errors (`isError: true`): known validation/execution
problems carry a client-safe message plus a hint, everything unexpected is masked to a
generic message.
### Configure a client via `.mcp.json`
MCP clients such as Claude Code read server entries from a `.mcp.json` at the project
root. To connect to an already-running HTTP server (started with `uv run metricflow-mcp`):
```json
{
"mcpServers": {
"metricflow": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp/"
}
}
}
```
Alternatively, let the client launch the server itself over stdio — note the
`MCP_TRANSPORT=stdio` override, since the server defaults to HTTP:
```json
{
"mcpServers": {
"metricflow": {
"command": "uv",
"args": ["run", "metricflow-mcp"],
"cwd": "/path/to/metricflow-mcp",
"env": {
"MCP_TRANSPORT": "stdio",
"MF_PROJECT_DIR": "/path/to/your/dbt_project",
"MF_PROFILES_DIR": "/path/to/your/dbt_project"
}
}
}
}
```
### Quality gates
Before considering a change done:
```bash
uv run ruff format
uv run ruff check
uv run mypy
uv run pytest
```
## Project layout
```
src/metricflow_mcp/
├── config.py # environment-driven configuration
├── errors.py # transport-agnostic error types
├── core/ # framework-agnostic MetricFlowService + Pydantic models
├── api/ # FastAPI transport
└── mcp/ # FastMCP transport
example/
└── mf_tutorial_project/ # runnable dbt + DuckDB project to query against
```