exec bug - MCP Tool Conversion Error: firecrawl__firecrawl_extract
What version of Codex is running?
codex-cli 0.42.0-alpha.1
Which model were you using?
gpt-5-codex
What platform is your computer?
Linux 6.8.0-84-generic x86_64 x86_64
TL;DR
When Codex bridges MCP tools into OpenAI Responses tools, it translates each server-provided JSON Schema into a narrow internal representation (JsonSchema). The Firecrawl MCP server advertises the tool firecrawl__firecrawl_extract with an object-valued additionalProperties. Our serializer only accepts booleans for that field, so serde_json::from_value::<JsonSchema> fails and we log:
ERROR codex_core::openai_tools: Failed to convert "firecrawl__firecrawl_extract" MCP tool to OpenAI tool: Error("invalid type: map, expected a boolean", line: 0, column: 0)
The rest of codex exec continues, but the Firecrawl tool never reaches the model.
---
How to Reproduce
- Ensure Codex is configured with an MCP server that exposes the Firecrawl tool (e.g. via the default
firecrawlconfig). - Run anything through
codex exec, for example:
``bash``
codex exec --skip-git-repo-check --experimental-json "Implement a nice looking fully featured tetris game in this directory using only html and javascript."
- Observe the
ERROR codex_core::openai_toolslog lines before command execution output.
This is deterministic: any command that causes Codex to enumerate MCP tools will hit the same log line as long as the Firecrawl server is reachable.
---
Execution Path Reference
codex exec(CLI) ->codex_core->McpConnectionManager::list_all_toolscollects MCP tool metadata.get_openai_toolsincodex-rs/core/src/openai_tools.rsmerges built-in tools with the MCP list.- Each MCP entry flows through
mcp_tool_to_openai_tool, which sanitizes and deserializes theinput_schema. sanitize_json_schemarecursively massages the schema before deserializing into theJsonSchemaenum.
Key locations:
| File | Lines | Notes |
| --- | --- | --- |
| codex-rs/core/src/openai_tools.rs | 330-362 | mcp_tool_to_openai_tool and sanitizing logic |
| codex-rs/core/src/openai_tools.rs | 125-158 | JsonSchema::Object allows additional_properties: Option<bool> only |
| codex-rs/core/src/openai_tools.rs | 457-463 | sanitize_json_schema recurses into object additionalProperties, but never changes the type |
| codex-rs/core/src/openai_tools.rs | 535-538 | Logging site for failed conversions |
---
Root Cause Details
Firecrawl’s Schema Shape
The Firecrawl MCP server reports an input schema similar to:
{
"type": "object",
"properties": {
"url": { "type": "string" },
"format": { "type": "string" },
"onlyMainContent": { "type": "boolean" },
"options": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"selector": { "type": "string" },
"action": { "type": "string" }
},
"required": ["selector", "action"],
"additionalProperties": false
}
}
},
"additionalProperties": false
}
The innermost additionalProperties is itself an object schema.
Codex Representation Gap
JsonSchema::Object in Codex models the limited subset { properties: BTreeMap<...>, required: Option<Vec<_>>, additional_properties: Option<bool> }. It cannot hold a schema value for additionalProperties even though JSON Schema allows it.
When sanitize_json_schema hits the Firecrawl schema, it recurses into the nested additionalProperties object but leaves it as a JSON object. After sanitation we call serde_json::from_value::<JsonSchema>(serialized_input_schema). Serde expects a boolean but encounters a map, raising invalid type: map, expected a boolean. That bubbles back to get_openai_tools, which logs and skips the tool.
---
Fix Options
Option 1: Extend JsonSchema::Object to Support Schema-Valued additionalProperties
Summary: Teach our internal schema to represent additionalProperties as either a boolean or another schema. This aligns with JSON Schema and ensures future MCP servers with similar constructs work.
Implementation Outline:
- Update
JsonSchema::Objectdefinition incodex-rs/core/src/openai_tools.rs:
```rust
Object {
properties: BTreeMap<String, JsonSchema>,
required: Option<Vec<String>>,
#[serde(rename = "additionalProperties", skip_serializing_if = "Option::is_none")]
additional_properties: Option<Box<AdditionalProperties>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum AdditionalProperties {
Bool(bool),
Schema(JsonSchema),
}
```
- Adjust all constructors/tests that assume
additional_properties: Option<bool>. - Update
sanitize_json_schemaso that:
- Boolean values are left untouched.
- Object values are recursively sanitized (existing behavior) and left as JSON objects so
JsonSchemacan pick them up asSchema.
- Ensure serialization back to OpenAI matches the expected schema. The Responses API tolerates JSON Schema objects in
additionalPropertiesas long as they conform to the standard. - Add unit tests verifying conversion succeeds for a schema with nested
additionalProperties, ideally modeled after Firecrawl.
Trade-offs: Slightly increases complexity of the schema model but is the most correct general solution.
Option 2: Coerce Unsupported additionalProperties to false
Summary: Drop schema-valued additionalProperties entirely and substitute false. This keeps the schema within our current enum without broad refactors.
Implementation Outline:
- In
sanitize_json_schema, when encounteringadditionalPropertieswith a non-boolean value, replace it withfalseand optionally log a warning to inform the user that extra properties will be rejected.
``rust``
if let Some(ap) = map.get_mut("additionalProperties") {
match ap {
JsonValue::Bool(_) => {}
_ => {
sanitize_json_schema(ap); // optional: sanitize to avoid surprises
*ap = JsonValue::Bool(false);
tracing::warn!("Coercing schema-valued additionalProperties to false for tool {name}");
}
}
}
- Add targeted tests verifying the coercion prevents runtime errors and leaves the rest of the schema unchanged.
Trade-offs: Quick to implement but lossy—agents lose hints about allowable dynamic keys. This may not be acceptable if Firecrawl relies on arbitrary option names.
Option 3: Reject Tool with Actionable Error Message
Summary: If we prefer not to support advanced schemas, fail fast with an explicit message instructing users to adjust their MCP server configuration.
Implementation Outline:
- Detect object-valued
additionalPropertiesand return an error variant that includes the offending tool name and a human-readable explanation. - Surface that message all the way to the CLI, so users learn how to fix their MCP configuration.
Trade-offs: Preserves current behavior (tool unavailable) but improves UX. Does not unlock the Firecrawl tool.
---
Recommended Fix
Adopt Option 1. Supporting schema-valued additionalProperties aligns with the JSON Schema spec, reduces future surprises, and preserves Firecrawl’s richer interface. The implementation is contained within openai_tools.rs and associated tests. Once complete, Codex will stop logging the conversion error and the Firecrawl tool will become available to the model.
---
Testing Checklist
After implementing any option, run:
just fmtjust fix -p codex-core(adjust crate name if necessary)cargo test -p codex-core
Add a unit test under #[cfg(test)] in openai_tools.rs that constructs a mock tool mimicking Firecrawl’s schema and asserts mcp_tool_to_openai_tool succeeds.
---
Follow-Up Considerations
- Audit other MCP servers for similar schema patterns (e.g., union types, array tuples). Extend
JsonSchemaincrementally as needed. - When adding deeper schema support, ensure the serialized output still complies with OpenAI’s tool schema requirements (particularly around
strictmode and top-leveltype). - Improve logging to include guidance when schemas are coerced or rejected.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