Commit Graph

5 Commits

Author SHA1 Message Date
xarmian eb896e4469 feat(mcp): server-level instructions advertised in initialize handshake (TASK-971) (#355)
Adds a top-level `instructions` string to the MCP initialize response
so agents know WHEN to reach for pad without having to guess from
tool descriptions alone. The Svelte MCP server in the dogfooding
session that triggered PLAN-969 does this; pad now does too.

Implementation:
- internal/mcp/instructions.md (new) — embedded source content.
  MCP-aware adaptation of skills/pad/SKILL.md's opener: what pad is,
  when to reach for it, the v0.2 tool catalog summary, resource
  cheatsheet, workspace resolution order, ref convention, update flow,
  conventions hint, and the four prompts.
- internal/mcp/instructions.go (new) — //go:embed wrapper exposing
  the content as the Instructions package var.
- internal/mcp/server.go — pass server.WithInstructions(Instructions)
  into NewMCPServer.

Single source of truth: the same string ships in both the local stdio
handshake AND PLAN-943's HTTPHandlerDispatcher (when remote /mcp
mounts in TASK-950 it will reuse the same constant — no docs drift
between local and remote surfaces).

Test: TestServer_InitializeAdvertisesInstructions drives a real
initialize round-trip and asserts the response's Instructions field
equals the embedded source. Sanity-checks the embed didn't truncate.

