Skip to content

feat(mcp): recipe_runner_view MCP App + recipe_list + recipe_run execute path (KLA-406)#32

Merged
jklaassenjc merged 2 commits into
mainfrom
juergen/kla-406-recipe-runner-view
May 22, 2026
Merged

feat(mcp): recipe_runner_view MCP App + recipe_list + recipe_run execute path (KLA-406)#32
jklaassenjc merged 2 commits into
mainfrom
juergen/kla-406-recipe-runner-view

Conversation

@jklaassenjc
Copy link
Copy Markdown
Collaborator

@jklaassenjc jklaassenjc commented May 20, 2026

Closes KLA-406. Browser sibling of the KLA-398 TUI recipe runner.

Ticket recon flagged that the assumed primitives didn't exist yet (recipe_list was missing; recipe_run only previewed). This PR ships the three pieces together so the UI has the catalog + execution endpoints it needs from day one.

What's in

  1. recipe_list — new no-args tool. Returns the merged built-in + user catalog with each recipe's name/description/author/version/tags, Parameters[], step count, step names, and source ("builtin" / "user"). Reusable outside the MCP App context.
  2. recipe_run extended with Execute bool — default false preserves the pre-KLA-406 plan-preview contract (never calls JumpCloud). Execute: true dispatches each step to the CLI command tree via RecipeDispatcher and returns { execution: ExecutionResult, progress: string }. The Execute field is auto-detected by the chokepoint reflection at tools.go:5181, so step-up auth + audit logging fire on recipe_run --execute=true exactly like users_delete / devices_erase. Read-only mode blocks the execute path explicitly.
  3. recipe_runner_view — new MCP App (no-args entry in appSpecs + apps_html/recipe_runner.html). Three-screen iframe: list (with search filter) → parameter form (auto-generated from Parameters[], string/bool/int input types + required markers) → result. Plan button → execute: false, renders StepPlan[]. Run button → execute: true, triggers the step-up modal, renders the ExecutionResult with per-step status icons + captured stdout + the on_success message.

Wiring

cmd.runMcpServe now sets mcp.RecipeDispatcher = recipe.NewDispatcher(newRootCmdForRecipeStep) before constructing the server. Mirrors the TUI's screen.RecipeDispatcher hook — keeps the cmd → mcp dependency one-way (no import cycle). MCP servers spun up in tests without that wiring fail recipe_run --execute=true closed with a clear "server-config issue" message instead of a nil-deref panic.

