Frontmatter
| title | >- |
| author | tobiu |
| state | Merged |
| createdAt | Apr 18, 2026, 9:14 PM |
| updatedAt | Apr 18, 2026, 10:26 PM |
| closedAt | Apr 18, 2026, 9:20 PM |
| mergedAt | Apr 18, 2026, 9:20 PM |
| branches | dev ← agent/9837-output-schema-drift |
| url | https://github.com/neomjs/neo/pull/10067 |

Input from Claude Opus 4.7 (Claude Code):
❋ # PR Review Summary
Status: Comment (Self-Review)
Self-Review Opening: Self-review of #9837 (re-purposed). I chose asymmetric validation (strict input / lenient output) over blanket
additionalProperties: truebecause server implementations drift faster than OpenAPI contracts, but agents calling tools with wrong top-level keys should still fail fast. The{lenient}flag threaded throughbuildZodSchemaFromNodekeeps the two paths honest. Trade-offs and honest gaps below.Tech Debt Radar: deliberately not invoked. Change is confined to one validation file + its spec; surgical, not a fundamental framework shift. The three-layer root-cause analysis in #9837's body already surfaces the relevant adjacent context (#10043 activation + #10064 sibling).
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 — Clean asymmetric design. Context threaded viaopts = {lenient}rather than duplicating the recursion. The.passthrough()wrapping respects existingadditionalPropertiesdeclarations (only kicks in when OpenAPI didn't specify). Matches the fault model: server drift is the real problem, not client strictness.[CONTENT_COMPLETENESS]: 90 — JSDoc on the newoptsparameter explains why leniency exists only for outputs, with a direct reference to #9837. Inline comment inbuildOutputZodSchemadocuments the input/output asymmetry rationale. PR body carries full forensic trace and cross-server sweep table.[EXECUTION_QUALITY]: 87 — 17/17 green covering direct zod emission, per-server output tolerance, and per-server input-strictness regression guard. Honest gaps: (a) the input-strictness test only checks root object schemas, not nested ones — a future leak oflenient:trueinto the input path could slip past; (b) I didn't run the fullnpm run test-unitsuite (only the validation subdirectory); (c) no live verification against Copilot — relying on emitted JSON Schema inspection as the proxy.[PRODUCTIVITY]: 100 — Step 1 + Step 2 of #9837's plan delivered; Step 3 correctly deferred to a follow-up audit ticket rather than scope-crept into this PR.[IMPACT]: 80 — Unblocks 74 tools across all 5 MCP servers for strict clients (Copilot + future stricter validators). Not framework-critical, but framework-wide.[COMPLEXITY]: 45 — Small line count but non-trivial: recursion context threading, Zod/zod-to-json-schema emission quirks, asymmetric design thought.[EFFORT_PROFILE]: Heavy Lift — high framework-wide impact, moderate cognitive complexity (diagnosis + cross-server audit + asymmetric design was the bulk of the work; the code change is surgical).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #9837 (re-purposed)
- Related Graph Nodes: Sibling #10064 (input-side of same MCP schema-validation family). Activated by #10043 (output-schema spec compliance). Originally cross-referenced by #9838.
- Origin Session ID:
97343e6e-9d1c-4170-b3a5-6e58d41511b6
🧠 Graph Ingestion Notes
[KB_GAP]:zod-to-json-schema's default of emittingadditionalProperties: falseon everyz.object(...)is a subtle quirk that trips up agents wiring OpenAPI → MCP. Worth a note in a README nearai/mcp/validation/or in the AgentOS guide series — "if you produce Zod objects for MCP output schemas, wrap them in.passthrough()unless you know the service return shape is exhaustively declared." Would prevent the next agent from re-introducing this asymmetrically.[TOOLING_GAP]: There is no automated check for OpenAPI-vs-implementation drift. A test or audit tool that samples actual service returns (via test-time introspection or a test double) and diffs against declared response shapes would catch this class of bug proactively, making the.passthrough()hotfix truly defense-in-depth rather than load-bearing. Sibling to the Step 3 audit ticket #9837's body proposes — worth filing explicitly.[RETROSPECTIVE]: Three-layer forensic pattern is a reusable debugging template. When a tool "used to work" and suddenly doesn't, check: (1) the tool-surface change (none found here), (2) a recent activation commit that turned on stricter validation (#10043 here), and (3) the latent pre-existing divergence the activation exposed (schema vs implementation drift here). Don't stop at layer 1 when the agent reports a regression — the change that reveals a bug is often different from the change that introduced it. Similar mental model togit bisect: find the "surfacing" commit, then separately investigate what it surfaced.
📋 Required Actions
- Human QA: open the devindex app in Copilot and confirm
get_worker_topology/get_instance_properties/mutate_frontier/get_context_frontierno longer crash the tool registration.- (Optional follow-up) File the Step 3 audit ticket from #9837's body — schema-vs-implementation realignment. Worth tracking separately so the leniency is defense-in-depth rather than hiding real drift.
- (Optional follow-up) File a ticket for an automated drift-detection check (per
[TOOLING_GAP]above).No block-level code changes — the final state is tight.
Handoff: awaiting human merge per
pull-request-workflow.md§5 (Human-in-the-Loop). I will not self-merge.

Input from Claude Opus 4.7 (Claude Code):
❋ Post-merge correction (2026-04-18): the "Follow-up: schema-vs-implementation audit" in this PR's body is superseded.
Discussing follow-up tickets after merge, tobiu flagged that my original framing had a blind spot: I treated MCP tool outputs as typed contracts across the board, but Neural Link's runtime introspection tools —
get_vnode,get_component_tree,get_instance_properties, VDOM/VNode accessors,inspect_component_render_tree, etc. — surface live application state where the response shape is as open-ended as the app itself. VDOM nodes carry arbitrary HTML attrs (aria-*,data-*, handlers, styles); component instances expose hundreds of configs per class.For this class of tool, open shapes are the feature. The
.passthrough()that landed here is architecturally correct design, not a hotfix awaiting a stricter fix. Auditing to narrow the schemas would fight the introspection pattern NL exists to provide.Revised follow-up framing
My original "Follow-up: Schema-vs-implementation audit per Step 3 of #9837's body" bullet should be read as superseded. Any future audit work must be category-aware:
Category Examples Correct strategy Closed contracts healthcheck, GitHub workflow ops, KB queries with fixed result shapeNarrow schemas + audit drift. additionalProperties: falseis a legitimate contract.Runtime introspection Neural Link's get_vnode,get_component_tree,get_instance_properties,query_vdom,query_componentOpen shapes by design. .passthrough()is correct, not a hotfix.The
.passthrough()change here protects both categories simultaneously (strict input / lenient output) — the asymmetric design is correct for the closed-contract case (where drift is a bug to be audited later if observed) AND for the runtime-introspection case (where openness is inherent). The framing of this PR as "defense-in-depth while waiting for Step 3" was wrong in retrospect; it's actually "architecturally correct for introspection; safety net for contracts."Saved as durable feedback (
feedback_mcp_output_category_split.md) so future sessions don't re-propose the same audit trap.Cross-posted to #9837 for symmetry.
Summary
Strict MCP clients (notably GitHub Copilot) have been crashing on output-heavy Neural Link / Memory Core tools with:
Every
z.object(...)emitted from OpenAPI response schemas becameadditionalProperties: falsein the JSON Schema, but the underlying services routinely return fields the OpenAPI contract forgot to declare (e.g.ConnectionService.sessionDatareturns{appName, connectedAt, logs, sessionId}while the schema declared a different six-field shape). The drift was invisible to lenient clients (Claude Desktop, Cursor) and became fatal the moment #10043 made outputSchemas spec-compliant, activating strict client-side validation.Fix (Output-side defense-in-depth)
In ai/mcp/validation/OpenApiValidator.mjs:
buildZodSchemaFromNode(...)now accepts a{lenient}opt threaded through the entire recursion.lenient && !schema.additionalProperties,z.object(shape)is wrapped in.passthrough()—zod-to-json-schemathen emitsadditionalProperties: true.buildOutputZodSchema(...)passeslenient: truethroughout and makes the{result: ...}envelope passthrough as well (covers the non-object-response wrapping path added by #10043).buildZodSchema(...)does not pass the flag — agents calling tools with unknown top-level fields still fail validation, preserving contract safety.Asymmetry matches the fault model: server implementations drift faster than OpenAPI contracts; client contracts stay pinned.
Scope — before / after
Output-side strict-object counts across every operation on every MCP server:
74 tools unblocked. Verified input side remains strict (e.g.
call_methodinputadditionalPropertiesstaysfalse).Verification
Extended test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjs with:
z.object(...).passthrough()flipsadditionalPropertiestotrue.additionalProperties: falseanywhere in its tree.additionalProperties: false. Regression guard against accidentally leaking the output-leniency into the input path.(2 unit guards + 5 servers × 3 per-server assertions = 17 tests)
Three-layer root cause (from self-repair diagnosis)
zod-to-json-schemaquirk:z.object(...)defaults toadditionalProperties: falseon emission.{result: ...}made outputSchemas spec-compliant (type: object), which flipped client-side strict validation on — exposing the latent drift.Full forensic trace captured in #9837's re-purposed body.
Avoided Traps
zod-to-json-schemaglobally.additionalProperties: falseis still correct for inputs; agents passing unknown fields should fail fast.Commit History
fix(mcp): tolerate extra properties on output schemas via lenientbuildZodSchemaFromNoderecursion +buildOutputZodSchemapassthrough envelopesFollow-up (not this PR)
Schema-vs-implementation audit per Step 3 of #9837's body — realign declared response shapes with actual service returns so the leniency is defense-in-depth rather than load-bearing.
Origin Session ID
97343e6e-9d1c-4170-b3a5-6e58d41511b6(diagnosed viaself-repairskill)Resolves #9837