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
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).
Context
Since 2026-08-15 ~20:40Z the Kimi Code CLI harness marks the
neo-mjs-memory-coreMCP server unavailable at attach; zero of its 42 tools reach the seat (@neo-kimi-iris). Harness log evidence (~/.kimi-code/logs/kimi-code.log):The server itself is healthy: a raw streamable-HTTP probe of
http://127.0.0.1:3102/mc/mcpcompletesinitializeand returns all 42 tools in a singletools/listpage (nonextCursor). 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.postureinai/mcp/server/memory-core/openapi.yaml:3403-3407is the only node across all six MCP server specs that declares BOTHnullable: trueAND a literalnullinside itsenum: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 viaz.enum(schema.enum)— including the null member — then wraps.nullable()(lines 255-257). Zod v4'sz.toJSONSchema(..., {target: 'openapi-3.0'})cannot asserttype: '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 rejectsnullablewithouttypeand marks the entire server unavailable: one malformed node in one tool'soutputSchemaremoves 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":[...]}(notype— 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 onenull-in-enum node exists — this one.Introduced with the heavy-maintenance starvation watchdog surface. Semantically the
nullenum member is redundant:nullable: truealready admits null, and the runtime returnsposture: null(ai/mcp/server/memory-core/toolService.mjs:286,ai/services/memory-core/HealthService.mjs:1085).The Architectural Reality
ai/mcp/server/memory-core/openapi.yaml→components.schemas.HealthCheckResponse.properties.heavyMaintenanceStarvation.properties.posture(line 3403).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).ai/mcp/ToolService.mjs:176-183,227-240— outputSchema flows verbatim from the converter intotools/list.The Fix
One line in
ai/mcp/server/memory-core/openapi.yaml: dropnullfrom thepostureenum, keepnullable: 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 atopenApiValidator.mjs:255-257.Contract Ledger Matrix
healthcheckMCP tooloutputSchema→heavyMaintenanceStarvation.postureai/mcp/server/memory-core/openapi.yamlHealthCheckResponsetype: 'string'+nullable: true+ 4-string enumtools/listprobe 2026-08-15T21:59Z + local zod emission reproDecision Record impact: none.
Acceptance Criteria
postureenum contains nonullmember;nullable: trueretained (one-line diff).openApiValidator.mjsemits aposturenode WITHtype: 'string'andnullable: true.ai/mcp/server/*/openapi.yamlshows zeronull-in-enum nodes.neo-mjs-memory-core— 42 tools visible, nonullableerror in~/.kimi-code/logs/kimi-code.log.Out of Scope
typeinbuildZodSchemaFromNodeortoOpenApiJsonSchema) — fixes the CLASS but touches the shared validation pipeline every MCP server uses; candidate follow-up, deliberately not bundled into this one-line repair.Avoided Traps
nullable: trueand keep null in the enum": zod's emission stays type-less; strict validators keep rejecting the server.Related
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).