Skip to content

LOU-Z1, LOU-Z2: robust MCP schema conversion and result handling - #46

Merged
LinuxDevil merged 2 commits into
mainfrom
lou-z1-z2-mcp-robustness
Oct 1, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
lou-z1-z2-mcp-robustness

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

  • LOU-Z1 jsonSchemaToZod handles anyOf/oneOf (nullable pattern -> .nullable()), allOf, local $ref into $defs/definitions (cycle-safe: inner recursive occurrence -> z.any()), type arrays, mixed enum, const, additionalProperties (bool/schema), default, nested arrays/objects and min/max/length/pattern constraints (invalid regex skipped). Unknown/empty schemas -> z.any(); it never throws on schema content.
  • loadMcpTools(client, name, { logger?, onSkip? }): a tool whose schema still fails is skipped, warned through the SDK Logger (server, tool, reason) and reported via onSkip({ name, reason }); the rest of the server loads. Return shape unchanged.
  • LOU-Z2 isError: true throws McpToolError, so the model gets { error: 'McpToolError', toolName, message } with the server text. Text parts are joined, structuredContent is preferred, image/audio/resource/resource_link parts are preserved as typed JSON (McpContentPart).
  • Docs: MCP section of docs/api-overview.md.

Why

One tool using anyOf/$ref made the whole server's load fail, and since LOU-U4 args are validated with these schemas, strict conversion rejects valid calls. isError results were returned as successes and non-text content was effectively invisible.

How verified

  • npx tsc --noEmit: clean
  • npm run lint: 0 errors (no new warnings in touched files beyond pre-existing anys)
  • npm run build + create-loushy-agent build: success
  • npm run test:coverage: Test Files 95 passed (95), Tests 1177 passed | 3 skipped
  • npm run fallow: exit 0 (0 above threshold)
  • npm run docs:verify-snippets: all 30 snippet(s) type-check; 5 also run cleanly
  • Merged origin/main (13a6131) before final verification; already up to date.

Notes for reviewer

  • Behaviour changes: successful results are now { text, content } (existing test still passes via toMatchObject) or the structuredContent object, instead of the raw CallToolResult. Objects now pass unknown keys through (JSON Schema default) instead of zod's strip.
  • Three old schema tests asserting throws for $ref / type: 'null' encoded the bug and were rewritten.
  • New files: src/tools/mcp/result.ts, mcpResult.test.ts.

🤖 Generated with Claude Code

LinuxDevil and others added 2 commits October 1, 2026 09:26
LOU-Z1: jsonSchemaToZod now converts anyOf/oneOf (nullable pattern ->
.nullable()), allOf, local $ref into $defs/definitions (a recursive ref
falls back to z.any() for the inner occurrence), type arrays, mixed enums,
const, additionalProperties, default and the constraints zod can express
(an invalid regex pattern is skipped). Unknown or empty schemas become
z.any() and the converter never throws on schema content; objects keep
extra properties unless additionalProperties is false. loadMcpTools skips
a tool whose schema still fails, warns through an optional Logger and
reports it via an onSkip callback, so one bad tool no longer fails the
whole server. The old tests that asserted a throw for $ref, type 'null'
were changed to assert the new behaviour.

LOU-Z2: MCP results with isError throw McpToolError so the model sees
{ error, toolName, message } through the normal tool-error path. Text
parts are joined, structuredContent is preferred, and image/audio/
resource/resource_link parts are kept in a typed JSON-serializable form.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant