Summary
When a tool result contains a resource_link content block (the io.modelcontextprotocol/ui MCP Apps extension), Codex fails the whole call with Unexpected response type. A result without it works. The resource_link block is the only variable.
Environment
- Codex Desktop 0.150.0-alpha.8 (also observed on ChatGPT desktop 26.820.60940)
- rmcp
=3.1.3 (per codex-rs/Cargo.toml)
- Server negotiates protocol
2025-11-25
Reproduce
Minimal, zero-dependency Node stdio server (101 lines): https://github.com/ahonn/codex-mcp-apps-repro
git clone https://github.com/ahonn/codex-mcp-apps-repro
# ~/.codex/config.toml
[mcp_servers.appsrepro]
command = "node"
args = ["/absolute/path/to/codex-mcp-apps-repro/server.js"]
Start a session and call each tool:
plain_tool -> { "content": [ { "type": "text" } ], "isError": false } ✅
view_tool -> fails ❌:
tool call failed for `appsrepro/view_tool`
Caused by:
Unexpected response type
The two tools differ only in the result: view_tool appends one resource_link block.
Triggering result (spec-valid)
{
"content": [
{ "type": "text", "text": "result with a view" },
{ "type": "resource_link", "uri": "ui://demo/view/v1", "mimeType": "text/html;profile=mcp-app" }
],
"isError": false
}
Expected vs actual
- Expected: the result is accepted; the
resource_link block is handled or ignored.
- Actual: the call fails with rmcp
ServiceError::UnexpectedResponse.
Analysis
Unexpected response type is rmcp's ServiceError::UnexpectedResponse, raised client-side when a ServerResult does not match CallToolResult. However, rmcp 3.1.3 deserializes this response without issue: a standalone rmcp 3.1.3 client, via both call_tool and the manual send_request + match ServerResult::CallToolResult path used for pre-2026-07-28 servers, accepts it and returns [text, resource_link]. So the defect appears to be in the layer that consumes tool results, not in rmcp_client deserialization. The server advertises the io.modelcontextprotocol/ui extension in initialize, and view_tool returns a spec-valid resource_link.
Impact
Any MCP server exposing MCP Apps content is unusable from Codex for its UI-bound tools. A server-side workaround is to stop advertising the ui extension to Codex.
Summary
When a tool result contains a
resource_linkcontent block (theio.modelcontextprotocol/uiMCP Apps extension), Codex fails the whole call withUnexpected response type. A result without it works. Theresource_linkblock is the only variable.Environment
=3.1.3(percodex-rs/Cargo.toml)2025-11-25Reproduce
Minimal, zero-dependency Node stdio server (101 lines): https://github.com/ahonn/codex-mcp-apps-repro
Start a session and call each tool:
plain_tool->{ "content": [ { "type": "text" } ], "isError": false }✅view_tool-> fails ❌:The two tools differ only in the result:
view_toolappends oneresource_linkblock.Triggering result (spec-valid)
{ "content": [ { "type": "text", "text": "result with a view" }, { "type": "resource_link", "uri": "ui://demo/view/v1", "mimeType": "text/html;profile=mcp-app" } ], "isError": false }Expected vs actual
resource_linkblock is handled or ignored.ServiceError::UnexpectedResponse.Analysis
Unexpected response typeis rmcp'sServiceError::UnexpectedResponse, raised client-side when aServerResultdoes not matchCallToolResult. However, rmcp3.1.3deserializes this response without issue: a standalone rmcp 3.1.3 client, via bothcall_tooland the manualsend_request+match ServerResult::CallToolResultpath used for pre-2026-07-28servers, accepts it and returns[text, resource_link]. So the defect appears to be in the layer that consumes tool results, not inrmcp_clientdeserialization. The server advertises theio.modelcontextprotocol/uiextension ininitialize, andview_toolreturns a spec-validresource_link.Impact
Any MCP server exposing MCP Apps content is unusable from Codex for its UI-bound tools. A server-side workaround is to stop advertising the ui extension to Codex.