Skip to content

MCP tool schema validation fails with HTTP 400 ("moonshot flavored json schema") due to missing schema sanitization in new CLI #792

Description

@creatiVision

What version of Kimi Code is running?

0.14.3

Which open platform/subscription were you using?

platform.kimi.ai

Which model were you using?

kimi-k2.7-code thinking

What platform is your computer?

Linux 6.8.0-124-generic x86_64 x86_64

What issue are you seeing?

Bug Description
When connecting standard MCP servers (such as an n8n Docker container) to the new kimi-code-cli and utilizing a native Moonshot API model (e.g., moonshot-ai/kimi-k2.7-code), the CLI crashes when attempting to parse the MCP tool capabilities.

The native API returns an HTTP 400 error stating that the tools.function.parameters is not a valid "moonshot flavored json schema." The new TypeScript-based CLI appears to be passing the raw, standard JSON schema emitted by the MCP server directly to the backend without adapting it to Kimi's strict flavor requirements.

Regression Context
This is a regression from the older Python-based kimi-cli. In the previous architecture, the kosong abstraction layer contained explicit interceptors (e.g., fix(kosong/kimi): fill in missing JSON Schema type for MCP tool parameters #1182) that would automatically repair standard JSON schemas. It successfully reformatted structures like anyOf items and missing type constraints to satisfy the strict Moonshot API validator before transmission.

Sanitized Debug Data / Session Export

WIPED SESSION DEBUG EXPORT - PII REDACTED
Session ID: session_e1894fd0-de3c-44ce-b4cf-78957c1026ed
Provider: kimi
Model: moonshot-ai/kimi-k2.7-code
Capabilities: ["thinking"]

[User Input]
✨ make n8n mcp (probably a docker container on xchg) available for hermes, also make sure n8n docker container or native runs on this system

[Provider API Error]
Error: [provider.api_error] 400 Invalid request: tools.function.parameters is not a valid moonshot flavored json schema, details: <At path 'properties.pageSetup.properties.size.anyOf.items': items must be an object>

The MCP server provided a standard JSON schema containing an anyOf array under pageSetup.size. The Moonshot API rejected the payload because standard JSON schema structures were passed directly without the required "moonshot flavor" type coercion for the array items.

What steps can reproduce the bug?

Run a local or Docker-based MCP server that outputs standard JSON Schema parameters with nested anyOf objects (e.g., n8n).

Add the MCP server to the CLI configuration.

Switch the active model to moonshot-ai/kimi-k2.7-code with thinking enabled.

Prompt the agent to evaluate or list the available tools from the MCP server.

The API immediately rejects the tool schema payload.

What is the expected behavior?

The CLI should feature a translation or sanitization layer that intercepts the inputSchema from external MCP servers. It should automatically coerce standard JSON Schema structures into the strict "Moonshot Flavored JSON Schema" (e.g., ensuring items inside anyOf are strictly typed as objects) before serializing the API request.

Additional information

Deep-Dive: Moonshot Flavored JSON Schema (MFJS) & Walle
The Moonshot API relies on a strictly enforced server-side Go schema validator called Walle (MoonshotAI/walle). Walle evaluates parameters against Moonshot Flavored JSON Schema (MFJS), which is a highly restrictive subset of the JSON Schema standard optimized for LLM readability.

MFJS introduces strict limitations that directly clash with standard MCP tool schemas:

Explicit Types: Every single property must have an explicitly defined type.

anyOf / oneOf Parent Constraints: While standard JSON Schema allows declaring a type in the parent of an anyOf array, MFJS mandates that type definitions reside strictly inside the children objects.

The anyOf.items Crash: In a typical document setup tool, parameters like size utilize anyOf to accept either a predefined string (like "LETTER") or an array of dimensions (like ``):

JSON
"size": {
"anyOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "number" } }
]
}
When the raw schema is sent, walle flattens and parses the array items inside the anyOf child. Because the schema has not been sanitized locally, Walle's type engine fails and throws the error:
At path 'properties.pageSetup.properties.size.anyOf.items': items must be an object.

Why This is a Regression
In the legacy Python-based kimi-cli, this was managed seamlessly by the kosong orchestration layer. kosong featured built-in schema-repair adapters (such as PR #1182: fix(kosong/kimi): fill in missing JSON Schema type for MCP tool parameters) which intercepted standard schemas and compiled them down to compliant MFJS before dispatching the payload.

The new TypeScript CLI lacks this pre-flight translation layer, sending raw schemas over the wire and triggering backend validation crashes.

Steps to Reproduce
Configure an MCP server (such as n8n or mcp-pdf) that defines an input schema using anyOf types or nested arrays.

Set the active model to moonshot-ai/kimi-k2.7-code with thinking on.

Prompt the agent with a task that triggers tool discovery or use.

The system immediately crashes with an HTTP 400 error.

Expected Behavior
The CLI should feature a local JSON Schema adapter layer (replicating the legacy Python kosong behavior) to automatically normalize and sanitize standard JSON Schemas to conform to MFJS requirements prior to network transmission.

Activity

  1. josephkehan-prog commented on Jul 25, 2026

    @josephkehan-prog

    Still reproducible on 0.23.5, with a second variant of this bug class — the current TS kosong normalizer doesn't cover it.

    Error

    [provider.api_error] 400 tools.function.parameters is not a valid moonshot flavored json schema,
    details: <At path 'root': when using anyOf, type should be defined in anyOf items instead of the parent schema>
    

    Root cause

    normalizeKimiToolSchema in packages/kosong (dist: kimi-*.mjs) fills in missing type fields, but:

    1. The root schema object is explicitly skipped — ensureKimiPropertyTypes only calls recurseSchema(normalized), which visits children but never normalizes the root node itself.
    2. Neither root nor nested nodes handle the MFJS rule above: a node declaring both type and a combinator (anyOf / oneOf / allOf) is passed through unchanged, so walle rejects it. Any tool whose parameter root is { "type": "object", ..., "anyOf": [...] } (or nests that shape under properties / not / if/then/else) triggers the 400.

    Tested fix

    Distribute the parent type into combinator items that lack one, then drop it from the parent — applied to the root and to every visited node:

    function fixCombinatorParentType(node) {
    	if (!isRecord(node) || !hasOwn(node, "type")) return;
    	for (const key of ["anyOf", "oneOf", "allOf"]) {
    		const items = node[key];
    		if (!Array.isArray(items) || items.length === 0) continue;
    		for (const item of items)
    			if (isRecord(item) && !hasOwn(item, "type")) item["type"] = node["type"];
    		delete node["type"];
    	}
    }
    
    function ensureKimiPropertyTypes(schema) {
    	const normalized = cloneJsonValue(schema);
    	if (!isRecord(normalized)) throw new Error("JSON Schema root must normalize to an object.");
    	fixCombinatorParentType(normalized); // was missing: root never normalized
    	recurseSchema(normalized);
    	return normalized;
    }
    
    function normalizeProperty(node) {
    	if (!isRecord(node)) return;
    	fixCombinatorParentType(node); // was missing: nested type+combinator nodes passed through
    	// ... existing logic unchanged
    }

    Verification — ran the patched module against three failing shapes:

    • {type:"object", anyOf:[{required:["input"]},{required:["runInput"]}]} → {anyOf:[{required:["input"],type:"object"},{required:["runInput"],type:"object"}]} ✓
    • nested combinator under properties.<name> → fixed recursively ✓
    • items that already declare their own type → preserved; only the parent type is dropped ✓

    The existing TYPE_COMPLETION_SKIP_KEYS already includes anyOf/oneOf/allOf, so the type-inference logic doesn't re-add a parent type afterwards.

  2. creatiVision commented on Aug 24, 2026

    @creatiVision
    Author

    Just a quick update for anyone following this: this issue is still fully viable and currently blocking the use of many standard MCP servers (like mcp-pdf, n8n, or the Xcode MCP) on kimi-code when using Moonshot models.

    The root cause remains the missing pre-flight schema adapter for Moonshot Flavored JSON Schema (MFJS), which was present in the legacy python CLI but got dropped in the TS rewrite.

    This is fully addressed by the open pull request #830, which introduces sanitizeMcpSchema during MCP tool discovery. It automatically intercepts, unpacks $ref pointers, and repairs anyOf / items structures into valid MFJS before the network call is ever made.

    Hopefully it gets merged soon!

  3. crycrywhy commented on Sep 9, 2026

    @crycrywhy

    Another shape of this bug — root cause pinned + minimal repro (OpenAI-compatible gateway fronting Vertex AI / Gemini)

    kimi-code 0.7.5 (VS Code extension), 11 MCP servers ≈ 280 tools. Every subagent spawn fails with the gateway wrapping a 400 into a 503:

    Invalid value at 'request.tools[0].function_declarations[89].parameters.properties[2].value' (type.googleapis.com/google.cloud.aiplatform.master.Schema), "object"
    

    (plus indices 98, 99, 110)

    Root cause. Mapping the rejected indices against the session's tool snapshot, then running a feature matrix over all 280 tool schemas: the 4 rejected tools are exactly the only 4 tools that have a parameter literally named properties — all from the official Notion MCP server (API-create-a-data-source, API-patch-page, API-post-page, API-update-a-data-source).

    Minimal repro (direct call, single tool):

    • {"type":"object","properties":{"foo":{"type":"string"}}} → accepted
    • {"type":"object","properties":{"properties":<anything>}} → same INVALID_ARGUMENT, value "object" — regardless of the value (anyOf, {"type":"object"}, {"type":"string"}, even {}). The converter appears to mistranslate a properties-map entry named properties into the bare string "object".

    Full-payload verification. Replaying the real 280-tool request: raw → exactly the 4 production violations; after dropping only the properties-named entries (and their required mentions) → accepted.

    Relation to the open PRs. Neither #808 nor #830 covers this shape — both dereference $ref and fill missing type but leave combinator-carrying properties untouched and never special-case a properties-map entry named properties. Suggest adding this case (drop/escape such entries client-side, or fix the properties key-name collision in the converter) so Vertex-fronted backends are covered too.

    Local workaround that unblocked me: wrapped toolToOpenAI in bundled dist/extension.js with a sanitizer that deletes properties-named entries from properties maps; node --check passes and the gateway replay succeeds.

  4. genesiscode2026 commented on Sep 27, 2026

    @genesiscode2026

    Confirming this normalization breakdown with standard MCP server schemas.

    The underlying disconnect is that the MCP specification allows full JSON Schema (draft 2020-12), whereas Moonshot / OpenAI-compatible function calling endpoints enforce strict structural constraints:

    1. type: "object" must be explicitly declared at the root of parameters.
    2. Meta-schema keywords like $schema, $defs, and definitions are rejected by the API parameter validator.
    3. Root-level oneOf / anyOf constructs that are not encapsulated within an object wrapper trigger the 400 tools.function.parameters is not a valid schema error.

    A lightweight sanitizer function applied before submitting tool manifests to the API resolves the rejection:

    export function sanitizeMcpToolParameters(schema: Record<string, any>): Record<string, any> {
      const clean = { ...schema };
      delete clean.$schema;
      delete clean.$defs;
      delete clean.definitions;
    
      if (!clean.type || clean.type !== "object") {
        clean.type = "object";
      }
      if (!clean.properties && !clean.additionalProperties) {
        clean.properties = {};
      }
      return clean;
    }

    In our Agent Assurance test suite, we evaluate MCP server tool definitions against diverse LLM gateway validation rules to prevent runtime dispatch crashes. If helpful, I can provide a comprehensive parameter normalizer handling recursive union properties.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions