MCP elicitation: deny_unknown_fields on McpElicitationSchema rejects top-level 'title', breaking FastMCP/Pydantic servers

Open 💬 5 comments Opened Jul 5, 2026 by fugu-ai

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 set additionalProperties: false on ElicitRequestFormParams.requestedSchema, so extra members like title validate against the spec.
  • The official Rust SDK models it: rmcp's elicitation_schema.rs has a top-level title field and does not use deny_unknown_fields.
  • Codex itself is inconsistent about it: the property-level schemas in the same file (McpElicitationStringSchema, McpElicitationNumberSchema, ...) all accept title — 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

  1. 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()
```

  1. Register it as an MCP server in Codex, call try_elicitation.
  2. Codex logs the unknown field 'title' error; the elicitation fails instead of rendering the form.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