Files
pad/internal/mcp/version.go
T
xarmian 475a70b57a fix(mcp): harden attachment image resource label + download bound (#933)
Follow-up to #930: label the blob from downloaded bytes (TOCTOU fix), bound FetchBytes buffering at the 1 MiB limit, and fix stale 'deferred to TASK-2076' docs. Adversarial-review + Codex findings; Codex CLEAN.

Claude-Session: https://claude.ai/code/session_01EZ6yr6pAUFb1uffan912ra
2026-07-14 15:56:17 -04:00

273 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package mcp implements pad's Model Context Protocol server.
//
// Layered build (PLAN-942):
// - TASK-944 (this file + server.go) — handshake skeleton.
// - TASK-945 — cmdhelp-derived tool registry + shell-out dispatch.
// - TASK-946 — MCP resources (items, dashboard, collections).
// - TASK-947 — MCP prompts (planning / ideation / retro).
// - TASK-948 — `pad mcp install <agent>` client config writer.
// - TASK-963 — cmdhelp_version handshake metadata + pad://_meta/version.
package mcp
// ServerName is the canonical name pad's MCP server advertises in the
// initialize handshake's serverInfo.name field. Stable across versions —
// MCP clients (Claude Desktop, Cursor, Windsurf) display it verbatim,
// so changing it would break user-visible installations.
const ServerName = "pad-mcp"
// FallbackVersion populates serverInfo.version when NewServer is
// constructed without an explicit Options.Version. Production callers
// (the cobra `pad mcp serve` command) always pass pad's runtime
// fullVersion(); this fallback covers tests, embedders, and `dev`
// builds where the version string is empty.
const FallbackVersion = "0.0.0-dev"
// CmdhelpVersion is the cmdhelp CLI-help-tree stability contract this
// MCP server advertises. cmdhelp is the source of truth for individual
// CLI command schemas (args, flags, types) consumed at MCP dispatch
// time by BuildCLIArgs. Bump the major when those CLI-side schemas
// change incompatibly:
//
// - "0.1" — initial cmdhelp surface from PLAN-942.
//
// This is independent of ToolSurfaceVersion below — cmdhelp owns the
// CLI's help-tree contract; ToolSurfaceVersion owns the MCP tool
// catalog's contract. Two contracts, two version constants.
//
// Discovery surfaces (paths into the JSON-RPC envelope):
//
// - result.capabilities.experimental.padCmdhelp.version (handshake).
// - pad://_meta/version resource (queryable JSON document).
const CmdhelpVersion = "0.1"
// ToolSurfaceVersion is the MCP tool catalog stability contract this
// server advertises. External agents (Claude Desktop, Cursor, ChatGPT
// connectors, future Pad Cloud remote MCP) pin against it so a future
// tool rename, action enum change, or parameter reshape doesn't
// silently break consumers. Bump the major when the catalog shape
// changes incompatibly:
//
// - "0.1" — historical. cmdhelp-derived ~85 flat verb tools
// (PLAN-942). Lived from PLAN-942 through TASK-980 of PLAN-969's
// 3-stage rollout; never bumped during rollout because the
// user-visible surface was a transitional mix of v0.1 walker
// output + the partial v0.2 catalog.
// - "0.2" — historical. Hand-curated resource × action catalog
// (PLAN-969, TASK-981). The cmdhelp leaf walker retired; tools/list
// advertises only the catalog (~7 tools + pad_set_workspace).
// - "0.3" — historical. PLAN-1377 / TASK-1380:
// - pad_meta gains an action: bootstrap that returns the
// AgentBootstrap blob (and pad_meta.Schema.Workspace flipped
// to true so the workspace param is available to that action).
// - pad_set_workspace's response shape extends from
// {workspace, status} to {workspace, status, bootstrap?} —
// the embedded blob lets one call hand the agent full session
// context. Purely additive; older clients that ignore unknown
// keys keep working.
// - pad://workspace/{ws}/bootstrap resource added.
// - "0.5" — historical. PLAN-1560 / TASK-1563: adds `pad_library` to
// the catalog as the ninth resource × action tool. Three actions —
// list / get / activate — surface the global convention + playbook
// library (previously CLI-only) to MCP callers. Pure addition; no
// existing tool/action/param/bootstrap shapes changed. Backwards-
// compatible for any v0.4 consumer that doesn't enumerate the new
// tool.
// - "0.6" — historical. PLAN-1593 / TASK-1596: adds `backlinks` action
// to `pad_item` so MCP callers can answer "what mentions X?"
// without scanning the full content corpus. Adds `offset` to the
// param vocabulary, extends `limit` to cover the backlinks
// pagination. Pure addition; existing pad_item actions unchanged.
// Backwards-compatible for v0.5 consumers that don't enumerate
// the new action.
// - "0.7" — historical. Artifact export/import (Phase 5): adds two
// actions to `pad_item` mirroring the CLI `pad item export` /
// `pad item import`. `export` (read-only) takes `ref` and returns
// the portable artifact TEXT (YAML frontmatter + Markdown body) —
// it forces the CLI's stdout sink (`-o -`) so the bytes come back
// as the tool result rather than being written to a file the MCP
// host can't see. `import` (mutating, not destructive — creates a
// draft like create) takes a new `artifact` param (the full
// artifact text) and returns {ref, slug, warnings}; because the
// ExecDispatcher doesn't pipe stdin, it spills the artifact to a
// temp file and dispatches `item import <tmpfile>`. Both cover
// playbooks AND conventions (the server gates by collection). Adds
// the `artifact` param to the vocabulary. Pure addition; existing
// pad_item actions unchanged. Backwards-compatible for v0.6
// consumers that don't enumerate the new actions.
// - "0.15" — current. TASK-2096: adds the `unparented` boolean parameter
// to `pad_item.list`, mutually exclusive with `parent`, and forwards it
// through both local exec and remote HTTP dispatchers. The parameter
// selects items with neither the legacy parent_id column nor an outgoing
// parent/implements relationship.
// - "0.14" — historical. TASK-2022: adds a `history` action to `pad_item`
// (read-only item version history — newest-first metadata: id,
// created_at, created_by, source, change_summary; the resolved
// content body is omitted for token thrift). Also adds an
// `expected_updated_at` param to `pad_item` for optimistic-
// concurrency on `update`: round-trip the updated_at you last read
// and the update fails with a structured 409 (code=update_conflict)
// if the item changed since. The `update` action's field writes are
// now a server-side field-level MERGE (only the keys you set change)
// rather than a full-blob replace, closing the concurrent-update
// lost-write race (IDEA-1480) — a behavior change to the update
// path plus a new action and a new param, hence the version bump.
// Pure addition to the action enum + param vocabulary; existing
// pad_item actions/params are unchanged and backwards-compatible.
// - "0.13" — TASK-2019: agent-oriented backlog queries.
// Adds `ready` + `stale` actions to `pad_project`, mirroring the
// existing CLI `pad project ready` / `pad project stale`. `ready`
// (read-only) returns the current actionable backlog — the
// query-oriented counterpart to `pad project next`, reusing the
// dashboard's suggested-next logic. `stale` (read-only) lists items
// needing attention (stalled, blocked, overdue, or otherwise out of
// the active workflow). Both HTTP dispatchers already existed
// (dispatch_http_project.go); this bump wires them onto the catalog
// surface. `pad project reconcile` stays CLI-only — it shells out to
// `gh` to compare stored PR metadata against live GitHub state, a
// local-git dependency an MCP agent lacks. Pure addition of two
// read-only actions; existing pad_project actions unchanged.
// Backwards-compatible for v0.12 consumers that don't enumerate the
// new actions.
// - "0.12" — TASK-2018: agent-accessible activity feed.
// Adds an `activity` action to `pad_project` mirroring the new CLI
// `pad project activity [--limit N] [--actor user|agent] [--since DATE]`.
// It's the non-streaming, bounded query counterpart to
// `pad project watch` (the live SSE stream, which stays CLI-only):
// a read-only snapshot of the workspace's enriched activity feed —
// item refs, titles, and field-level change details — so an agent
// can catch up on what OTHER agents/users did since it last worked.
// Backed by the existing `GET /workspaces/{ws}/activity` endpoint
// (previously web-UI-only), extended with a server-side `since`
// date filter so `limit`, `actor`, and `since` behave identically
// across the CLI, local stdio MCP, and cloud HTTP transports. Adds
// `actor` + `limit` params to the pad_project vocabulary (`since`
// already existed for changelog). Pure addition of one action +
// params; existing pad_project actions unchanged. Backwards-
// compatible for v0.11 consumers that don't enumerate the new
// action.
// - "0.11" — TASK-2017: read-only attachments surface.
// Adds a new `pad_attachment` tool (the tenth resource × action
// tool) with two read-only actions — `list` and `show` — mirroring
// the CLI `pad attachment list` / `pad attachment show`. `list`
// (read-only) enumerates a workspace's attachments with optional
// filters (item, category, collection, attached/unattached, sort,
// limit, offset); `show` (read-only) returns one attachment's
// metadata (MIME, size, filename, ETag, last-modified) via a HEAD
// request without transferring bytes. Both HTTP dispatchers already
// existed (dispatch_http_attachments.go, TASK-871 era); this bump
// just wires them onto the catalog surface. Upload / download / view
// stay CLI-only (filesystem-bound) and are NOT exposed. Pure
// addition of one tool + two read actions; existing tools/actions
// unchanged. Backwards-compatible for v0.10 consumers that don't
// enumerate the new tool. The base64 image RESOURCE for multimodal
// agents was tracked separately (TASK-2076/2077) and shipped later
// in PR #930; it was not part of this bump.
// - "0.10" — BUG-2020: server-side draft-playbook gate.
// `pad_playbook.run` now refuses a playbook whose status isn't
// "active" (a draft still being authored) with a structured
// `playbook_not_active` error, and adds an `allow_draft` boolean
// param (escape hatch) that runs a draft anyway. Both the `run` and
// `get` responses now echo the playbook's `status`. Pure addition of
// one param + one echoed field + a new refusal path; existing active
// playbooks run unchanged. Backwards-compatible for v0.9 consumers
// that don't set allow_draft — except that running a draft (which
// the skill already told agents not to do) now errors instead of
// silently returning the body.
// - "0.9" — TASK-2000: `pad_item.list` is now summary-shaped
// and bounded. Two changes for agent-token thrift:
// - The `list` action injects a default `limit` (50) and clamps an
// oversized one (max 300), mirroring the backlinks default/max, so
// a bare agent list can't dump the whole workspace into context.
// - The list RESULT shape changed: `pad item list` (which the
// ExecDispatcher shells out to) now defaults to a token-light
// SUMMARY projection — the rich `content` body is replaced by a
// short `content_preview`, UUID plumbing (id, workspace_id,
// collection_id, *_user_id, parent_id, agent_role_id) and the
// duplicate collection/parent join fields are dropped, and
// `fields`/`tags` are emitted as nested JSON rather than escaped
// strings. This is a BREAKING result-shape change for consumers
// that read `content` or the dropped fields off a list row; the
// full former shape is available via the CLI `--full` flag (not
// yet surfaced as an MCP param — agents that need a full body
// fetch it per-item via action=get). No action-enum or param
// removals; `limit` semantics unchanged for callers that pass one
// under the max.
// - "0.8" — historical. TASK-1973: workspace soft-delete recovery.
// Adds two actions to `pad_workspace` mirroring the CLI
// `pad workspace deleted` / `pad workspace restore` (TASK-1972):
// `deleted` (read-only) lists the caller's soft-deleted workspaces
// still inside the 30-day restore window; `restore` (mutating, not
// destructive) un-soft-deletes a workspace by `slug` while it's
// still restorable (owner-only). Both non-interactive. Reuses the
// existing `slug` param (now also required for action=restore); no
// new params. Pure addition; existing pad_workspace actions
// unchanged. Backwards-compatible for v0.7 consumers that don't
// enumerate the new actions.
// - "0.4" — PLAN-1410: comprehensive bootstrap-payload
// trim, cutting ~40% of bytes off the AgentBootstrap response.
// Same tool catalog (still eight resource × action tools +
// pad_set_workspace); the shape changes are entirely inside the
// bootstrap JSON those tools/resources return:
// - Slim BootstrapCollection projection (TASK-1412): drops `id`,
// `workspace_id`, `created_at`, `updated_at`, `settings`;
// `schema` is now a nested JSON object rather than an
// escaped JSON-encoded string.
// - Slim BootstrapRole projection (TASK-1423): drops `id`,
// `workspace_id`, `tools`, `created_at`, `updated_at`.
// - Convention `slug` dropped (TASK-1413) — agent addresses by ref.
// - Top-level `recent_activity` removed (TASK-1413) — was a
// bit-for-bit duplicate of `dashboard.recent_activity`.
// - BootstrapDashboard wrapper caps five dashboard sub-arrays
// (attention, recent_activity, active_items, active_plans,
// by_role) at 5 entries each, with parallel
// `<name>_overflow_count` fields surfaced when truncation
// fired. TASK-1413 added the first two; TASK-1422 added the
// remaining three. suggested_next deliberately excluded —
// already capped to 3 upstream in buildDashboardResponse.
// - Schema field `label` omitted when label == TitleCase(key)
// (TASK-1424); custom labels preserved.
//
// Compatibility note: most v0.4 changes are subtractive (dropped
// fields) or additive (overflow counts), but ONE field had its
// JSON type change — collections[].schema went from a
// JSON-encoded string ("schema":"{\"fields\":[...]}") to a nested
// JSON object ("schema":{"fields":[...]}). This is a breaking
// change for any v0.3 consumer that read schema as a string and
// JSON.parse()'d it themselves. Agents now read it as a parsed
// object directly. Clients that relied on the dropped fields
// (UUIDs, timestamps, settings, the duplicate recent_activity,
// convention.slug) need to switch to the canonical alternatives
// (slugs for addressing; pad collection list / pad role list for
// the full models when needed).
//
// Discovery surfaces:
//
// - result.capabilities.experimental.padToolSurface.version (handshake).
// - pad://_meta/version resource (queryable JSON document).
// - pad_meta.action: tool-surface (full catalog introspection).
const ToolSurfaceVersion = "0.15"
// MetaVersionURI is the canonical URI of the queryable version document.
// Lives outside the pad://workspace/{ws}/... namespace because it's a
// server-wide attribute, not a workspace-scoped resource.
const MetaVersionURI = "pad://_meta/version"
// The MCP wire protocol revision this server speaks isn't a constant
// owned by pad — it's whatever mcp-go's `LATEST_PROTOCOL_VERSION`
// resolves to at build time, since that's what NewMCPServer will
// negotiate with clients that request the latest. The meta resource
// reads it dynamically (see meta.go) so the value never drifts from
// what the library actually advertises.
// experimentalCapabilityKey is the JSON object key under
// capabilities.experimental that carries the cmdhelp tier in the
// initialize handshake. Namespaced so other servers' experimental
// capabilities don't collide.
const experimentalCapabilityKey = "padCmdhelp"
// experimentalToolSurfaceKey is the JSON object key under
// capabilities.experimental that carries the MCP tool-catalog tier in
// the initialize handshake. Distinct from experimentalCapabilityKey so
// the cmdhelp and tool-surface contracts can version independently.
const experimentalToolSurfaceKey = "padToolSurface"