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-3 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 (buildZodSchema → toOpenApiJsonSchema 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 oneOf → z.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 buildZodSchemaFromNode → z.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
tobiu referenced in commit 49072a9 - "fix(ai): explicit JsonValue union for call_method args (#15678) (#15685)" on Jul 22, 2026, 10:54 AM
tobiu closed this issue on Jul 22, 2026, 10:54 AM