Parent: TASK-971 → PLAN-969.
2026-05-01 18:11:38 -04:00
xarmian df8a3631e7 feat(mcp): v0.2 catalog scaffold + ToolSurfaceVersion + pad_meta tool (TASK-979) (#352)
* feat(mcp): v0.2 catalog scaffold + ToolSurfaceVersion + pad_meta tool (TASK-979)

First commit of TASK-970's 3-stage rollout (PLAN-969). Introduces the
hand-curated v0.2 catalog types (ToolDef, ActionFn, ActionEnv) and ships
one tool — pad_meta — end-to-end. v0.1 cmdhelp-walk surface stays live
alongside; subsequent commits (TASK-980, TASK-981) migrate the rest and
flip v0.1 off.

Architecture record: DOC-978. The fan-out registry sits ABOVE the
dispatcher boundary — Dispatcher / route table are unchanged, so both
ExecDispatcher (stdio) and HTTPHandlerDispatcher (HTTP) inherit the new
shape for free.

Changes:
- internal/mcp/catalog.go (new) — ToolDef, ActionFn, ActionEnv,
  passThrough helper, RegisterCatalog, makeFanOutHandler, structured
  error helpers.
- internal/mcp/catalog_meta.go (new) — pad_meta tool with three inline
  actions: server-info, version, tool-surface (full catalog dump for
  PLAN-943 docs generation).
- internal/mcp/version.go — add ToolSurfaceVersion = "0.2" + matching
  experimentalToolSurfaceKey. Independent of CmdhelpVersion (cmdhelp
  owns CLI help-tree contract; ToolSurfaceVersion owns MCP catalog).
- internal/mcp/meta.go — extend MetaPayload with ToolSurfaceVersion;
  experimentalCapabilities advertises both padCmdhelp + padToolSurface.
- cmd/pad/mcp.go — call RegisterCatalog alongside Register so v0.2
  surface is live.
- Tests: catalog_test.go + catalog_meta_test.go (new); meta_test.go
  + server_test.go updated to assert the new field/capability.

Parent: TASK-970 → PLAN-969.

* fix(mcp): keep ToolSurfaceVersion at "0.1" until catalog is complete per Codex review (round 1)

Codex P1: advertising tool_surface_version=0.2 while the user-visible
surface is still predominantly v0.1 (cmdhelp walker active alongside,
only pad_meta in the catalog) misleads consumers that pin against the
handshake or pad://_meta/version. The padToolSurface namespace would
suggest the full resource/action shape is available when in reality
only pad_meta uses it.

Delay the 0.1 → 0.2 bump to TASK-981 — the commit that retires the
cmdhelp walker and ships the complete catalog. The constant stays
declared so the surface contract is wired through the handshake +
meta resource + pad_meta.tool-surface, the version string just
truthfully reflects "still v0.1" until the catalog is complete.

No test changes needed: every assertion uses the constant, not a
literal "0.2".

Parent: TASK-979 → TASK-970 → PLAN-969.

* fix(mcp): scope pad_meta.tool-surface to v0.2 catalog only per Codex review (round 2)

Codex P1: pad_meta.tool-surface description claimed "Full catalog dump:
every tool" but during PLAN-969's parallel rollout, tools/list contains
both the catalog (currently just pad_meta) AND the cmdhelp walker's
~85 verb tools. Calling the catalog dump "every tool" misleads consumers
who expect a complete enumeration.

Same spirit as round 1's fix: stop claiming what isn't true. The catalog
dump is the v0.2 catalog by design — consumers wanting the complete
advertised surface should read tools/list directly. Hand-mapping the
walker output into the catalog dump would cost duplication for a
surface that's about to disappear in TASK-981.

Wire-level changes:
- Tighten the action description in padMetaToolDescription to say
  "v0.2 catalog dump: every tool managed by the hand-curated catalog"
  and explicitly note tools/list is the source for the complete surface.
- Add rollout_status field to the response payload: "in-progress" while
  ToolSurfaceVersion stays at "0.1", "complete" once TASK-981 bumps it.
  Lets consumers detect the rollout state programmatically.
- Test asserts the new field tracks ToolSurfaceVersion.

Parent: TASK-979 → TASK-970 → PLAN-969.

* fix(mcp): include params in pad_meta.tool-surface dump per Codex review (round 3)

Codex P1: tool description claimed the dump includes each tool's
"input schema" but the payload only emitted name/description/workspace/
actions[]. Misleading for docs generators (TASK-957) that would build
getpad.dev/docs/mcp from this canonical source.

Going with the substantive fix rather than just trimming the
description: include a synthesized params[] per tool entry. Mirrors
what consumers see in tools/list — `action` (always required, enum of
declared action names), `workspace` (when ToolDef.Schema.Workspace=true),
and per-tool ParamDefs.

Synthesizing `action` and `workspace` rather than copying them from
ToolDef makes the dump self-contained: a docs generator doesn't need
to reproduce buildToolFromDef's implicit-param logic separately.

Test asserts each catalog entry has params[] starting with `action`
(enum length matches action handler count) and the right total length
based on Schema.Workspace + Schema.Params.

Parent: TASK-979 → TASK-970 → PLAN-969.
2026-05-01 17:30:27 -04:00
xarmian e05ea07d62 fix(docs): use canonical wire path capabilities.experimental.padCmdhelp (#342)
Codex caught the same accuracy issue on pad-web that exists in five
spots in this repo: prose described the handshake location as
"serverCapabilities.experimental.padCmdhelp", but per the MCP spec
the InitializeResult shape is

  { result: { capabilities: { experimental: { ... } } } }

There's no `serverCapabilities` field on the wire — `ServerCapabilities`
is the Go-side struct type name in mcp-go; the JSON tag is
`capabilities`. Anyone copying the path out of our docs to navigate
a real JSON-RPC envelope was getting the wrong key.

Updated to `capabilities.experimental.padCmdhelp` (or the fully
qualified `result.capabilities.experimental.padCmdhelp` where the
JSON-RPC envelope context wasn't otherwise obvious) in:

- README.md — public-facing prose
- CLAUDE.md — agent-facing prose
- internal/mcp/version.go — discovery-surfaces doc comment + the
  experimentalCapabilityKey doc comment
- internal/mcp/server.go — comment near WithExperimental
- internal/mcp/meta.go — experimentalCapabilities() doc + the wire
  shape example (now wrapped under `result` for accuracy)
- internal/mcp/server_test.go — test docstring + failure message
- cmd/pad/mcp.go — comment near RegisterMeta

The Go type `serverCapabilities` in `internal/server/handlers_capabilities.go`
is unrelated (it's the response shape for `GET /api/v1/server/capabilities`)
and stays as-is.

No code/behaviour changes; pure prose accuracy fix. `make check` clean.

Companion fix to pad-web PR #42, which Codex flagged the same issue on.
2026-05-01 11:15:21 -04:00
xarmian 2d98f2a170 feat(mcp): advertise cmdhelp_version stability tier in handshake (TASK-963) (#340)
* feat(mcp): advertise cmdhelp_version stability tier in handshake (TASK-963)

External agents (Cursor, Claude Desktop, the future Pad Cloud remote MCP
in PLAN-943) depend on tool names, argument shapes, and resource URIs
being stable across pad releases. Without an explicit contract, any
future surface change breaks consumers silently.

This commit ships the contract on two complementary surfaces:

- serverCapabilities.experimental.padCmdhelp in the initialize handshake
  — namespaced map carrying {version, tool_surface_stable}, discoverable
  in one round-trip.
- pad://_meta/version static resource — full JSON document with
  {pad_version, cmdhelp_version, tool_surface_stable, mcp_protocol_version}
  for clients that prefer reading a typed payload.

CmdhelpVersion is pinned at "0.1" — the initial cmdhelp-derived surface
shipped in PLAN-942. Bump the major when tool names / arg shapes /
resource URIs change incompatibly.

Tests:
- TestServer_InitializeHandshake extended to assert the experimental
  capability shape on the wire (not just the existence of the field).
- TestBuildMetaPayload_* lock the payload field names + fallback
  behaviour.
- TestRegisterMeta_ResourceRoundTrip drives the resource through the
  real HandleMessage path so a regression in the dispatcher would
  surface as a test failure.

Docs:
- README's MCP section briefly mentions the contract surfaces.
- CLAUDE.md's MCP section gets a stability-contract paragraph + the new
  resource URI.
- Public docs at getpad.dev/mcp/local will need a follow-up PR in the
  pad-web repo (per CONVE-159) — captured at the end of TASK-963.

Parent: PLAN-942.

* fix(mcp): source MCP protocol version from mcp-go LATEST_PROTOCOL_VERSION per Codex review (round 1)

Codex caught: the local MCPProtocolVersion constant was pinned at
"2024-11-05", but mcp-go@v0.50.0 negotiates "2025-11-25" for clients
that request mcp.LATEST_PROTOCOL_VERSION. The meta resource was
therefore reporting a protocol revision newer than what the server
actually speaks, which defeats the field's purpose for feature
detection (e.g. RFC 8707 Resource Indicators land in 2025-11-25).

Drop the local constant and read mcp.LATEST_PROTOCOL_VERSION at
BuildMetaPayload time so the value tracks whatever revision the
linked library will negotiate. The handshake's serverInfo.version
already does this implicitly via NewMCPServer; making the meta
resource follow the same source-of-truth keeps both surfaces in
lockstep across mcp-go upgrades.

Test updated to assert against mcp.LATEST_PROTOCOL_VERSION instead of
the removed constant, plus an "empty-string" guard in case a future
library refactor unsets the constant.

Parent: PLAN-942.
2026-05-01 10:54:01 -04:00
xarmian 9905a83134 feat(mcp): pad mcp serve skeleton on stdio (TASK-944) (#333)
Stand up internal/mcp + the cobra `pad mcp serve` subcommand. v1 is
handshake-only — the server completes initialize and stays alive over
stdio, advertising tool capability with an empty registry. TASK-945
fills that registry from `pad help --format json`.

- New internal/mcp package wraps mark3labs/mcp-go's stdio transport;
  graceful shutdown on EOF / SIGINT / SIGTERM / ctx-cancel.
- New cmd/pad/mcp.go registers `pad mcp` as a top-level cobra group
  with the `serve` subcommand wired to internal/mcp.NewServer.
- 4 unit tests: NewServer construction, real initialize round-trip
  (asserts serverInfo.name + version), fallback version locked,
  graceful shutdown on ctx-cancel.

Live smoke: `echo '<initialize>' | pad mcp serve` returns
`serverInfo:{name:"pad-mcp",version:...}` with `tools:{listChanged:true}`.
cmdhelp emits the new command tree at `pad help mcp serve --format json`.

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