Problem
neural-link/list_stores fails with Error: data must have required property 'result'. Confirmed live against devindex on 2026-04-18.
Root Cause — client/schema drift surfaced by #10043
Two structural mismatches:
src/ai/client/DataService.mjs:83-91 returns {stores: [...]}: listStores(params) {
return {
stores: StoreManager.items.map(s => ({id, model, count, isLoaded}))
}
}
openapi.yaml:501-503 declares the response as: schema:
type: array
items:
type: object
properties: { id, model, count }
Pre-#10043, the server serialized the {stores: [...]} object as-is and the tool appeared to work (MCP clients did loose validation on non-object outputs — same thread as #9837: strict validation wasn't activated until #10043 made outputSchemas spec-compliant). #10043's {result: <array>} wrapping made validation honest and surfaced the drift.
Also noticed: the client's actual payload includes an isLoaded field the OpenAPI doesn't declare — same open-shape drift category as get_worker_topology's appName/logs fields (#9837 scope).
Fix Options
- A) Align OpenAPI to implementation (recommended): declare response as
{type: object, properties: {stores: {type: array, items: {...}}}} and add isLoaded to the item schema. Reflects reality and produces the most informative tool contract for agents.
- B) Align implementation to OpenAPI: change
DataService.listStores to return StoreManager.items.map(...) (raw array). Less informative payload; also requires adding isLoaded to the schema or dropping it.
- C) Combined: (A) for the envelope shape + document
isLoaded in the item schema. Cleanest.
Recommendation: (C). The named envelope is useful (easy to extend with paging/filters later), and isLoaded is real information the client actually returns — the schema should admit it.
Verification Plan
- Direct MCP call
list_stores() against a live devindex session returns a valid payload without schema error
- Extend test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjs with a parse round-trip:
buildOutputZodSchema(doc, listStoresOp).parse({stores: [{id: 'x', model: 'Y', count: 0, isLoaded: true}]}) should succeed
- Optional: whitebox e2e against a store-heavy example asserting
list_stores returns the declared shape
Related
- #9837 — same output-drift family (undeclared server fields rejected by strict validation); the
isLoaded mismatch falls in that category
- #10043 — the refactor that activated strict output validation across tools;
list_stores was a latent casualty
Origin Session ID
51640d07-2931-4d38-a071-a0e13e3d6452 (live NL exploration session that surfaced the regression)
Problem
neural-link/list_storesfails withError: data must have required property 'result'. Confirmed live against devindex on 2026-04-18.Root Cause — client/schema drift surfaced by #10043
Two structural mismatches:
src/ai/client/DataService.mjs:83-91returns{stores: [...]}:listStores(params) { return { stores: StoreManager.items.map(s => ({id, model, count, isLoaded})) } }openapi.yaml:501-503declares the response as:schema: type: array items: type: object properties: { id, model, count }Pre-#10043, the server serialized the
{stores: [...]}object as-is and the tool appeared to work (MCP clients did loose validation on non-object outputs — same thread as #9837: strict validation wasn't activated until #10043 made outputSchemas spec-compliant). #10043's{result: <array>}wrapping made validation honest and surfaced the drift.Also noticed: the client's actual payload includes an
isLoadedfield the OpenAPI doesn't declare — same open-shape drift category asget_worker_topology'sappName/logsfields (#9837 scope).Fix Options
{type: object, properties: {stores: {type: array, items: {...}}}}and addisLoadedto the item schema. Reflects reality and produces the most informative tool contract for agents.DataService.listStoresto returnStoreManager.items.map(...)(raw array). Less informative payload; also requires addingisLoadedto the schema or dropping it.isLoadedin the item schema. Cleanest.Recommendation: (C). The named envelope is useful (easy to extend with paging/filters later), and
isLoadedis real information the client actually returns — the schema should admit it.Verification Plan
list_stores()against a live devindex session returns a valid payload without schema errorbuildOutputZodSchema(doc, listStoresOp).parse({stores: [{id: 'x', model: 'Y', count: 0, isLoaded: true}]})should succeedlist_storesreturns the declared shapeRelated
isLoadedmismatch falls in that categorylist_storeswas a latent casualtyOrigin Session ID
51640d07-2931-4d38-a071-a0e13e3d6452(live NL exploration session that surfaced the regression)