Tests

  • fetchRecipeListData returns all built-in recipes with non-zero step counts; source labeled "builtin" in the test process (no user recipes under tmpdir-rooted JC_CONFIG)
  • recipe_list has no _meta.ui (it's not an MCP App, just a tool)
  • recipe_list returns the catalog over the MCP wire
  • recipe_runner_view carries _meta.ui.resourceUri = "ui://jc/recipe-runner"
  • recipe_runner_view ui:// resource serves HTML with common.js injected (no marker left behind)
  • recipe_run execute: true invokes the wired dispatcher (recording dispatcher captures the args; built-in recipe picked dynamically by zero-required-params)
  • recipe_run execute: false preserves plan-only contract even with RecipeDispatcher = nil
  • recipe_run execute: true with nil dispatcher returns a clear server-config error rather than panicking
  • TestMCP_ListTools_AllRegistered count bumped 199 → 201, expected list grows by recipe_list + recipe_runner_view

Out of scope (deliberate)

  • Live step-by-step streaming to the iframe. recipe.Execute writes progress to an io.Writer but the caller only observes the final ExecutionResult — no mid-flight state hooks. The runner renders the full step list once Execute returns, plus the captured progress trace. A future ticket can add streaming via multiple tools/result pushes if real-time progress becomes a priority.

Test plan

  • go test ./... clean (incl. 8 new recipe tests)
  • go vet ./... clean
  • gofmt -l clean on touched files
  • make build && ./jc mcp tools | grep recipe_ shows recipe_list, recipe_run, recipe_runner_view
  • Manual: wire the rebuilt jc binary into Claude Desktop; from a chat, ask Claude to use recipe_runner_view; pick a parameterless built-in (e.g. compliance-report), Plan it, then Run it. Confirm Touch ID prompt fires on Run and the execution renders per-step status afterwards.

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new interactive MCP App and extends recipe_run to actually execute multi-step workflows, which can perform real JumpCloud mutations and depends on correct step-up/auth/read-only gating.

Overview
Adds a new MCP App, recipe_runner_view, that serves an embedded ui://jc/recipe-runner HTML interface for browsing recipes, filling parameters, previewing plans, and triggering runs.

Introduces a new recipe_list tool that returns a structured built-in + user recipe catalog (including parameters, step names/counts, and source labeling) and extends recipe_run with an execute flag: plan-only remains the default, while execute=true dispatches steps via a new mcp.RecipeDispatcher, captures progress output, and is explicitly blocked in read-only mode.

Wires the dispatcher during jc mcp serve startup, adds a fail-closed error when execution is requested but unwired, and updates/expands tests and tool registration expectations for the new tool + app.

Reviewed by Cursor Bugbot for commit 96cdb27. Bugbot is set up for automated code reviews on this repo. Configure here.

…ute path (KLA-406)

Browser equivalent of the KLA-398 TUI recipe runner. Three pieces of
new plumbing; one PR ships them together so the UI has the catalog
and execution endpoints it needs from day one.

1. New `recipe_list` tool — no-args; returns the catalog (built-in +
   user recipes from RecipesDir) with each recipe's name, description,
   author/version/tags, Parameters[], step count, step names, and
   source ("builtin" or "user"). Pairs with recipe_run; reusable
   outside the MCP App context.

2. Extended `recipe_run` input with `Execute bool`. Default false
   preserves the pre-KLA-406 plan-preview contract — never calls the
   JumpCloud API. Execute: true dispatches each step to the CLI
   command tree via RecipeDispatcher and returns an ExecutionResult
   (per-step status + on_success/on_failure message) plus the
   captured progress string. The Execute field is auto-detected by
   the chokepoint reflection at tools.go:5181, so step-up auth and
   audit logging fire on recipe_run --execute=true exactly like
   users_delete / devices_erase. read-only mode blocks the execute
   path explicitly.

3. New `recipe_runner_view` MCP App (no-args entry in appSpecs +
   apps_html/recipe_runner.html). Initial payload is the recipe
   catalog; the iframe walks the operator through three screens —
   list (with search filter) → parameter form (auto-generated from
   Parameters[], with string/bool/int input types + required
   markers) → result. Plan button calls recipe_run with execute:
   false and renders the StepPlan[]; Run button calls execute: true,
   triggers the step-up modal, and renders the ExecutionResult with
   per-step status icons + captured stdout + the on_success message.

Wiring: cmd.runMcpServe now sets `mcp.RecipeDispatcher =
recipe.NewDispatcher(newRootCmdForRecipeStep)` before constructing
the server. Mirrors the TUI hook at screen.RecipeDispatcher; keeps
the cmd → mcp dependency one-way (no cycle). MCP servers spun up in
tests without that wiring fail recipe_run --execute=true closed with
a clear "server-config issue" message instead of nil-deref.

Tests:
- fetchRecipeListData returns all built-in recipes with non-zero
  step counts; source labeled "builtin" in the test process (no
  user recipes in tmpdir-rooted JC_CONFIG)
- recipe_list tool has no _meta.ui (it's not an MCP App)
- recipe_list returns the catalog over the wire
- recipe_runner_view tool carries _meta.ui.resourceUri ui://jc/recipe-runner
- recipe_runner_view ui:// resource serves HTML with common.js
  injected (no marker left behind)
- recipe_run execute: true invokes the wired dispatcher
  (recordingDispatcher captures the args; built-in recipe picked
  dynamically from the catalog by zero-required-params)
- recipe_run execute: false preserves plan-only contract even with
  RecipeDispatcher = nil (no dispatcher needed for preview)
- recipe_run execute: true with nil dispatcher returns a clear
  server-config error rather than panicking
- TestMCP_ListTools_AllRegistered tool count bumped 199 -> 201 and
  expected list grows by recipe_list + recipe_runner_view

Out of scope (deliberate):
- Live step-by-step streaming to the iframe. recipe.Execute writes
  progress to an io.Writer but the caller only observes the final
  ExecutionResult — no mid-flight state. The runner renders the
  full step list once Execute returns, plus the captured progress
  trace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 2af3a85. Configure here.

Comment thread internal/mcp/apps_html/recipe_runner.html Outdated
…KLA-406)

renderPlan checked `p.skipped` which never existed — recipe.StepPlan
serializes as `{ name, command, when?, would_run }`. Negation of
would_run is what tells us the step's `when` condition evaluated to
false. With the check wired to a phantom field, the "would skip"
indicator never rendered, so an operator previewing a plan with
conditional steps saw every step as "planned" even when execute time
would have skipped them.

Fix:
- Check `p.would_run === false` (strict — would_run absent or true
  both mean "will run").
- When skipping, swap the step's CSS class from "planned" to "skipped"
  so it picks up the yellow border + dimmed opacity styling that
  matches the execution view.
- Header status text reads "would skip" instead of "planned" in that
  case, and the trailing line clarifies the when condition rather
  than the imperative "would skip" (which is now redundant with the
  status badge).

HTML-only change; Go tests unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jklaassenjc jklaassenjc merged commit a2fafa7 into main May 22, 2026
7 checks passed
@jklaassenjc jklaassenjc deleted the juergen/kla-406-recipe-runner-view branch May 22, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants