Ctrl K
ComunidadGuías…

specs/022-mcp-change-tool.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Spec: MCP Change Tool

Status: Implemented.

## Scope

This spec defines the `dagu_change` MCP tool-call contract: input fields,
preview behavior, apply behavior, successful output, resource links, mutation
effects, and tool-level errors.

This spec does not define the MCP endpoint, authentication, API-key surfaces,
`dagu://` URI grammar, DAG YAML language semantics, `dagu_read`,
`dagu_execute`, prompts, subscriptions, Web UI behavior, storage internals,
workspace selection, or MCP SDK internals.

Related specs:

- [020: MCP Server](020-mcp-server.md) defines the MCP endpoint,
  authentication, and `dagu://` resource URI model used by this tool.
- [021: MCP Read Tool](021-mcp-read-tool.md) defines how clients read the DAG
  spec resource linked from this tool.

## Goal

`dagu_change` gives MCP clients one safe way to validate a DAG YAML change
before writing it. Preview is the default behavior. Apply is explicit and
writes only after the same validation succeeds.

## Behavior

### Tool Identity

Rules:

- The tool name is `dagu_change`.
- The tool supports DAG YAML upsert only.
- A preview call must not create, update, delete, start, enqueue, retry, or
  stop any DAG or DAG run.
- An apply call may create or update one stored DAG only when validation
  succeeds.
- Audit, metrics, logging, and transport side effects are allowed.

### Input

Tool input is a JSON object. Fields outside this table fail with
`invalid_tool_input`.

| Field | Type | Required rule | Meaning |
| --- | --- | --- | --- |
| `mode` | string | Optional. Defaults to `preview`. | Change execution mode. Supported values are `preview` and `apply`. |
| `type` | string | Optional. Defaults to `upsert_dag`. | Change type. The only supported value is `upsert_dag`. |
| `name` | string | Required. | Target DAG name. |
| `spec` | string | Required. | DAG YAML document to validate and optionally store. |

Rules:

- Supported fields, when present and not `null`, must be strings.
- `null` is treated as absent.
- `mode`, `type`, and `name` are trimmed of leading and trailing whitespace
  before validation.
- `spec` is not trimmed or normalized. It fails only when it is empty or all
  whitespace.
- Supported values are case-sensitive.
- A missing or empty `name` fails with `invalid_tool_input`.
- A missing or all-whitespace `spec` fails with `invalid_tool_input`.
- An unsupported `mode` fails with `unsupported_change_mode`.
- An unsupported `type` fails with `unsupported_change_type`.

### Change Type

`upsert_dag` validates a DAG YAML document for the supplied DAG name.

Rules:

- Validation uses Dagu's DAG validation rules.
- A validation failure is not a tool-level error.
- When validation fails, the tool returns a successful MCP tool result with
  `valid=false`, `applied=false`, and validation errors in the structured
  output.
- This spec defines the MCP tool contract. The DAG YAML fields and validation
  rules are owned by the DAG YAML specs.

### Preview Mode

Rules:

- `mode=preview` validates the supplied DAG YAML and returns the validation
  result.
- `mode=preview` must not write the DAG even when validation succeeds.
- `mode=preview` must not write the DAG when validation fails.
- `applied` is `false` in preview output.
- `created` and `updated` are omitted in preview output.

### Apply Mode

Rules:

- `mode=apply` validates the supplied DAG YAML before writing.
- If validation fails, the call must not write the DAG and returns
  `valid=false` with `applied=false`.
- If validation succeeds and no stored DAG exists for `name`, the call creates
  the DAG and returns `created=true`, `updated=false`, and `applied=true`.
- If validation succeeds and a stored DAG exists for `name`, the call updates
  that DAG and returns `created=false`, `updated=true`, and `applied=true`.
- A successful apply affects only the DAG identified by `name`.
- A successful apply must not start, enqueue, retry, or stop a DAG run.

### Output

A successful MCP tool result has one text content item and structured output.

Text content is mode and validation dependent:

| Condition | Text |
| --- | --- |
| Validation failed. | `DAG spec is not valid; no changes were applied.` |
| Preview validation succeeded. | `DAG spec is valid. Re-run with mode=apply to write it.` |
| Apply validation succeeded and the DAG was written. | `DAG change applied.` |

Structured output has this top-level envelope:

```json
{
  "mode": "preview",
  "type": "upsert_dag",
  "dagName": "nightly-report",
  "valid": true,
  "errors": [],
  "applied": false,
  "dag": {},
  "dagUri": "dagu://dags/nightly-report/spec",
  "references": [
    "dagu://reference/authoring",
    "dagu://reference/tools",
    "dagu://reference/notifications"
  ]
}
```

Required fields:

