LearnNewsExamplesServices
Frontmatter
id17220
titleMemory-core MCP spec: posture enum-null emits a type-less schema
stateClosed
labels
bugai
assigneesneo-kimi-iris
createdAtAug 16, 2026, 12:09 AM
updatedAtAug 16, 2026, 12:54 AM
githubUrlhttps://github.com/neomjs/neo/issues/17220
authorneo-kimi-iris
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 16, 2026, 12:33 AM

Memory-core MCP spec: posture enum-null emits a type-less schema

neo-kimi-iris
neo-kimi-iris commented on Aug 16, 2026, 12:09 AM

Context

Since 2026-08-15 ~20:40Z the Kimi Code CLI harness marks the neo-mjs-memory-core MCP server unavailable at attach; zero of its 42 tools reach the seat (@neo-kimi-iris). Harness log evidence (~/.kimi-code/logs/kimi-code.log):

2026-08-15T20:40:29.554Z ERROR mcp server unavailable server=neo-mjs-memory-core transport=http status=failed reason="\"nullable\" cannot be used without \"type\""
2026-08-15T21:54:56.751Z ERROR mcp server unavailable server=neo-mjs-memory-core transport=http status=failed reason="\"nullable\" cannot be used without \"type\""

The server itself is healthy: a raw streamable-HTTP probe of http://127.0.0.1:3102/mc/mcp completes initialize and returns all 42 tools in a single tools/list page (no nextCursor). The other three configured servers (24 + 13 + 59 = 96 tools) attach fine. This falsifies a harness tool-cap/pagination theory: the failure is schema validation, not volume. Other harnesses (Claude Code, Gemini CLI, Codex, OpenCode) attach the same server without complaint — they do not strictly validate tool schemas.

The Problem

HealthCheckResponse.heavyMaintenanceStarvation.posture in ai/mcp/server/memory-core/openapi.yaml:3403-3407 is the only node across all six MCP server specs that declares BOTH nullable: true AND a literal null inside its enum:

posture:
  type: string
  nullable: true
  enum: [degraded, healthy, unknown, disabled, null]

Conversion pipeline (ai/mcp/validation/openApiValidator.mjs): buildZodSchemaFromNode (lines 218-219) compiles the enum via z.enum(schema.enum) — including the null member — then wraps .nullable() (lines 255-257). Zod v4's z.toJSONSchema(..., {target: 'openapi-3.0'}) cannot assert type: 'string' over an enum containing null, so it emits:

{"nullable": true, "enum": ["degraded","healthy","unknown","disabled", null]}

— no type. Kimi Code CLI's tool-schema validator rejects nullable without type and marks the entire server unavailable: one malformed node in one tool's outputSchema removes all 42 memory-core tools from the seat.

Local reproduction (repo node_modules/zod):

  • z.enum(['degraded','healthy','unknown','disabled', null]).nullable() → {"nullable":true,"enum":[...]} (no type — matches the live-served node byte-shape)
  • z.enum(['degraded','healthy','unknown','disabled']).nullable() → {"nullable":true,"type":"string","enum":[...]}

Census (js-yaml walk over all ai/mcp/server/*/openapi.yaml): exactly one null-in-enum node exists — this one.

Introduced with the heavy-maintenance starvation watchdog surface. Semantically the null enum member is redundant: nullable: true already admits null, and the runtime returns posture: null (ai/mcp/server/memory-core/toolService.mjs:286, ai/services/memory-core/HealthService.mjs:1085).

The Architectural Reality

  • Contract SSOT: ai/mcp/server/memory-core/openapi.yaml → components.schemas.HealthCheckResponse.properties.heavyMaintenanceStarvation.properties.posture (line 3403).
  • Converter: ai/mcp/validation/openApiValidator.mjs — string-enum branch (218-222), nullable wrap (255-257), toOpenApiJsonSchema (15-19, zod v4 native emission, openapi-3.0 target).
  • Listing assembly: ai/mcp/ToolService.mjs:176-183,227-240 — outputSchema flows verbatim from the converter into tools/list.
  • The MCP surface is served by a remote daemon behind an ssh forward (127.0.0.1:3102, local LISTEN held by the ssh client process) — the fix requires a daemon redeploy to take effect; that redeploy is operator territory.

The Fix

One line in ai/mcp/server/memory-core/openapi.yaml: drop null from the posture enum, keep nullable: true. Served schema becomes {type: 'string', nullable: true, enum: [degraded, healthy, unknown, disabled]} — passes strict validation; runtime null-acceptance is preserved by the .nullable() wrap at openApiValidator.mjs:255-257.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
healthcheck MCP tool outputSchema → heavyMaintenanceStarvation.posture ai/mcp/server/memory-core/openapi.yaml HealthCheckResponse Served node carries type: 'string' + nullable: true + 4-string enum Strict MCP clients reject the whole server today unchanged (the openapi description already documents null semantics) live tools/list probe 2026-08-15T21:59Z + local zod emission repro

Decision Record impact: none.

Acceptance Criteria

  • AC1: posture enum contains no null member; nullable: true retained (one-line diff).
  • AC2: Pre-merge local proof: building the healthcheck output schema via openApiValidator.mjs emits a posture node WITH type: 'string' and nullable: true.
  • AC3: Census re-run over ai/mcp/server/*/openapi.yaml shows zero null-in-enum nodes.
  • AC4 (post-merge + daemon redeploy, operator action): a fresh Kimi Code CLI session attaches neo-mjs-memory-core — 42 tools visible, no nullable error in ~/.kimi-code/logs/kimi-code.log.

Out of Scope

  • Converter-side hardening (strip null members from enums / re-add type in buildZodSchemaFromNode or toOpenApiJsonSchema) — fixes the CLASS but touches the shared validation pipeline every MCP server uses; candidate follow-up, deliberately not bundled into this one-line repair.
  • Kimi Code CLI validator relaxation — upstream, not this repo; the strictness is legitimate, our spec was the outlier.
  • Daemon redeploy mechanics.

Avoided Traps

  • Treating this as a harness tool-count limit: the 100-tool/pagination theory is falsified by the live probe (single page, no cursor) and the log's schema-validation error. Fix the contract, not the arithmetic.
  • "Drop nullable: true and keep null in the enum": zod's emission stays type-less; strict validators keep rejecting the server.

Related

  • #16677 — the 08-09 memory-core wedge was a different failure (daemon-side GitHub PAT validation timeouts, same log file 14:16-15:47Z on 2026-08-09); today's is harness-side schema rejection. Same symptom family (seat loses mc), disjoint causes.

Origin Session ID: session_d72a0190-8d4a-47aa-b22f-58d988e1c878

Handoff Retrieval Hints

Retrieval Hint: "memory-core openapi posture enum nullable type-less zod openapi-3.0 strict MCP client"

Live latest-open sweep: checked latest 20 created open issues (#17140–#17217) at 2026-08-15T22:08Z; no equivalent found. A2A claim sweep: latest 30 mailbox messages (15:49Z–22:00Z), no [lane-claim]/[lane-intent] overlapping this scope (read via a direct server call; the harness-side mc attach is the very defect being fixed).

tobiu referenced in commit 9f38f6a - "fix(ai): drop null from healthcheck posture enum (#17220) (#17221)" on Aug 16, 2026, 12:33 AM
tobiu closed this issue on Aug 16, 2026, 12:33 AM