Files
pad/internal
xarmian e90ee18907 feat(mcp): pad mcp install / uninstall / status (TASK-948) (#338)
* feat(mcp): pad mcp install / uninstall / status (TASK-948)

One-shot config writers for the three MCP-capable client apps:

  pad mcp install claude-desktop   # ~/.config/Claude/claude_desktop_config.json (linux)
  pad mcp install cursor           # ~/.cursor/mcp.json
  pad mcp install windsurf         # ~/.codeium/windsurf/mcp_config.json
  pad mcp install --all            # all three
  pad mcp uninstall cursor         # remove
  pad mcp status                   # report install state

Implementation:
- internal/mcp/install.go (new) — Agent registry with per-OS path
  resolvers (PathFor takes (home, goos) so tests inject); AddPadEntry
  / RemovePadEntry / HasPadEntry primitives that read-modify-write
  JSON, preserving every entry except mcpServers.pad. Installer
  façade with Home/GOOS overrides for tests.
- cmd/pad/mcp.go — three new cobra subcommands wired into mcpCmd:
  install (no-args = status, --all = batch), uninstall, status.
  Binary path resolved via os.Executable().

DOD coverage:
- Existing entries preserved (TestAddPadEntry_PreservesOtherServers
  asserts both other mcpServers and unrelated top-level keys survive).
- Idempotent install (binary unchanged → modified=false).
- Update install (binary changed → modified=true).
- Idempotent uninstall (missing file / missing entry → no-op).
- Per-platform path resolution tested for linux + darwin.
- 16 unit tests including edge cases: empty/whitespace files,
  malformed JSON rejected (no silent overwrite), case-insensitive
  agent aliases.

Live verified end-to-end:
- HOME=/tmp/fakehome pad mcp install cursor → writes valid JSON
- pad mcp status → shows [x] Cursor with command path
- pad mcp uninstall cursor → leaves mcpServers:{} skeleton

Config file perms: 0600 (configs may hold credentials for OTHER
MCP servers; tighten on principle).

Parent: PLAN-942.

* fix(mcp): tighten install argument validation + chmod existing configs (Codex round 1)

Two findings on PR #338:

1. `pad mcp install` had no Args validator so cobra silently accepted
   extras: `pad mcp install cursor windsurf` only installed Cursor.
   Added cobra.MaximumNArgs(1) plus an explicit guard rejecting
   `--all` combined with an agent name (those flows are
   mutually exclusive).

2. os.WriteFile(path, data, 0o600) only honors the mode when CREATING
   the file. A pre-existing 0644 config kept 0644 after the install,
   defeating the security-tightening claim in the comment. Added an
   explicit os.Chmod(path, 0o600) after writing; chmod failures are
   stderr warnings, not hard errors (the data write already
   succeeded; perms hardening is best-effort defense-in-depth).

New test TestAddPadEntry_TightensExistingFilePerms locks the
0600-after-install contract; live verified the cobra guards reject
both error cases with clean messages.

Parent: PLAN-942.

* fix(mcp): tighten perms on idempotent install path too (Codex round 2)

Codex caught: AddPadEntry's no-op early-return (when desired config
matches existing) skipped the chmod step from round 1's fix. So an
already-up-to-date 0644 config retained 0644 after re-running
`pad mcp install`.

Extracted tightenPerms() as a helper called from BOTH paths:
- writeJSONConfig (modified path) — chmod after write
- AddPadEntry's no-op return — chmod even when content is unchanged

Best-effort: chmod failures still emit a warning rather than failing
the install (the user's intent already succeeded; perms tightening is
defense-in-depth, not core functionality).

New test TestAddPadEntry_TightensPermsOnIdempotentNoop locks the
no-op-path contract.

Parent: PLAN-942.
2026-05-01 09:15:23 -04:00
..
2026-03-26 01:52:36 +00:00