mirror of
https://github.com/openai/codex.git
synced 2026-09-07 15:40:00 +00:00
## Summary Codex Apps file parameters are exposed to the model as local paths, uploaded at execution time, and rewritten into provided-file payloads before the MCP tool call. The rewrite currently includes the documented optional fields `mime_type` and `file_name` for every file parameter. Apps with strict schemas can reject those fields when they are not declared. ## Changes - Derive the supported optional file fields from each `openai/fileParams` parameter's raw input schema before replacing it with the model-visible local-path schema. - Always include `download_url` and `file_id`. - Include `mime_type` and `file_name` only when that specific file parameter's schema accepts them, including schemas that allow additional properties. - Handle scalar and array file parameters, including items-only arrays, composed schemas, and local JSON Schema references. - Preserve the existing restriction that only the host-owned Codex Apps MCP server can use this upload path. This supports tools with different file contracts in the same app: one parameter can accept the optional fields while another remains strict. ## Validation - `just test -p codex-mcp` - `just test -p codex-core mcp_openai_file` - `just test -p codex-core codex_apps_file_params_` - `just fix -p codex-mcp` - `just fix -p codex-core` - `just fmt` - `git diff --check` - Manually verified in the Codex Electron app that: - a strict file schema receives only `download_url` and `file_id` - a rich file schema also receives `mime_type` and `file_name` Related: #31330
71 lines
1.9 KiB
Rust
71 lines
1.9 KiB
Rust
//! Codex Apps support for the host-owned apps MCP server.
|
|
//!
|
|
//! This module owns the normalization that turns ChatGPT-hosted app
|
|
//! connector/tool metadata into model-visible MCP callable names.
|
|
|
|
use codex_utils_plugins::mcp_connector::sanitize_name;
|
|
|
|
mod file_params;
|
|
|
|
pub use file_params::declared_openai_file_input_param_names;
|
|
pub(crate) use file_params::prepare_openai_file_params_for_model;
|
|
|
|
pub(crate) fn normalize_codex_apps_tool_title(connector_name: Option<&str>, value: &str) -> String {
|
|
let Some(connector_name) = connector_name
|
|
.map(str::trim)
|
|
.filter(|name| !name.is_empty())
|
|
else {
|
|
return value.to_string();
|
|
};
|
|
|
|
let prefix = format!("{connector_name}_");
|
|
if let Some(stripped) = value.strip_prefix(&prefix)
|
|
&& !stripped.is_empty()
|
|
{
|
|
return stripped.to_string();
|
|
}
|
|
|
|
value.to_string()
|
|
}
|
|
|
|
pub(crate) fn normalize_codex_apps_callable_name(
|
|
tool_name: &str,
|
|
connector_id: Option<&str>,
|
|
connector_name: Option<&str>,
|
|
) -> String {
|
|
let tool_name = sanitize_name(tool_name);
|
|
|
|
if let Some(connector_name) = connector_name
|
|
.map(str::trim)
|
|
.map(sanitize_name)
|
|
.filter(|name| !name.is_empty())
|
|
&& let Some(stripped) = tool_name.strip_prefix(&connector_name)
|
|
&& !stripped.is_empty()
|
|
{
|
|
return stripped.to_string();
|
|
}
|
|
|
|
if let Some(connector_id) = connector_id
|
|
.map(str::trim)
|
|
.map(sanitize_name)
|
|
.filter(|name| !name.is_empty())
|
|
&& let Some(stripped) = tool_name.strip_prefix(&connector_id)
|
|
&& !stripped.is_empty()
|
|
{
|
|
return stripped.to_string();
|
|
}
|
|
|
|
tool_name
|
|
}
|
|
|
|
pub(crate) fn normalize_codex_apps_callable_namespace(
|
|
server_name: &str,
|
|
connector_name: Option<&str>,
|
|
) -> String {
|
|
if let Some(connector_name) = connector_name {
|
|
format!("{}__{}", server_name, sanitize_name(connector_name))
|
|
} else {
|
|
server_name.to_string()
|
|
}
|
|
}
|