Split out of #26.
Problem
MCP tools/call arguments are always a JSON object (CallToolRequest.params.arguments is Record<string, unknown> in the SDK), and Tool.inputSchema.type must be the literal "object". Cordierite lets an app register inputSchema: z.string() or z.array(...); the app then validates the incoming args against that schema (packages/react-native/src/client/tool-invocation.ts:143).
After #26 the MCP server falls back to an empty object schema for such tools so tools/list keeps working, but an agent's {} arguments still fail the app's validation, so the tool is effectively uncallable from MCP. The CLI (invoke --input '"text"') and cordierite/client are unaffected because they pass the raw value through the daemon.
Options
- Wrap in the MCP layer only: emit
inputSchema: { type: "object", properties: { value: <real schema> }, required: ["value"] } and unwrap arguments.value in server.ts before calling the daemon. Keeps the wire and CLI unchanged. Needs a marker so the unwrap only applies to tools that were wrapped.
- Restrict at registration: dev-time error in
registerTool when the exported input schema is not object-rooted, pointing users at z.object({ value: ... }). Simplest, but a breaking dev-time change.
Recommend option 2 unless there is a known consumer relying on non-object inputs, since option 1 hides a shape difference the CLI user will still see.
Acceptance
- Either wrapped calls work end to end over MCP with a test, or
registerTool rejects non-object input schemas in dev with a test, and the README documents the rule.
Split out of #26.
Problem
MCP
tools/callarguments are always a JSON object (CallToolRequest.params.argumentsisRecord<string, unknown>in the SDK), andTool.inputSchema.typemust be the literal"object". Cordierite lets an app registerinputSchema: z.string()orz.array(...); the app then validates the incomingargsagainst that schema (packages/react-native/src/client/tool-invocation.ts:143).After #26 the MCP server falls back to an empty object schema for such tools so
tools/listkeeps working, but an agent's{}arguments still fail the app's validation, so the tool is effectively uncallable from MCP. The CLI (invoke --input '"text"') andcordierite/clientare unaffected because they pass the raw value through the daemon.Options
inputSchema: { type: "object", properties: { value: <real schema> }, required: ["value"] }and unwraparguments.valueinserver.tsbefore calling the daemon. Keeps the wire and CLI unchanged. Needs a marker so the unwrap only applies to tools that were wrapped.registerToolwhen the exported input schema is not object-rooted, pointing users atz.object({ value: ... }). Simplest, but a breaking dev-time change.Recommend option 2 unless there is a known consumer relying on non-object inputs, since option 1 hides a shape difference the CLI user will still see.
Acceptance
registerToolrejects non-object input schemas in dev with a test, and the README documents the rule.