feat(mcp): add a per-call dry run to every mutating tool #135

Merged
Claude-Run merged 1 commit from feat/YT-81-mcp-dry-run into main 2026-08-01 03:45:33 +02:00
Member

Every mutating MCP tool now takes dry_run: true and reports what it would do without writing anything, the per-call equivalent of the CLI's process-wide --dry-run. The gate lives in the single choke point every tool already funnels through (YouTrackMcpServer::dispatch, formerly run_tool): a tool registers as read-only (run_tool), mutating (run_mutation), or mutating-with-its-own-preview (run_previewable_mutation), and the pure gate function turns the caller's flag plus that policy into run / preview / reject. The preview is a JSON envelope shaped like the CLI's: dry_run, applied, the tool name, and the same redacted LogArgs argument summary the tool-call log records.

dry_run on a read-only tool is rejected with an error naming the tool rather than silently ignored, so the mistake surfaces instead of returning a fake preview of a read. That requires every argument type to capture the field, so LogArgs::is_dry_run has no default body: a new arg type will not compile until it answers. delete_article and delete_issue_attachment get their own argument types so they can advertise dry_run in their schemas while the read-only get_article / get_issue_attachment do not.

The client is resolved before the dry-run branch on purpose: an auth or account-setup failure has to surface as a failure, not be masked by a preview of a call that would never have worked.

apply_command previews differently. A skipped call is a weak preview for the command language, where the dangerous case is a syntactically valid command that means something other than intended. Under dry_run it now posts to /api/commands/assist, YouTrack's own parser, and returns the parsed interpretation plus any parse errors, having applied nothing. yt issue apply --dry-run prints the same parse instead of only skipping the POST. The endpoint is absent from the public REST reference, so its request and response shape is recorded next to the new api::assist_command, following the convention used for the vcsIntegrationSettings endpoints.

Guards: the router-driven tests assert that all 17 mutating tools advertise dry_run and that none of the 22 read-only tools do, so adding a mutating tool without a preview fails the suite.

#YT-81

Every mutating MCP tool now takes `dry_run: true` and reports what it would do without writing anything, the per-call equivalent of the CLI's process-wide `--dry-run`. The gate lives in the single choke point every tool already funnels through (`YouTrackMcpServer::dispatch`, formerly `run_tool`): a tool registers as read-only (`run_tool`), mutating (`run_mutation`), or mutating-with-its-own-preview (`run_previewable_mutation`), and the pure `gate` function turns the caller's flag plus that policy into run / preview / reject. The preview is a JSON envelope shaped like the CLI's: `dry_run`, `applied`, the tool name, and the same redacted `LogArgs` argument summary the tool-call log records. `dry_run` on a read-only tool is rejected with an error naming the tool rather than silently ignored, so the mistake surfaces instead of returning a fake preview of a read. That requires every argument type to capture the field, so `LogArgs::is_dry_run` has no default body: a new arg type will not compile until it answers. `delete_article` and `delete_issue_attachment` get their own argument types so they can advertise `dry_run` in their schemas while the read-only `get_article` / `get_issue_attachment` do not. The client is resolved before the dry-run branch on purpose: an auth or account-setup failure has to surface as a failure, not be masked by a preview of a call that would never have worked. `apply_command` previews differently. A skipped call is a weak preview for the command language, where the dangerous case is a syntactically valid command that means something other than intended. Under `dry_run` it now posts to `/api/commands/assist`, YouTrack's own parser, and returns the parsed interpretation plus any parse errors, having applied nothing. `yt issue apply --dry-run` prints the same parse instead of only skipping the POST. The endpoint is absent from the public REST reference, so its request and response shape is recorded next to the new `api::assist_command`, following the convention used for the `vcsIntegrationSettings` endpoints. Guards: the router-driven tests assert that all 17 mutating tools advertise `dry_run` and that none of the 22 read-only tools do, so adding a mutating tool without a preview fails the suite. #YT-81
feat(mcp): add a per-call dry run to every mutating tool
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m42s
Create release / Create release from merged PR (pull_request) Has been skipped
764a9560ad
Every mutating MCP tool now takes `dry_run: true` and reports what it would do without writing anything, the per-call equivalent of the CLI's process-wide `--dry-run`. The gate lives in the single choke point every tool already funnels through (`YouTrackMcpServer::dispatch`, formerly `run_tool`): a tool registers as read-only (`run_tool`), mutating (`run_mutation`), or mutating-with-its-own-preview (`run_previewable_mutation`), and the pure `gate` function turns the caller's flag plus that policy into run / preview / reject. The preview is a JSON envelope shaped like the CLI's: `dry_run`, `applied`, the tool name, and the same redacted `LogArgs` argument summary the tool-call log records.

`dry_run` on a read-only tool is rejected with an error naming the tool rather than silently ignored, so the mistake surfaces instead of returning a fake preview of a read. That requires every argument type to capture the field, so `LogArgs::is_dry_run` has no default body: a new arg type will not compile until it answers. `delete_article` and `delete_issue_attachment` get their own argument types so they can advertise `dry_run` in their schemas while the read-only `get_article` / `get_issue_attachment` do not.

The client is resolved before the dry-run branch on purpose: an auth or account-setup failure has to surface as a failure, not be masked by a preview of a call that would never have worked.

`apply_command` previews differently. A skipped call is a weak preview for the command language, where the dangerous case is a syntactically valid command that means something other than intended. Under `dry_run` it now posts to `/api/commands/assist`, YouTrack's own parser, and returns the parsed interpretation plus any parse errors, having applied nothing. `yt issue apply --dry-run` prints the same parse instead of only skipping the POST. The endpoint is absent from the public REST reference, so its request and response shape is recorded next to the new `api::assist_command`, following the convention used for the `vcsIntegrationSettings` endpoints.

Guards: the router-driven tests assert that all 17 mutating tools advertise `dry_run` and that none of the 22 read-only tools do, so adding a mutating tool without a preview fails the suite.

#YT-81
Claude-Run deleted branch feat/YT-81-mcp-dry-run 2026-08-01 03:45:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-cli!135
No description provided.