fix(mcp): rename wire fields to contract names before dispatch#3215
Open
bjohas wants to merge 2 commits intosuperdoc-dev:mainfrom
Open
fix(mcp): rename wire fields to contract names before dispatch#3215bjohas wants to merge 2 commits intosuperdoc-dev:mainfrom
bjohas wants to merge 2 commits intosuperdoc-dev:mainfrom
Conversation
The MCP wire schema (generated from apps/cli operation params) exposes PARAM_FLAG_OVERRIDES renames such as `commentId` → `id` and `parentCommentId` → `parentId`. Without an inverse translation at dispatch time, the contract validator rejects the wire field names because it expects the canonical contract names — so `superdoc_comment` actions `delete`/`get`/`update` fail with `comments.<action> commentId must be a non-empty string` even when the caller follows the documented schema. The CLI applies the inverse rename in extractInvokeInput() (apps/cli/src/lib/invoke-input.ts PARAM_RENAMES). This commit adds the matching layer for the MCP path: a small `applyParamRenames` helper in a standalone module, applied in `executeOperation` immediately before `api.invoke`. Affected operations: comments.create (parentId → parentCommentId), comments.patch / comments.delete / comments.get (id → commentId), getNodeById (id → nodeId). Includes unit tests for the rename helper.
caio-pizzol
approved these changes
May 10, 2026
Contributor
caio-pizzol
left a comment
There was a problem hiding this comment.
@bjohas thanks for the PR :) good fix and the tests cover the renames cleanly.
two small things inline.
| * | ||
| * The CLI applies the inverse rename in extractInvokeInput() | ||
| * (apps/cli/src/lib/invoke-input.ts PARAM_RENAMES). This module is the MCP | ||
| * mirror — kept in lockstep by hand. Three operations are affected today |
Contributor
There was a problem hiding this comment.
count's off (map has 5 entries) and the em-dash trips our style rule.
Suggested change
| * mirror — kept in lockstep by hand. Three operations are affected today | |
| * mirror, kept in lockstep by hand. Five operations are affected today | |
| * (comments.delete, comments.get, comments.patch, comments.create's | |
| * parentId, and getNodeById's id), so duplication is small. |
| // Generated dispatch uses 'doc.' prefix (e.g. 'doc.query.match'); strip it for DocumentApi.invoke() | ||
| const opId = operationId.startsWith('doc.') ? operationId.slice(4) : operationId; | ||
| return api.invoke({ operationId: opId, input } as DynamicInvokeRequest); | ||
| return api.invoke({ operationId: opId, input: applyParamRenames(opId, input) } as DynamicInvokeRequest); |
Contributor
There was a problem hiding this comment.
input gets renamed (id → commentId) but the response goes back as-is, so MCP clients see commentId in the body even though their input uses id. probably fine since the agent reads it as text, but is the asymmetry intentional? worth a line in the PR body, or a follow-up if it should be inverted on the way out too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The MCP wire schema (generated from
apps/clioperation params) exposes thePARAM_FLAG_OVERRIDESrenamescommentId→idandparentCommentId→parentId. The contract validator on the receiving side still expects the canonical names, but the MCP dispatch path passes args through raw with no inverse rename — sosuperdoc_commentactionsdelete/get/updatefail withcomments.<action> commentId must be a non-empty stringeven when callers follow the documented schema.The CLI handles this in
extractInvokeInput()(apps/cli/src/lib/invoke-input.tsPARAM_RENAMES). This PR mirrors that translation for the MCP path: a smallapplyParamRenameshelper in a standalone module, applied inexecuteOperationright beforeapi.invoke.Repro (before this fix)
superdoc_open.superdoc_comment action:"list"— returns comments with validids.superdoc_comment action:"delete" id:"<that-id>"— fails withcomments.delete commentId must be a non-empty string.action:"create"andaction:"list"are unaffected (they don't take a comment id).Affected operations
comments.createparentIdparentCommentIdcomments.patch(update)idcommentIdcomments.deleteidcommentIdcomments.getidcommentIdgetNodeByIdidnodeIdChanges
apps/mcp/src/tools/param-renames.ts— rename map +applyParamRenameshelper. Mirrors the CLI'sPARAM_RENAMES; comment in the file points to it.apps/mcp/src/tools/intent.ts—executeOperationnow wrapsinputwithapplyParamRenames(opId, input)beforeapi.invoke.apps/mcp/src/__tests__/intent-param-renames.test.ts— 7 unit tests.Test plan
bun test apps/mcp/src/__tests__/intent-param-renames.test.ts— 7 pass / 0 fail.bun build apps/mcp/src/tools/param-renames.ts --target=node— transpiles clean.superdoc_comment action:"delete"against a doc that round-trips throughlist.Notes
PARAM_RENAMESis intentional for now — the rename table is small (5 entries across 4 operations) and stable. A follow-up could promote it to a shared location, ideally driven by codegen so both the wire schema and the inverse map come from a single source.commentId,parentCommentId,nodeId) directly. Cleaner but a breaking change for any caller relying on the current short names.