MCP tool schema with $defs/$ref is exposed incorrectly to the model
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:
- The Snowflake MCP source defines these fields as structured objects, not strings.
- The Snowflake MCP
tools/list/ FastMCP-inspectedinputSchemais correct and includes$defs/$refforSemanticExpression. - Runtime validation still expects
SemanticExpressionobjects. - 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.
5 Comments
I previously reported what looks like the same bug in #3152. That issue was closed without a fix, and this
$defs/$refMCP 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?
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:
inputSchemagoes throughmcp_tool_to_openai_tool_parts/parse_tool_input_schema"$ref": "#/$defs/SemanticExpression"are not resolved beforesanitize_json_schematype,properties, oritems, 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:$defs/$refparse_tool_input_schemapreserving the same structureThose targeted
codex-coretests passed locally.So my current read is:
#/...refs before sanitization appears to fix itThis 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.
This is a blocker for me to use codex as well.
I ran into this same failure mode while testing MCP
tools/listmetadata: object parameters behind local$defs/$refcan 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:
It reads only a redacted/non-sensitive
tools/listJSON file, does not call tools, and now flags local refs asinput_schema_local_ref, for example: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
$refin 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