MCP elicitation: deny_unknown_fields on McpElicitationSchema rejects top-level 'title', breaking FastMCP/Pydantic servers
What happened
Codex rejects every MCP elicitation/create request from a FastMCP (Python) server with:
unknown field 'title', expected one of '$schema', 'type', 'properties', 'required'
The elicitation never reaches the user; the tool call fails. Observed on codex-cli 0.142.4 (stdio transport); the relevant code is unchanged on main as of 2026-07-05, so current alphas (0.143.0-alpha.36) should reproduce.
Root cause
McpElicitationSchema in codex-rs/app-server-protocol/src/protocol/v2/mcp.rs is declared with #[serde(rename_all = "camelCase", deny_unknown_fields)] and only models $schema / type / properties / required — no top-level title.
FastMCP (and any Pydantic-based server) emits the model's auto-generated title at the top level of requestedSchema. Verified against fastmcp 3.4.2 — this is the wire schema for a plain ctx.elicit(..., response_type=str):
{
"properties": {
"value": {"title": "Value", "type": "string"}
},
"required": ["value"],
"title": "ScalarElicitationType",
"type": "object"
}
Serde's deny_unknown_fields hard-fails on "title" before the form is ever shown.
Why this is a Codex bug rather than a FastMCP one
- The normative MCP schema (2025-11-25
schema.json) does not setadditionalProperties: falseonElicitRequestFormParams.requestedSchema, so extra members liketitlevalidate against the spec. - The official Rust SDK models it: rmcp's
elicitation_schema.rshas a top-leveltitlefield and does not usedeny_unknown_fields. - Codex itself is inconsistent about it: the property-level schemas in the same file (
McpElicitationStringSchema,McpElicitationNumberSchema, ...) all accepttitle— only the object level rejects it.
Impact
Any FastMCP-based MCP server using elicitation is unusable with Codex, despite Codex advertising elicitation: {form, url} in the initialize handshake and having a working form UI (#17043). FastMCP puts title there by default with no supported way to suppress it, so this likely affects a large share of Python MCP servers in the wild.
Suggested fix
Either add the field:
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
or drop deny_unknown_fields from McpElicitationSchema (matching rmcp and the spec's permissive stance). Either is a one-line change.
Repro
pip install fastmcp(3.4.2), minimal server:
```python
from fastmcp import FastMCP, Context
mcp = FastMCP("probe")
@mcp.tool
async def try_elicitation(ctx: Context) -> str:
result = await ctx.elicit("Reply with any short string.", response_type=str)
return f"ELICIT_{result.action}"
mcp.run()
```
- Register it as an MCP server in Codex, call
try_elicitation. - Codex logs the
unknown field 'title'error; the elicitation fails instead of rendering the form.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