LearnNewsExamplesServices
Frontmatter
id15678
titleKimi MCP client collapses call_method args schema to strings (server is correct)
stateClosed
labels
bugdocumentationai
assigneesneo-kimi-iris
createdAtJul 22, 2026, 3:38 AM
updatedAtJul 22, 2026, 10:54 AM
githubUrlhttps://github.com/neomjs/neo/issues/15678
authorneo-kimi-iris
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 22, 2026, 10:54 AM

Kimi MCP client collapses call_method args schema to strings (server is correct)

Closed Backlog/active-chunk-8 bugdocumentationai
neo-kimi-iris
neo-kimi-iris commented on Jul 22, 2026, 3:38 AM

Context

Surfaced by Emmy's /video-create ideation fold (D#15673, 2026-07-22T00:50Z): she challenged my incident claim that "NL call_method cannot carry object payloads," citing the OpenAPI contract — and asked the claimant to either reproduce a harness-specific stringification boundary as a focused defect ticket or keep it as incident history. Reproduction exists — and the follow-up root-cause the same evening found the defect sits one layer away from where the original body put it.

Amended 2026-07-22T01:5xZ after live root-cause: the server schema is correct; the defect is client-side schema normalization. The write-lock half of the original body split to #15681 (child of #13056).

The Problem (corrected decomposition)

The chain, with receipts:

  1. Server emits the correct schema. Running the actual conversion (buildZodSchematoOpenApiJsonSchema over CallMethodRequest:2685-2688) yields args: {type: 'array', items: {}, description: 'heterogeneous — any JSON-serializable value'} — exactly the HTTP contract, x-pass-as-object: true honored.
  2. The agent-facing tool listing collapses it. The Kimi Code CLI renders the same tool's args as items: {type: 'string'} — an empty/unknown items schema is normalized to string by the client. Agents then either pass strings (forwarded verbatim — tonight's two failed wireFleetBridge variants: bare URL string and JSON-string, both dying at installFleetBridge's validator) or are blocked from passing native objects by client-side validation.
  3. The in-process path is healthy. NeuralLink_InstanceService.callMethod({args: [{url, bearerToken}]}) (a real object) ack'd {"result":true} on the same method, same evening. The e2e fixture uses this path exclusively — which is why CI never sees the boundary.

So: no Neo server code fix exists for the marshalling — the defect lives in the MCP client's schema normalizer.

The Architectural Reality

  • A server-side "revive strings that parse as objects" is the classic coercion trap (legitimate '{…}' data strings must survive verbatim) — rejected except under a future signature-aware contract that does not exist today.
  • The sanctioned workaround already exists and is documented by the fleet harness: the in-process service call (test/playwright/e2e/agentos/authenticatedFleetHarness.mjs uses it for exactly this object-bearing wire).
  • The write-lock sequel (persistent MCP writer monopolizing components; no release-on-error; no TTL) is a designed minimum with the TTL lease already named in WriteGuard.mjs JSDoc as a follow-up slice — now ticketed as #15681 (child of #13056) with tonight's live reproduction.

The Fix (what remains, honestly)

  1. Docs line in openapi.yaml call_method description: name the arg-marshalling reality — pass values natively where the client supports heterogeneous items; strings are forwarded verbatim; the in-process service is the object-bearing workaround.
  2. Upstream defect report to the Kimi Code project (the items: {}{type: 'string'} normalization collapse), linking this ticket. Optional but the only true repair.
  3. The write-lock sequel: #15681 owns it.

Acceptance Criteria

  • AC1: openapi.yaml call_method description carries the marshalling reality + the in-process workaround (docs-only PR).
  • AC2 (optional, operator's call): upstream Kimi Code defect report filed and linked.
  • AC3: moved to #15681 (write-lock TTL/release-on-error/same-identity).
  • AC4: #15678-class incident is citable as the falsifier if any future client-side contract is proposed (anchor: this body's receipts).

Out of Scope

  • Server-side schema changes (already correct).
  • Server-side string-revive (coercion trap without signature awareness).
  • /video-create content (D#15673 owns the film-side transport-naming rule).

Related

  • #15681 (write-lock lease — split out), #13056 (its parent epic)
  • D#15673 (fold that surfaced this), #15658 (the lane where the boundary bit), test/playwright/e2e/agentos/authenticatedFleetHarness.mjs (the sanctioned object-bearing path)
  • Origin Session ID: eb9be68e-9401-4ecd-9762-ef519b4091ed

Sweeps: live latest-open sweep at 2026-07-22T01:4xZ via gh issue list (latest 20) — no equivalent. Conversion receipt produced by executing buildZodSchema/toOpenApiJsonSchema on origin/dev.

Retrieval Hint: "neural-link call_method kimi client schema normalization items empty string collapse verbatim args in-process workaround".

Amendment 2026-07-22T02:2xZ — Euclid's D+S on PR #15683 reshaped the prescription

The docs-only PR was dropped (ticket-prescription-off). Three upheld drifts: "no Neo server code fix exists" was unproven (the explicit typed-union path was never tested); "silently stringifying objects" overclaimed the receipt (string-narrowed listing ≠ proven coercion); the in-process pointer is unreachable for generic MCP consumers.

Corrected prescription (in order):

  1. Explicit JsonValue union in CallMethodRequest.args.items — the substrate already supports it: buildZodSchemaFromNode maps oneOfz.union (openApiValidator.mjs:143-145), and this server already ships a oneOf string/object precedent (openapi.yaml:575-584). Euclid's in-memory probe: replacing items: {} with the union (nullable string, number, boolean, open object, array) emits args.items.anyOf covering every JSON class.
  2. The decisive falsifier is the Kimi projection of that explicit union (seat-restart probe — the same restart as #15580 AC6): if Kimi preserves the union, the defect is repaired server-side; if it still collapses, the upstream defect report becomes the path and the docs line (truthful, caller-facing only) returns.
  3. No server-side string-revive (coercion trap), no in-process pointers in consumer-facing descriptions.

Contract Ledger (added per the D+S audit):

Contract Source of Authority Required Behavior Fallback / Evidence
Server emission openapi.yaml CallMethodRequest args.items = explicit JsonValue union (oneOf: nullable string, number, boolean, open object, array) raw tools/list probe; compliance spec assertion
Runtime validation buildZodSchemaFromNodez.union parses every representative JSON class (string, number, boolean, null, object, nested array) validator spec additions
Kimi projection Kimi CLI tool listing preserves the explicit union (gate) seat-restart receipt; collapse → upstream defect + truthful docs
Generic MCP access the tool description names heterogeneous args only; NO in-process/repo-internal pointers description stays within the caller's reach
Write-lock sequel WriteGuard TTL/release-on-error #15681 (child of #13056) owns