Skip to content

feat(mcp): harden the MCP server — renderability, verb narrowing, real version#37

Merged
yultyyev merged 3 commits into
feat/ui-timelinefrom
feat/mcp-server-hardening
Jun 18, 2026
Merged

feat(mcp): harden the MCP server — renderability, verb narrowing, real version#37
yultyyev merged 3 commits into
feat/ui-timelinefrom
feat/mcp-server-hardening

Conversation

@yultyyev

@yultyyev yultyyev commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Harden the MCP server (3 v0.3.0 audit follow-ups)

Implements the three non-blocking follow-ups the v0.3.0 pre-release audit flagged for the new MCP server. All are read-or-validate hardening — no change to the editing/render semantics.

Targets feat/ui-timeline (#36's branch), not main — so it folds into #36 and ships inside a single v0.3.0 release rather than triggering a separate npm publish. Merge this into #36's branch; the eventual #36main merge cuts one release with everything. Independent of #38 (different files) — verified conflict-free either order.

1 — timeline_show / timeline_edit report renderability

timeline_show returned only rev/duration/canvas/clips — unlike the CLI timeline show and the UI's /api/timeline/exportable, which both report {exportable, blockers}. An MCP agent could author a gapped/unrenderable doc that showed clean and only discovered the problem at timeline_export. Both show and edit now include exportable + blockers.

The dry-run-compile dance (compileTimeline → catch CompileError{exportable, blockers}) was copy-pasted in the CLI and the UI route, so I lifted it into one exported checkExportable in compile.ts; all three surfaces now share it and answer identically. An empty doc still answers (exportable:false + a blocker) — show never throws.

2 — timeline_edit no longer accepts set_transform

timeline_edit validated against the full CompositionVerbSchema, which includes set_transform — a verb the compiler refuses (non-identity transforms fail assertCompilable), and which the UI's ClipInspector already omits for exactly this reason. Split the verb union into EditableVerbSchema (the eight renderable verbs) + the full schema (still used by the CLI/op layer), and pointed the MCP input schema at the editable set. set_transform is now rejected at the boundary instead of producing a doc that only fails at export.

3 — Server version is read from package.json, not hardcoded

The handshake version was a hardcoded string. It happened to be right for the current release, but the next bump would have announced the wrong version. It's now read from package.json by walking up from the module — which resolves correctly from both the source (under vitest) and the bundled dist/cli.js at runtime, where the depth to package.json differs (a fixed relative path would break one).

Verified against the bundled artifact, not just source: a real node dist/cli.js mcp stdio handshake reports the version straight from package.json, with clean stdout.

Verification

pnpm type-check ✓ · 545 tests ✓ (3 new MCP tests: version, exportability-in-show, set_transform rejection) · pnpm lint exit 0 · pnpm build ✓ · bundled stdio smoke ✓.

🤖 Generated with Claude Code

yultyyev added 3 commits June 18, 2026 16:27
The 'dry-run compileTimeline and turn a CompileError into {exportable,
blockers}' dance was copy-pasted in the CLI timeline show and the clip ui
exportable route. Lift it into one exported checkExportable in compile.ts so
a third caller (the MCP server) reuses it and every surface reports
renderability identically. Pure refactor — no behaviour change.
Split set_transform out of the verb union into its own schema and expose
EditableVerbSchema — the eight verbs whose results the compiler can render.
CompositionVerbSchema is unchanged (the full set incl. set_transform, for the
CLI/op layer). This lets the UI and MCP accept only renderable edits without
re-listing the verbs. No behaviour change on its own.
Three pre-release-audit follow-ups for the MCP server:

- timeline_show/edit now report {exportable, blockers} (via the shared
  checkExportable), so an agent learns a doc cannot render up front instead
  of only at timeline_export. summarizeComposition gains a typed return.
- timeline_edit validates against EditableVerbSchema, so set_transform — which
  the compiler refuses — is no longer accepted (mirrors the ClipInspector UI).
- the server version is read from package.json (walking up so it resolves from
  both src under vitest and the bundled dist/cli.js) instead of a hardcoded
  string that would go stale at the next release. A test asserts the handshake
  version matches package.json.

Verified the bundled path with a real node dist/cli.js mcp stdio handshake.
@yultyyev
yultyyev changed the base branch from main to feat/ui-timeline June 18, 2026 23:46
@yultyyev
yultyyev merged commit 136e9cd into feat/ui-timeline Jun 18, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant