{"id":"37008905-2b4b-49f4-a837-e43651b1c9f5","slug":"dagster-mcp","kind":"mcp","name":"dagster-mcp","summary":"MCP server that wraps the Dagster GraphQL API — monitor and operate runs, assets, schedules, sensors, and backfills from any MCP client","owner":"fabdendev","repoUrl":"https://github.com/fabdendev/dagster-mcp","license":"MIT","licenseTier":"permissive","trust":"community","stars":17,"match":null,"fewStars":false,"format":"mcp","createdAt":null,"addedAt":"2026-10-14T05:19:29.732Z","logoUrl":"https://avatars.githubusercontent.com/u/63844263?u=946f2a0845c0afaa6a749665f18bf6d9ad2d7800&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","data-analytics","memory-knowledge"],"tags":["ai-agent","dagster","data-pipelines","dataops","graphql","llm"],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-14T07:01:15.408Z","sample":false,"url":"https://conduits.dev/tools/dagster-mcp","language":"Python","transports":["stdio"],"install":{"pypi":"dagster-mcp"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# Dagster MCP\n\n[![PyPI version](https://img.shields.io/pypi/v/dagster-mcp)](https://pypi.org/project/dagster-mcp/)\n[![Downloads](https://static.pepy.tech/badge/dagster-mcp/month)](https://pepy.tech/project/dagster-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)\n[![Tests](https://github.com/fabdendev/dagster-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/fabdendev/dagster-mcp/actions/workflows/tests.yml)\n\nAn [MCP](https://modelcontextprotocol.io/) server that gives AI agents full visibility and control over your [Dagster](https://dagster.io/) instance — like an SRE for your data pipelines.\n\nWorks with any MCP client: [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Claude Desktop](https://claude.ai), [Cursor](https://cursor.sh), and more.\n\nDagster officially develops and supports their own Dagster Plus MCP server. See the documentation [here](https://docs.dagster.io/guides/labs/dagster-mcp).\n\n## Why this exists\n\nData pipelines break at 3 AM. Schedules silently stop firing. Assets go stale. Instead of waking up to a dashboard full of red, give your AI agent the tools to **monitor, diagnose, and fix** your Dagster instance autonomously.\n\n```\nAgent: Checking instance health...\n       get_instance_status() -> healthy: false, daemon \"SCHEDULER\" unhealthy\n\nAgent: Scheduler daemon is down. Let me check recent failures...\n       get_runs(statuses=[\"FAILURE\"], limit=5) -> 3 failed runs in the last hour\n\nAgent: Diagnosing the most recent failure...\n       get_run_failure_summary(\"run_abc123\") ->\n         failed_steps: [\"transform_orders\"]\n         root_cause: \"NullPointerError: column 'price' is null\"\n         suggestions: [\"Single step failed — consider re-running from failure\"]\n\nAgent: Re-launching the failed job...\n       launch_job(\"etl_pipeline\", \"my_project\") -> run_id: \"run_def456\", status: STARTED\n\nAgent: One sensor keeps firing on bad data. Checking its ticks...\n       get_tick_history(\"orders_sensor\", \"SENSOR\") -> 12 consecutive FAILURE ticks\n\nAgent: Stopping it until the upstream fix lands...\n       stop_sensor(\"orders_sensor\") -> status: STOPPED\n```\n\n## What it does\n\n27 tools across 6 categories, designed for autonomous DataOps workflows:\n\n| Category | Tools | What an agent can do |\n|----------|-------|---------------------|\n| **Runs** | `get_runs` `get_run_status` `get_run_logs` `get_run_stats` `get_run_failure_summary` | Find failures, diagnose root causes, inspect logs and step timing |\n| **Assets** | `search_assets` `resolve_asset_selection` `get_asset_details` `get_recent_materializations` `get_asset_health` | Discover assets, preview lineage selections, check freshness, detect stale data |\n| **Jobs** | `list_jobs` | Inventory jobs across code locations, with optional repository/location filters |\n| **Schedules & Sensors** | `list_schedules` `list_sensors` `get_tick_history` | Detect silent failures, missed ticks, sensor errors |\n| **Instance** | `get_instance_status` `list_code_locations` `list_backfills` | Global health check, daemon status, code location errors |\n| **Actions** | `materialize_assets` `backfill_assets` `launch_job` `launch_job_with_partitions` `terminate_run` `start_schedule` `stop_schedule` `start_sensor` `stop_sensor` `reload_code_location` | Materialize concrete assets with config, backfill partitions, launch jobs, stop stuck runs, start or stop schedules and sensors, reload after deploy |\n\n> Actions are opt-in: set `DAGSTER_READ_ONLY=false` to enable write operations.\n\n## Quick start\n\n### Prerequisites\n\n- Python 3.12+\n- [uv](https://docs.astral.sh/uv/) (recommended) or pip\n- A running [Dagster](https://dagster.io/) instance (self-hosted or Cloud)\n\n### Install\n\nThe package is published on [PyPI](https://pypi.org/project/dagster-mcp/).\n\n**Option A — run directly with uvx (no install needed):**\n\n```bash\nuvx dagster-mcp\n```\n\n**Option B — install with pip:**\n\n```bash\npip install dagster-mcp\n```\n\n**Option C — clone and run:**\n\n```bash\ngit clone https://github.com/fabdendev/dagster-mcp.git\ncd dagster-mcp\nuv sync\n```\n\n### Configure\n\n#### Single environment\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `DAGSTER_URL` | Base URL of your Dagster instance | `http://localhost:3000` |\n| `DAGSTER_API_TOKEN` | Dagster Cloud API token (leave empty for self-hosted) | _(empty)_ |\n| `DAGSTER_EXTRA_HEADERS` | JSON object of additional request headers sent to Dagster GraphQL | _(empty)_ |\n| `DAGSTER_READ_ONLY` | When `true`, only read tools are exposed (no launch/terminate/reload, no schedule or sensor start/stop) | `true` |\n\n**Self-hosted:**\n\n```bash\nexport DAGSTER_URL=http://localhost:3000\n```\n\n**Dagster Cloud:**\n\n```bash\nexport DAGSTER_URL=https://myorg.dagster.cloud/prod\nexport DAGSTER_API_TOKEN=your-dagster-cloud-user-token\n```\n\n**Custom auth / proxy headers:**\n\n```bash\nexport DAGSTER_EXTRA_HEADERS='{\"Authorization\":\"Bearer your-token\",\"X-My-Header\":\"value\"}'\n```\n\n#### Multiple environments\n\nUse `DAGSTER_ENVS` to configure several Dagster instances in one server. Every tool then accepts an optional `env` parameter so the LLM can target the right instance.\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `DAGSTER_ENVS` | JSON object mapping env names to `{url, token?, extra_headers?}` configs | _(empty)_ |\n| `DAGSTER_DEFAULT_ENV` | Env name to use when `env` is not passed to a tool | _(empty)_ |\n\n```bash\nexport DAGSTER_ENVS='{\n  \"prod\": {\"url\": \"https://myorg.dagster.cloud/prod\", \"token\": \"prod-token\"},\n  \"staging\": {\"url\": \"https://myorg.dagster.cloud/staging\", \"token\": \"stg-token\"},\n  \"dev\": {\"url\": \"http://localhost:3000\"}\n}'\nexport DAGSTER_DEFAULT_ENV=prod\n```\n\nWhen `DAGSTER_ENVS` is set, `DAGSTER_URL` / `DAGSTER_API_TOKEN` / `DAGSTER_EXTRA_HEADERS` are ignored. If only one env is defined, it is used automatically even without `DAGSTER_DEFAULT_ENV`.\n\n### Add to your MCP client\n\n<details>\n<summary><strong>Claude Code</strong></summary>\n\nAdd to `~/.claude/settings.json`:\n\n**Single env:**\n\n```json\n{\n  \"mcpServers\": {\n    \"dagster\": {\n      \"command\": \"uvx\",\n      \"args\": [\"dagster-mcp\"],\n      \"env\": {\n        \"DAGSTER_URL\": \"http://localhost:3000\"\n      }\n    }\n  }\n}\n```\n\n**Multiple envs:**\n\n```json\n{\n  \"mcpServers\": {\n    \"dagster\": {\n      \"command\": \"uvx\",\n      \"args\": [\"dagster-mcp\"],\n      \"env\": {\n        \"DAGSTER_ENVS\": \"{\\\"prod\\\":{\\\"url\\\":\\\"https://myorg.dagster.cloud/prod\\\",\\\"token\\\":\\\"prod-token\\\"},\\\"dev\\\":{\\\"url\\\":\\\"http://localhost:3000\\\"}}\",\n        \"DAGSTER_DEFAULT_ENV\": \"prod\"\n      }\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary><strong>Claude Desktop</strong></summary>\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"dagster\": {\n      \"command\": \"uvx\",\n      \"args\": [\"dagster-mcp\"],\n      \"env\": {\n        \"DAGSTER_URL\": \"http://localhost:3000\"\n      }\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary><strong>From a local clone</strong></summary>\n\n```json\n{\n  \"mcpServers\": {\n    \"dagster\": {\n      \"command\": \"uv\",\n      \"args\": [\"run\", \"--directory\", \"/path/to/dagster-mcp\", \"dagster-mcp\"],\n      \"env\": {\n        \"DAGSTER_URL\": \"http://localhost:3000\"\n      }\n    }\n  }\n}\n```\n\n</details>\n\n## Tool reference\n\n### Runs\n\n| Tool | Description |\n|------|-------------|\n| `get_runs` | List recent runs, filter by job name and/or status |\n| `get_run_status` | Get status, config, tags, and run lineage (re-execution chain via rootRunId/parentRunId) |\n| `get_run_logs` | Get structured log events with pagination and optional level filtering (`ERROR`, `WARNING`, `INFO`); EngineEvent events include `metadataEntries` |\n| `get_run_stats` | Get per-step execution stats: timing, materializations, expectation results |\n| `get_run_failure_summary` | **Consolidated failure diagnosis** — failed steps, root cause error, step durations, and suggestions in one call |\n\n### Assets\n\n| Tool | Description |\n|------|-------------|\n| `search_assets` | Discover assets by key prefix or group name |\n| `resolve_asset_selection` | Resolve key/group/tag/kind/owner predicates, wildcards, boolean logic, roots/sinks, and lineage traversal into concrete asset keys without launching anything |\n| `get_asset_details` | Get description, upstream/downstream dependencies, partitions, latest materialization |\n| `get_recent_materializations` | Get materialization history with metadata for an asset |\n| `get_asset_health` | **Consolidated health view** — staleness, freshness policy, last run status (works with single asset or entire group) |\n\n#### Two-step asset workflow\n\nStart by resolving and reviewing a selection expression:\n\n```text\nresolve_asset_selection(\n  asset_selection=\"group:analytics and (kind:dbt or key:*benchmark)\"\n)\n\n→ {\n    \"asset_keys\": [\n      \"warehouse/analytics/orders\",\n      \"warehouse/analytics/reranker_benchmark\"\n    ],\n    \"assets\": [...]\n  }\n```\n\nFor concrete, unpartitioned assets, pass the returned keys to\n`materialize_assets` with any required launch config and tags:\n\n```text\nmaterialize_assets(\n  asset_keys=[\n    \"warehouse/analytics/orders\",\n    \"warehouse/analytics/reranker_benchmark\"\n  ],\n  run_config={\"ops\": {\"benchmark\": {\"config\": {\"limit\": 1000}}}},\n  tags={\"triggered_by\": \"agent\"}\n)\n```\n\nFor partitioned assets, pass the same resolved keys to `backfill_assets`\ninstead:\n\n```text\nbackfill_assets(\n  asset_keys=[\"warehouse/analytics/daily_orders\"],\n  partition_start=\"2026-07-01\",\n  partition_end=\"2026-07-07\",\n  run_config={\"resources\": {\"warehouse\": {\"config\": {\"pool\": \"benchmark\"}}}}\n)\n```\n\n`resolve_asset_selection` is available in both read-only and read-write modes.\nIt returns external, observable, non-executable, and partitioned matches so the\ncaller can inspect the complete result. The write tools re-fetch current asset\ndefinitions before execution. `resolve_asset_selection` and `materialize_assets`\nrequire Dagster 1.9+.\n\n### Jobs, Schedules & Sensors\n\n| Tool | Description |\n|------|-------------|\n| `list_jobs` | List jobs across code locations, optionally filtered by exact `repository_name` and/or `location_name` (use to find names for `launch_job`) |\n| `list_schedules` | List schedules with status (RUNNING/STOPPED), cron, target job, next tick |\n| `list_sensors` | List sensors with status and target jobs |\n| `get_tick_history` | Tick-by-tick history for a schedule or sensor — **essential for detecting silent failures**. Accepts optional `repository_name` / `location_name` to disambiguate a name shared by several code locations |\n\n`list_jobs` filters are independent and use AND semantics when combined. For\nexample, `list_jobs(repository_name=\"example_repository\")` finds that repository\nacross locations, while\n`list_jobs(repository_name=\"example_repository\", location_name=\"example_location\")`\ntargets one repository and code-location pair. Calls with only one filter use a\nlightweight repository-discovery request followed by one batched job request —\nit isn't free: that's a second round-trip against Dagster, traded for a\nsmaller payload when only a subset of repositories matches. Request counts\nper call: 1 for no filters, 1 for both filters, 2 for exactly one filter.\n\nIf a code location relevant to the filter failed to load or is still loading,\n`list_jobs` raises a `RuntimeError` naming the location instead of silently\nreturning an empty result — an empty filtered result means no match among\ncode locations Dagster could actually load. A non-empty filtered result can\nstill be incomplete: if some repositories matched, `list_jobs` returns them\nrather than raising, even when another code location could not be searched.\nThis check rides along in the same requests above (`workspaceOrError`, which\n`list_code_locations` already queries), so it adds no extra round-trip. The\nunfiltered call\n(`list_jobs()` with no filters) is unchanged: it omits this check and keeps\nreturning whatever is loaded, since an agent won't misread a long inventory\nlisting as \"nothing exists\" the way it would misread an empty filtered\nresult — use `list_code_locations` or `get_instance_status` to check load\nhealth directly.\n\n### Instance & Code Locations\n\n| Tool | Description |\n|------|-------------|\n| `get_instance_status` | **Start here** — global health: daemon status, queued run count, code location errors |\n| `list_code_locations` | List all code locations and their load status |\n| `list_backfills` | List recent backfills with status and partition progress |\n\n### Write Operations\n\n| Tool | Description |\n|------|-------------|\n| `materialize_assets` | Launch concrete, unpartitioned asset keys with run config and tags; infers one compatible repository/job, includes compatible checks, and expands required non-subsettable multi-asset neighbors. On a failed launch it returns the preflight context plus `error` and `message` rather than raising, and omits `launched_asset_keys` |\n| `backfill_assets` | Launch a partition backfill by **asset selection** with optional run config; respects each asset's `BackfillPolicy` server-side |\n| `launch_job` | Launch a named job; `asset_keys` remains supported for compatibility and is sent through GraphQL `assetSelection`, but the two-step asset workflow is preferred |\n| `launch_job_with_partitions` | Launch a partitioned job for one or more partition keys; creates a backfill (supports `from_failure` to retry only failed steps) |\n| `start_schedule` | Start (enable) a schedule so it launches runs on its cron |\n| `stop_schedule` | Stop (disable) a schedule — persists across restarts; does not terminate in-flight runs |\n| `start_sensor` | Start (enable) a sensor so it resumes evaluating |\n| `stop_sensor` | Stop (disable) a sensor — the fix for a runaway or erroring sensor found via `get_tick_history` |\n\n> Schedule/sensor names are unique only within a repository. If the same name exists in several code locations, the start/stop tools refuse to act and list the candidates; pass `repository_name` / `location_name` to disambiguate. Successful calls echo the resolved `repository` and `location`.\n| `terminate_run` | Stop a stuck or runaway run |\n| `reload_code_location` | Reload a code location after deploy |\n\n> Write tools require `DAGSTER_READ_ONLY=false` (default is `true`).\n\n## How it differs from Dagster's official AI tooling\n\nDagster builds and supports its own AI tooling. If you are a **Dagster+ customer**, start with theirs — it is first-party, needs no local runtime, and reaches Dagster+ platform objects (alerting, Issues, Insights) that this project cannot. This project exists mainly for people running **open-source / self-hosted Dagster**, which the official server's documented setup does not cover.\n\nEverything in the \"official\" column comes from Dagster's own docs at <https://docs.dagster.io/guides/labs/dagster-mcp> (retrieved 2026-07-30), or from Dagster directly in [#21](https://github.com/fabdendev/dagster-mcp/pull/21). Dagster's docs nowhere state that open-source or self-hosted Dagster is unsupported; what they document is a Dagster-hosted URL, a required `Dagster-Cloud-Organization` header, and a Dagster+ user token. Their capability matrix is a snapshot of today and Dagster expects to keep adding to it, so treat the gaps below as current rather than permanent.\n\n| | **dagster-mcp** (this project) | **[Dagster+ MCP server](https://docs.dagster.io/guides/labs/dagster-mcp)** (official) | **[`dagster-expert` skill](https://docs.dagster.io/getting-started/ai-tools)** (official) |\n|---|---|---|---|\n| **What it is** | MCP server you run yourself (Python, MIT) | Dagster-hosted remote MCP endpoint at `https://mcp.agent.dagster.cloud/mcp` | An Agent Skill — markdown instructions loaded by your coding agent, not an MCP server |\n| **When you use it** | Operations time, against a live instance | Operations time, against a Dagster+ deployment | Development time, against your local codebase |\n| **Works with self-hosted / OSS Dagster** | Yes — points at any Dagster webserver (`DAGSTER_URL`, default `http://localhost:3000`; `/graphql` is appended). No token required | Not per the documented setup: the only URL given is Dagster-hosted, and connecting requires a `Dagster-Cloud-Organization` header plus a Dagster+ user token | Yes — Apache-2.0 markdown files you copy locally |\n| **Works with Dagster+** | Yes — sends a `Dagster-Cloud-Api-Token` header; extra headers via `DAGSTER_EXTRA_HEADERS` | Yes — this is its only documented target | n/a |\n| **Setup** | Local process launched by your MCP client (Python 3.12+, e.g. `uvx dagster-mcp`), configured with env vars | No local runtime: `claude mcp add --transport http dagster-plus https://mcp.agent.dagster.cloud/mcp --header \"Dagster-Cloud-Organization: …\" --header \"Authorization: Bearer …\"` | Copy the skill files into your agent |\n| **Maturity** | v0.8.0 on PyPI (pre-1.0), MIT, single maintainer with occasional outside contributions, \"AS IS, WITHOUT WARRANTY OF ANY KIND\", no SLA | Labelled by Dagster as Preview: *\"under active development, and not considered ready for production use… the APIs may change\"*, and published under `/guides/labs/` | Current, no preview label |\n| **Runs** | View, launch, terminate, per-step stats, log retrieval, consolidated failure summary | View ✅, Create/Launch ✅, Delete/Terminate ✅, Insights metrics ✅, Update ❌ (Dagster's matrix); run logs View ✅ | n/a |\n| **Assets** | View, search, health, resolve selection syntax, and **materialize specific assets** (`resolve_asset_selection` → `materialize_assets`, with run config and partition ranges) | View ✅ and Insights metrics ✅; Create/Launch, Update and Delete marked ❌ — i.e. no asset-level materialization tool, though launching a run is supported | n/a |\n| **Schedules & sensors** | List, tick history, start/stop | Not supported today — confirmed by Dagster, who expect to add it | n/a |\n| **Backfills & partitions** | List backfills, launch partitioned jobs, backfill assets over a partition range | Not listed in Dagster's capability matrix | n/a |\n| **Code locations & instance health** | List/reload code locations, instance status, daemon heartbeats, queued-run counts | Deployments View ✅ and Insights ✅; code locations and daemon health not listed (Dagster+ manages the daemon) | n/a |\n| **Alerting, Insights, Dagster+ Issues** | Not supported — no alerting, cost/Insights metrics, or Issues equivalent | Alert policies and Dagster+ Issues: View / Create / Update / Delete all ✅. Insights metrics ✅ on Runs, Assets and Deployments. All three are documented Dagster+ features (Issues is in limited early access) | n/a |\n| **Multiple instances** | Yes — `DAGSTER_ENVS` maps names to arbitrary URLs and tokens, with a per-tool `env` argument; you can mix OSS and Dagster+ | Deployments within one Dagster+ organization, selected per tool call | n/a |\n| **Write safety** | 17 read tools always registered; the 10 write tools are registered only when `DAGSTER_READ_ONLY=false` (default `true`), so clients cannot even see them otherwise. This is a convenience guardrail, **not** a security boundary: it is a process-level env var read at import, and the API token you configure keeps whatever rights it has | Enforced server-side by Dagster+ token permissions. The docs default to a personal user token; a [service user](https://docs.dagster.io/deployment/dagster-plus/authentication-and-access-control/rbac/users#service-users) is offered as the alternative for scoped, non-human auth (service users are a Dagster+ Pro feature) | n/a |\n| **Support** | Best-effort, community, GitHub issues; no SECURITY.md or documented disclosure process | First-party vendor | First-party vendor |\n\n**Use the official Dagster+ MCP server if** you are on Dagster+ and want alerting, Issues or Insights/cost analysis from an agent, want no local runtime, or need vendor support and a supplier your procurement process will accept.\n\n**Use this project if** you run open-source or self-hosted Dagster, or you need asset-level materialization, schedule/sensor control, backfills, or code-location and daemon operations from an agent.\n\nFor Dagster+ users the two are complementary rather than competing — nothing stops you running both. This project is not affiliated with or endorsed by Dagster Labs.\n\n## Compatibility\n\nThe monitoring and existing action tools are tested with Dagster 1.6+.\n`resolve_asset_selection` and `materialize_assets` require Dagster 1.9+ and\nverify the required GraphQL capabilities before querying or launching. The\n`RunsFilter` field name (`jobName` vs `pipelineName`) is auto-detected via schema\nintrospection. Configured asset backfills also feature-detect\n`LaunchBackfillParams.runConfigData` and return a clear compatibility error when\nan older schema does not expose it. Schedule and sensor start/stop work on\nDagster 1.6+: the stop mutations are sent with the `originId`/`selectorId`\nargument pair sourced from `InstigationState.id`/`selectorId`, which modern\nDagster re-parses as a compound id and older versions accept directly, so no\nversion branch is needed. The mutations select error messages via the\n`... on Error` interface fragment rather than per-type fragments, because\n`ScheduleNotFoundError` only joined `ScheduleMutationResult` in Dagster 1.9 and\nspreading it on 1.6-1.8 would fail document validation.\n\n## Development\n\n```bash\nuv sync --extra dev\nuv run ruff check dagster_mcp/    # lint\nuv run pytest                     # run the test suite\nuv run python -m dagster_mcp      # start server locally\n```\n\n## License\n\nMIT\n\n"}