feat(mcp): recipe_runner_view MCP App + recipe_list + recipe_run execute path (KLA-406)#32
Merged
Merged
Conversation
…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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 2af3a85. Configure here.
…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>
jrennichjc
approved these changes
May 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes KLA-406. Browser sibling of the KLA-398 TUI recipe runner.
Ticket recon flagged that the assumed primitives didn't exist yet (
recipe_listwas missing;recipe_runonly previewed). This PR ships the three pieces together so the UI has the catalog + execution endpoints it needs from day one.What's in
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.recipe_runextended withExecute bool— default false preserves the pre-KLA-406 plan-preview contract (never calls JumpCloud).Execute: truedispatches each step to the CLI command tree viaRecipeDispatcherand returns{ execution: ExecutionResult, progress: string }. TheExecutefield is auto-detected by the chokepoint reflection attools.go:5181, so step-up auth + audit logging fire onrecipe_run --execute=trueexactly likeusers_delete/devices_erase. Read-only mode blocks the execute path explicitly.recipe_runner_view— new MCP App (no-args entry inappSpecs+apps_html/recipe_runner.html). Three-screen iframe: list (with search filter) → parameter form (auto-generated fromParameters[], string/bool/int input types + required markers) → result. Plan button →execute: false, rendersStepPlan[]. Run button →execute: true, triggers the step-up modal, renders theExecutionResultwith per-step status icons + captured stdout + theon_successmessage.Wiring
cmd.runMcpServenow setsmcp.RecipeDispatcher = recipe.NewDispatcher(newRootCmdForRecipeStep)before constructing the server. Mirrors the TUI'sscreen.RecipeDispatcherhook — keeps the cmd → mcp dependency one-way (no import cycle). MCP servers spun up in tests without that wiring failrecipe_run --execute=trueclosed with a clear "server-config issue" message instead of a nil-deref panic.Tests
fetchRecipeListDatareturns 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_listhas no_meta.ui(it's not an MCP App, just a tool)recipe_listreturns the catalog over the MCP wirerecipe_runner_viewcarries_meta.ui.resourceUri = "ui://jc/recipe-runner"recipe_runner_viewui:// resource serves HTML withcommon.jsinjected (no marker left behind)recipe_run execute: trueinvokes the wired dispatcher (recording dispatcher captures the args; built-in recipe picked dynamically by zero-required-params)recipe_run execute: falsepreserves plan-only contract even withRecipeDispatcher = nilrecipe_run execute: truewith nil dispatcher returns a clear server-config error rather than panickingTestMCP_ListTools_AllRegisteredcount bumped 199 → 201, expected list grows byrecipe_list+recipe_runner_viewOut of scope (deliberate)
recipe.Executewrites progress to anio.Writerbut the caller only observes the finalExecutionResult— 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 multipletools/resultpushes if real-time progress becomes a priority.Test plan
go test ./...clean (incl. 8 new recipe tests)go vet ./...cleangofmt -lclean on touched filesmake build && ./jc mcp tools | grep recipe_showsrecipe_list,recipe_run,recipe_runner_viewjcbinary into Claude Desktop; from a chat, ask Claude to userecipe_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_runto 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 embeddedui://jc/recipe-runnerHTML interface for browsing recipes, filling parameters, previewing plans, and triggering runs.Introduces a new
recipe_listtool that returns a structured built-in + user recipe catalog (including parameters, step names/counts, and source labeling) and extendsrecipe_runwith anexecuteflag: plan-only remains the default, whileexecute=truedispatches steps via a newmcp.RecipeDispatcher, captures progress output, and is explicitly blocked in read-only mode.Wires the dispatcher during
jc mcp servestartup, 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.