| Field | Required shape |
| --- | --- |
| `mode` | Effective mode: `preview` or `apply`. |
| `type` | Effective type: `upsert_dag`. |
| `dagName` | Effective DAG name. |
| `valid` | Boolean validation result. |
| `errors` | Array of validation errors. Empty when `valid=true`. |
| `applied` | Boolean. `true` only when the DAG was written. |
| `dagUri` | Canonical `dagu://dags/{name}/spec` URI for the target DAG. |
| `references` | Exactly `dagu://reference/authoring`, `dagu://reference/tools`, and `dagu://reference/notifications`; order is not normative. |

Conditional fields:

| Field | Required when |
| --- | --- |
| `dag` | Validation succeeds and Dagu can return a normalized DAG model. |
| `created` | `applied=true`. |
| `updated` | `applied=true`. |

Implementations may add fields inside the structured output. Conformance tests
must not require absence of additional fields.

The successful result includes a resource-link content item for `dagUri` when
`dagUri` is present.

Resource-link content item rules:

- MCP content type is `resource_link`.
- `uri` is the canonical `dagu://dags/{name}/spec` URI.
- `name` is `dag_spec`.
- `mimeType` is `application/yaml`.

URI path segments in returned URIs follow Spec 020 escaping.

## Errors

Failed tool calls return an MCP tool result with `isError=true`. When the
transport supports structured output, the structured output is the error object.
Text content may include the error message as a fallback.

The error object has this shape:

```json
{
  "code": "invalid_tool_input",
  "message": "The spec field is required.",
  "mode": "preview",
  "type": "upsert_dag",
  "dagName": "nightly-report",
  "field": "spec",
  "dagUri": "dagu://dags/nightly-report/spec",
  "details": {}
}
```

`code` and `message` are required. Clients must branch on `code`, not parse
`message`.

Optional field rules:

| Field | Required when |
| --- | --- |
| `mode` | A valid mode has been resolved, or an unsupported mode value was supplied. |
| `type` | A valid type has been resolved, or an unsupported type value was supplied. |
| `dagName` | A non-empty DAG name has been resolved. |
| `field` | Exactly one input field caused the failure. For an unknown field, this is the unknown field name. |
| `dagUri` | A non-empty DAG name has been resolved. |
| `details` | More than one field caused the failure, or the server needs to expose structured error details. |

Common conditions map to codes as follows:

| Condition | Code |
| --- | --- |
| Authentication is required and the request is not authenticated. | `unauthenticated` |
| The accepted credential is not authorized for the requested change. | `unauthorized` |
| Tool input is not a JSON object. | `invalid_tool_input` |
| Tool input contains an unknown field. | `invalid_tool_input` |
| A supported field has a non-string, non-null value. | `invalid_tool_input` |
| `name` is absent or empty after trimming. | `invalid_tool_input` |
| `spec` is absent, empty, or all whitespace. | `invalid_tool_input` |
| `mode` is present but is not `preview` or `apply`. | `unsupported_change_mode` |
| `type` is present but is not `upsert_dag`. | `unsupported_change_type` |
| DAG validation fails. | No tool error. Return `valid=false`. |
| The target DAG cannot currently be read or written during apply. | `resource_unavailable` |
| The server fails unexpectedly while handling the change. | `internal_error` |

## Examples

Preview a valid DAG:

```json
{
  "mode": "preview",
  "type": "upsert_dag",
  "name": "nightly-report",
  "spec": "steps:\n  - name: hello\n    run: echo hello\n"
}
```

Expected structured output subset:

```json
{
  "mode": "preview",
  "type": "upsert_dag",
  "dagName": "nightly-report",
  "valid": true,
  "applied": false,
  "dagUri": "dagu://dags/nightly-report/spec"
}
```

Apply a valid DAG:

```json
{
  "mode": "apply",
  "type": "upsert_dag",
  "name": "nightly-report",
  "spec": "steps:\n  - name: hello\n    run: echo hello\n"
}
```

Expected structured output subset:

```json
{
  "mode": "apply",
  "type": "upsert_dag",
  "dagName": "nightly-report",
  "valid": true,
  "applied": true,
  "created": true,
  "updated": false,
  "dagUri": "dagu://dags/nightly-report/spec"
}
```

Preview an invalid DAG:

```json
{
  "name": "broken",
  "spec": "steps:\n  - name: [\n"
}
```

Expected structured output subset:

```json
{
  "mode": "preview",
  "type": "upsert_dag",
  "dagName": "broken",
  "valid": false,
  "applied": false
}
```

Unsupported mode:

```json
{
  "mode": "write",
  "name": "nightly-report",
  "spec": "steps:\n  - name: hello\n    run: echo hello\n"
}
```

Expected error code:

```json
{
  "code": "unsupported_change_mode"
}
```