Skip to content

Generated cad_* tools declare only sessionId, making array arguments unusable on some MCP clients #12

Description

@BobHouseholder

Summary

The eight cad_* tools are generated from a stub that declares only sessionId and sets additionalProperties: true. Every other argument — including all arrays — travels undeclared.

Some MCP clients do not preserve structured values for parameters a tool does not declare. On such a client, an array argument arrives at PartMode as a non-array and is rejected. Because every CAD mutation is carried in undeclared arguments (sketch.shapes, poly.pts, edges, and so on), the practical effect is that no CAD transaction can be sent at all from that client, even though session setup, approval, inspection and capability discovery all work.

This is not a defect in PartMode's server or validator — I verified both behave correctly (evidence below). It is a robustness and discoverability gap that PartMode is well placed to close, and the fix already exists in the same file for cad_artifact.

Environment

  • Repo at fe88558a34c8d7a3e03b34c2a374ba5ce6febe9f
  • Hosted endpoint https://partmode.com/mcp, studio 8.0.0, kernel replicad-open-cascade/runtime-5A
  • Browser-approved session, preview-required and read-only both tried
  • Client: Claude Code

Reproduction

In one approved session, three calls minutes apart:

# parameter declared in tool schema? value sent result
1 partmode_connect.permissions yes, typed array ["project.read"] arrived intact — session echoed exactly ["project.read"], not the default permission set
2 cad_capabilities.detail no "schemas" (string) arrived intact — accepted and acted on
3 cad_capabilities.operationKinds no ["feature.revolve"] rejected: operationKinds must be a bounded list of at most 20 stable capability IDs.

Rows 1 and 2 are the controls. A declared array survives; an undeclared scalar survives; an undeclared array does not. So the failure is specific to structured values on undeclared parameters.

Why this is not a server-side bug

  • The kind name was valid. selectCadCapabilities checks names against the manifest immediately after the bounded-list check and raises a different error, so the request failed at Array.isArray, not on an unknown kind.
  • Field names were correct. Probing kinds and a nested capabilityDiscovery both returned Unknown capability discovery fields: …, while operationKinds never did.
  • The server does not touch the value. src/mcp.ts:466-473 strips sessionId and forwards {...args} verbatim.
// src/static/studio-agent-service.js:1426
function boundedCapabilityIds(value, name) {
  if (value == null) return [];
  if (!Array.isArray(value) || value.length > 20 || value.some(...)) {
    invalidCapabilityQuery(`${name} must be a bounded list of at most 20 stable capability IDs.`);
  }

The validator is correct. The value simply is not an array by the time it arrives.

Suggested fix

Give the eight generated tools real input schemas. src/mcp.ts:176-186 currently produces:

inputSchema: {
  type: 'object',
  required: ['sessionId'],
  properties: { sessionId: SESSION_ID_SCHEMA },
  additionalProperties: true,
},

cad_artifact, immediately above at src/mcp.ts:135-164, already does the right thing — it declares format, scope, and a typed entities array. Extending that treatment to cad_capabilities, cad_inspect, cad_query, cad_preview, cad_commit, cad_history, cad_ui and cad_events would:

  1. make array arguments survive on clients that only marshal declared parameters;
  2. let clients validate transactions locally before spending a round-trip and a commit budget;
  3. make the tools self-describing in tools/list, which removes the cad_capabilities detail: "schemas" round-trip for the common cases.

Even a partial step helps: declaring just detail, operationKinds and queryKinds on cad_capabilities would unblock schema discovery, which is the entry point for everything else.

Secondary observation

cad_capabilities with detail: "full" times out on a browser-approved session against the 164-operation manifest, and leaves the session SESSION_BUSY for a while afterwards. With detail: "schemas" unreachable (above) and full timing out, summary is the only working detail level — which is what made this a hard stop rather than an inconvenience. A paginated or chunked full would be a useful fallback.

What I was trying to do

Rebuild a single parametric lens barrel in PartMode that had already been built in build123d, export STEP from both, and compare named critical dimensions to ≤0.01 mm — an independent-toolchain check on the same OpenCascade kernel. Session setup, visible approval, cad_inspect and cad_capabilities at summary detail all worked exactly as documented; the operation schemas I eventually read from this repository's source. Only argument transport blocked it.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions