Files
pad/cmd
xarmian 2e4a815d0c feat(mcp): cmdhelp-derived tool registry + shell-out dispatch (TASK-945) (#335)
* feat(mcp): cmdhelp-derived tool registry + shell-out dispatch (TASK-945)

The strategic centerpiece of PLAN-942: walk the cmdhelp Document built
from `pad`'s cobra tree and register every leaf as an MCP tool, with
shell-out dispatch back to the running binary. New pad commands (or
new flags) extend the MCP surface for free — no hand-mapping ~73
commands.

- internal/mcp/registry.go — Register() walks cmdhelp.Document, picks
  leaves, applies a curated DefaultExcludes (db ops, auth, init,
  agent install/update, server lifecycle, completion, edit, watch,
  workspace lifecycle), builds an MCP Tool per leaf with input schema
  derived from cmdhelp Arg/Flag types. Snake-case names: "item create"
  → "item_create".
- internal/mcp/dispatch.go — ExecDispatcher shells out to the pad
  binary; BuildCLIArgs is a pure function that translates the JSON
  args into a CLI invocation (positionals → flags → workspace
  injection → --format json default). JSON stdout is surfaced as
  StructuredContent for rich client rendering.
- internal/mcp/workspace.go — WorkspaceState (RWMutex-protected) +
  pad_set_workspace built-in tool. Empty string clears the session
  default; missing arg returns IsError without mutating state.
- cmd/pad/mcp.go — wire registry into `pad mcp serve` startup; build
  the cmdhelp Document from cmd.Root(), resolve the running binary
  via os.Executable, seed workspace from --workspace flag.
- 23 new unit tests across registry / dispatch / workspace files
  (race-detector clean) covering: leaf identification, exclusion
  prefix suppression, snake-case naming, pure CLI arg translation
  (positionals + bool presence form + repeatable args & flags +
  workspace/format injection), exec dispatcher (binary missing,
  stdout capture, non-zero exit), workspace state mutation, and
  end-to-end pad_set_workspace handler contract.

Live smoke (real binary, real stdio):
  - tools/list returns 66 tools — pad_set_workspace + item_create
    present, db_backup + mcp_serve correctly excluded.
  - tools/call pad_set_workspace updates session state, then
    auth_whoami shells out and returns structured JSON.

Parent: PLAN-942.

* fix(mcp): forward --url root flag + drop unwired --stdin (Codex round 1)

Two findings from Codex review of #335:

P1: --url root persistent flag was not forwarded to dispatched
subprocesses. If an MCP client launches `pad --url X mcp serve`,
every tool call ran against the default URL instead of X. Fixed by
adding RootFlags map[string]string to RegistryOptions; cmd/pad/mcp.go
captures urlFlag at startup and threads it through. BuildCLIArgs
now also takes a rootFlags map and injects each entry when not in
input (empty values skipped, agent value wins on collision).

P2: MCP tool schemas exposed `--stdin` flags but ExecDispatcher
never piped the agent's stdin to the subprocess. Calling e.g.
`item_create {stdin: true}` would block on EOF and create empty
content. The `--content` flag covers the same semantic via JSON
args, which IS wired. Hide stdin from the MCP surface (buildTool
filters out flagsHiddenFromMCP) AND drop it defensively in
BuildCLIArgs in case an agent's stale schema cache passes it.

Tests added (4 new + 2 updated):
- BuildCLIArgs: stdin dropped defensively, root flags injected,
  empty root flag skipped, agent value wins over root flag.
- buildTool: omits stdin from input schema.
- Dispatch handler: forwards root flags through to CLI args.

Existing TestBuildCLIArgs_BoolPresenceForm rewritten to use
`dry-run` flag (since stdin is now filtered).

Live verified: `pad mcp serve` tools/list shows item_create with
content+10 other flags, no stdin. Round-trip preserved.

Parent: PLAN-942.
2026-05-01 08:36:02 -04:00
..