Skip to content

Commit 7bbe3b6

Browse files
authored
Add output_schema to code mode render (openai#17210)
This updates code-mode tool rendering so MCP tools can surface structured output types from their `outputSchema`. What changed: - Detect MCP tool-call result wrappers from the output schema shape instead of relying on tool-name parsing or provenance flags. - Render shared TypeScript aliases once for MCP tool results (`CallToolResult`, `ContentBlock`, etc.) so multiple MCP tool declarations stay compact. - Type `structuredContent` from the tool definition's `outputSchema` instead of rendering it as `unknown`. - Update the shared MCP aliases to match the MCP draft `CallToolResult` schema more closely. Example: - Before: `declare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };` - After: `declare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };`
1 parent 1de0085 commit 7bbe3b6

12 files changed

Lines changed: 516 additions & 49 deletions

File tree

codex-rs/code-mode/src/description.rs

Lines changed: 342 additions & 27 deletions
Large diffs are not rendered by default.

codex-rs/code-mode/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ pub use description::CODE_MODE_PRAGMA_PREFIX;
77
pub use description::CodeModeToolKind;
88
pub use description::ToolDefinition;
99
pub use description::ToolNamespaceDescription;
10-
pub use description::append_code_mode_sample;
1110
pub use description::augment_tool_definition;
1211
pub use description::build_exec_tool_description;
1312
pub use description::build_wait_tool_description;
1413
pub use description::is_code_mode_nested_tool;
1514
pub use description::normalize_code_mode_identifier;
1615
pub use description::parse_exec_source;
16+
pub use description::render_code_mode_sample;
1717
pub use description::render_json_schema_to_typescript;
1818
pub use response::FunctionCallOutputContentItem;
1919
pub use response::ImageDetail;

codex-rs/core/tests/suite/code_mode.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2280,7 +2280,14 @@ text(JSON.stringify(tool));
22802280
parsed,
22812281
serde_json::json!({
22822282
"name": "mcp__rmcp__echo",
2283-
"description": "Echo back the provided message and include environment data.\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };\n```",
2283+
"description": concat!(
2284+
"Echo back the provided message and include environment data.\n\n",
2285+
"exec tool declaration:\n",
2286+
"```ts\n",
2287+
"declare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): ",
2288+
"Promise<CallToolResult<{ echo: string; env: string | null; }>>; };\n",
2289+
"```",
2290+
),
22842291
})
22852292
);
22862293

codex-rs/rmcp-client/src/bin/rmcp_test_server.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,29 @@ impl TestToolServer {
4545
}))
4646
.expect("echo tool schema should deserialize");
4747

48-
Tool::new(
48+
let mut tool = Tool::new(
4949
Cow::Borrowed("echo"),
5050
Cow::Borrowed("Echo back the provided message and include environment data."),
5151
Arc::new(schema),
52-
)
52+
);
53+
#[expect(clippy::expect_used)]
54+
let output_schema: JsonObject = serde_json::from_value(json!({
55+
"type": "object",
56+
"properties": {
57+
"echo": { "type": "string" },
58+
"env": {
59+
"anyOf": [
60+
{ "type": "string" },
61+
{ "type": "null" }
62+
]
63+
}
64+
},
65+
"required": ["echo", "env"],
66+
"additionalProperties": false
67+
}))
68+
.expect("echo tool output schema should deserialize");
69+
tool.output_schema = Some(Arc::new(output_schema));
70+
tool
5371
}
5472
}
5573

codex-rs/rmcp-client/src/bin/test_stdio_server.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,23 @@ impl TestToolServer {
9191
Cow::Borrowed(description),
9292
Arc::new(schema),
9393
);
94+
#[expect(clippy::expect_used)]
95+
let output_schema: JsonObject = serde_json::from_value(json!({
96+
"type": "object",
97+
"properties": {
98+
"echo": { "type": "string" },
99+
"env": {
100+
"anyOf": [
101+
{ "type": "string" },
102+
{ "type": "null" }
103+
]
104+
}
105+
},
106+
"required": ["echo", "env"],
107+
"additionalProperties": false
108+
}))
109+
.expect("echo tool output schema should deserialize");
110+
tool.output_schema = Some(Arc::new(output_schema));
94111
tool.annotations = Some(ToolAnnotations::new().read_only(true));
95112
tool
96113
}

codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ impl TestToolServer {
9090
Cow::Borrowed("Echo back the provided message and include environment data."),
9191
Arc::new(schema),
9292
);
93+
#[expect(clippy::expect_used)]
94+
let output_schema: JsonObject = serde_json::from_value(json!({
95+
"type": "object",
96+
"properties": {
97+
"echo": { "type": "string" },
98+
"env": {
99+
"anyOf": [
100+
{ "type": "string" },
101+
{ "type": "null" }
102+
]
103+
}
104+
},
105+
"required": ["echo", "env"],
106+
"additionalProperties": false
107+
}))
108+
.expect("echo tool output schema should deserialize");
109+
tool.output_schema = Some(Arc::new(output_schema));
93110
tool.annotations = Some(ToolAnnotations::new().read_only(true));
94111
tool
95112
}

codex-rs/tools/src/code_mode.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ pub fn collect_code_mode_tool_definitions<'a>(
4949
tool_definitions
5050
}
5151

52+
pub fn collect_code_mode_exec_prompt_tool_definitions<'a>(
53+
specs: impl IntoIterator<Item = &'a ToolSpec>,
54+
) -> Vec<CodeModeToolDefinition> {
55+
let mut tool_definitions = specs
56+
.into_iter()
57+
.filter_map(code_mode_tool_definition_for_spec)
58+
.filter(|definition| codex_code_mode::is_code_mode_nested_tool(&definition.name))
59+
.collect::<Vec<_>>();
60+
tool_definitions.sort_by(|left, right| left.name.cmp(&right.name));
61+
tool_definitions.dedup_by(|left, right| left.name == right.name);
62+
tool_definitions
63+
}
64+
5265
pub fn create_wait_tool() -> ToolSpec {
5366
let properties = BTreeMap::from([
5467
(
@@ -95,7 +108,7 @@ pub fn create_wait_tool() -> ToolSpec {
95108
}
96109

97110
pub fn create_code_mode_tool(
98-
enabled_tools: &[(String, String)],
111+
enabled_tools: &[CodeModeToolDefinition],
99112
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
100113
code_mode_only_enabled: bool,
101114
) -> ToolSpec {

codex-rs/tools/src/code_mode_tests.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,13 @@ fn create_wait_tool_matches_expected_spec() {
182182

183183
#[test]
184184
fn create_code_mode_tool_matches_expected_spec() {
185-
let enabled_tools = vec![("update_plan".to_string(), "Update the plan".to_string())];
185+
let enabled_tools = vec![codex_code_mode::ToolDefinition {
186+
name: "update_plan".to_string(),
187+
description: "Update the plan".to_string(),
188+
kind: codex_code_mode::CodeModeToolKind::Function,
189+
input_schema: None,
190+
output_schema: None,
191+
}];
186192

187193
assert_eq!(
188194
create_code_mode_tool(

codex-rs/tools/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub use apply_patch_tool::ApplyPatchToolArgs;
4444
pub use apply_patch_tool::create_apply_patch_freeform_tool;
4545
pub use apply_patch_tool::create_apply_patch_json_tool;
4646
pub use code_mode::augment_tool_spec_for_code_mode;
47+
pub use code_mode::collect_code_mode_exec_prompt_tool_definitions;
4748
pub use code_mode::collect_code_mode_tool_definitions;
4849
pub use code_mode::create_code_mode_tool;
4950
pub use code_mode::create_wait_tool;

codex-rs/tools/src/mcp_tool.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ pub fn mcp_call_tool_result_output_schema(structured_content_schema: JsonValue)
4242
"properties": {
4343
"content": {
4444
"type": "array",
45-
"items": {}
45+
"items": {
46+
"type": "object"
47+
}
4648
},
4749
"structuredContent": structured_content_schema,
4850
"isError": {
4951
"type": "boolean"
5052
},
51-
"_meta": {}
53+
"_meta": {
54+
"type": "object"
55+
}
5256
},
5357
"required": ["content"],
5458
"additionalProperties": false

0 commit comments

Comments
 (0)