MCP tool schema with $defs/$ref is exposed incorrectly to the model

Open 💬 5 comments Opened Mar 6, 2026 by penggongkui-job

Summary

When using the Snowflake MCP server from Codex, the MCP server returns a correct tools/list schema for query_semantic_view, but the tool definition exposed to the model appears to be degraded/incorrect.

The result is that the model sees dimensions / metrics / facts as if they were string[], while runtime validation still expects structured objects.

Expected behavior

Codex should expose the MCP tool schema to the model in a way that preserves object-array parameter types, including schemas that use local $defs / $ref.

For example, this kind of schema should remain usable:

{
  "$defs": {
    "SemanticExpression": {
      "type": "object",
      "required": ["table", "name"],
      "properties": {
        "table": {"type": "string"},
        "name": {"type": "string"}
      }
    }
  },
  "properties": {
    "dimensions": {
      "type": "array",
      "items": {"$ref": "#/$defs/SemanticExpression"}
    }
  }
}

Actual behavior

The live Snowflake MCP server returns a correct schema, and runtime validation enforces SemanticExpression objects.

But the tool definition visible to the model is effectively flattened/degraded, so the first attempt uses strings like:

{
  "dimensions": ["PAID.PAID_DT"],
  "metrics": ["PAID.PAID_REVENUE"]
}

Then runtime rejects the call with validation errors like:

Input should be a valid dictionary or instance of SemanticExpression

Why this appears to be a Codex-side issue

I verified all of the following locally:

  1. The Snowflake MCP source defines these fields as structured objects, not strings.
  2. The Snowflake MCP tools/list / FastMCP-inspected inputSchema is correct and includes $defs / $ref for SemanticExpression.
  3. Runtime validation still expects SemanticExpression objects.
  4. The mismatch only appears in the tool definition exposed to the model.

That strongly suggests the issue is in Codex's MCP schema adaptation / tool registration layer, not in the MCP server itself.

Suspected root cause

Codex may be dropping or not resolving local $defs / $ref when converting MCP inputSchema into the model-visible tool definition.

Suggested fix

Before registering an MCP tool for model use, dereference or inline local $ref / $defs so object-array schemas are preserved.

Related context

View original on GitHub ↗

5 Comments

ewgdg · 5 months ago

I previously reported what looks like the same bug in #3152. That issue was closed without a fix, and this $defs / $ref MCP schema issue still appears unresolved.

Could someone clarify the current best practice for tool integrations in Codex?

Is MCP still the recommended path, or should users prefer a more traditional OpenAPI-style integration plus a skill file when they have tools with more complex schemas?

SproutSeeds · 5 months ago

I reproduced the likely degradation path locally, and my current read is that this is a Codex-side schema adaptation bug.

What seems to be happening:

  • MCP inputSchema goes through mcp_tool_to_openai_tool_parts / parse_tool_input_schema
  • local refs like "$ref": "#/$defs/SemanticExpression" are not resolved before sanitize_json_schema
  • at that point the referenced node has no type, properties, or items, so the sanitizer falls through and defaults it to "type": "string"

That matches the reported behavior: object-array items become model-visible strings, while runtime validation still expects structured objects.

Locally, I patched that path to inline local #/... refs before sanitization and added focused regression tests for:

  • MCP tool conversion preserving object-array items behind $defs / $ref
  • shared parse_tool_input_schema preserving the same structure

Those targeted codex-core tests passed locally.

So my current read is:

  • the issue is real
  • the narrow failure mode is unresolved local refs degrading to string schemas
  • resolving/inlining local #/... refs before sanitization appears to fix it
Docat0209 · 5 months ago

This is a real pain point. I ran into the same $ref resolution issue when building MCP servers for complex OpenAPI specs.

The approach I found works: resolve all $ref/$defs at build time and flatten nested objects into dot-notation parameters. LLMs handle flat schemas much better than deeply nested ones.

I wrote about the technical details here: Why LLMs Suck at Calling APIs (And How Flat Schemas Fix It)

The implementation is in mcp-openapi if anyone wants to see how the flattening works in practice.

mtessar · 3 months ago

This is a blocker for me to use codex as well.

kayalopez · 3 months ago

I ran into this same failure mode while testing MCP tools/list metadata: object parameters behind local $defs / $ref can look fine in the server response but degrade before model-visible tool calling, so the model sends a string where runtime validation expects an object.

For anyone trying to audit their own MCP server before a client-side fix lands, the quick check I found useful is:

npx --yes --package github:kayalopez/ai-agent-launch-tools#v0.1.11 mcp-permission-matrix --file tools-list.json --json

It reads only a redacted/non-sensitive tools/list JSON file, does not call tools, and now flags local refs as input_schema_local_ref, for example:

inputSchema.properties.parent.$ref

The practical workaround remains the same as the proposed fix here: inline/dereference local refs before handing tool schemas to an LLM tool adapter, or avoid $ref in public tool input schemas until the client path resolves them reliably.

Free browser version if local CLI is inconvenient:

https://ai-launch-risk-check-public.vercel.app/mcp-permission-matrix-importer.html