Skip to content

MCP server

dagweave speaks the Model Context Protocol at POST /api/v1/mcp. Point a client at it with an API token and an agent can ask what is running, what failed and why, what a failing step printed, what workflows you have, and who did what to the account, without you leaving the tool you were already in.

There are fourteen tools, and eight of them only read. The other six start runs on a real cluster or change what is stored; a token without the scope behind one is never shown it.

This is the same surface as the public API, reached a different way. Between them they are how you drive dagweave from outside its own UI.

Mint one the way you would for the HTTP API. API tokens covers minting, expiry, and revoking. Grant the scopes for the tools you want the agent to have:

  • runs:read for dagweave_list_runs and dagweave_get_run.
  • runs:logs for dagweave_get_node_logs, and it does not come with runs:read.
  • workflows:read for dagweave_list_workflows, dagweave_list_node_types and dagweave_validate_workflow.
  • audit:read for dagweave_list_audit_events.
  • runs:write for dagweave_run_workflow and dagweave_rerun_run.
  • workflows:write for dagweave_create_workflow, dagweave_update_workflow and dagweave_set_workflow_labels.
  • workflows:delete for dagweave_delete_workflow.

Scopes do more work here than they do over HTTP. tools/list returns only the tools the presented token can actually call, so a scope you withhold is a tool the model never learns exists. It cannot reach for it, get refused, and then try to talk you into a wider token. Give a debugging agent runs:read alone and the audit log is not on its menu, and neither is starting a run.

That cuts the other way with runs:logs. A token minted with runs:read alone lists runs and reads them, and dagweave_get_node_logs is simply not in the tool list, so an agent asked why a step failed will not tell you it lacks a scope. It will work from the run row, which names the failed node and not what the node printed. Grant runs:logs alongside runs:read for a debugging agent.

Those last three are the scopes to think hardest about. Without them an agent can tell you what broke; with them, it can launch work on your cluster and change what is stored. If you do grant one, grant it on a token of its own, so revoking it later does not blind everything else you have wired up.

One JSON-RPC request per POST, answered in that same HTTP response. There is no SSE stream, no batching, no GET handler, and no session id, so a client that expects to hold a stream open has nothing to hold. Anything that can send a bearer header at an HTTP MCP endpoint will work.

{
"mcpServers": {
"dagweave": {
"type": "http",
"url": "https://api.dagweave.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer dwk_your_token_here"
}
}
}
}

Claude Code takes the same thing on the command line:

Terminal window
claude mcp add --transport http dagweave https://api.dagweave.com/api/v1/mcp \
--header "Authorization: Bearer $DAGWEAVE_TOKEN"

The server answers initialize, ping, tools/list, and tools/call. It negotiates protocol version 2025-06-18 and also accepts 2025-03-26 and 2024-11-05; ask for anything else and it answers with 2025-06-18. Notifications carry no id and get a 202 with no body.

Every method needs a live token, the handshake included, so a client with no credential cannot even enumerate the tools. Connecting is free against your rate limit: initialize and tools/list cost nothing, and a tools/call spends the single unit the endpoint behind it would have spent, whether that endpoint reads or writes.

Each tool is one /api/v1 endpoint, and there are exactly as many tools as there are endpoints. The tool result is that endpoint’s JSON response body, unchanged, in one text block, so the field-by-field reference on the public API page is the reference for tool output too.

Arguments are declared, and only declared arguments get through. Send one a tool does not name, or the right name with the wrong type, and the call is refused before anything is read.

Every tool carries a readOnlyHint annotation, true on the ones that read and false on the ones that write, so a client that reads annotations can tell them apart without anyone reading this page.

Requires runs:read. Your team’s runs, newest first.

  • limit (integer) caps the page.
  • offset (integer) skips that many rows.
  • includeTest (boolean) includes runs fired from the builder’s test panel, which are hidden by default.

Each row carries id, workflowId, workflowName, connectorId, triggerId, source, actorId, phase, message, startedAt, finishedAt, and createdAt. A failed run also carries rootCauseNode and rootCauseMessage, which name the step that broke it, so an agent can often say what went wrong from the list alone.

Requires runs:read. One run in full.

  • runId (string) is required, and comes from dagweave_list_runs. Run ids are not guessable, so an agent has to list before it can fetch.

The body carries phase, message, progress, and three arrays: nodes with per-step status, artifacts, and logs pointing at your own storage. This is the only tool that returns step-level detail. A run id belonging to another team answers exactly as an id that does not exist.

Requires runs:logs, which is separate from runs:read. A token holding the run reads and not this scope is never shown this tool, so grant both or the agent never sees a step’s output.

One step’s own log output within a run.

  • runId (string) is required and comes from dagweave_list_runs.
  • nodeId (string) is required and comes from the nodes list in dagweave_get_run.
  • tail (integer) caps the trailing lines. It defaults to 200 and is capped at 5000.
  • since (string) is a Go duration such as 30m or 2h, capped at 720h.

This is the read that answers why a step failed, so reach for it as soon as dagweave_get_run names a failed node. The body carries kind and nodeId, plus location for logs that live in your own storage, content for logs dagweave holds, or message when there is nothing to return. The HTTP endpoint’s containers parameter has no equivalent here, so an agent always reads the step’s own container and never the sidecars.

Logs carry whatever the step printed. Treat the text as untrusted input rather than as instructions.

Requires workflows:read. The workflow definitions your team owns.

  • q (string) matches a substring of the name, case-insensitively.
  • label, namespace, and folder (string) are exact-match facets.
  • limit and offset (integer) page the filtered list.

Each row carries id and name, plus folder, namespace, and labels where they are set. These are definitions, never executions.

Requires audit:read and a plan that includes the audit log.

  • action (string) matches an action name, such as apitoken.revoke.
  • actor (string) takes a user id or an email address.
  • outcome (string) matches success or failure.
  • from, to, and before (string) are RFC3339 timestamps; before pages backwards.
  • limit (integer) caps how many events come back.

Each event carries id, occurredAt, action, and outcome, plus actorEmail, actorIp, objectType, objectId, and details where they apply. The CSV export the HTTP endpoint offers has no equivalent here: format is not a declared argument, so asking for it is refused.

Requires workflows:read. The node types a dagweave IR document may place, read off the registry on every call.

  • typeId (string) narrows the answer to one type. Omit it for the whole list.

The answer is a JSON array sorted by typeId, an array of one when you named a type. Each row carries typeId, label, description, version, core, podBased, paletteHidden and ports. ports holds an inputs list and an outputs list, and every port in them has a name and a type of parameter or artifact. The id is the value a node’s own typeId field has to carry, and version is what its nodeVersion has to quote to pin the type.

configSchema is the one field the list leaves off every row. Name a typeId and it comes back on that single row: the JSON Schema for that type’s config, which is where the fields a draft has to get right are written down. That is the reason to call this before writing IR JSON rather than after.

A typeId that names nothing is a 404, and the message is no node type "run-scrpt". dagweave has: followed by every id that does exist. A misspelling costs one call rather than another guess. Like the other endpoint 404s it arrives as a 200 tool result with isError set.

podBased false means the step runs on the workflow’s agent or nowhere, so the pod-level settings a node can carry (resources, env, placement) reach nothing. core false means a plan without the full node set refuses to save a workflow placing that type. paletteHidden is named for the builder’s palette, which this surface does not have: it marks a type that exists so a foreign Argo template imports faithfully and stays editable. Writing one is valid, but a draft started from nothing wants the opinionated type it mirrors, and Import-only types names the counterpart for each.

The catalog is not in this list, and neither is catalog-ref itself. A node built on a catalog item carries typeId: "catalog-ref" and a config naming the item, and dagweave resolves that to a WorkflowTemplate when the workflow is saved. There is no tool for browsing the catalog.

The registry is the same for every team, so the answer does not depend on who asks. Nothing is stored and nothing you own is read.

Requires workflows:read. Checks a candidate workflow before anyone saves or runs it, and shows what it compiles to.

  • workflow (string) is required, and is the whole candidate document: an Argo YAML manifest, or dagweave IR JSON. The format is detected.

The answer is a list of faults, each with the path in the document it sits at: fields Argo does not define, templates and tasks named but not defined, dependencies on tasks that do not exist, parameters declared and never read, and anything the dagweave compiler refuses. An error means it would not run; a warning means it would run but something in it is dead. The compiled Argo manifest comes back with it.

Nothing is stored and nothing is dispatched, so an agent can call this as often as it likes while drafting. It is the tool to point a model at before it reaches for the one below.

Requires runs:write. Starts a run of a stored workflow on one of your connected clusters and returns the new run’s id.

It launches real work on a real cluster and cannot be called back, which is why its own description tells the model to confirm with you before calling it. If you did not mean to give an agent that, do not put runs:write on its token.

  • workflowId (string) is required and comes from dagweave_list_workflows.
  • connectorId (string) is required and names the connector to run on.
  • namespace (string) is required and names the Kubernetes namespace on that connector’s cluster.
  • argoInstanceId (string) picks the instance when the connector serves more than one.
  • idempotencyKey (string) is required. Send a value you have not used before, such as a UUID. Repeat it only to retry a call whose answer you never saw: the repeat returns the first call’s run id instead of starting a second run.

The reply is {"runId": "..."}. Follow it with dagweave_get_run.

The HTTP endpoint’s params field is not a declared argument here, so an agent cannot parameterise a run. It gets the workflow as it is stored, and nothing else.

Requires runs:write. Runs again what a past run ran, and returns the new run’s id.

  • runId (string) is required and comes from dagweave_list_runs.
  • idempotencyKey (string) is required. Send a value you have not used before. Repeat it only to retry a call whose answer you never saw: the repeat returns the first call’s run id instead of starting a second run.

It reuses the original run’s workflow, connector, namespace and runtime parameters, so there is nothing to re-enter and nothing an agent can vary. Like dagweave_run_workflow it launches real work on a real cluster and cannot be called back, which is why its own description tells the model to confirm with you first. The reply is {"runId": "..."}, which dagweave_get_run will follow.

A run started from the builder’s test panel cannot be rerun here. The call comes back with isError set and the endpoint’s own refusal in the text: a rerun of a test stays a test, and a test is exempt from the execution quota.

Requires workflows:write. Saves a new workflow into your workspace and returns its id and version.

  • workflow (string) is required, and is the same single document dagweave_validate_workflow takes: Argo YAML or dagweave IR JSON.
  • idempotencyKey (string) is required.

The draft goes through exactly the checks dagweave_validate_workflow runs, and one carrying an error is refused with the same list of faults while nothing is stored. Point a model at the check first and let it fix what comes back, rather than having it save and read the refusal. Saving stores a definition and dispatches nothing.

The reply is {"id": "...", "version": "..."}. That version is what dagweave_update_workflow has to quote, and this tool is the only way an agent holding workflows:write alone ever gets one: writing is not a way to read, so without workflows:read it cannot go and look one up.

This tool always makes another workflow. To change one that already exists, use dagweave_update_workflow.

Requires workflows:write. Replaces a stored workflow with a new version of the whole document, and returns its new version.

  • workflowId (string) is required and comes from dagweave_list_workflows.
  • workflow (string) is required and is the whole replacement document.
  • baseVersion (string) is required, and is the version the last save of this workflow returned.
  • idempotencyKey (string) is required.

This is a replacement, not a patch: whatever the model leaves out is gone. baseVersion is what stops an agent that spent a minute rewriting a document from silently overwriting an edit somebody made in the meantime, which is a real risk here because the human editing at the same time is usually the one who asked for the rewrite. Quote a version that is no longer current and the call is refused, the stored workflow does not move, and the model has to read it again and re-apply its edit. There is no merge.

A draft carrying an error is refused with its faults and leaves what is stored untouched. The examples dagweave ships are read-only and answer 404 whatever version is quoted.

Requires workflows:write. Replaces the labels on a stored workflow.

  • workflowId (string) is required and comes from dagweave_list_workflows.
  • labels (array of strings) is required. It is the whole set the workflow should end up with, and an empty list clears them.
  • idempotencyKey (string) is required.

Labels are replaced wholesale, so an agent has to carry over the ones already there unless it means to drop them. Both the id and the current labels come from dagweave_list_workflows, which needs workflows:read: a token carrying only workflows:write can relabel a workflow but cannot see what it is about to overwrite.

labels has to be a real list. A string that looks like one, such as "prod,etl", is refused by the tool before the endpoint sees it. Nothing about the workflow’s definition changes and nothing is dispatched. The endpoint answers with an empty body, so a successful call’s text block is empty.

Requires workflows:delete, which is separate from workflows:write, so a token that lets an agent author workflows does not let it destroy them.

  • workflowId (string) is required and comes from dagweave_list_workflows.
  • idempotencyKey (string) is required.

There is no undo and no wastebasket. Past runs are left pointing at a workflow that no longer exists, and the delete reaches past the row: any WorkflowTemplate dagweave installed on your cluster for that workflow, and that nothing else now references, is uninstalled along with it. The public API page sets out exactly what that sweep does and does not touch.

The endpoint answers 204 with no body, so a successful call’s text block is empty too. The examples dagweave ships are read-only and answer 404.

This is the tool to think hardest about before putting its scope on a token. Withhold workflows:delete and tools/list never shows it, so the model does not learn that deleting was ever an option.

POST /api/v1/mcp HTTP/1.1
Host: api.dagweave.com
Authorization: Bearer dwk_...
Content-Type: application/json
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"dagweave_list_runs","arguments":{"limit":1}}}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "[{\"id\":\"run-8f21\",\"workflowId\":\"wf-04c9\",\"workflowName\":\"nightly etl\",\"connectorId\":\"con-1a77\",\"source\":\"cron\",\"phase\":\"Failed\",\"message\":\"pod deleted\",\"rootCauseNode\":\"transform\",\"rootCauseMessage\":\"exit code 137\",\"startedAt\":\"2026-07-25T02:00:04Z\",\"finishedAt\":\"2026-07-25T02:06:41Z\",\"createdAt\":\"2026-07-25T02:00:00Z\"}]"
}
],
"isError": false
}
}

That text is the body of GET /api/v1/runs?limit=1, character for character. A test compares the two on every build, so a tool answer cannot drift from the endpoint behind it.

Refusals arrive two ways, and the difference is worth knowing when you are reading a client log.

Anything wrong with the credential, the plan, or the budget is mirrored onto the HTTP status and comes back as a JSON-RPC error with code -32001. The client sees a failed request rather than a tool that answered.

  • 401 for an unknown, expired, or revoked token, with the message invalid or revoked API token; a missing token is the same 401 with the message unauthorized. Revocation bites on the very next call, because every call re-reads the token’s row, and it kills the handshake too, not only the reads.
  • 403 for a token whose scopes do not cover the tool, with the message this token does not carry the scope this endpoint requires. Reaching for a tool tools/list did not offer is the usual way to land here. The same 403 covers a token pinned to a team you have since left, and a dagweave_run_workflow call by someone who may not run that workflow.
  • 402 for a plan without the feature behind the tool, with the message this feature is not included in your plan; upgrade to enable it. dagweave_list_audit_events is the one that does this. It is still advertised in tools/list, because the token does carry audit:read, and it is refused on call.
  • 429 when you are over the rate limit, with Retry-After and the three X-RateLimit- headers copied onto the MCP response.

Everything else comes back as an ordinary 200 carrying a tool result with isError set to true and the reason in the text block, which is the form an agent can read and retry from:

  • unknown argument "format" for an argument no tool declares.
  • argument "limit" must be a integer for the right name at the wrong type.
  • argument "runId" is required for a missing required argument. A dagweave_run_workflow call with no idempotencyKey is refused here, by the tool, before the endpoint gets a say.
  • the endpoint’s own 404 body for a run that does not exist or is not yours.
  • this Idempotency-Key was already used for a different request when a model reuses a key for a run it actually wants. Send a fresh one.
  • the run endpoint’s own refusal when a workflow or connector is not yours, or when the connector has gone quiet.

A tool name that does not exist is neither of those. It is a JSON-RPC invalid-params error, code -32602, because the client asked for something that was never part of the surface.