LearnNewsExamplesServices
Frontmatter
id10072
titleMCP: list_stores regression — client response shape diverges from OpenAPI declaration
stateOpen
labels
bugai
assigneestobiu
createdAtApr 18, 2026, 11:38 PM
updatedAtApr 18, 2026, 11:39 PM
githubUrlhttps://github.com/neomjs/neo/issues/10072
authortobiu
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]

MCP: list_stores regression — client response shape diverges from OpenAPI declaration

Openbugai
tobiu
tobiu commented on Apr 18, 2026, 11:38 PM

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)

tobiu added the bug label on Apr 18, 2026, 11:38 PM
tobiu added the ai label on Apr 18, 2026, 11:38 PM
tobiu assigned to @tobiu on Apr 18, 2026, 11:39 PM