From 0f15ed43256e229d40bac944cbdcf0424fa75ae0 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 2 Feb 2026 13:13:14 -0800 Subject: [PATCH 1/8] Updated labeler workflow prompt to include "app" label (#10411) Support for desktop app issues --- .github/workflows/issue-labeler.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml index da77812fec..8146b8c5bd 100644 --- a/.github/workflows/issue-labeler.yml +++ b/.github/workflows/issue-labeler.yml @@ -38,9 +38,10 @@ jobs: - If applicable, add one of the following labels to specify which sub-product or product surface the issue relates to. 1. CLI — the Codex command line interface. 2. extension — VS Code (or other IDE) extension-specific issues. - 3. codex-web — Issues targeting the Codex web UI/Cloud experience. - 4. github-action — Issues with the Codex GitHub action. - 5. iOS — Issues with the Codex iOS app. + 3. app - Issues related to the Codex desktop application. + 4. codex-web — Issues targeting the Codex web UI/Cloud experience. + 5. github-action — Issues with the Codex GitHub action. + 6. iOS — Issues with the Codex iOS app. - Additionally add zero or more of the following labels that are relevant to the issue content. Prefer a small set of precise labels over many broad ones. 1. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures). From a5066bef7899a99279ed9172a8d31692d4146c0c Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Mon, 2 Feb 2026 15:31:08 -0800 Subject: [PATCH 2/8] emit a separate metric when the user cancels UAT during elevated setup (#10399) Currently this shows up as elevated setup failure, which isn't quite accurate. --- codex-rs/core/src/windows_sandbox.rs | 19 +++++++++++++++++++ codex-rs/tui/src/app.rs | 4 +++- .../windows-sandbox-rs/src/setup_error.rs | 3 +++ .../src/setup_orchestrator.rs | 8 +++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/windows_sandbox.rs b/codex-rs/core/src/windows_sandbox.rs index 7b9451c246..9a34a9f90f 100644 --- a/codex-rs/core/src/windows_sandbox.rs +++ b/codex-rs/core/src/windows_sandbox.rs @@ -65,6 +65,25 @@ pub fn elevated_setup_failure_details(_err: &anyhow::Error) -> Option<(String, S None } +#[cfg(target_os = "windows")] +pub fn elevated_setup_failure_metric_name(err: &anyhow::Error) -> &'static str { + if codex_windows_sandbox::extract_setup_failure(err).is_some_and(|failure| { + matches!( + failure.code, + codex_windows_sandbox::SetupErrorCode::OrchestratorHelperLaunchCanceled + ) + }) { + "codex.windows_sandbox.elevated_setup_canceled" + } else { + "codex.windows_sandbox.elevated_setup_failure" + } +} + +#[cfg(not(target_os = "windows"))] +pub fn elevated_setup_failure_metric_name(_err: &anyhow::Error) -> &'static str { + panic!("elevated_setup_failure_metric_name is only supported on Windows") +} + #[cfg(target_os = "windows")] pub fn run_elevated_setup( policy: &SandboxPolicy, diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 8dc2276b75..48ef0a772d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1700,7 +1700,9 @@ impl App { tags.push(("message", message)); } otel_manager.counter( - "codex.windows_sandbox.elevated_setup_failure", + codex_core::windows_sandbox::elevated_setup_failure_metric_name( + &err, + ), 1, &tags, ); diff --git a/codex-rs/windows-sandbox-rs/src/setup_error.rs b/codex-rs/windows-sandbox-rs/src/setup_error.rs index 21d260b4d4..9ee9ebf7f5 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_error.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_error.rs @@ -22,6 +22,8 @@ pub enum SetupErrorCode { OrchestratorPayloadSerializeFailed, /// Failed to launch the setup helper process (spawn or ShellExecuteExW). OrchestratorHelperLaunchFailed, + /// User canceled the UAC prompt while launching the helper. + OrchestratorHelperLaunchCanceled, /// Helper exited non-zero and no structured report was available. OrchestratorHelperExitNonzero, /// Helper exited non-zero and reading `setup_error.json` failed. @@ -72,6 +74,7 @@ impl SetupErrorCode { Self::OrchestratorElevationCheckFailed => "orchestrator_elevation_check_failed", Self::OrchestratorPayloadSerializeFailed => "orchestrator_payload_serialize_failed", Self::OrchestratorHelperLaunchFailed => "orchestrator_helper_launch_failed", + Self::OrchestratorHelperLaunchCanceled => "orchestrator_helper_launch_canceled", Self::OrchestratorHelperExitNonzero => "orchestrator_helper_exit_nonzero", Self::OrchestratorHelperReportReadFailed => "orchestrator_helper_report_read_failed", Self::HelperRequestArgsFailed => "helper_request_args_failed", diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 74137379ec..78e6d566fb 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -34,6 +34,7 @@ use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY; pub const SETUP_VERSION: u32 = 5; pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; +const ERROR_CANCELLED: u32 = 1223; const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020; const DOMAIN_ALIAS_RID_ADMINS: u32 = 0x0000_0220; @@ -411,8 +412,13 @@ fn run_setup_exe( let ok = unsafe { ShellExecuteExW(&mut sei) }; if ok == 0 || sei.hProcess == 0 { let last_error = unsafe { GetLastError() }; + let code = if last_error == ERROR_CANCELLED { + SetupErrorCode::OrchestratorHelperLaunchCanceled + } else { + SetupErrorCode::OrchestratorHelperLaunchFailed + }; return Err(failure( - SetupErrorCode::OrchestratorHelperLaunchFailed, + code, format!("ShellExecuteExW failed to launch setup helper: {last_error}"), )); } From 98debeda8aa19336a063645b7414498ad56f17ee Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Mon, 2 Feb 2026 15:35:37 -0800 Subject: [PATCH 3/8] chore(tui) /personalities tip (#10377) ## Summary We have /personality now. ## Testing - [x] tested locally --- codex-rs/tui/tooltips.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/tui/tooltips.txt b/codex-rs/tui/tooltips.txt index a8521f359f..e1e4d23712 100644 --- a/codex-rs/tui/tooltips.txt +++ b/codex-rs/tui/tooltips.txt @@ -9,6 +9,7 @@ Use /status to see the current model, approvals, and token usage. Use /fork to branch the current chat into a new thread. Use /init to create an AGENTS.md with project-specific guidance. Use /mcp to list configured MCP tools. +Use /personality to customize how Codex communicates. Use /rename to rename your threads for easier thread resuming. Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`. Join the OpenAI community Discord: http://discord.gg/openai From fb2df99cf132c0980d1b54e1a8fb64a1f9ed8906 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Mon, 2 Feb 2026 16:06:44 -0800 Subject: [PATCH 4/8] [feat] persist thread_dynamic_tools in db (#10252) Persist thread_dynamic_tools in sqlite and read first from it. Fall back to rollout files if it's not found. Persist dynamic tools to both sqlite and rollout files. Saw that new sessions get populated to db correctly & old sessions get backfilled correctly at startup: ``` celia@com-92114 codex-rs % sqlite3 ~/.codex/state.sqlite \ "select thread_id, position,name,description,input_schema from thread_dynamic_tools;" 019c0cad-ec0d-74b2-a787-e8b33a349117|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"} .... 019c10ca-aa4b-7620-ae40-c0919fbd7ea7|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"} ``` --- codex-rs/app-server-test-client/src/main.rs | 117 ++++++++++++++++-- codex-rs/core/src/codex.rs | 31 ++++- codex-rs/core/src/rollout/metadata.rs | 23 ++++ codex-rs/core/src/state_db.rs | 47 +++++++ codex-rs/core/tests/suite/sqlite_state.rs | 37 +++++- .../migrations/0004_thread_dynamic_tools.sql | 11 ++ codex-rs/state/src/runtime.rs | 109 ++++++++++++++++ 7 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 codex-rs/state/migrations/0004_thread_dynamic_tools.sql diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index d949c3a734..6d3fc06d81 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -1,7 +1,9 @@ use std::collections::VecDeque; +use std::fs; use std::io::BufRead; use std::io::BufReader; use std::io::Write; +use std::path::Path; use std::process::Child; use std::process::ChildStdin; use std::process::ChildStdout; @@ -24,6 +26,7 @@ use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::CommandExecutionApprovalDecision; use codex_app_server_protocol::CommandExecutionRequestApprovalParams; use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; +use codex_app_server_protocol::DynamicToolSpec; use codex_app_server_protocol::FileChangeApprovalDecision; use codex_app_server_protocol::FileChangeRequestApprovalParams; use codex_app_server_protocol::FileChangeRequestApprovalResponse; @@ -83,6 +86,15 @@ struct Cli { )] config_overrides: Vec, + /// JSON array of dynamic tool specs or a single tool object. + /// Prefix a filename with '@' to read from a file. + /// + /// Example: + /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' + /// --dynamic-tools @/path/to/tools.json + #[arg(long, value_name = "json-or-@file", global = true)] + dynamic_tools: Option, + #[command(subcommand)] command: CliCommand, } @@ -140,23 +152,29 @@ fn main() -> Result<()> { let Cli { codex_bin, config_overrides, + dynamic_tools, command, } = Cli::parse(); + let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; + match command { CliCommand::SendMessage { user_message } => { + ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; send_message(&codex_bin, &config_overrides, user_message) } CliCommand::SendMessageV2 { user_message } => { - send_message_v2(&codex_bin, &config_overrides, user_message) + send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) } CliCommand::TriggerCmdApproval { user_message } => { - trigger_cmd_approval(&codex_bin, &config_overrides, user_message) + trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) } CliCommand::TriggerPatchApproval { user_message } => { - trigger_patch_approval(&codex_bin, &config_overrides, user_message) + trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::NoTriggerCmdApproval => { + no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) } - CliCommand::NoTriggerCmdApproval => no_trigger_cmd_approval(&codex_bin, &config_overrides), CliCommand::SendFollowUpV2 { first_message, follow_up_message, @@ -165,10 +183,20 @@ fn main() -> Result<()> { &config_overrides, first_message, follow_up_message, + &dynamic_tools, ), - CliCommand::TestLogin => test_login(&codex_bin, &config_overrides), - CliCommand::GetAccountRateLimits => get_account_rate_limits(&codex_bin, &config_overrides), - CliCommand::ModelList => model_list(&codex_bin, &config_overrides), + CliCommand::TestLogin => { + ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; + test_login(&codex_bin, &config_overrides) + } + CliCommand::GetAccountRateLimits => { + ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; + get_account_rate_limits(&codex_bin, &config_overrides) + } + CliCommand::ModelList => { + ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; + model_list(&codex_bin, &config_overrides) + } } } @@ -198,14 +226,23 @@ fn send_message_v2( codex_bin: &str, config_overrides: &[String], user_message: String, + dynamic_tools: &Option>, ) -> Result<()> { - send_message_v2_with_policies(codex_bin, config_overrides, user_message, None, None) + send_message_v2_with_policies( + codex_bin, + config_overrides, + user_message, + None, + None, + dynamic_tools, + ) } fn trigger_cmd_approval( codex_bin: &str, config_overrides: &[String], user_message: Option, + dynamic_tools: &Option>, ) -> Result<()> { let default_prompt = "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; @@ -216,6 +253,7 @@ fn trigger_cmd_approval( message, Some(AskForApproval::OnRequest), Some(SandboxPolicy::ReadOnly), + dynamic_tools, ) } @@ -223,6 +261,7 @@ fn trigger_patch_approval( codex_bin: &str, config_overrides: &[String], user_message: Option, + dynamic_tools: &Option>, ) -> Result<()> { let default_prompt = "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; @@ -233,12 +272,24 @@ fn trigger_patch_approval( message, Some(AskForApproval::OnRequest), Some(SandboxPolicy::ReadOnly), + dynamic_tools, ) } -fn no_trigger_cmd_approval(codex_bin: &str, config_overrides: &[String]) -> Result<()> { +fn no_trigger_cmd_approval( + codex_bin: &str, + config_overrides: &[String], + dynamic_tools: &Option>, +) -> Result<()> { let prompt = "Run `touch should_not_trigger_approval.txt`"; - send_message_v2_with_policies(codex_bin, config_overrides, prompt.to_string(), None, None) + send_message_v2_with_policies( + codex_bin, + config_overrides, + prompt.to_string(), + None, + None, + dynamic_tools, + ) } fn send_message_v2_with_policies( @@ -247,13 +298,17 @@ fn send_message_v2_with_policies( user_message: String, approval_policy: Option, sandbox_policy: Option, + dynamic_tools: &Option>, ) -> Result<()> { let mut client = CodexClient::spawn(codex_bin, config_overrides)?; let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let thread_response = client.thread_start(ThreadStartParams::default())?; + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; println!("< thread/start response: {thread_response:?}"); let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), @@ -280,13 +335,17 @@ fn send_follow_up_v2( config_overrides: &[String], first_message: String, follow_up_message: String, + dynamic_tools: &Option>, ) -> Result<()> { let mut client = CodexClient::spawn(codex_bin, config_overrides)?; let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let thread_response = client.thread_start(ThreadStartParams::default())?; + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; println!("< thread/start response: {thread_response:?}"); let first_turn_params = TurnStartParams { @@ -372,6 +431,40 @@ fn model_list(codex_bin: &str, config_overrides: &[String]) -> Result<()> { Ok(()) } +fn ensure_dynamic_tools_unused( + dynamic_tools: &Option>, + command: &str, +) -> Result<()> { + if dynamic_tools.is_some() { + bail!( + "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" + ); + } + Ok(()) +} + +fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { + let Some(raw_arg) = dynamic_tools.as_deref() else { + return Ok(None); + }; + + let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { + fs::read_to_string(Path::new(path)) + .with_context(|| format!("read dynamic tools file {path}"))? + } else { + raw_arg.to_string() + }; + + let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; + let tools = match value { + Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, + Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], + _ => bail!("dynamic tools JSON must be an object or array"), + }; + + Ok(Some(tools)) +} + struct CodexClient { child: Child, stdin: Option, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ba5ee02d69..b4e3859500 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -333,9 +333,36 @@ impl Codex { .clone() .or_else(|| conversation_history.get_base_instructions().map(|s| s.text)) .unwrap_or_else(|| model_info.get_model_instructions(config.personality)); - // Respect explicit thread-start tools; fall back to persisted tools when resuming a thread. + + // Respect thread-start tools. When missing (resumed/forked threads), read from the db + // first, then fall back to rollout-file tools. + let persisted_tools = if dynamic_tools.is_empty() + && config.features.enabled(Feature::Sqlite) + { + let thread_id = match &conversation_history { + InitialHistory::Resumed(resumed) => Some(resumed.conversation_id), + InitialHistory::Forked(_) => conversation_history.forked_from_id(), + InitialHistory::New => None, + }; + match thread_id { + Some(thread_id) => { + let state_db_ctx = state_db::open_if_present( + config.codex_home.as_path(), + config.model_provider_id.as_str(), + ) + .await; + state_db::get_dynamic_tools(state_db_ctx.as_deref(), thread_id, "codex_spawn") + .await + } + None => None, + } + } else { + None + }; let dynamic_tools = if dynamic_tools.is_empty() { - conversation_history.get_dynamic_tools().unwrap_or_default() + persisted_tools + .or_else(|| conversation_history.get_dynamic_tools()) + .unwrap_or_default() } else { dynamic_tools }; diff --git a/codex-rs/core/src/rollout/metadata.rs b/codex-rs/core/src/rollout/metadata.rs index 2d59d71af3..42e52f78d6 100644 --- a/codex-rs/core/src/rollout/metadata.rs +++ b/codex-rs/core/src/rollout/metadata.rs @@ -187,6 +187,29 @@ pub(crate) async fn backfill_sessions( warn!("failed to upsert rollout {}: {err}", path.display()); } else { stats.upserted = stats.upserted.saturating_add(1); + if let Ok(meta_line) = rollout::list::read_session_meta_line(&path).await { + if let Err(err) = runtime + .persist_dynamic_tools( + meta_line.meta.id, + meta_line.meta.dynamic_tools.as_deref(), + ) + .await + { + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_dynamic_tools")], + ); + } + warn!("failed to backfill dynamic tools {}: {err}", path.display()); + } + } else { + warn!( + "failed to read session meta for dynamic tools {}", + path.display() + ); + } } } Err(err) => { diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index a52f46593a..ff95ed946f 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -9,6 +9,7 @@ use chrono::Timelike; use chrono::Utc; use codex_otel::OtelManager; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionSource; use codex_state::DB_METRIC_COMPARE_ERROR; @@ -196,6 +197,37 @@ pub async fn find_rollout_path_by_id( }) } +/// Get dynamic tools for a thread id using SQLite. +pub async fn get_dynamic_tools( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + stage: &str, +) -> Option> { + let ctx = context?; + match ctx.get_dynamic_tools(thread_id).await { + Ok(tools) => tools, + Err(err) => { + warn!("state db get_dynamic_tools failed during {stage}: {err}"); + None + } + } +} + +/// Persist dynamic tools for a thread id using SQLite, if none exist yet. +pub async fn persist_dynamic_tools( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + tools: Option<&[DynamicToolSpec]>, + stage: &str, +) { + let Some(ctx) = context else { + return; + }; + if let Err(err) = ctx.persist_dynamic_tools(thread_id, tools).await { + warn!("state db persist_dynamic_tools failed during {stage}: {err}"); + } +} + /// Reconcile rollout items into SQLite, falling back to scanning the rollout file. pub async fn reconcile_rollout( context: Option<&codex_state::StateRuntime>, @@ -235,6 +267,21 @@ pub async fn reconcile_rollout( "state db reconcile_rollout upsert failed {}: {err}", rollout_path.display() ); + return; + } + if let Ok(meta_line) = crate::rollout::list::read_session_meta_line(rollout_path).await { + persist_dynamic_tools( + Some(ctx), + meta_line.meta.id, + meta_line.meta.dynamic_tools.as_deref(), + "reconcile_rollout", + ) + .await; + } else { + warn!( + "state db reconcile_rollout missing session meta {}", + rollout_path.display() + ); } } diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 2582f90cbe..218da34825 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -1,6 +1,7 @@ use anyhow::Result; use codex_core::features::Feature; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; @@ -74,6 +75,28 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { let rollout_rel_path = format!("sessions/2026/01/27/rollout-2026-01-27T12-00-00-{uuid}.jsonl"); let rollout_rel_path_for_hook = rollout_rel_path.clone(); + let dynamic_tools = vec![ + DynamicToolSpec { + name: "geo_lookup".to_string(), + description: "lookup a city".to_string(), + input_schema: json!({ + "type": "object", + "required": ["city"], + "properties": { "city": { "type": "string" } } + }), + }, + DynamicToolSpec { + name: "weather_lookup".to_string(), + description: "lookup weather".to_string(), + input_schema: json!({ + "type": "object", + "required": ["zip"], + "properties": { "zip": { "type": "string" } } + }), + }, + ]; + let dynamic_tools_for_hook = dynamic_tools.clone(); + let mut builder = test_codex() .with_pre_build_hook(move |codex_home| { let rollout_path = codex_home.join(&rollout_rel_path_for_hook); @@ -81,7 +104,6 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { .parent() .expect("rollout path should have parent"); fs::create_dir_all(parent).expect("should create rollout directory"); - let session_meta_line = SessionMetaLine { meta: SessionMeta { id: thread_id, @@ -93,7 +115,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { source: SessionSource::default(), model_provider: None, base_instructions: None, - dynamic_tools: None, + dynamic_tools: Some(dynamic_tools_for_hook), }, git: None, }; @@ -155,6 +177,17 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { assert_eq!(metadata.model_provider, default_provider); assert!(metadata.has_user_event); + let mut stored_tools = None; + for _ in 0..40 { + stored_tools = db.get_dynamic_tools(thread_id).await?; + if stored_tools.is_some() { + break; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } + let stored_tools = stored_tools.expect("dynamic tools should be stored"); + assert_eq!(stored_tools, dynamic_tools); + Ok(()) } diff --git a/codex-rs/state/migrations/0004_thread_dynamic_tools.sql b/codex-rs/state/migrations/0004_thread_dynamic_tools.sql new file mode 100644 index 0000000000..0f40b5f800 --- /dev/null +++ b/codex-rs/state/migrations/0004_thread_dynamic_tools.sql @@ -0,0 +1,11 @@ +CREATE TABLE thread_dynamic_tools ( + thread_id TEXT NOT NULL, + position INTEGER NOT NULL, + name TEXT NOT NULL, + description TEXT NOT NULL, + input_schema TEXT NOT NULL, + PRIMARY KEY(thread_id, position), + FOREIGN KEY(thread_id) REFERENCES threads(id) ON DELETE CASCADE +); + +CREATE INDEX idx_thread_dynamic_tools_thread ON thread_dynamic_tools(thread_id); diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 6f2e128967..3b37b6d424 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -16,8 +16,10 @@ use chrono::DateTime; use chrono::Utc; use codex_otel::OtelManager; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::protocol::RolloutItem; use log::LevelFilter; +use serde_json::Value; use sqlx::ConnectOptions; use sqlx::QueryBuilder; use sqlx::Row; @@ -117,6 +119,38 @@ WHERE id = ? .transpose() } + /// Get dynamic tools for a thread, if present. + pub async fn get_dynamic_tools( + &self, + thread_id: ThreadId, + ) -> anyhow::Result>> { + let rows = sqlx::query( + r#" +SELECT name, description, input_schema +FROM thread_dynamic_tools +WHERE thread_id = ? +ORDER BY position ASC + "#, + ) + .bind(thread_id.to_string()) + .fetch_all(self.pool.as_ref()) + .await?; + if rows.is_empty() { + return Ok(None); + } + let mut tools = Vec::with_capacity(rows.len()); + for row in rows { + let input_schema: String = row.try_get("input_schema")?; + let input_schema = serde_json::from_str::(input_schema.as_str())?; + tools.push(DynamicToolSpec { + name: row.try_get("name")?, + description: row.try_get("description")?, + input_schema, + }); + } + Ok(Some(tools)) + } + /// Find a rollout path by thread id using the underlying database. pub async fn find_rollout_path_by_id( &self, @@ -369,6 +403,58 @@ ON CONFLICT(id) DO UPDATE SET Ok(()) } + /// Persist dynamic tools for a thread if none have been stored yet. + /// + /// Dynamic tools are defined at thread start and should not change afterward. + /// This only writes the first time we see tools for a given thread. + pub async fn persist_dynamic_tools( + &self, + thread_id: ThreadId, + tools: Option<&[DynamicToolSpec]>, + ) -> anyhow::Result<()> { + let Some(tools) = tools else { + return Ok(()); + }; + if tools.is_empty() { + return Ok(()); + } + let mut tx = self.pool.begin().await?; + let thread_id = thread_id.to_string(); + let existing: Option = + sqlx::query_scalar("SELECT 1 FROM thread_dynamic_tools WHERE thread_id = ? LIMIT 1") + .bind(thread_id.as_str()) + .fetch_optional(&mut *tx) + .await?; + if existing.is_some() { + tx.commit().await?; + return Ok(()); + } + for (idx, tool) in tools.iter().enumerate() { + let position = i64::try_from(idx).unwrap_or(i64::MAX); + let input_schema = serde_json::to_string(&tool.input_schema)?; + sqlx::query( + r#" +INSERT INTO thread_dynamic_tools ( + thread_id, + position, + name, + description, + input_schema +) VALUES (?, ?, ?, ?, ?) + "#, + ) + .bind(thread_id.as_str()) + .bind(position) + .bind(tool.name.as_str()) + .bind(tool.description.as_str()) + .bind(input_schema) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + Ok(()) + } + /// Apply rollout items incrementally using the underlying database. pub async fn apply_rollout_items( &self, @@ -390,12 +476,25 @@ ON CONFLICT(id) DO UPDATE SET if let Some(updated_at) = file_modified_time_utc(builder.rollout_path.as_path()).await { metadata.updated_at = updated_at; } + // Keep the thread upsert before dynamic tools to satisfy the foreign key constraint: + // thread_dynamic_tools.thread_id -> threads.id. if let Err(err) = self.upsert_thread(&metadata).await { if let Some(otel) = otel { otel.counter(DB_ERROR_METRIC, 1, &[("stage", "apply_rollout_items")]); } return Err(err); } + let dynamic_tools = extract_dynamic_tools(items); + if let Some(dynamic_tools) = dynamic_tools + && let Err(err) = self + .persist_dynamic_tools(builder.id, dynamic_tools.as_deref()) + .await + { + if let Some(otel) = otel { + otel.counter(DB_ERROR_METRIC, 1, &[("stage", "persist_dynamic_tools")]); + } + return Err(err); + } Ok(()) } @@ -507,6 +606,16 @@ fn push_like_filters<'a>( builder.push(")"); } +fn extract_dynamic_tools(items: &[RolloutItem]) -> Option>> { + items.iter().find_map(|item| match item { + RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.dynamic_tools.clone()), + RolloutItem::ResponseItem(_) + | RolloutItem::Compacted(_) + | RolloutItem::TurnContext(_) + | RolloutItem::EventMsg(_) => None, + }) +} + async fn open_sqlite(path: &Path) -> anyhow::Result { let options = SqliteConnectOptions::new() .filename(path) From e24058b7a872054e3e76f9301d64eccccb75811d Mon Sep 17 00:00:00 2001 From: Gav Verma Date: Mon, 2 Feb 2026 16:49:23 -0800 Subject: [PATCH 5/8] feat: Read personal skills from .agents/skills (#10437) - Issue: https://github.com/agentskills/agentskills/issues/15 - Follow-up to https://github.com/openai/codex/pull/10317 (for team/repo skills) - This change now also loads personal/user skills from `$HOME/.agents/skills` (or `~/.agents/skills`) in addition to loading from `.agents/skills` inside of git repos. - The location of `.system` skills remains unchanged. - Keeping backwards compatibility with `~/.codex/skills` for now until we fully deprecate. With skills in both personal folders: image We load from both places: image --- codex-rs/Cargo.lock | 1 + codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/skills/loader.rs | 92 +++++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 57186f82f1..716bc4cd5a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1422,6 +1422,7 @@ dependencies = [ "core-foundation 0.9.4", "core_test_support", "ctor 0.6.3", + "dirs", "dunce", "encoding_rs", "env-flags", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index a857da8b3a..a95981cca2 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -45,6 +45,7 @@ codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } +dirs = { workspace = true } dunce = { workspace = true } encoding_rs = { workspace = true } env-flags = { workspace = true } diff --git a/codex-rs/core/src/skills/loader.rs b/codex-rs/core/src/skills/loader.rs index a3c7731a6e..a04f3089b0 100644 --- a/codex-rs/core/src/skills/loader.rs +++ b/codex-rs/core/src/skills/loader.rs @@ -13,6 +13,7 @@ use crate::skills::model::SkillToolDependency; use crate::skills::system::system_cache_root_dir; use codex_app_server_protocol::ConfigLayerSource; use codex_protocol::protocol::SkillScope; +use dirs::home_dir; use dunce::canonicalize as canonicalize_path; use serde::Deserialize; use std::collections::HashSet; @@ -164,7 +165,10 @@ where outcome } -fn skill_roots_from_layer_stack_inner(config_layer_stack: &ConfigLayerStack) -> Vec { +fn skill_roots_from_layer_stack_inner( + config_layer_stack: &ConfigLayerStack, + home_dir: Option<&Path>, +) -> Vec { let mut roots = Vec::new(); for layer in @@ -182,12 +186,21 @@ fn skill_roots_from_layer_stack_inner(config_layer_stack: &ConfigLayerStack) -> }); } ConfigLayerSource::User { .. } => { - // `$CODEX_HOME/skills` (user-installed skills). + // Deprecated user skills location (`$CODEX_HOME/skills`), kept for backward + // compatibility. roots.push(SkillRoot { path: config_folder.as_path().join(SKILLS_DIR_NAME), scope: SkillScope::User, }); + // `$HOME/.agents/skills` (user-installed skills). + if let Some(home_dir) = home_dir { + roots.push(SkillRoot { + path: home_dir.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME), + scope: SkillScope::User, + }); + } + // Embedded system skills are cached under `$CODEX_HOME/skills/.system` and are a // special case (not a config layer). roots.push(SkillRoot { @@ -220,15 +233,16 @@ fn skill_roots(config: &Config) -> Vec { #[cfg(test)] pub(crate) fn skill_roots_from_layer_stack( config_layer_stack: &ConfigLayerStack, + home_dir: Option<&Path>, ) -> Vec { - skill_roots_from_layer_stack_inner(config_layer_stack) + skill_roots_from_layer_stack_inner(config_layer_stack, home_dir) } pub(crate) fn skill_roots_from_layer_stack_with_agents( config_layer_stack: &ConfigLayerStack, cwd: &Path, ) -> Vec { - let mut roots = skill_roots_from_layer_stack_inner(config_layer_stack); + let mut roots = skill_roots_from_layer_stack_inner(config_layer_stack, home_dir().as_deref()); roots.extend(repo_agents_skill_roots(config_layer_stack, cwd)); dedupe_skill_roots_by_path(&mut roots); roots @@ -829,7 +843,8 @@ mod tests { let tmp = tempfile::tempdir()?; let system_folder = tmp.path().join("etc/codex"); - let user_folder = tmp.path().join("home/codex"); + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); fs::create_dir_all(&system_folder)?; fs::create_dir_all(&user_folder)?; @@ -853,7 +868,7 @@ mod tests { ConfigRequirementsToml::default(), )?; - let got = skill_roots_from_layer_stack(&stack) + let got = skill_roots_from_layer_stack(&stack, Some(&home_folder)) .into_iter() .map(|root| (root.scope, root.path)) .collect::>(); @@ -862,6 +877,10 @@ mod tests { got, vec![ (SkillScope::User, user_folder.join("skills")), + ( + SkillScope::User, + home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME) + ), ( SkillScope::System, user_folder.join("skills").join(".system") @@ -877,7 +896,8 @@ mod tests { fn skill_roots_from_layer_stack_includes_disabled_project_layers() -> anyhow::Result<()> { let tmp = tempfile::tempdir()?; - let user_folder = tmp.path().join("home/codex"); + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); fs::create_dir_all(&user_folder)?; let project_root = tmp.path().join("repo"); @@ -906,7 +926,7 @@ mod tests { ConfigRequirementsToml::default(), )?; - let got = skill_roots_from_layer_stack(&stack) + let got = skill_roots_from_layer_stack(&stack, Some(&home_folder)) .into_iter() .map(|root| (root.scope, root.path)) .collect::>(); @@ -916,6 +936,10 @@ mod tests { vec![ (SkillScope::Repo, dot_codex.join("skills")), (SkillScope::User, user_folder.join("skills")), + ( + SkillScope::User, + home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME) + ), ( SkillScope::System, user_folder.join("skills").join(".system") @@ -926,6 +950,55 @@ mod tests { Ok(()) } + #[test] + fn loads_skills_from_home_agents_dir_for_user_scope() -> anyhow::Result<()> { + let tmp = tempfile::tempdir()?; + + let home_folder = tmp.path().join("home"); + let user_folder = home_folder.join("codex"); + fs::create_dir_all(&user_folder)?; + + let user_file = AbsolutePathBuf::from_absolute_path(user_folder.join("config.toml"))?; + let layers = vec![ConfigLayerEntry::new( + ConfigLayerSource::User { file: user_file }, + TomlValue::Table(toml::map::Map::new()), + )]; + let stack = ConfigLayerStack::new( + layers, + ConfigRequirements::default(), + ConfigRequirementsToml::default(), + )?; + + let skill_path = write_skill_at( + &home_folder.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME), + "agents-home", + "agents-home-skill", + "from home agents", + ); + + let outcome = + load_skills_from_roots(skill_roots_from_layer_stack(&stack, Some(&home_folder))); + assert!( + outcome.errors.is_empty(), + "unexpected errors: {:?}", + outcome.errors + ); + assert_eq!( + outcome.skills, + vec![SkillMetadata { + name: "agents-home-skill".to_string(), + description: "from home agents".to_string(), + short_description: None, + interface: None, + dependencies: None, + path: normalized(&skill_path), + scope: SkillScope::User, + }] + ); + + Ok(()) + } + fn write_skill(codex_home: &TempDir, dir: &str, name: &str, description: &str) -> PathBuf { write_skill_at(&codex_home.path().join("skills"), dir, name, description) } @@ -2136,6 +2209,9 @@ interface: .map(|root| root.scope) .collect(); let mut expected = vec![SkillScope::User, SkillScope::System]; + if home_dir().is_some() { + expected.insert(1, SkillScope::User); + } if cfg!(unix) { expected.push(SkillScope::Admin); } From 019d89ff86587196341edc30c9bb6205f82537ce Mon Sep 17 00:00:00 2001 From: pash-openai Date: Mon, 2 Feb 2026 16:57:29 -0800 Subject: [PATCH 6/8] make codex better at git (#10145) adds basic git context to the session prefix so the model can anchor git actions and be a bit more version-aware. structured it in a multiroot-friendly shape even though we only have one root today --- codex-rs/core/src/client.rs | 66 +++++- codex-rs/core/src/codex.rs | 8 +- codex-rs/core/src/compact.rs | 4 +- codex-rs/core/src/environment_context.rs | 1 + codex-rs/core/src/git_info.rs | 76 ++++++- codex-rs/core/src/lib.rs | 2 + codex-rs/core/src/turn_metadata.rs | 43 ++++ .../core/tests/chat_completions_payload.rs | 2 +- codex-rs/core/tests/chat_completions_sse.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 201 +++++++++++++++++- codex-rs/core/tests/suite/client.rs | 2 +- .../core/tests/suite/client_websockets.rs | 10 +- 12 files changed, 394 insertions(+), 23 deletions(-) create mode 100644 codex-rs/core/src/turn_metadata.rs diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 6564e86c49..37fef163af 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1,10 +1,13 @@ +use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; +use std::sync::RwLock; use crate::api_bridge::CoreAuthProvider; use crate::api_bridge::auth_provider_from_auth; use crate::api_bridge::map_api_error; use crate::auth::UnauthorizedRecovery; +use crate::turn_metadata::build_turn_metadata_header; use codex_api::AggregateStreamExt; use codex_api::ChatClient as ApiChatClient; use codex_api::CompactClient as ApiCompactClient; @@ -72,6 +75,13 @@ use crate::transport_manager::TransportManager; pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; +pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; + +#[derive(Debug, Default)] +struct TurnMetadataCache { + cwd: Option, + header: Option, +} #[derive(Debug)] struct ModelClientState { @@ -85,6 +95,7 @@ struct ModelClientState { summary: ReasoningSummaryConfig, session_source: SessionSource, transport_manager: TransportManager, + turn_metadata_cache: Arc>, } #[derive(Debug, Clone)] @@ -136,11 +147,13 @@ impl ModelClient { summary, session_source, transport_manager, + turn_metadata_cache: Arc::new(RwLock::new(TurnMetadataCache::default())), }), } } - pub fn new_session(&self) -> ModelClientSession { + pub fn new_session(&self, turn_metadata_cwd: Option) -> ModelClientSession { + self.prewarm_turn_metadata_header(turn_metadata_cwd); ModelClientSession { state: Arc::clone(&self.state), connection: None, @@ -149,6 +162,38 @@ impl ModelClient { turn_state: Arc::new(OnceLock::new()), } } + + /// Refresh turn metadata in the background and update a cached header that request + /// builders can read without blocking. + fn prewarm_turn_metadata_header(&self, turn_metadata_cwd: Option) { + let turn_metadata_cwd = + turn_metadata_cwd.map(|cwd| std::fs::canonicalize(&cwd).unwrap_or(cwd)); + + if let Ok(mut cache) = self.state.turn_metadata_cache.write() + && cache.cwd != turn_metadata_cwd + { + cache.cwd = turn_metadata_cwd.clone(); + cache.header = None; + } + + let Some(cwd) = turn_metadata_cwd else { + return; + }; + let turn_metadata_cache = Arc::clone(&self.state.turn_metadata_cache); + if let Ok(handle) = tokio::runtime::Handle::try_current() { + let _task = handle.spawn(async move { + let header = build_turn_metadata_header(cwd.as_path()) + .await + .and_then(|value| HeaderValue::from_str(value.as_str()).ok()); + + if let Ok(mut cache) = turn_metadata_cache.write() + && cache.cwd.as_ref() == Some(&cwd) + { + cache.header = header; + } + }); + } + } } impl ModelClient { @@ -257,6 +302,14 @@ impl ModelClient { } impl ModelClientSession { + fn turn_metadata_header(&self) -> Option { + self.state + .turn_metadata_cache + .try_read() + .ok() + .and_then(|cache| cache.header.clone()) + } + /// Streams a single model turn using either the Responses or Chat /// Completions wire API, depending on the configured provider. /// @@ -332,6 +385,7 @@ impl ModelClientSession { prompt: &Prompt, compression: Compression, ) -> ApiResponsesOptions { + let turn_metadata_header = self.turn_metadata_header(); let model_info = &self.state.model_info; let default_reasoning_effort = model_info.default_reasoning_level; @@ -380,7 +434,11 @@ impl ModelClientSession { store_override: None, conversation_id: Some(conversation_id), session_source: Some(self.state.session_source.clone()), - extra_headers: build_responses_headers(&self.state.config, Some(&self.turn_state)), + extra_headers: build_responses_headers( + &self.state.config, + Some(&self.turn_state), + turn_metadata_header.as_ref(), + ), compression, turn_state: Some(Arc::clone(&self.turn_state)), } @@ -713,6 +771,7 @@ fn experimental_feature_headers(config: &Config) -> ApiHeaderMap { fn build_responses_headers( config: &Config, turn_state: Option<&Arc>>, + turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { let mut headers = experimental_feature_headers(config); headers.insert( @@ -731,6 +790,9 @@ fn build_responses_headers( { headers.insert(X_CODEX_TURN_STATE_HEADER, header_value); } + if let Some(header_value) = turn_metadata_header { + headers.insert(X_CODEX_TURN_METADATA_HEADER, header_value.clone()); + } headers } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b4e3859500..31a81846e2 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -119,6 +119,7 @@ use crate::error::Result as CodexResult; use crate::exec::StreamOutput; use crate::exec_policy::ExecPolicyUpdateError; use crate::feedback_tags; +use crate::git_info::get_git_repo_root; use crate::instructions::UserInstructions; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp::auth::compute_auth_statuses; @@ -531,7 +532,6 @@ pub(crate) struct TurnContext { pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, } - impl TurnContext { pub(crate) fn resolve_path(&self, path: Option) -> PathBuf { path.as_ref() @@ -3406,7 +3406,9 @@ pub(crate) async fn run_turn( // many turns, from the perspective of the user, it is a single turn. let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let mut client_session = turn_context.client.new_session(); + let mut client_session = turn_context + .client + .new_session(Some(turn_context.cwd.clone())); loop { // Note that pending_input would be something like a message the user @@ -4469,8 +4471,6 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) - #[cfg(test)] pub(crate) use tests::make_session_and_context; - -use crate::git_info::get_git_repo_root; #[cfg(test)] pub(crate) use tests::make_session_and_context_with_rx; diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 7e7445a6f6..b44d43f42e 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -335,7 +335,9 @@ async fn drain_to_completed( turn_context: &TurnContext, prompt: &Prompt, ) -> CodexResult<()> { - let mut client_session = turn_context.client.new_session(); + let mut client_session = turn_context + .client + .new_session(Some(turn_context.cwd.clone())); let mut stream = client_session.stream(prompt).await?; loop { let maybe_event = stream.next().await; diff --git a/codex-rs/core/src/environment_context.rs b/codex-rs/core/src/environment_context.rs index f0b0877eba..87692a2f05 100644 --- a/codex-rs/core/src/environment_context.rs +++ b/codex-rs/core/src/environment_context.rs @@ -28,6 +28,7 @@ impl EnvironmentContext { cwd, // should compare all fields except shell shell: _, + .. } = other; self.cwd == *cwd diff --git a/codex-rs/core/src/git_info.rs b/codex-rs/core/src/git_info.rs index 065f1280a4..dcabc3a341 100644 --- a/codex-rs/core/src/git_info.rs +++ b/codex-rs/core/src/git_info.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; @@ -109,6 +110,73 @@ pub async fn collect_git_info(cwd: &Path) -> Option { Some(git_info) } +/// Collect fetch remotes in a multi-root-friendly format: {"origin": "https://..."}. +pub async fn get_git_remote_urls(cwd: &Path) -> Option> { + let is_git_repo = run_git_command_with_timeout(&["rev-parse", "--git-dir"], cwd) + .await? + .status + .success(); + if !is_git_repo { + return None; + } + + get_git_remote_urls_assume_git_repo(cwd).await +} + +/// Collect fetch remotes without checking whether `cwd` is in a git repo. +pub async fn get_git_remote_urls_assume_git_repo(cwd: &Path) -> Option> { + let output = run_git_command_with_timeout(&["remote", "-v"], cwd).await?; + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8(output.stdout).ok()?; + parse_git_remote_urls(stdout.as_str()) +} + +/// Return the current HEAD commit hash without checking whether `cwd` is in a git repo. +pub async fn get_head_commit_hash(cwd: &Path) -> Option { + let output = run_git_command_with_timeout(&["rev-parse", "HEAD"], cwd).await?; + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8(output.stdout).ok()?; + let hash = stdout.trim(); + if hash.is_empty() { + None + } else { + Some(hash.to_string()) + } +} + +fn parse_git_remote_urls(stdout: &str) -> Option> { + let mut remotes = BTreeMap::new(); + for line in stdout.lines() { + let Some(fetch_line) = line.strip_suffix(" (fetch)") else { + continue; + }; + + let Some((name, url_part)) = fetch_line + .split_once('\t') + .or_else(|| fetch_line.split_once(' ')) + else { + continue; + }; + + let url = url_part.trim_start(); + if !url.is_empty() { + remotes.insert(name.to_string(), url.to_string()); + } + } + + if remotes.is_empty() { + None + } else { + Some(remotes) + } +} + /// A minimal commit summary entry used for pickers (subject + timestamp + sha). #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CommitLogEntry { @@ -185,11 +253,9 @@ pub async fn git_diff_to_remote(cwd: &Path) -> Option { /// Run a git command with a timeout to prevent blocking on large repositories async fn run_git_command_with_timeout(args: &[&str], cwd: &Path) -> Option { - let result = timeout( - GIT_COMMAND_TIMEOUT, - Command::new("git").args(args).current_dir(cwd).output(), - ) - .await; + let mut command = Command::new("git"); + command.args(args).current_dir(cwd).kill_on_drop(true); + let result = timeout(GIT_COMMAND_TIMEOUT, command.output()).await; match result { Ok(Ok(output)) => Some(output), diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index e11b81e6fc..1f81f3eab2 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -101,6 +101,7 @@ pub mod state_db; pub mod terminal; mod tools; pub mod turn_diff_tracker; +mod turn_metadata; pub use rollout::ARCHIVED_SESSIONS_SUBDIR; pub use rollout::INTERACTIVE_SESSION_SOURCES; pub use rollout::RolloutRecorder; @@ -131,6 +132,7 @@ pub mod util; pub use apply_patch::CODEX_APPLY_PATCH_ARG1; pub use client::WEB_SEARCH_ELIGIBLE_HEADER; +pub use client::X_CODEX_TURN_METADATA_HEADER; pub use command_safety::is_dangerous_command; pub use command_safety::is_safe_command; pub use exec_policy::ExecPolicyError; diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs new file mode 100644 index 0000000000..58b394a2f2 --- /dev/null +++ b/codex-rs/core/src/turn_metadata.rs @@ -0,0 +1,43 @@ +use std::collections::BTreeMap; +use std::path::Path; + +use serde::Serialize; + +use crate::git_info::get_git_remote_urls_assume_git_repo; +use crate::git_info::get_git_repo_root; +use crate::git_info::get_head_commit_hash; + +#[derive(Serialize)] +struct TurnMetadataWorkspace { + #[serde(skip_serializing_if = "Option::is_none")] + associated_remote_urls: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + latest_git_commit_hash: Option, +} + +#[derive(Serialize)] +struct TurnMetadata { + workspaces: BTreeMap, +} + +pub(crate) async fn build_turn_metadata_header(cwd: &Path) -> Option { + let repo_root = get_git_repo_root(cwd)?; + + let (latest_git_commit_hash, associated_remote_urls) = tokio::join!( + get_head_commit_hash(cwd), + get_git_remote_urls_assume_git_repo(cwd) + ); + if latest_git_commit_hash.is_none() && associated_remote_urls.is_none() { + return None; + } + + let mut workspaces = BTreeMap::new(); + workspaces.insert( + repo_root.to_string_lossy().into_owned(), + TurnMetadataWorkspace { + associated_remote_urls, + latest_git_commit_hash, + }, + ); + serde_json::to_string(&TurnMetadata { workspaces }).ok() +} diff --git a/codex-rs/core/tests/chat_completions_payload.rs b/codex-rs/core/tests/chat_completions_payload.rs index ffa2caa59f..5406b44ad6 100644 --- a/codex-rs/core/tests/chat_completions_payload.rs +++ b/codex-rs/core/tests/chat_completions_payload.rs @@ -102,7 +102,7 @@ async fn run_request(input: Vec) -> Value { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = input; diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 160699733e..8360237752 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -103,7 +103,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 86d6e8ce61..a40c92d02d 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -1,3 +1,4 @@ +use std::process::Command; use std::sync::Arc; use codex_app_server_protocol::AuthMode; @@ -23,6 +24,7 @@ use core_test_support::load_default_config_for_test; use core_test_support::responses; use core_test_support::test_codex::test_codex; use futures::StreamExt; +use pretty_assertions::assert_eq; use tempfile::TempDir; use wiremock::matchers::header; @@ -98,7 +100,7 @@ async fn responses_stream_includes_subagent_header_on_review() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -197,7 +199,7 @@ async fn responses_stream_includes_subagent_header_on_other() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -354,7 +356,7 @@ async fn responses_respects_model_info_overrides_from_config() { session_source, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -393,3 +395,196 @@ async fn responses_respects_model_info_overrides_from_config() { Some("detailed") ); } + +#[tokio::test] +async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() { + core_test_support::skip_if_no_network!(); + + let server = responses::start_mock_server().await; + let response_body = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); + let provider = ModelProviderInfo { + name: "mock".into(), + base_url: Some(format!("{}/v1", server.uri())), + env_key: None, + env_key_instructions: None, + experimental_bearer_token: None, + wire_api: WireApi::Responses, + query_params: None, + http_headers: None, + env_http_headers: None, + request_max_retries: Some(0), + stream_max_retries: Some(0), + stream_idle_timeout_ms: Some(5_000), + requires_openai_auth: false, + supports_websockets: false, + }; + + let codex_home = TempDir::new().expect("failed to create TempDir"); + let mut config = load_default_config_for_test(&codex_home).await; + config.model_provider_id = provider.name.clone(); + config.model_provider = provider.clone(); + let effort = config.model_reasoning_effort; + let summary = config.model_reasoning_summary; + let model = ModelsManager::get_model_offline(config.model.as_deref()); + config.model = Some(model.clone()); + let config = Arc::new(config); + + let conversation_id = ThreadId::new(); + let auth_mode = AuthMode::Chatgpt; + let session_source = + SessionSource::SubAgent(SubAgentSource::Other("turn-metadata-e2e".to_string())); + let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); + let otel_manager = OtelManager::new( + conversation_id, + model.as_str(), + model_info.slug.as_str(), + None, + Some("test@test.com".to_string()), + Some(auth_mode), + false, + "test".to_string(), + session_source.clone(), + ); + + let client = ModelClient::new( + Arc::clone(&config), + None, + model_info, + otel_manager, + provider, + effort, + summary, + conversation_id, + session_source, + TransportManager::new(), + ); + + let workspace = TempDir::new().expect("workspace tempdir"); + let cwd = workspace.path(); + + let mut prompt = Prompt::default(); + prompt.input = vec![ResponseItem::Message { + id: None, + role: "user".into(), + content: vec![ContentItem::InputText { + text: "hello".into(), + }], + end_turn: None, + }]; + + let first_request = responses::mount_sse_once(&server, response_body.clone()).await; + let mut first_session = client.new_session(Some(cwd.to_path_buf())); + let mut first_stream = first_session + .stream(&prompt) + .await + .expect("stream first turn"); + while let Some(event) = first_stream.next().await { + if matches!(event, Ok(ResponseEvent::Completed { .. })) { + break; + } + } + assert_eq!( + first_request + .single_request() + .header("x-codex-turn-metadata"), + None + ); + + let git_config_global = cwd.join("empty-git-config"); + std::fs::write(&git_config_global, "").expect("write empty git config"); + let run_git = |args: &[&str]| { + let output = Command::new("git") + .env("GIT_CONFIG_GLOBAL", &git_config_global) + .env("GIT_CONFIG_NOSYSTEM", "1") + .args(args) + .current_dir(cwd) + .output() + .expect("git command should run"); + assert!( + output.status.success(), + "git {:?} failed: stdout={} stderr={}", + args, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + output + }; + + run_git(&["init"]); + run_git(&["config", "user.name", "Test User"]); + run_git(&["config", "user.email", "test@example.com"]); + std::fs::write(cwd.join("README.md"), "hello").expect("write README"); + run_git(&["add", "."]); + run_git(&["commit", "-m", "initial commit"]); + run_git(&[ + "remote", + "add", + "origin", + "https://github.com/openai/codex.git", + ]); + + let expected_head = String::from_utf8(run_git(&["rev-parse", "HEAD"]).stdout) + .expect("git rev-parse output should be valid UTF-8") + .trim() + .to_string(); + let expected_origin = String::from_utf8(run_git(&["remote", "get-url", "origin"]).stdout) + .expect("git remote get-url output should be valid UTF-8") + .trim() + .to_string(); + + let repo_root = std::fs::canonicalize(cwd) + .unwrap_or_else(|_| cwd.to_path_buf()) + .to_string_lossy() + .into_owned(); + let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5); + loop { + let request_recorder = responses::mount_sse_once(&server, response_body.clone()).await; + let mut session = client.new_session(Some(cwd.to_path_buf())); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + let mut stream = session.stream(&prompt).await.expect("stream post-git turn"); + while let Some(event) = stream.next().await { + if matches!(event, Ok(ResponseEvent::Completed { .. })) { + break; + } + } + + let maybe_header = request_recorder + .single_request() + .header("x-codex-turn-metadata"); + if let Some(header_value) = maybe_header { + let parsed: serde_json::Value = serde_json::from_str(&header_value) + .expect("x-codex-turn-metadata should be valid JSON"); + let workspace = parsed + .get("workspaces") + .and_then(serde_json::Value::as_object) + .and_then(|workspaces| workspaces.get(&repo_root)) + .expect("metadata should include cwd repo root workspace entry"); + + assert_eq!( + workspace + .get("latest_git_commit_hash") + .and_then(serde_json::Value::as_str), + Some(expected_head.as_str()) + ); + assert_eq!( + workspace + .get("associated_remote_urls") + .and_then(serde_json::Value::as_object) + .and_then(|remotes| remotes.get("origin")) + .and_then(serde_json::Value::as_str), + Some(expected_origin.as_str()) + ); + return; + } + + if tokio::time::Instant::now() >= deadline { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(25)).await; + } + + panic!("x-codex-turn-metadata was never observed within 5s after git setup"); +} diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 439233d8af..d79ef22b51 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1190,7 +1190,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { SessionSource::Exec, TransportManager::new(), ) - .new_session(); + .new_session(None); let mut prompt = Prompt::default(); prompt.input.push(ResponseItem::Reasoning { diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index fc0ff37cf4..1acc3756d0 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -53,7 +53,7 @@ async fn responses_websocket_streams_request() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); stream_until_complete(&mut session, &prompt).await; @@ -83,7 +83,7 @@ async fn responses_websocket_emits_websocket_telemetry_events() { let harness = websocket_harness(&server).await; harness.otel_manager.reset_runtime_metrics(); - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); stream_until_complete(&mut session, &prompt).await; @@ -113,7 +113,7 @@ async fn responses_websocket_emits_reasoning_included_event() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt = prompt_with_input(vec![message_item("hello")]); let mut stream = session @@ -147,7 +147,7 @@ async fn responses_websocket_appends_on_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); @@ -183,7 +183,7 @@ async fn responses_websocket_creates_on_non_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(); + let mut session = harness.client.new_session(None); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("different")]); From 5694dadb3afcdfeb843a79dde6ee1a5795629ee3 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 2 Feb 2026 17:18:41 -0800 Subject: [PATCH 7/8] feat: migrate MCP implementation from mcp-types to rmcp --- codex-rs/Cargo.lock | 14 +- codex-rs/Cargo.toml | 1 - codex-rs/app-server-protocol/Cargo.toml | 1 - .../schema/json/EventMsg.json | 420 +-------- .../schema/json/ServerNotification.json | 424 +-------- .../codex_app_server_protocol.schemas.json | 836 +----------------- .../json/v1/ForkConversationResponse.json | 420 +-------- .../json/v1/ResumeConversationResponse.json | 420 +-------- .../v1/SessionConfiguredNotification.json | 420 +-------- .../json/v2/ItemCompletedNotification.json | 278 +----- .../json/v2/ItemStartedNotification.json | 278 +----- .../json/v2/ListMcpServerStatusResponse.json | 174 +--- .../schema/json/v2/ReviewStartResponse.json | 278 +----- .../schema/json/v2/ThreadForkResponse.json | 278 +----- .../schema/json/v2/ThreadListResponse.json | 278 +----- .../schema/json/v2/ThreadReadResponse.json | 278 +----- .../schema/json/v2/ThreadResumeResponse.json | 278 +----- .../json/v2/ThreadRollbackResponse.json | 278 +----- .../schema/json/v2/ThreadStartResponse.json | 278 +----- .../json/v2/ThreadStartedNotification.json | 278 +----- .../json/v2/ThreadUnarchiveResponse.json | 278 +----- .../json/v2/TurnCompletedNotification.json | 278 +----- .../schema/json/v2/TurnStartResponse.json | 278 +----- .../json/v2/TurnStartedNotification.json | 278 +----- .../schema/typescript/Annotations.ts | 9 - .../schema/typescript/AudioContent.ts | 9 - .../schema/typescript/BlobResourceContents.ts | 5 - .../schema/typescript/CallToolResult.ts | 3 +- .../schema/typescript/ContentBlock.ts | 10 - .../typescript/ElicitationRequestEvent.ts | 3 +- .../schema/typescript/EmbeddedResource.ts | 13 - .../typescript/EmbeddedResourceResource.ts | 7 - .../schema/typescript/ImageContent.ts | 9 - .../schema/typescript/Resource.ts | 4 +- .../schema/typescript/ResourceLink.ts | 11 - .../schema/typescript/ResourceTemplate.ts | 4 +- .../schema/typescript/Role.ts | 8 - .../schema/typescript/TextContent.ts | 9 - .../schema/typescript/TextResourceContents.ts | 5 - .../schema/typescript/Tool.ts | 6 +- .../schema/typescript/ToolAnnotations.ts | 15 - .../schema/typescript/ToolInputSchema.ts | 9 - .../schema/typescript/ToolOutputSchema.ts | 10 - .../schema/typescript/index.ts | 14 - .../schema/typescript/v2/McpToolCallResult.ts | 3 +- .../app-server-protocol/src/protocol/v2.rs | 14 +- .../src/schema_fixtures.rs | 90 +- codex-rs/app-server-test-client/Cargo.lock | 11 - codex-rs/app-server/Cargo.toml | 2 - .../app-server/src/bespoke_event_handling.rs | 15 +- codex-rs/core/Cargo.toml | 7 +- codex-rs/core/src/codex.rs | 55 +- codex-rs/core/src/mcp/mod.rs | 122 ++- codex-rs/core/src/mcp_connection_manager.rs | 111 ++- codex-rs/core/src/mcp_tool_call.rs | 9 +- codex-rs/core/src/tools/context.rs | 2 +- .../core/src/tools/handlers/mcp_resource.rs | 66 +- codex-rs/core/src/tools/router.rs | 3 +- codex-rs/core/src/tools/spec.rs | 295 +++--- codex-rs/core/tests/common/lib.rs | 2 +- codex-rs/core/tests/suite/rmcp_client.rs | 13 +- codex-rs/exec/Cargo.toml | 3 +- .../src/event_processor_with_human_output.rs | 3 +- codex-rs/exec/src/exec_events.rs | 10 +- .../tests/event_processor_with_json_output.rs | 19 +- codex-rs/mcp-server/Cargo.toml | 2 +- codex-rs/mcp-server/src/codex_tool_config.rs | 81 +- codex-rs/mcp-server/src/codex_tool_runner.rs | 33 +- codex-rs/mcp-server/src/error_code.rs | 2 - codex-rs/mcp-server/src/exec_approval.rs | 35 +- codex-rs/mcp-server/src/lib.rs | 27 +- codex-rs/mcp-server/src/message_processor.rs | 517 +++++------ codex-rs/mcp-server/src/outgoing_message.rs | 125 ++- codex-rs/mcp-server/src/patch_approval.rs | 30 +- codex-rs/mcp-server/tests/common/Cargo.toml | 2 +- codex-rs/mcp-server/tests/common/lib.rs | 6 +- .../mcp-server/tests/common/mcp_process.rs | 168 ++-- codex-rs/mcp-server/tests/suite/codex_tool.rs | 158 ++-- codex-rs/protocol/Cargo.toml | 1 - codex-rs/protocol/src/approvals.rs | 3 +- codex-rs/protocol/src/mcp.rs | 16 +- codex-rs/protocol/src/models.rs | 149 +++- codex-rs/protocol/src/protocol.rs | 10 +- codex-rs/rmcp-client/Cargo.toml | 1 - .../rmcp-client/src/logging_client_handler.rs | 7 +- codex-rs/rmcp-client/src/rmcp_client.rs | 101 +-- codex-rs/rmcp-client/src/utils.rs | 84 -- codex-rs/rmcp-client/tests/resources.rs | 79 +- codex-rs/tui/Cargo.toml | 2 +- .../tui/src/bottom_pane/approval_overlay.rs | 2 +- ...et__tests__binary_size_ideal_response.snap | 2 +- codex-rs/tui/src/history_cell.rs | 219 +++-- 92 files changed, 1629 insertions(+), 8273 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Annotations.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AudioContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ImageContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Role.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TextContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts delete mode 100644 codex-rs/mcp-server/src/error_code.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 716bc4cd5a..bc8cac244c 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1108,7 +1108,6 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-json-to-toml", "core_test_support", - "mcp-types", "os_info", "pretty_assertions", "rmcp", @@ -1137,7 +1136,6 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-cargo-bin", "inventory", - "mcp-types", "pretty_assertions", "schemars 0.8.22", "serde", @@ -1437,7 +1435,6 @@ dependencies = [ "landlock", "libc", "maplit", - "mcp-types", "multimap", "once_cell", "openssl-sys", @@ -1449,6 +1446,7 @@ dependencies = [ "regex", "regex-lite", "reqwest", + "rmcp", "schemars 0.8.22", "seccompiler", "serde", @@ -1512,10 +1510,10 @@ dependencies = [ "codex-utils-cargo-bin", "core_test_support", "libc", - "mcp-types", "owo-colors", "predicates", "pretty_assertions", + "rmcp", "serde", "serde_json", "shlex", @@ -1717,10 +1715,10 @@ dependencies = [ "codex-protocol", "codex-utils-json-to-toml", "core_test_support", - "mcp-types", "mcp_test_support", "os_info", "pretty_assertions", + "rmcp", "schemars 0.8.22", "serde", "serde_json", @@ -1829,7 +1827,6 @@ dependencies = [ "icu_decimal", "icu_locale_core", "icu_provider", - "mcp-types", "mime_guess", "pretty_assertions", "schemars 0.8.22", @@ -1873,7 +1870,6 @@ dependencies = [ "codex-utils-home-dir", "futures", "keyring", - "mcp-types", "oauth2", "pretty_assertions", "reqwest", @@ -1966,7 +1962,6 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "libc", - "mcp-types", "pathdiff", "pretty_assertions", "pulldown-cmark", @@ -1975,6 +1970,7 @@ dependencies = [ "ratatui-macros", "regex-lite", "reqwest", + "rmcp", "serde", "serde_json", "serial_test", @@ -4672,9 +4668,9 @@ dependencies = [ "codex-mcp-server", "codex-utils-cargo-bin", "core_test_support", - "mcp-types", "os_info", "pretty_assertions", + "rmcp", "serde", "serde_json", "shlex", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 3171c8a96c..a452e98e7c 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -112,7 +112,6 @@ codex-utils-string = { path = "utils/string" } codex-windows-sandbox = { path = "windows-sandbox-rs" } core_test_support = { path = "core/tests/common" } exec_server_test_support = { path = "exec-server/tests/common" } -mcp-types = { path = "mcp-types" } mcp_test_support = { path = "mcp-server/tests/common" } # External diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index 2788801051..7b2b8c53d7 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -17,7 +17,6 @@ clap = { workspace = true, features = ["derive"] } codex-protocol = { workspace = true } codex-experimental-api-macros = { workspace = true } codex-utils-absolute-path = { workspace = true } -mcp-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 407626d82d..9e3cbb4156 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index b6777d5c8b..6df9a010b1 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -165,34 +165,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -226,36 +198,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -298,27 +240,6 @@ }, "type": "object" }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -362,10 +283,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -889,25 +809,6 @@ ], "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -1099,42 +1000,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "ErrorNotification": { "properties": { "error": { @@ -3714,36 +3579,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "ItemCompletedNotification": { "properties": { "item": { @@ -4037,9 +3872,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -4641,7 +4474,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -4697,22 +4531,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -4745,74 +4578,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -5403,14 +5172,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -5874,32 +5635,6 @@ ], "type": "object" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -5982,27 +5717,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { @@ -6714,38 +6428,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -6759,82 +6461,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "Turn": { "properties": { "error": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index aca840f6cb..e92d46e7b6 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -123,34 +123,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "ApplyPatchApprovalParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -258,36 +230,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -332,27 +274,6 @@ "title": "AuthStatusChangeNotification", "type": "object" }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -377,10 +298,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -2105,25 +2025,6 @@ "title": "CommandExecutionRequestApprovalResponse", "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -2383,42 +2284,6 @@ "title": "DynamicToolCallResponse", "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", @@ -5408,36 +5273,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "InitializeCapabilities": { "description": "Client-declared capabilities negotiated during initialize.", "properties": { @@ -6539,7 +6374,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -6595,22 +6431,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -6643,74 +6478,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -7431,14 +7202,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxMode": { "enum": [ "read-only", @@ -8841,32 +8604,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -8890,27 +8627,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -8971,38 +8687,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -9016,82 +8720,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "ToolRequestUserInputAnswer": { "description": "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", "properties": { @@ -9940,34 +9568,6 @@ }, "type": "object" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/v2/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AppInfo": { "properties": { "description": { @@ -10072,36 +9672,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -10128,27 +9698,6 @@ } ] }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -11280,25 +10829,6 @@ "title": "ConfigWriteResponse", "type": "object" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/v2/TextContent" - }, - { - "$ref": "#/definitions/v2/ImageContent" - }, - { - "$ref": "#/definitions/v2/AudioContent" - }, - { - "$ref": "#/definitions/v2/ResourceLink" - }, - { - "$ref": "#/definitions/v2/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -11440,42 +10970,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/v2/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/v2/TextResourceContents" - }, - { - "$ref": "#/definitions/v2/BlobResourceContents" - } - ] - }, "ErrorNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -11769,36 +11263,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "ItemCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -12242,9 +11706,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/v2/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -12882,22 +12344,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -12930,74 +12391,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -13520,14 +12917,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxMode": { "enum": [ "read-only", @@ -14050,32 +13439,6 @@ "title": "TerminalInteractionNotification", "type": "object" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -14135,27 +13498,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { @@ -15499,38 +14841,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/v2/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/v2/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/v2/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -15544,82 +14874,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "ToolsV2": { "properties": { "view_image": { diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json index f4c68a9f9d..ae6e705221 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json index fcb39cd05c..cdad2018c6 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json index 24e71bc27a..e6ed4d0427 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -93,34 +93,6 @@ } ] }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.", "oneOf": [ @@ -154,57 +126,6 @@ } ] }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -229,10 +150,9 @@ "CallToolResult": { "description": "The server's response to a tool call.", "properties": { + "_meta": true, "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "isError": { @@ -390,25 +310,6 @@ } ] }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, "ContentItem": { "oneOf": [ { @@ -544,42 +445,6 @@ ], "type": "object" }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "EventMsg": { "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", "oneOf": [ @@ -3071,36 +2936,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "LocalShellAction": { "oneOf": [ { @@ -3599,7 +3434,8 @@ "format": "int64", "type": "integer" } - ] + ], + "description": "ID of a request, which can be either a string or an integer." }, "RequestUserInputQuestion": { "properties": { @@ -3655,22 +3491,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -3703,74 +3538,10 @@ ], "type": "object" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -4361,14 +4132,6 @@ } ] }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "description": "Determines execution restrictions for model shell commands.", "oneOf": [ @@ -4678,32 +4441,6 @@ ], "type": "string" }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byte_range": { @@ -4727,27 +4464,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadId": { "type": "string" }, @@ -4808,38 +4524,26 @@ "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -4853,82 +4557,6 @@ ], "type": "object" }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, "TurnAbortReason": { "enum": [ "interrupted", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 866ce9aa2b..56ef813231 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -263,61 +184,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -337,36 +203,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -381,9 +217,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -468,95 +302,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -580,27 +325,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 2fd71404f6..625c99af93 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -263,61 +184,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -337,36 +203,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -381,9 +217,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -468,95 +302,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -580,27 +325,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json index 2ee4926965..fc181c2702 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ListMcpServerStatusResponse.json @@ -1,34 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "McpAuthStatus": { "enum": [ "unsupported", @@ -77,22 +49,21 @@ "Resource": { "description": "A known resource that the server is capable of reading.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, "mimeType": { "type": [ "string", @@ -128,16 +99,7 @@ "ResourceTemplate": { "description": "A template description for resources available on the server.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, + "annotations": true, "description": { "type": [ "string", @@ -169,49 +131,29 @@ ], "type": "object" }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "Tool": { "description": "Definition for a tool the client can call.", "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/ToolAnnotations" - }, - { - "type": "null" - } - ] - }, + "_meta": true, + "annotations": true, "description": { "type": [ "string", "null" ] }, - "inputSchema": { - "$ref": "#/definitions/ToolInputSchema" + "icons": { + "items": true, + "type": [ + "array", + "null" + ] }, + "inputSchema": true, "name": { "type": "string" }, - "outputSchema": { - "anyOf": [ - { - "$ref": "#/definitions/ToolOutputSchema" - }, - { - "type": "null" - } - ] - }, + "outputSchema": true, "title": { "type": [ "string", @@ -224,82 +166,6 @@ "name" ], "type": "object" - }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations received from untrusted servers.", - "properties": { - "destructiveHint": { - "type": [ - "boolean", - "null" - ] - }, - "idempotentHint": { - "type": [ - "boolean", - "null" - ] - }, - "openWorldHint": { - "type": [ - "boolean", - "null" - ] - }, - "readOnlyHint": { - "type": [ - "boolean", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - }, - "ToolInputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" - }, - "ToolOutputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult.", - "properties": { - "properties": true, - "required": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "default": "object", - "type": "string" - } - }, - "type": "object" } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 341ff78c0a..dfbd76a20b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 0a3826d8fd..61b12ceff3 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index eb4ec81f68..50c3d60e37 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index 02dab752e3..c4fa3b2028 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index dad4ccbfeb..0534f6e16e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 3ebb5052d3..148bbe7d7d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index d88d3694de..c85d7ce97a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -5,34 +5,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, "AskForApproval": { "enum": [ "untrusted", @@ -42,57 +14,6 @@ ], "type": "string" }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -418,61 +339,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -515,36 +381,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -559,9 +395,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -665,69 +499,6 @@ ], "type": "string" }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SandboxPolicy": { "oneOf": [ { @@ -900,32 +671,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -949,27 +694,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 0c8d47136e..44aca775c1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 46478554bb..055c3fd4a9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -502,36 +368,6 @@ }, "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -546,9 +382,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -633,69 +467,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, "SessionSource": { "oneOf": [ { @@ -773,32 +544,6 @@ } ] }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -822,27 +567,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "Thread": { "properties": { "cliVersion": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 918f82a456..798caa5995 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index d0d09a3f20..65b2a66be0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 9dd23a33e6..1a63c0d7d0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -1,85 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "items": { - "$ref": "#/definitions/Role" - }, - "type": [ - "array", - "null" - ] - }, - "lastModified": { - "type": [ - "string", - "null" - ] - }, - "priority": { - "format": "double", - "type": [ - "number", - "null" - ] - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "type": "string" - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "uri": { - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, "ByteRange": { "properties": { "end": { @@ -405,61 +326,6 @@ ], "type": "string" }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "resource": { - "$ref": "#/definitions/EmbeddedResourceResource" - }, - "type": { - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmbeddedResourceResource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -479,36 +345,6 @@ ], "type": "object" }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, "McpToolCallError": { "properties": { "message": { @@ -523,9 +359,7 @@ "McpToolCallResult": { "properties": { "content": { - "items": { - "$ref": "#/definitions/ContentBlock" - }, + "items": true, "type": "array" }, "structuredContent": true @@ -610,95 +444,6 @@ } ] }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": "string" - }, - "size": { - "format": "int64", - "type": [ - "integer", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - }, - "type": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, "TextElement": { "properties": { "byteRange": { @@ -722,27 +467,6 @@ ], "type": "object" }, - "TextResourceContents": { - "properties": { - "mimeType": { - "type": [ - "string", - "null" - ] - }, - "text": { - "type": "string" - }, - "uri": { - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, "ThreadItem": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/typescript/Annotations.ts b/codex-rs/app-server-protocol/schema/typescript/Annotations.ts deleted file mode 100644 index b89cbd725f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Annotations.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Role } from "./Role"; - -/** - * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed - */ -export type Annotations = { audience?: Array, lastModified?: string, priority?: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts b/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts deleted file mode 100644 index 90dd930c93..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AudioContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * Audio provided to or from an LLM. - */ -export type AudioContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts deleted file mode 100644 index 362adcfe14..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/BlobResourceContents.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type BlobResourceContents = { blob: string, mimeType?: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts index 6c6b590d1a..e7a471d465 100644 --- a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts +++ b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts @@ -1,10 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ContentBlock } from "./ContentBlock"; import type { JsonValue } from "./serde_json/JsonValue"; /** * The server's response to a tool call. */ -export type CallToolResult = { content: Array, isError?: boolean, structuredContent?: JsonValue, }; +export type CallToolResult = { content: Array, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts b/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts deleted file mode 100644 index 2dd62d87b4..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ContentBlock.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AudioContent } from "./AudioContent"; -import type { EmbeddedResource } from "./EmbeddedResource"; -import type { ImageContent } from "./ImageContent"; -import type { ResourceLink } from "./ResourceLink"; -import type { TextContent } from "./TextContent"; - -export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; diff --git a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts index a4d641fd44..045e304bdc 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts @@ -1,6 +1,5 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RequestId } from "./RequestId"; -export type ElicitationRequestEvent = { server_name: string, id: RequestId, message: string, }; +export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts deleted file mode 100644 index 1b81e37ab1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResource.ts +++ /dev/null @@ -1,13 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; -import type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; - -/** - * The contents of a resource, embedded into a prompt or tool call result. - * - * It is up to the client how best to render embedded resources for the benefit - * of the LLM and/or the user. - */ -export type EmbeddedResource = { annotations?: Annotations, resource: EmbeddedResourceResource, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts b/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts deleted file mode 100644 index 58f70f6e82..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/EmbeddedResourceResource.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { BlobResourceContents } from "./BlobResourceContents"; -import type { TextResourceContents } from "./TextResourceContents"; - -export type EmbeddedResourceResource = TextResourceContents | BlobResourceContents; diff --git a/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts b/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts deleted file mode 100644 index 0537639c02..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ImageContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * An image provided to or from an LLM. - */ -export type ImageContent = { annotations?: Annotations, data: string, mimeType: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Resource.ts b/codex-rs/app-server-protocol/schema/typescript/Resource.ts index 238cd9008c..6eca7941d6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Resource.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Resource.ts @@ -1,9 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * A known resource that the server is capable of reading. */ -export type Resource = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, uri: string, }; +export type Resource = { annotations?: JsonValue, description?: string, mimeType?: string, name: string, size?: number, title?: string, uri: string, icons?: Array, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts deleted file mode 100644 index fe934c3a5d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ResourceLink.ts +++ /dev/null @@ -1,11 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * A resource that the server is capable of reading, included in a prompt or tool call result. - * - * Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. - */ -export type ResourceLink = { annotations?: Annotations, description?: string, mimeType?: string, name: string, size?: bigint, title?: string, type: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts index 03c9a116c8..6dc395129c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResourceTemplate.ts @@ -1,9 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * A template description for resources available on the server. */ -export type ResourceTemplate = { annotations?: Annotations, description?: string, mimeType?: string, name: string, title?: string, uriTemplate: string, }; +export type ResourceTemplate = { annotations?: JsonValue, uriTemplate: string, name: string, title?: string, description?: string, mimeType?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Role.ts b/codex-rs/app-server-protocol/schema/typescript/Role.ts deleted file mode 100644 index 009307efce..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Role.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * The sender or recipient of messages and data in a conversation. - */ -export type Role = "assistant" | "user"; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextContent.ts b/codex-rs/app-server-protocol/schema/typescript/TextContent.ts deleted file mode 100644 index 760102f81d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TextContent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Annotations } from "./Annotations"; - -/** - * Text provided to or from an LLM. - */ -export type TextContent = { annotations?: Annotations, text: string, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts b/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts deleted file mode 100644 index 5bef79210c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TextResourceContents.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TextResourceContents = { mimeType?: string, text: string, uri: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Tool.ts b/codex-rs/app-server-protocol/schema/typescript/Tool.ts index 8255eecf2c..b795916140 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Tool.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Tool.ts @@ -1,11 +1,9 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ToolAnnotations } from "./ToolAnnotations"; -import type { ToolInputSchema } from "./ToolInputSchema"; -import type { ToolOutputSchema } from "./ToolOutputSchema"; +import type { JsonValue } from "./serde_json/JsonValue"; /** * Definition for a tool the client can call. */ -export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; +export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts b/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts deleted file mode 100644 index 70348a9964..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolAnnotations.ts +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Additional properties describing a Tool to clients. - * - * NOTE: all properties in ToolAnnotations are **hints**. - * They are not guaranteed to provide a faithful description of - * tool behavior (including descriptive properties like `title`). - * - * Clients should never make tool use decisions based on ToolAnnotations - * received from untrusted servers. - */ -export type ToolAnnotations = { destructiveHint?: boolean, idempotentHint?: boolean, openWorldHint?: boolean, readOnlyHint?: boolean, title?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts deleted file mode 100644 index 78f40bbe40..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolInputSchema.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -/** - * A JSON Schema object defining the expected parameters for the tool. - */ -export type ToolInputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts b/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts deleted file mode 100644 index 9270c8e7b7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ToolOutputSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -/** - * An optional JSON Schema object defining the structure of the tool's output returned in - * the structuredContent field of a CallToolResult. - */ -export type ToolOutputSchema = { properties?: JsonValue, required?: Array, type: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index fafc6aad9d..191f993864 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -14,18 +14,15 @@ export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawCont export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; export type { AgentStatus } from "./AgentStatus"; -export type { Annotations } from "./Annotations"; export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; export type { ArchiveConversationParams } from "./ArchiveConversationParams"; export type { ArchiveConversationResponse } from "./ArchiveConversationResponse"; export type { AskForApproval } from "./AskForApproval"; -export type { AudioContent } from "./AudioContent"; export type { AuthMode } from "./AuthMode"; export type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; export type { BackgroundEventEvent } from "./BackgroundEventEvent"; -export type { BlobResourceContents } from "./BlobResourceContents"; export type { ByteRange } from "./ByteRange"; export type { CallToolResult } from "./CallToolResult"; export type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; @@ -44,7 +41,6 @@ export type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; export type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; export type { CollaborationMode } from "./CollaborationMode"; export type { CollaborationModeMask } from "./CollaborationModeMask"; -export type { ContentBlock } from "./ContentBlock"; export type { ContentItem } from "./ContentItem"; export type { ContextCompactedEvent } from "./ContextCompactedEvent"; export type { ContextCompactionItem } from "./ContextCompactionItem"; @@ -55,8 +51,6 @@ export type { CustomPrompt } from "./CustomPrompt"; export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; export type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; export type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; -export type { EmbeddedResource } from "./EmbeddedResource"; -export type { EmbeddedResourceResource } from "./EmbeddedResourceResource"; export type { ErrorEvent } from "./ErrorEvent"; export type { EventMsg } from "./EventMsg"; export type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; @@ -92,7 +86,6 @@ export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; export type { GitSha } from "./GitSha"; export type { HistoryEntry } from "./HistoryEntry"; -export type { ImageContent } from "./ImageContent"; export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; @@ -152,7 +145,6 @@ export type { RequestUserInputEvent } from "./RequestUserInputEvent"; export type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; export type { Resource } from "./Resource"; -export type { ResourceLink } from "./ResourceLink"; export type { ResourceTemplate } from "./ResourceTemplate"; export type { ResponseItem } from "./ResponseItem"; export type { ResumeConversationParams } from "./ResumeConversationParams"; @@ -164,7 +156,6 @@ export type { ReviewLineRange } from "./ReviewLineRange"; export type { ReviewOutputEvent } from "./ReviewOutputEvent"; export type { ReviewRequest } from "./ReviewRequest"; export type { ReviewTarget } from "./ReviewTarget"; -export type { Role } from "./Role"; export type { SandboxMode } from "./SandboxMode"; export type { SandboxPolicy } from "./SandboxPolicy"; export type { SandboxSettings } from "./SandboxSettings"; @@ -191,9 +182,7 @@ export type { StepStatus } from "./StepStatus"; export type { StreamErrorEvent } from "./StreamErrorEvent"; export type { SubAgentSource } from "./SubAgentSource"; export type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; -export type { TextContent } from "./TextContent"; export type { TextElement } from "./TextElement"; -export type { TextResourceContents } from "./TextResourceContents"; export type { ThreadId } from "./ThreadId"; export type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; export type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; @@ -201,9 +190,6 @@ export type { TokenCountEvent } from "./TokenCountEvent"; export type { TokenUsage } from "./TokenUsage"; export type { TokenUsageInfo } from "./TokenUsageInfo"; export type { Tool } from "./Tool"; -export type { ToolAnnotations } from "./ToolAnnotations"; -export type { ToolInputSchema } from "./ToolInputSchema"; -export type { ToolOutputSchema } from "./ToolOutputSchema"; export type { Tools } from "./Tools"; export type { TurnAbortReason } from "./TurnAbortReason"; export type { TurnAbortedEvent } from "./TurnAbortedEvent"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts index 93f942a7f9..f493a86094 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallResult.ts @@ -1,7 +1,6 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ContentBlock } from "../ContentBlock"; import type { JsonValue } from "../serde_json/JsonValue"; -export type McpToolCallResult = { content: Array, structuredContent: JsonValue | null, }; +export type McpToolCallResult = { content: Array, structuredContent: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 3ca344ee49..4f37e2889d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -15,6 +15,9 @@ use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; use codex_protocol::items::TurnItem as CoreTurnItem; +use codex_protocol::mcp::Resource as McpResource; +use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate; +use codex_protocol::mcp::Tool as McpTool; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; @@ -41,10 +44,6 @@ use codex_protocol::user_input::ByteRange as CoreByteRange; use codex_protocol::user_input::TextElement as CoreTextElement; use codex_protocol::user_input::UserInput as CoreUserInput; use codex_utils_absolute_path::AbsolutePathBuf; -use mcp_types::ContentBlock as McpContentBlock; -use mcp_types::Resource as McpResource; -use mcp_types::ResourceTemplate as McpResourceTemplate; -use mcp_types::Tool as McpTool; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -2360,7 +2359,12 @@ impl From for CollabAgentState { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallResult { - pub content: Vec, + // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust + // representation of MCP content blocks. We intentionally use `serde_json::Value` here because + // this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types + // aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust + // representations). Using `JsonValue` keeps the payload wire-shaped and easy to export. + pub content: Vec, pub structured_content: Option, } diff --git a/codex-rs/app-server-protocol/src/schema_fixtures.rs b/codex-rs/app-server-protocol/src/schema_fixtures.rs index 918011e95c..5412da8632 100644 --- a/codex-rs/app-server-protocol/src/schema_fixtures.rs +++ b/codex-rs/app-server-protocol/src/schema_fixtures.rs @@ -2,6 +2,7 @@ use anyhow::Context; use anyhow::Result; use serde_json::Map; use serde_json::Value; +use std::cmp::Ordering; use std::collections::BTreeMap; use std::path::Path; use std::path::PathBuf; @@ -92,7 +93,49 @@ fn read_file_bytes(path: &Path) -> Result> { fn canonicalize_json(value: &Value) -> Value { match value { - Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()), + Value::Array(items) => { + // NOTE: We sort some JSON arrays to make schema fixture comparisons stable across + // platforms. + // + // In general, JSON array ordering is significant. However, this code path is used + // only by `schema_fixtures_match_generated` to compare our *vendored* JSON schema + // files against freshly generated output. Some parts of schema generation end up + // with non-deterministic ordering across platforms (often due to map iteration order + // upstream), which can cause Windows CI failures even when the generated schema is + // semantically equivalent. + // + // JSON Schema itself also contains a number of array-valued keywords whose ordering + // does not affect validation semantics (e.g. `required`, `type`, `enum`, `anyOf`, + // `oneOf`, `allOf`). That makes it reasonable to treat many schema-emitted arrays as + // order-insensitive for the purpose of fixture diffs. + // + // To avoid accidentally changing the meaning of arrays where order *could* matter + // (e.g. tuple validation / `prefixItems`-style arrays), we only sort arrays when we + // can derive a stable sort key for *every* element. If we cannot, we preserve the + // original ordering. + let items = items.iter().map(canonicalize_json).collect::>(); + let mut sortable = Vec::with_capacity(items.len()); + for item in &items { + let Some(key) = schema_array_item_sort_key(item) else { + return Value::Array(items); + }; + let stable = serde_json::to_string(item).unwrap_or_default(); + sortable.push((key, stable)); + } + + let mut items = items.into_iter().zip(sortable).collect::>(); + + items.sort_by( + |(_, (key_left, stable_left)), (_, (key_right, stable_right))| match key_left + .cmp(key_right) + { + Ordering::Equal => stable_left.cmp(stable_right), + other => other, + }, + ); + + Value::Array(items.into_iter().map(|(item, _)| item).collect()) + } Value::Object(map) => { let mut entries: Vec<_> = map.iter().collect(); entries.sort_by(|(left, _), (right, _)| left.cmp(right)); @@ -106,6 +149,25 @@ fn canonicalize_json(value: &Value) -> Value { } } +fn schema_array_item_sort_key(item: &Value) -> Option { + match item { + Value::Null => Some("null".to_string()), + Value::Bool(b) => Some(format!("b:{b}")), + Value::Number(n) => Some(format!("n:{n}")), + Value::String(s) => Some(format!("s:{s}")), + Value::Object(map) => { + if let Some(Value::String(reference)) = map.get("$ref") { + Some(format!("ref:{reference}")) + } else if let Some(Value::String(title)) = map.get("title") { + Some(format!("title:{title}")) + } else { + None + } + } + Value::Array(_) => None, + } +} + fn collect_files_recursive(root: &Path) -> Result>> { let mut files = BTreeMap::new(); @@ -146,3 +208,29 @@ fn collect_files_recursive(root: &Path) -> Result>> { Ok(files) } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn canonicalize_json_sorts_string_arrays() { + let value = serde_json::json!(["b", "a"]); + let expected = serde_json::json!(["a", "b"]); + assert_eq!(canonicalize_json(&value), expected); + } + + #[test] + fn canonicalize_json_sorts_schema_ref_arrays() { + let value = serde_json::json!([ + {"$ref": "#/definitions/B"}, + {"$ref": "#/definitions/A"} + ]); + let expected = serde_json::json!([ + {"$ref": "#/definitions/A"}, + {"$ref": "#/definitions/B"} + ]); + assert_eq!(canonicalize_json(&value), expected); + } +} diff --git a/codex-rs/app-server-test-client/Cargo.lock b/codex-rs/app-server-test-client/Cargo.lock index 1720850cd2..c6e4241d2c 100644 --- a/codex-rs/app-server-test-client/Cargo.lock +++ b/codex-rs/app-server-test-client/Cargo.lock @@ -175,7 +175,6 @@ dependencies = [ "base64", "icu_decimal", "icu_locale_core", - "mcp-types", "mime_guess", "serde", "serde_json", @@ -521,16 +520,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "mcp-types" -version = "0.45.0" -source = "git+https://github.com/openai/codex.git?tag=rust-v0.45.0#a7c7869c23f88f6c468281e6f438ba4a91b81f26" -dependencies = [ - "serde", - "serde_json", - "ts-rs", -] - [[package]] name = "memchr" version = "2.7.6" diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 11c57ebbc8..778d57bab0 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -35,7 +35,6 @@ codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -mcp-types = { workspace = true } tempfile = { workspace = true } time = { workspace = true } toml = { workspace = true } @@ -60,7 +59,6 @@ axum = { workspace = true, default-features = false, features = [ base64 = { workspace = true } codex-execpolicy = { workspace = true } core_test_support = { workspace = true } -mcp-types = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } rmcp = { workspace = true, default-features = false, features = [ diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index bf8fad3a9c..55b185e5a8 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -1841,12 +1841,11 @@ mod tests { use codex_core::protocol::RateLimitWindow; use codex_core::protocol::TokenUsage; use codex_core::protocol::TokenUsageInfo; + use codex_protocol::mcp::CallToolResult; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; - use mcp_types::CallToolResult; - use mcp_types::ContentBlock; - use mcp_types::TextContent; use pretty_assertions::assert_eq; + use rmcp::model::Content; use serde_json::Value as JsonValue; use std::collections::HashMap; use std::time::Duration; @@ -2374,15 +2373,15 @@ mod tests { #[tokio::test] async fn test_construct_mcp_tool_call_end_notification_success() { - let content = vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "{\"resources\":[]}".to_string(), - r#type: "text".to_string(), - })]; + let content = vec![ + serde_json::to_value(Content::text("{\"resources\":[]}")) + .expect("content should serialize"), + ]; let result = CallToolResult { content: content.clone(), is_error: Some(false), structured_content: None, + meta: None, }; let end_event = McpToolCallEndEvent { diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index a95981cca2..38da38b929 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -57,7 +57,6 @@ indexmap = { workspace = true } indoc = { workspace = true } keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } -mcp-types = { workspace = true } multimap = { workspace = true } once_cell = { workspace = true } os_info = { workspace = true } @@ -65,6 +64,12 @@ rand = { workspace = true } regex = { workspace = true } regex-lite = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } +rmcp = { workspace = true, default-features = false, features = [ + "base64", + "macros", + "schemars", + "server", +] } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 31a81846e2..6cc389b531 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -50,6 +50,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::items::PlanItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::BaseInstructions; use codex_protocol::models::format_allow_prefixes; use codex_protocol::openai_models::ModelInfo; @@ -72,14 +73,12 @@ use codex_rmcp_client::OAuthCredentialsStoreMode; use futures::future::BoxFuture; use futures::prelude::*; use futures::stream::FuturesOrdered; -use mcp_types::CallToolResult; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; use serde_json; use serde_json::Value; use tokio::sync::Mutex; @@ -2224,7 +2223,7 @@ impl Session { pub async fn list_resources( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2237,7 +2236,7 @@ impl Session { pub async fn list_resource_templates( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2250,7 +2249,7 @@ impl Session { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2577,10 +2576,10 @@ mod handlers { use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::dynamic_tools::DynamicToolResponse; + use codex_protocol::mcp::RequestId as ProtocolRequestId; use codex_protocol::user_input::UserInput; use codex_rmcp_client::ElicitationAction; use codex_rmcp_client::ElicitationResponse; - use mcp_types::RequestId; use std::path::PathBuf; use std::sync::Arc; use tracing::info; @@ -2709,7 +2708,7 @@ mod handlers { pub async fn resolve_elicitation( sess: &Arc, server_name: String, - request_id: RequestId, + request_id: ProtocolRequestId, decision: codex_protocol::approvals::ElicitationAction, ) { let action = match decision { @@ -2724,6 +2723,12 @@ mod handlers { ElicitationAction::Decline | ElicitationAction::Cancel => None, }; let response = ElicitationResponse { action, content }; + let request_id = match request_id { + ProtocolRequestId::String(value) => { + rmcp::model::NumberOrString::String(std::sync::Arc::from(value)) + } + ProtocolRequestId::Integer(value) => rmcp::model::NumberOrString::Number(value), + }; if let Err(err) = sess .resolve_elicitation(server_name, request_id, response) .await @@ -4516,8 +4521,7 @@ mod tests { use std::time::Duration; use tokio::time::sleep; - use mcp_types::ContentBlock; - use mcp_types::TextContent; + use codex_protocol::mcp::CallToolResult as McpCallToolResult; use pretty_assertions::assert_eq; use serde::Deserialize; use serde_json::json; @@ -5101,7 +5105,7 @@ mod tests { #[test] fn prefers_structured_content_when_present() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { // Content present but should be ignored because structured_content is set. content: vec![text_block("ignored")], is_error: None, @@ -5109,6 +5113,7 @@ mod tests { "ok": true, "value": 42 })), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5147,10 +5152,11 @@ mod tests { #[test] fn falls_back_to_content_when_structured_is_null() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("hello"), text_block("world")], is_error: None, structured_content: Some(serde_json::Value::Null), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5166,10 +5172,11 @@ mod tests { #[test] fn success_flag_reflects_is_error_true() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("unused")], is_error: Some(true), structured_content: Some(json!({ "message": "bad" })), + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5184,10 +5191,11 @@ mod tests { #[test] fn success_flag_true_with_no_error_and_content_used() { - let ctr = CallToolResult { + let ctr = McpCallToolResult { content: vec![text_block("alpha")], is_error: Some(false), structured_content: None, + meta: None, }; let got = FunctionCallOutputPayload::from(&ctr); @@ -5238,11 +5246,10 @@ mod tests { } } - fn text_block(s: &str) -> ContentBlock { - ContentBlock::TextContent(TextContent { - annotations: None, - text: s.to_string(), - r#type: "text".to_string(), + fn text_block(s: &str) -> serde_json::Value { + json!({ + "type": "text", + "text": s, }) } diff --git a/codex-rs/core/src/mcp/mod.rs b/codex-rs/core/src/mcp/mod.rs index 12c61ddaf1..cb0b5e2f26 100644 --- a/codex-rs/core/src/mcp/mod.rs +++ b/codex-rs/core/src/mcp/mod.rs @@ -1,6 +1,5 @@ pub mod auth; mod skill_dependencies; - pub(crate) use skill_dependencies::maybe_prompt_and_install_mcp_dependencies; use std::collections::HashMap; @@ -9,9 +8,12 @@ use std::path::PathBuf; use std::time::Duration; use async_channel::unbounded; +use codex_protocol::mcp::Resource; +use codex_protocol::mcp::ResourceTemplate; +use codex_protocol::mcp::Tool; use codex_protocol::protocol::McpListToolsResponseEvent; use codex_protocol::protocol::SandboxPolicy; -use mcp_types::Tool as McpTool; +use serde_json::Value; use tokio_util::sync::CancellationToken; use crate::AuthManager; @@ -201,8 +203,8 @@ pub fn split_qualified_tool_name(qualified_name: &str) -> Option<(String, String } pub fn group_tools_by_server( - tools: &HashMap, -) -> HashMap> { + tools: &HashMap, +) -> HashMap> { let mut grouped = HashMap::new(); for (qualified_name, tool) in tools { if let Some((server_name, tool_name)) = split_qualified_tool_name(qualified_name) { @@ -230,11 +232,96 @@ pub(crate) async fn collect_mcp_snapshot_from_manager( .map(|(name, entry)| (name.clone(), entry.auth_status)) .collect(); + let tools = tools + .into_iter() + .filter_map(|(name, tool)| match serde_json::to_value(tool.tool) { + Ok(value) => match Tool::from_mcp_value(value) { + Ok(tool) => Some((name, tool)), + Err(err) => { + tracing::warn!("Failed to convert MCP tool '{name}': {err}"); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP tool '{name}': {err}"); + None + } + }) + .collect(); + + let resources = resources + .into_iter() + .map(|(name, resources)| { + let resources = resources + .into_iter() + .filter_map(|resource| match serde_json::to_value(resource) { + Ok(value) => match Resource::from_mcp_value(value.clone()) { + Ok(resource) => Some(resource), + Err(err) => { + let (uri, resource_name) = match value { + Value::Object(obj) => ( + obj.get("uri") + .and_then(|v| v.as_str().map(ToString::to_string)), + obj.get("name") + .and_then(|v| v.as_str().map(ToString::to_string)), + ), + _ => (None, None), + }; + + tracing::warn!( + "Failed to convert MCP resource (uri={uri:?}, name={resource_name:?}): {err}" + ); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP resource: {err}"); + None + } + }) + .collect::>(); + (name, resources) + }) + .collect(); + + let resource_templates = resource_templates + .into_iter() + .map(|(name, templates)| { + let templates = templates + .into_iter() + .filter_map(|template| match serde_json::to_value(template) { + Ok(value) => match ResourceTemplate::from_mcp_value(value.clone()) { + Ok(template) => Some(template), + Err(err) => { + let (uri_template, template_name) = match value { + Value::Object(obj) => ( + obj.get("uriTemplate") + .or_else(|| obj.get("uri_template")) + .and_then(|v| v.as_str().map(ToString::to_string)), + obj.get("name") + .and_then(|v| v.as_str().map(ToString::to_string)), + ), + _ => (None, None), + }; + + tracing::warn!( + "Failed to convert MCP resource template (uri_template={uri_template:?}, name={template_name:?}): {err}" + ); + None + } + }, + Err(err) => { + tracing::warn!("Failed to serialize MCP resource template: {err}"); + None + } + }) + .collect::>(); + (name, templates) + }) + .collect(); + McpListToolsResponseEvent { - tools: tools - .into_iter() - .map(|(name, tool)| (name, tool.tool)) - .collect(), + tools, resources, resource_templates, auth_statuses, @@ -244,21 +331,18 @@ pub(crate) async fn collect_mcp_snapshot_from_manager( #[cfg(test)] mod tests { use super::*; - use mcp_types::ToolInputSchema; use pretty_assertions::assert_eq; - fn make_tool(name: &str) -> McpTool { - McpTool { - annotations: None, - description: None, - input_schema: ToolInputSchema { - properties: None, - required: None, - r#type: "object".to_string(), - }, + fn make_tool(name: &str) -> Tool { + Tool { name: name.to_string(), - output_schema: None, title: None, + description: None, + input_schema: serde_json::json!({"type": "object", "properties": {}}), + output_schema: None, + annotations: None, + icons: None, + meta: None, } } diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 1b5048ab0c..1a5ec559b8 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -23,6 +23,8 @@ use async_channel::Sender; use codex_async_utils::CancelErr; use codex_async_utils::OrCancelExt; use codex_protocol::approvals::ElicitationRequestEvent; +use codex_protocol::mcp::CallToolResult; +use codex_protocol::mcp::RequestId as ProtocolRequestId; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::McpStartupCompleteEvent; @@ -37,22 +39,23 @@ use codex_rmcp_client::SendElicitation; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::future::Shared; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::Tool; +use rmcp::model::ClientCapabilities; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ProtocolVersion; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; +use rmcp::model::Resource; +use rmcp::model::ResourceTemplate; +use rmcp::model::Tool; use serde::Deserialize; use serde::Serialize; -use serde_json::json; use sha1::Digest; use sha1::Sha1; use tokio::sync::Mutex; @@ -198,7 +201,14 @@ impl ElicitationRequestManager { id: "mcp_elicitation_request".to_string(), msg: EventMsg::ElicitationRequest(ElicitationRequestEvent { server_name, - id, + id: match id.clone() { + rmcp::model::NumberOrString::String(value) => { + ProtocolRequestId::String(value.to_string()) + } + rmcp::model::NumberOrString::Number(value) => { + ProtocolRequestId::Integer(value) + } + }, message: elicitation.message, }), }) @@ -493,7 +503,7 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor.as_ref().map(|next| ListResourcesRequestParams { + let params = cursor.as_ref().map(|next| PaginatedRequestParam { cursor: Some(next.clone()), }); let response = match client.list_resources(params, timeout).await { @@ -558,11 +568,9 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor - .as_ref() - .map(|next| ListResourceTemplatesRequestParams { - cursor: Some(next.clone()), - }); + let params = cursor.as_ref().map(|next| PaginatedRequestParam { + cursor: Some(next.clone()), + }); let response = match client.list_resource_templates(params, timeout).await { Ok(result) => result, Err(err) => return (server_name_cloned, Err(err)), @@ -615,7 +623,7 @@ impl McpConnectionManager { server: &str, tool: &str, arguments: Option, - ) -> Result { + ) -> Result { let client = self.client_by_name(server).await?; if !client.tool_filter.allows(tool) { return Err(anyhow!( @@ -623,18 +631,34 @@ impl McpConnectionManager { )); } - client + let result: rmcp::model::CallToolResult = client .client .call_tool(tool.to_string(), arguments, client.tool_timeout) .await - .with_context(|| format!("tool call failed for `{server}/{tool}`")) + .with_context(|| format!("tool call failed for `{server}/{tool}`"))?; + + let content = result + .content + .into_iter() + .map(|content| { + serde_json::to_value(content) + .unwrap_or_else(|_| serde_json::Value::String("".to_string())) + }) + .collect(); + + Ok(CallToolResult { + content, + structured_content: result.structured_content, + is_error: result.is_error, + meta: result.meta.and_then(|meta| serde_json::to_value(meta).ok()), + }) } /// List resources from the specified server. pub async fn list_resources( &self, server: &str, - params: Option, + params: Option, ) -> Result { let managed = self.client_by_name(server).await?; let timeout = managed.tool_timeout; @@ -650,7 +674,7 @@ impl McpConnectionManager { pub async fn list_resource_templates( &self, server: &str, - params: Option, + params: Option, ) -> Result { let managed = self.client_by_name(server).await?; let client = managed.client.clone(); @@ -666,7 +690,7 @@ impl McpConnectionManager { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, ) -> Result { let managed = self.client_by_name(server).await?; let client = managed.client.clone(); @@ -849,25 +873,25 @@ async fn start_server_task( tx_event: Sender, elicitation_requests: ElicitationRequestManager, ) -> Result { - let params = mcp_types::InitializeRequestParams { + let params = InitializeRequestParam { capabilities: ClientCapabilities { experimental: None, roots: None, sampling: None, // https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#capabilities // indicates this should be an empty object. - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), }, client_info: Implementation { name: "codex-mcp-client".to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(), title: Some("Codex".into()), - // This field is used by Codex when it is an MCP - // server: it should not be used when Codex is - // an MCP client. - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_owned(), + protocol_version: ProtocolVersion::V_2025_06_18, }; let send_elicitation = elicitation_requests.make_sender(server_name.clone(), tx_event); @@ -964,7 +988,7 @@ async fn list_tools_for_client( } ToolInfo { server_name: server_name.to_owned(), - tool_name: tool_def.name.clone(), + tool_name: tool_def.name.to_string(), tool: tool_def, connector_id: tool.connector_id, connector_name, @@ -1047,24 +1071,23 @@ mod mcp_init_error_display_tests {} mod tests { use super::*; use codex_protocol::protocol::McpAuthStatus; - use mcp_types::ToolInputSchema; + use rmcp::model::JsonObject; use std::collections::HashSet; + use std::sync::Arc; fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo { ToolInfo { server_name: server_name.to_string(), tool_name: tool_name.to_string(), tool: Tool { - annotations: None, - description: Some(format!("Test tool: {tool_name}")), - input_schema: ToolInputSchema { - properties: None, - required: None, - r#type: "object".to_string(), - }, - name: tool_name.to_string(), - output_schema: None, + name: tool_name.to_string().into(), title: None, + description: Some(format!("Test tool: {tool_name}").into()), + input_schema: Arc::new(JsonObject::default()), + output_schema: None, + annotations: None, + icons: None, + meta: None, }, connector_id: None, connector_name: None, diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 6425392fb5..75248f34cc 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -10,6 +10,7 @@ use crate::protocol::EventMsg; use crate::protocol::McpInvocation; use crate::protocol::McpToolCallBeginEvent; use crate::protocol::McpToolCallEndEvent; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::protocol::AskForApproval; @@ -18,7 +19,7 @@ use codex_protocol::request_user_input::RequestUserInputArgs; use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::request_user_input::RequestUserInputResponse; -use mcp_types::ToolAnnotations; +use rmcp::model::ToolAnnotations; use std::sync::Arc; /// Handles the specified tool call dispatches the appropriate @@ -72,7 +73,7 @@ pub(crate) async fn handle_mcp_tool_call( .await; let start = Instant::now(); - let result = sess + let result: Result = sess .call_tool(&server, &tool_name, arguments_value.clone()) .await .map_err(|e| format!("tool call error: {e:?}")); @@ -134,7 +135,7 @@ pub(crate) async fn handle_mcp_tool_call( let start = Instant::now(); // Perform the tool call. - let result = sess + let result: Result = sess .call_tool(&server, &tool_name, arguments_value.clone()) .await .map_err(|e| format!("tool call error: {e:?}")); @@ -341,7 +342,7 @@ async fn notify_mcp_tool_call_skip( call_id: &str, invocation: McpInvocation, message: String, -) -> Result { +) -> Result { let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent { call_id: call_id.to_string(), invocation: invocation.clone(), diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index abe488681e..f0bbb158f5 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -4,12 +4,12 @@ use crate::tools::TELEMETRY_PREVIEW_MAX_BYTES; use crate::tools::TELEMETRY_PREVIEW_MAX_LINES; use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE; use crate::turn_diff_tracker::TurnDiffTracker; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ShellToolCallParams; use codex_utils_string::take_bytes_at_char_boundary; -use mcp_types::CallToolResult; use std::borrow::Cow; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index 62f7a83e1a..df421bd6d3 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -4,17 +4,14 @@ use std::time::Duration; use std::time::Instant; use async_trait::async_trait; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::TextContent; +use codex_protocol::mcp::CallToolResult; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::PaginatedRequestParam; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::Resource; +use rmcp::model::ResourceTemplate; use serde::Deserialize; use serde::Serialize; use serde::de::DeserializeOwned; @@ -264,7 +261,7 @@ async fn handle_list_resources( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor.clone().map(|value| ListResourcesRequestParams { + let params = cursor.clone().map(|value| PaginatedRequestParam { cursor: Some(value), }); let result = session @@ -371,11 +368,9 @@ async fn handle_list_resource_templates( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor - .clone() - .map(|value| ListResourceTemplatesRequestParams { - cursor: Some(value), - }); + let params = cursor.clone().map(|value| PaginatedRequestParam { + cursor: Some(value), + }); let result = session .list_resource_templates(&server_name, params) .await @@ -482,7 +477,7 @@ async fn handle_read_resource( let payload_result: Result = async { let result = session - .read_resource(&server, ReadResourceRequestParams { uri: uri.clone() }) + .read_resource(&server, ReadResourceRequestParam { uri: uri.clone() }) .await .map_err(|err| { FunctionCallError::RespondToModel(format!("resources/read failed: {err:#}")) @@ -551,13 +546,10 @@ async fn handle_read_resource( fn call_tool_result_from_content(content: &str, success: Option) -> CallToolResult { CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: content.to_string(), - r#type: "text".to_string(), - })], - is_error: success.map(|value| !value), + content: vec![serde_json::json!({"type": "text", "text": content})], structured_content: None, + is_error: success.map(|value| !value), + meta: None, } } @@ -678,32 +670,33 @@ where #[cfg(test)] mod tests { use super::*; - use mcp_types::ListResourcesResult; - use mcp_types::ResourceTemplate; use pretty_assertions::assert_eq; + use rmcp::model::AnnotateAble; use serde_json::json; fn resource(uri: &str, name: &str) -> Resource { - Resource { - annotations: None, + rmcp::model::RawResource { + uri: uri.to_string(), + name: name.to_string(), + title: None, description: None, mime_type: None, - name: name.to_string(), size: None, - title: None, - uri: uri.to_string(), + icons: None, + meta: None, } + .no_annotation() } fn template(uri_template: &str, name: &str) -> ResourceTemplate { - ResourceTemplate { - annotations: None, - description: None, - mime_type: None, + rmcp::model::RawResourceTemplate { + uri_template: uri_template.to_string(), name: name.to_string(), title: None, - uri_template: uri_template.to_string(), + description: None, + mime_type: None, } + .no_annotation() } #[test] @@ -719,6 +712,7 @@ mod tests { #[test] fn list_resources_payload_from_single_server_copies_next_cursor() { let result = ListResourcesResult { + meta: None, next_cursor: Some("cursor-1".to_string()), resources: vec![resource("memo://id", "memo")], }; diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index d3ff84a2b0..51328ccc9f 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -15,6 +15,7 @@ use codex_protocol::models::LocalShellAction; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::models::ShellToolCallParams; +use rmcp::model::Tool; use std::collections::HashMap; use std::sync::Arc; use tracing::instrument; @@ -34,7 +35,7 @@ pub struct ToolRouter { impl ToolRouter { pub fn from_config( config: &ToolsConfig, - mcp_tools: Option>, + mcp_tools: Option>, dynamic_tools: &[DynamicToolSpec], ) -> Self { let builder = build_specs(config, mcp_tools, dynamic_tools); diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index a51372b9b3..c08feeef9a 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1087,20 +1087,26 @@ pub(crate) fn create_tools_json_for_chat_completions_api( pub(crate) fn mcp_tool_to_openai_tool( fully_qualified_name: String, - tool: mcp_types::Tool, + tool: rmcp::model::Tool, ) -> Result { - let mcp_types::Tool { + let rmcp::model::Tool { description, - mut input_schema, + input_schema, .. } = tool; - // OpenAI models mandate the "properties" field in the schema. The Agents - // SDK fixed this by inserting an empty object for "properties" if it is not - // already present https://github.com/openai/openai-agents-python/issues/449 - // so here we do the same. - if input_schema.properties.is_none() { - input_schema.properties = Some(serde_json::Value::Object(serde_json::Map::new())); + let mut serialized_input_schema = serde_json::Value::Object(input_schema.as_ref().clone()); + + // OpenAI models mandate the "properties" field in the schema. Some MCP + // servers omit it (or set it to null), so we insert an empty object to + // match the behavior of the Agents SDK. + if let serde_json::Value::Object(obj) = &mut serialized_input_schema + && obj.get("properties").is_none_or(serde_json::Value::is_null) + { + obj.insert( + "properties".to_string(), + serde_json::Value::Object(serde_json::Map::new()), + ); } // Serialize to a raw JSON value so we can sanitize schemas coming from MCP @@ -1108,13 +1114,12 @@ pub(crate) fn mcp_tool_to_openai_tool( // Schemas (e.g. using enum/anyOf), or use unsupported variants like // `integer`. Our internal JsonSchema is a small subset and requires // `type`, so we coerce/sanitize here for compatibility. - let mut serialized_input_schema = serde_json::to_value(input_schema)?; sanitize_json_schema(&mut serialized_input_schema); let input_schema = serde_json::from_value::(serialized_input_schema)?; Ok(ResponsesApiTool { name: fully_qualified_name, - description: description.unwrap_or_default(), + description: description.map(Into::into).unwrap_or_default(), strict: false, parameters: input_schema, }) @@ -1254,7 +1259,7 @@ fn sanitize_json_schema(value: &mut JsonValue) { /// Builds the tool registry builder while collecting tool specs for later serialization. pub(crate) fn build_specs( config: &ToolsConfig, - mcp_tools: Option>, + mcp_tools: Option>, dynamic_tools: &[DynamicToolSpec], ) -> ToolRegistryBuilder { use crate::tools::handlers::ApplyPatchHandler; @@ -1410,7 +1415,7 @@ pub(crate) fn build_specs( } if let Some(mcp_tools) = mcp_tools { - let mut entries: Vec<(String, mcp_types::Tool)> = mcp_tools.into_iter().collect(); + let mut entries: Vec<(String, rmcp::model::Tool)> = mcp_tools.into_iter().collect(); entries.sort_by(|a, b| a.0.cmp(&b.0)); for (name, tool) in entries.into_iter() { @@ -1452,11 +1457,50 @@ mod tests { use crate::config::test_config; use crate::models_manager::manager::ModelsManager; use crate::tools::registry::ConfiguredToolSpec; - use mcp_types::ToolInputSchema; use pretty_assertions::assert_eq; use super::*; + fn mcp_tool( + name: &str, + description: &str, + input_schema: serde_json::Value, + ) -> rmcp::model::Tool { + rmcp::model::Tool { + name: name.to_string().into(), + title: None, + description: Some(description.to_string().into()), + input_schema: std::sync::Arc::new(rmcp::model::object(input_schema)), + output_schema: None, + annotations: None, + icons: None, + meta: None, + } + } + + #[test] + fn mcp_tool_to_openai_tool_inserts_empty_properties() { + let mut schema = rmcp::model::JsonObject::new(); + schema.insert("type".to_string(), serde_json::json!("object")); + + let tool = rmcp::model::Tool { + name: "no_props".to_string().into(), + title: None, + description: Some("No properties".to_string().into()), + input_schema: std::sync::Arc::new(schema), + output_schema: None, + annotations: None, + icons: None, + meta: None, + }; + + let openai_tool = + mcp_tool_to_openai_tool("server/no_props".to_string(), tool).expect("convert tool"); + let parameters = serde_json::to_value(openai_tool.parameters).expect("serialize schema"); + + assert_eq!(parameters.get("properties"), Some(&serde_json::json!({}))); + } + fn tool_name(tool: &ToolSpec) -> &str { match tool { ToolSpec::Function(ResponsesApiTool { name, .. }) => name, @@ -2026,37 +2070,26 @@ mod tests { &tools_config, Some(HashMap::from([( "test_server/do_something_cool".to_string(), - mcp_types::Tool { - name: "do_something_cool".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "string_argument": { - "type": "string", - }, - "number_argument": { - "type": "number", - }, + mcp_tool( + "do_something_cool", + "Do something cool", + serde_json::json!({ + "type": "object", + "properties": { + "string_argument": { "type": "string" }, + "number_argument": { "type": "number" }, "object_argument": { "type": "object", "properties": { "string_property": { "type": "string" }, "number_property": { "type": "number" }, }, - "required": [ - "string_property", - "number_property", - ], - "additionalProperties": Some(false), + "required": ["string_property", "number_property"], + "additionalProperties": false, }, - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Do something cool".to_string()), - }, + }, + }), + ), )])), &[], ) @@ -2120,51 +2153,18 @@ mod tests { }); // Intentionally construct a map with keys that would sort alphabetically. - let tools_map: HashMap = HashMap::from([ + let tools_map: HashMap = HashMap::from([ ( "test_server/do".to_string(), - mcp_types::Tool { - name: "a".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("a".to_string()), - }, + mcp_tool("a", "a", serde_json::json!({"type": "object"})), ), ( "test_server/something".to_string(), - mcp_types::Tool { - name: "b".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("b".to_string()), - }, + mcp_tool("b", "b", serde_json::json!({"type": "object"})), ), ( "test_server/cool".to_string(), - mcp_types::Tool { - name: "c".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({})), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("c".to_string()), - }, + mcp_tool("c", "c", serde_json::json!({"type": "object"})), ), ]); @@ -2200,22 +2200,16 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/search".to_string(), - mcp_types::Tool { - name: "search".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "query": { - "description": "search query" - } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Search docs".to_string()), - }, + mcp_tool( + "search", + "Search docs", + serde_json::json!({ + "type": "object", + "properties": { + "query": {"description": "search query"} + } + }), + ), )])), &[], ) @@ -2258,20 +2252,14 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/paginate".to_string(), - mcp_types::Tool { - name: "paginate".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "page": { "type": "integer" } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Pagination".to_string()), - }, + mcp_tool( + "paginate", + "Pagination", + serde_json::json!({ + "type": "object", + "properties": {"page": {"type": "integer"}} + }), + ), )])), &[], ) @@ -2313,20 +2301,14 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/tags".to_string(), - mcp_types::Tool { - name: "tags".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "tags": { "type": "array" } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Tags".to_string()), - }, + mcp_tool( + "tags", + "Tags", + serde_json::json!({ + "type": "object", + "properties": {"tags": {"type": "array"}} + }), + ), )])), &[], ) @@ -2370,20 +2352,16 @@ mod tests { &tools_config, Some(HashMap::from([( "dash/value".to_string(), - mcp_types::Tool { - name: "value".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "value": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("AnyOf Value".to_string()), - }, + mcp_tool( + "value", + "AnyOf Value", + serde_json::json!({ + "type": "object", + "properties": { + "value": {"anyOf": [{"type": "string"}, {"type": "number"}]} + } + }), + ), )])), &[], ) @@ -2482,46 +2460,33 @@ Examples of valid command strings: &tools_config, Some(HashMap::from([( "test_server/do_something_cool".to_string(), - mcp_types::Tool { - name: "do_something_cool".to_string(), - input_schema: ToolInputSchema { - properties: Some(serde_json::json!({ - "string_argument": { - "type": "string", - }, - "number_argument": { - "type": "number", - }, + mcp_tool( + "do_something_cool", + "Do something cool", + serde_json::json!({ + "type": "object", + "properties": { + "string_argument": {"type": "string"}, + "number_argument": {"type": "number"}, "object_argument": { "type": "object", "properties": { - "string_property": { "type": "string" }, - "number_property": { "type": "number" }, + "string_property": {"type": "string"}, + "number_property": {"type": "number"} }, - "required": [ - "string_property", - "number_property", - ], + "required": ["string_property", "number_property"], "additionalProperties": { "type": "object", "properties": { - "addtl_prop": { "type": "string" }, + "addtl_prop": {"type": "string"} }, - "required": [ - "addtl_prop", - ], - "additionalProperties": false, - }, - }, - })), - required: None, - r#type: "object".to_string(), - }, - output_schema: None, - title: None, - annotations: None, - description: Some("Do something cool".to_string()), - }, + "required": ["addtl_prop"], + "additionalProperties": false + } + } + } + }), + ), )])), &[], ) diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index 5b80f80ba3..5df2a74edd 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -209,7 +209,7 @@ where use tokio::time::timeout; loop { // Allow a bit more time to accommodate async startup work (e.g. config IO, tool discovery) - let ev = timeout(wait_time.max(Duration::from_secs(5)), codex.next_event()) + let ev = timeout(wait_time.max(Duration::from_secs(10)), codex.next_event()) .await .expect("timeout waiting for event") .expect("stream ended unexpectedly"); diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 7eaeef6590..cdf6a1adc5 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -26,7 +26,6 @@ use core_test_support::skip_if_no_network; use core_test_support::stdio_server_bin; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; -use mcp_types::ContentBlock; use serde_json::Value; use serde_json::json; use serial_test::serial; @@ -307,14 +306,10 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { let base64_only = OPENAI_PNG .strip_prefix("data:image/png;base64,") .expect("data url prefix"); - match &result.content[0] { - ContentBlock::ImageContent(img) => { - assert_eq!(img.mime_type, "image/png"); - assert_eq!(img.r#type, "image"); - assert_eq!(img.data, base64_only); - } - other => panic!("expected image content, got {other:?}"), - } + let entry = result.content[0].as_object().expect("content object"); + assert_eq!(entry.get("type"), Some(&json!("image"))); + assert_eq!(entry.get("mimeType"), Some(&json!("image/png"))); + assert_eq!(entry.get("data"), Some(&json!(base64_only))); wait_for_event(&fixture.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index 862d8d1694..860f4034be 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -28,7 +28,6 @@ codex-common = { workspace = true, features = [ codex-core = { workspace = true } codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } -mcp-types = { workspace = true } owo-colors = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } @@ -56,9 +55,9 @@ assert_cmd = { workspace = true } codex-utils-cargo-bin = { workspace = true } core_test_support = { workspace = true } libc = { workspace = true } -mcp-types = { workspace = true } predicates = { workspace = true } pretty_assertions = { workspace = true } +rmcp = { workspace = true } tempfile = { workspace = true } uuid = { workspace = true } walkdir = { workspace = true } diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 8469dc42ed..3b4a3bf86b 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -375,7 +375,8 @@ impl EventProcessor for EventProcessorWithHumanOutput { ts_msg!(self, "{}", title.style(title_style)); if let Ok(res) = result { - let val: serde_json::Value = res.into(); + let val = serde_json::to_value(res) + .unwrap_or_else(|_| serde_json::Value::String("".to_string())); let pretty = serde_json::to_string_pretty(&val).unwrap_or_else(|_| val.to_string()); diff --git a/codex-rs/exec/src/exec_events.rs b/codex-rs/exec/src/exec_events.rs index 47c67a6dca..368098f16b 100644 --- a/codex-rs/exec/src/exec_events.rs +++ b/codex-rs/exec/src/exec_events.rs @@ -1,5 +1,4 @@ use codex_protocol::models::WebSearchAction; -use mcp_types::ContentBlock as McpContentBlock; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; @@ -256,7 +255,14 @@ pub struct CollabToolCallItem { /// Result payload produced by an MCP tool invocation. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)] pub struct McpToolCallItemResult { - pub content: Vec, + // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a + // more precise Rust representation of MCP content blocks. We intentionally + // use `serde_json::Value` here because this crate exports JSON schema + TS + // types (`schemars`/`ts-rs`), and the rmcp model types aren't set up to be + // schema/TS friendly (and would introduce heavier coupling to rmcp's Rust + // representations). Using `JsonValue` keeps the payload wire-shaped and + // easy to export. + pub content: Vec, pub structured_content: Option, } diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index 47b7a9f331..6a853857da 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -56,6 +56,7 @@ use codex_exec::exec_events::Usage; use codex_exec::exec_events::WebSearchItem; use codex_protocol::ThreadId; use codex_protocol::config_types::ModeKind; +use codex_protocol::mcp::CallToolResult; use codex_protocol::models::WebSearchAction; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; @@ -63,10 +64,8 @@ use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ExecCommandOutputDeltaEvent; use codex_protocol::protocol::ExecOutputStream; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::TextContent; use pretty_assertions::assert_eq; +use rmcp::model::Content; use serde_json::json; use std::path::PathBuf; use std::time::Duration; @@ -385,6 +384,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() { content: Vec::new(), is_error: None, structured_content: None, + meta: None, }), }), ); @@ -499,13 +499,10 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { invocation, duration: Duration::from_millis(10), result: Ok(CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "done".to_string(), - r#type: "text".to_string(), - })], + content: vec![serde_json::to_value(Content::text("done")).unwrap()], is_error: None, structured_content: Some(json!({ "status": "ok" })), + meta: None, }), }), ); @@ -520,11 +517,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { tool: "tool_z".to_string(), arguments: serde_json::Value::Null, result: Some(McpToolCallItemResult { - content: vec![ContentBlock::TextContent(TextContent { - annotations: None, - text: "done".to_string(), - r#type: "text".to_string(), - })], + content: vec![serde_json::to_value(Content::text("done")).unwrap()], structured_content: Some(json!({ "status": "ok" })), }), error: None, diff --git a/codex-rs/mcp-server/Cargo.toml b/codex-rs/mcp-server/Cargo.toml index 6236384c95..7a952342de 100644 --- a/codex-rs/mcp-server/Cargo.toml +++ b/codex-rs/mcp-server/Cargo.toml @@ -22,7 +22,7 @@ codex-common = { workspace = true, features = ["cli"] } codex-core = { workspace = true } codex-protocol = { workspace = true } codex-utils-json-to-toml = { workspace = true } -mcp-types = { workspace = true } +rmcp = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 8131d7da52..94bf4369a9 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -6,15 +6,15 @@ use codex_core::protocol::AskForApproval; use codex_protocol::ThreadId; use codex_protocol::config_types::SandboxMode; use codex_utils_json_to_toml::json_to_toml; -use mcp_types::Tool; -use mcp_types::ToolInputSchema; -use mcp_types::ToolOutputSchema; +use rmcp::model::JsonObject; +use rmcp::model::Tool; use schemars::JsonSchema; use schemars::r#gen::SchemaSettings; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; /// Client-supplied configuration for a `codex` tool-call. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)] @@ -115,35 +115,35 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool { .into_generator() .into_root_schema_for::(); - #[expect(clippy::expect_used)] - let schema_value = - serde_json::to_value(&schema).expect("Codex tool schema should serialise to JSON"); - - let tool_input_schema = - serde_json::from_value::(schema_value).unwrap_or_else(|e| { - panic!("failed to create Tool from schema: {e}"); - }); + let input_schema = create_tool_input_schema(schema, "Codex tool schema should serialize"); Tool { - name: "codex".to_string(), + name: "codex".into(), title: Some("Codex".to_string()), - input_schema: tool_input_schema, + input_schema, output_schema: Some(codex_tool_output_schema()), description: Some( - "Run a Codex session. Accepts configuration parameters matching the Codex Config struct.".to_string(), + "Run a Codex session. Accepts configuration parameters matching the Codex Config struct." + .into(), ), annotations: None, + icons: None, + meta: None, } } -fn codex_tool_output_schema() -> ToolOutputSchema { - ToolOutputSchema { - properties: Some(serde_json::json!({ +fn codex_tool_output_schema() -> Arc { + let schema = serde_json::json!({ + "type": "object", + "properties": { "threadId": { "type": "string" }, "content": { "type": "string" } - })), - required: Some(vec!["threadId".to_string(), "content".to_string()]), - r#type: "object".to_string(), + }, + "required": ["threadId", "content"], + }); + match schema { + serde_json::Value::Object(map) => Arc::new(map), + _ => unreachable!("json literal must be an object"), } } @@ -237,27 +237,46 @@ pub(crate) fn create_tool_for_codex_tool_call_reply_param() -> Tool { .into_generator() .into_root_schema_for::(); - #[expect(clippy::expect_used)] - let schema_value = - serde_json::to_value(&schema).expect("Codex reply tool schema should serialise to JSON"); - - let tool_input_schema = - serde_json::from_value::(schema_value).unwrap_or_else(|e| { - panic!("failed to create Tool from schema: {e}"); - }); + let input_schema = create_tool_input_schema(schema, "Codex reply tool schema should serialize"); Tool { - name: "codex-reply".to_string(), + name: "codex-reply".into(), title: Some("Codex Reply".to_string()), - input_schema: tool_input_schema, + input_schema, output_schema: Some(codex_tool_output_schema()), description: Some( - "Continue a Codex conversation by providing the thread id and prompt.".to_string(), + "Continue a Codex conversation by providing the thread id and prompt.".into(), ), annotations: None, + icons: None, + meta: None, } } +fn create_tool_input_schema( + schema: schemars::schema::RootSchema, + panic_message: &str, +) -> Arc { + #[expect(clippy::expect_used)] + let schema_value = serde_json::to_value(&schema).expect(panic_message); + let mut schema_object = match schema_value { + serde_json::Value::Object(object) => object, + _ => panic!("tool schema should serialize to a JSON object"), + }; + + // Prefer keeping the "core" JSON Schema keys while still preserving `$defs` + // in case any `$ref` leaks into the generated schema (even though we try + // to inline subschemas). + let mut input_schema = JsonObject::new(); + for key in ["properties", "required", "type", "$defs", "definitions"] { + if let Some(value) = schema_object.remove(key) { + input_schema.insert(key.to_string(), value); + } + } + + Arc::new(input_schema) +} + #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 1dbdbc767b..6273607a22 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -23,15 +23,12 @@ use codex_core::protocol::Submission; use codex_core::protocol::TurnCompleteEvent; use codex_protocol::ThreadId; use codex_protocol::user_input::UserInput; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; -use mcp_types::RequestId; -use mcp_types::TextContent; +use rmcp::model::CallToolResult; +use rmcp::model::Content; +use rmcp::model::RequestId; use serde_json::json; use tokio::sync::Mutex; -pub(crate) const INVALID_PARAMS_ERROR_CODE: i64 = -32602; - /// To adhere to MCP `tools/call` response format, include the Codex /// `threadId` in the `structured_content` field of the response. /// Some MCP clients ignore `content` when `structuredContent` is present, so @@ -42,11 +39,7 @@ pub(crate) fn create_call_tool_result_with_thread_id( is_error: Option, ) -> CallToolResult { let content_text = text; - let content = vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: content_text.clone(), - annotations: None, - })]; + let content = vec![Content::text(content_text.clone())]; let structured_content = json!({ "threadId": thread_id, "content": content_text, @@ -55,6 +48,7 @@ pub(crate) fn create_call_tool_result_with_thread_id( content, is_error, structured_content: Some(structured_content), + meta: None, } } @@ -78,13 +72,10 @@ pub async fn run_codex_tool_session( Ok(res) => res, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: format!("Failed to start Codex session: {e}"), - annotations: None, - })], + content: vec![Content::text(format!("Failed to start Codex session: {e}"))], is_error: Some(true), structured_content: None, + meta: None, }; outgoing.send_response(id.clone(), result).await; return; @@ -109,10 +100,7 @@ pub async fn run_codex_tool_session( // Use the original MCP request ID as the `sub_id` for the Codex submission so that // any events emitted for this tool-call can be correlated with the // originating `tools/call` request. - let sub_id = match &id { - RequestId::String(s) => s.clone(), - RequestId::Integer(n) => n.to_string(), - }; + let sub_id = id.to_string(); running_requests_id_to_codex_uuid .lock() .await @@ -207,10 +195,7 @@ async fn run_codex_tool_session_inner( request_id: RequestId, running_requests_id_to_codex_uuid: Arc>>, ) { - let request_id_str = match &request_id { - RequestId::String(s) => s.clone(), - RequestId::Integer(n) => n.to_string(), - }; + let request_id_str = request_id.to_string(); // Stream events until the task needs to pause for user interaction or // completes. diff --git a/codex-rs/mcp-server/src/error_code.rs b/codex-rs/mcp-server/src/error_code.rs deleted file mode 100644 index 1ffd889d40..0000000000 --- a/codex-rs/mcp-server/src/error_code.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) const INVALID_REQUEST_ERROR_CODE: i64 = -32600; -pub(crate) const INTERNAL_ERROR_CODE: i64 = -32603; diff --git a/codex-rs/mcp-server/src/exec_approval.rs b/codex-rs/mcp-server/src/exec_approval.rs index a98099dcf2..c7914d1e60 100644 --- a/codex-rs/mcp-server/src/exec_approval.rs +++ b/codex-rs/mcp-server/src/exec_approval.rs @@ -6,20 +6,16 @@ use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; use codex_protocol::ThreadId; use codex_protocol::parse_command::ParsedCommand; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPCErrorError; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; +use rmcp::model::ErrorData; +use rmcp::model::RequestId; use serde::Deserialize; use serde::Serialize; +use serde_json::Value; use serde_json::json; use tracing::error; -use crate::codex_tool_runner::INVALID_PARAMS_ERROR_CODE; - -/// Conforms to [`mcp_types::ElicitRequestParams`] so that it can be used as the -/// `params` field of an [`ElicitRequest`]. +/// Conforms to the MCP elicitation request params shape, so it can be used as +/// the `params` field of an `elicitation/create` request. #[derive(Debug, Deserialize, Serialize)] pub struct ExecApprovalElicitRequestParams { // These fields are required so that `params` @@ -27,7 +23,7 @@ pub struct ExecApprovalElicitRequestParams { pub message: String, #[serde(rename = "requestedSchema")] - pub requested_schema: ElicitRequestParamsRequestedSchema, + pub requested_schema: Value, // These are additional fields the client can use to // correlate the request with the codex tool call. @@ -73,11 +69,7 @@ pub(crate) async fn handle_exec_approval_request( let params = ExecApprovalElicitRequestParams { message, - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, + requested_schema: json!({"type":"object","properties":{}}), thread_id, codex_elicitation: "exec-approval".to_string(), codex_mcp_tool_call_id: tool_call_id.clone(), @@ -94,14 +86,7 @@ pub(crate) async fn handle_exec_approval_request( error!("{message}"); outgoing - .send_error( - request_id.clone(), - JSONRPCErrorError { - code: INVALID_PARAMS_ERROR_CODE, - message, - data: None, - }, - ) + .send_error(request_id.clone(), ErrorData::invalid_params(message, None)) .await; return; @@ -109,7 +94,7 @@ pub(crate) async fn handle_exec_approval_request( }; let on_response = outgoing - .send_request(ElicitRequest::METHOD, Some(params_json)) + .send_request("elicitation/create", Some(params_json)) .await; // Listen for the response on a separate task so we don't block the main agent loop. @@ -124,7 +109,7 @@ pub(crate) async fn handle_exec_approval_request( async fn on_exec_approval_response( event_id: String, - receiver: tokio::sync::oneshot::Receiver, + receiver: tokio::sync::oneshot::Receiver, codex: Arc, ) { let response = receiver.await; diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index dabd7cca0f..eed176c575 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -8,7 +8,10 @@ use std::path::PathBuf; use codex_common::CliConfigOverrides; use codex_core::config::Config; -use mcp_types::JSONRPCMessage; +use rmcp::model::ClientNotification; +use rmcp::model::ClientRequest; +use rmcp::model::JsonRpcMessage; +use serde_json::Value; use tokio::io::AsyncBufReadExt; use tokio::io::AsyncWriteExt; use tokio::io::BufReader; @@ -21,13 +24,13 @@ use tracing_subscriber::EnvFilter; mod codex_tool_config; mod codex_tool_runner; -mod error_code; mod exec_approval; pub(crate) mod message_processor; mod outgoing_message; mod patch_approval; use crate::message_processor::MessageProcessor; +use crate::outgoing_message::OutgoingJsonRpcMessage; use crate::outgoing_message::OutgoingMessage; use crate::outgoing_message::OutgoingMessageSender; @@ -43,6 +46,8 @@ pub use crate::patch_approval::PatchApprovalResponse; /// plenty for an interactive CLI. const CHANNEL_CAPACITY: usize = 128; +type IncomingMessage = JsonRpcMessage; + pub async fn run_main( codex_linux_sandbox_exe: Option, cli_config_overrides: CliConfigOverrides, @@ -55,7 +60,7 @@ pub async fn run_main( .init(); // Set up channels. - let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); + let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); // Task: read from stdin, push to `incoming_tx`. @@ -66,14 +71,14 @@ pub async fn run_main( let mut lines = reader.lines(); while let Some(line) = lines.next_line().await.unwrap_or_default() { - match serde_json::from_str::(&line) { + match serde_json::from_str::(&line) { Ok(msg) => { if incoming_tx.send(msg).await.is_err() { // Receiver gone – nothing left to do. break; } } - Err(e) => error!("Failed to deserialize JSONRPCMessage: {e}"), + Err(e) => error!("Failed to deserialize JSON-RPC message: {e}"), } } @@ -106,10 +111,10 @@ pub async fn run_main( async move { while let Some(msg) = incoming_rx.recv().await { match msg { - JSONRPCMessage::Request(r) => processor.process_request(r).await, - JSONRPCMessage::Response(r) => processor.process_response(r).await, - JSONRPCMessage::Notification(n) => processor.process_notification(n).await, - JSONRPCMessage::Error(e) => processor.process_error(e), + JsonRpcMessage::Request(r) => processor.process_request(r).await, + JsonRpcMessage::Response(r) => processor.process_response(r).await, + JsonRpcMessage::Notification(n) => processor.process_notification(n).await, + JsonRpcMessage::Error(e) => processor.process_error(e), } } @@ -121,7 +126,7 @@ pub async fn run_main( let stdout_writer_handle = tokio::spawn(async move { let mut stdout = io::stdout(); while let Some(outgoing_message) = outgoing_rx.recv().await { - let msg: JSONRPCMessage = outgoing_message.into(); + let msg: OutgoingJsonRpcMessage = outgoing_message.into(); match serde_json::to_string(&msg) { Ok(json) => { if let Err(e) = stdout.write_all(json.as_bytes()).await { @@ -133,7 +138,7 @@ pub async fn run_main( break; } } - Err(e) => error!("Failed to serialize JSONRPCMessage: {e}"), + Err(e) => error!("Failed to serialize JSON-RPC message: {e}"), } } diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 9d947cda3f..1d9ab192aa 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -1,41 +1,40 @@ use std::collections::HashMap; use std::path::PathBuf; -use crate::codex_tool_config::CodexToolCallParam; -use crate::codex_tool_config::CodexToolCallReplyParam; -use crate::codex_tool_config::create_tool_for_codex_tool_call_param; -use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; -use crate::error_code::INVALID_REQUEST_ERROR_CODE; -use crate::outgoing_message::OutgoingMessageSender; -use codex_protocol::ThreadId; -use codex_protocol::protocol::SessionSource; - use codex_core::AuthManager; use codex_core::ThreadManager; use codex_core::config::Config; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; use codex_core::protocol::Submission; -use mcp_types::CallToolRequestParams; -use mcp_types::CallToolResult; -use mcp_types::ClientRequest as McpClientRequest; -use mcp_types::ContentBlock; -use mcp_types::JSONRPCError; -use mcp_types::JSONRPCErrorError; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ListToolsResult; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; -use mcp_types::ServerCapabilitiesTools; -use mcp_types::ServerNotification; -use mcp_types::TextContent; +use codex_protocol::ThreadId; +use codex_protocol::protocol::SessionSource; +use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolResult; +use rmcp::model::ClientNotification; +use rmcp::model::ClientRequest; +use rmcp::model::ErrorCode; +use rmcp::model::ErrorData; +use rmcp::model::Implementation; +use rmcp::model::InitializeResult; +use rmcp::model::JsonRpcError; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::RequestId; +use rmcp::model::ServerCapabilities; +use rmcp::model::ToolsCapability; use serde_json::json; use std::sync::Arc; use tokio::sync::Mutex; use tokio::task; +use crate::codex_tool_config::CodexToolCallParam; +use crate::codex_tool_config::CodexToolCallReplyParam; +use crate::codex_tool_config::create_tool_for_codex_tool_call_param; +use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; +use crate::outgoing_message::OutgoingMessageSender; + pub(crate) struct MessageProcessor { outgoing: Arc, initialized: bool, @@ -72,126 +71,113 @@ impl MessageProcessor { } } - pub(crate) async fn process_request(&mut self, request: JSONRPCRequest) { - // Hold on to the ID so we can respond. + pub(crate) async fn process_request(&mut self, request: JsonRpcRequest) { let request_id = request.id.clone(); + let client_request = request.request; - let client_request = match McpClientRequest::try_from(request) { - Ok(client_request) => client_request, - Err(e) => { - tracing::warn!("Failed to convert request: {e}"); - return; - } - }; - - // Dispatch to a dedicated handler for each request type. match client_request { - McpClientRequest::InitializeRequest(params) => { - self.handle_initialize(request_id, params).await; + ClientRequest::InitializeRequest(params) => { + self.handle_initialize(request_id, params.params).await; } - McpClientRequest::PingRequest(params) => { - self.handle_ping(request_id, params).await; + ClientRequest::PingRequest(_params) => { + self.handle_ping(request_id).await; } - McpClientRequest::ListResourcesRequest(params) => { - self.handle_list_resources(params); + ClientRequest::ListResourcesRequest(params) => { + self.handle_list_resources(params.params); } - McpClientRequest::ListResourceTemplatesRequest(params) => { - self.handle_list_resource_templates(params); + ClientRequest::ListResourceTemplatesRequest(params) => { + self.handle_list_resource_templates(params.params); } - McpClientRequest::ReadResourceRequest(params) => { - self.handle_read_resource(params); + ClientRequest::ReadResourceRequest(params) => { + self.handle_read_resource(params.params); } - McpClientRequest::SubscribeRequest(params) => { - self.handle_subscribe(params); + ClientRequest::SubscribeRequest(params) => { + self.handle_subscribe(params.params); } - McpClientRequest::UnsubscribeRequest(params) => { - self.handle_unsubscribe(params); + ClientRequest::UnsubscribeRequest(params) => { + self.handle_unsubscribe(params.params); } - McpClientRequest::ListPromptsRequest(params) => { - self.handle_list_prompts(params); + ClientRequest::ListPromptsRequest(params) => { + self.handle_list_prompts(params.params); } - McpClientRequest::GetPromptRequest(params) => { - self.handle_get_prompt(params); + ClientRequest::GetPromptRequest(params) => { + self.handle_get_prompt(params.params); } - McpClientRequest::ListToolsRequest(params) => { - self.handle_list_tools(request_id, params).await; + ClientRequest::ListToolsRequest(params) => { + self.handle_list_tools(request_id, params.params).await; } - McpClientRequest::CallToolRequest(params) => { - self.handle_call_tool(request_id, params).await; + ClientRequest::CallToolRequest(params) => { + self.handle_call_tool(request_id, params.params).await; } - McpClientRequest::SetLevelRequest(params) => { - self.handle_set_level(params); + ClientRequest::SetLevelRequest(params) => { + self.handle_set_level(params.params); } - McpClientRequest::CompleteRequest(params) => { - self.handle_complete(params); + ClientRequest::CompleteRequest(params) => { + self.handle_complete(params.params); + } + ClientRequest::CustomRequest(custom) => { + let method = custom.method.clone(); + self.outgoing + .send_error( + request_id, + ErrorData::new( + ErrorCode::METHOD_NOT_FOUND, + format!("method not found: {method}"), + Some(json!({ "method": method })), + ), + ) + .await; } } } - /// Handle a standalone JSON-RPC response originating from the peer. - pub(crate) async fn process_response(&mut self, response: JSONRPCResponse) { + pub(crate) async fn process_response(&mut self, response: JsonRpcResponse) { tracing::info!("<- response: {:?}", response); - let JSONRPCResponse { id, result, .. } = response; + let JsonRpcResponse { id, result, .. } = response; self.outgoing.notify_client_response(id, result).await } - /// Handle a fire-and-forget JSON-RPC notification. - pub(crate) async fn process_notification(&mut self, notification: JSONRPCNotification) { - let server_notification = match ServerNotification::try_from(notification) { - Ok(n) => n, - Err(e) => { - tracing::warn!("Failed to convert notification: {e}"); - return; + pub(crate) async fn process_notification( + &mut self, + notification: JsonRpcNotification, + ) { + match notification.notification { + ClientNotification::CancelledNotification(params) => { + self.handle_cancelled_notification(params.params).await; } - }; - - // Similar to requests, route each notification type to its own stub - // handler so additional logic can be implemented incrementally. - match server_notification { - ServerNotification::CancelledNotification(params) => { - self.handle_cancelled_notification(params).await; + ClientNotification::ProgressNotification(params) => { + self.handle_progress_notification(params.params); } - ServerNotification::ProgressNotification(params) => { - self.handle_progress_notification(params); + ClientNotification::RootsListChangedNotification(_params) => { + self.handle_roots_list_changed(); } - ServerNotification::ResourceListChangedNotification(params) => { - self.handle_resource_list_changed(params); + ClientNotification::InitializedNotification(_) => { + self.handle_initialized_notification(); } - ServerNotification::ResourceUpdatedNotification(params) => { - self.handle_resource_updated(params); - } - ServerNotification::PromptListChangedNotification(params) => { - self.handle_prompt_list_changed(params); - } - ServerNotification::ToolListChangedNotification(params) => { - self.handle_tool_list_changed(params); - } - ServerNotification::LoggingMessageNotification(params) => { - self.handle_logging_message(params); + ClientNotification::CustomNotification(_) => { + tracing::warn!("ignoring custom client notification"); } } } - /// Handle an error object received from the peer. - pub(crate) fn process_error(&mut self, err: JSONRPCError) { + pub(crate) fn process_error(&mut self, err: JsonRpcError) { tracing::error!("<- error: {:?}", err); } async fn handle_initialize( &mut self, id: RequestId, - params: ::Params, + params: rmcp::model::InitializeRequestParam, ) { tracing::info!("initialize -> params: {:?}", params); if self.initialized { - // Already initialised: send JSON-RPC error response. - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: "initialize called more than once".to_string(), - data: None, - }; - self.outgoing.send_error(id, error).await; + self.outgoing + .send_error( + id, + ErrorData::invalid_request("initialize called more than once", None), + ) + .await; return; } @@ -203,109 +189,109 @@ impl MessageProcessor { *suffix = Some(user_agent_suffix); } - self.initialized = true; + let server_info = Implementation { + name: "codex-mcp-server".to_string(), + title: Some("Codex".to_string()), + version: env!("CARGO_PKG_VERSION").to_string(), + icons: None, + website_url: None, + }; - // Build a minimal InitializeResult. Fill with placeholders. - let result = mcp_types::InitializeResult { - capabilities: mcp_types::ServerCapabilities { - completions: None, - experimental: None, - logging: None, - prompts: None, - resources: None, - tools: Some(ServerCapabilitiesTools { + // Preserve Codex's existing non-spec `serverInfo.user_agent` field. + let mut server_info_value = match serde_json::to_value(&server_info) { + Ok(value) => value, + Err(err) => { + self.outgoing + .send_error( + id, + ErrorData::internal_error( + format!("failed to serialize server info: {err}"), + None, + ), + ) + .await; + return; + } + }; + if let serde_json::Value::Object(ref mut obj) = server_info_value { + obj.insert("user_agent".to_string(), json!(get_codex_user_agent())); + } + + let mut result_value = match serde_json::to_value(InitializeResult { + capabilities: ServerCapabilities { + tools: Some(ToolsCapability { list_changed: Some(true), }), + ..Default::default() }, instructions: None, protocol_version: params.protocol_version.clone(), - server_info: mcp_types::Implementation { - name: "codex-mcp-server".to_string(), - version: env!("CARGO_PKG_VERSION").to_string(), - title: Some("Codex".to_string()), - user_agent: Some(get_codex_user_agent()), - }, + server_info, + }) { + Ok(value) => value, + Err(err) => { + self.outgoing + .send_error( + id, + ErrorData::internal_error( + format!("failed to serialize initialize response: {err}"), + None, + ), + ) + .await; + return; + } }; - self.send_response::(id, result) - .await; + if let serde_json::Value::Object(ref mut obj) = result_value { + obj.insert("serverInfo".to_string(), server_info_value); + } + + self.initialized = true; + self.outgoing.send_response(id, result_value).await; } - async fn send_response(&self, id: RequestId, result: T::Result) - where - T: ModelContextProtocolRequest, - { - self.outgoing.send_response(id, result).await; + async fn handle_ping(&self, id: RequestId) { + tracing::info!("ping"); + self.outgoing.send_response(id, json!({})).await; } - async fn handle_ping( - &self, - id: RequestId, - params: ::Params, - ) { - tracing::info!("ping -> params: {:?}", params); - let result = json!({}); - self.send_response::(id, result) - .await; - } - - fn handle_list_resources( - &self, - params: ::Params, - ) { + fn handle_list_resources(&self, params: Option) { tracing::info!("resources/list -> params: {:?}", params); } - fn handle_list_resource_templates( - &self, - params: - ::Params, - ) { + fn handle_list_resource_templates(&self, params: Option) { tracing::info!("resources/templates/list -> params: {:?}", params); } - fn handle_read_resource( - &self, - params: ::Params, - ) { + fn handle_read_resource(&self, params: rmcp::model::ReadResourceRequestParam) { tracing::info!("resources/read -> params: {:?}", params); } - fn handle_subscribe( - &self, - params: ::Params, - ) { + fn handle_subscribe(&self, params: rmcp::model::SubscribeRequestParam) { tracing::info!("resources/subscribe -> params: {:?}", params); } - fn handle_unsubscribe( - &self, - params: ::Params, - ) { + fn handle_unsubscribe(&self, params: rmcp::model::UnsubscribeRequestParam) { tracing::info!("resources/unsubscribe -> params: {:?}", params); } - fn handle_list_prompts( - &self, - params: ::Params, - ) { + fn handle_list_prompts(&self, params: Option) { tracing::info!("prompts/list -> params: {:?}", params); } - fn handle_get_prompt( - &self, - params: ::Params, - ) { + fn handle_get_prompt(&self, params: rmcp::model::GetPromptRequestParam) { tracing::info!("prompts/get -> params: {:?}", params); } async fn handle_list_tools( &self, id: RequestId, - params: ::Params, + params: Option, ) { tracing::trace!("tools/list -> {params:?}"); - let result = ListToolsResult { + let result = rmcp::model::ListToolsResult { + meta: None, tools: vec![ create_tool_for_codex_tool_call_param(), create_tool_for_codex_tool_call_reply_param(), @@ -313,19 +299,14 @@ impl MessageProcessor { next_cursor: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; } - async fn handle_call_tool( - &self, - id: RequestId, - params: ::Params, - ) { + async fn handle_call_tool(&self, id: RequestId, params: CallToolRequestParam) { tracing::info!("tools/call -> params: {:?}", params); - let CallToolRequestParams { name, arguments } = params; + let CallToolRequestParam { name, arguments } = params; - match name.as_str() { + match name.as_ref() { "codex" => self.handle_tool_call_codex(id, arguments).await, "codex-reply" => { self.handle_tool_call_codex_session_reply(id, arguments) @@ -333,20 +314,22 @@ impl MessageProcessor { } _ => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: format!("Unknown tool '{name}'"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!("Unknown tool '{name}'"))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; } } } - async fn handle_tool_call_codex(&self, id: RequestId, arguments: Option) { + + async fn handle_tool_call_codex( + &self, + id: RequestId, + arguments: Option, + ) { + let arguments = arguments.map(serde_json::Value::Object); let (initial_prompt, config): (String, Config) = match arguments { Some(json_val) => match serde_json::from_value::(json_val) { Ok(tool_cfg) => match tool_cfg @@ -356,50 +339,40 @@ impl MessageProcessor { Ok(cfg) => cfg, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!( - "Failed to load Codex configuration from overrides: {e}" - ), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to load Codex configuration from overrides: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }, Err(e) => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse configuration for Codex tool: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse configuration for Codex tool: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }, None => { let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_string(), - text: - "Missing arguments for codex tool-call; the `prompt` field is required." - .to_string(), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text( + "Missing arguments for codex tool-call; the `prompt` field is required.", + )], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(id, result) - .await; + self.outgoing.send_response(id, result).await; return; } }; @@ -428,8 +401,9 @@ impl MessageProcessor { async fn handle_tool_call_codex_session_reply( &self, request_id: RequestId, - arguments: Option, + arguments: Option, ) { + let arguments = arguments.map(serde_json::Value::Object); tracing::info!("tools/call -> params: {:?}", arguments); // parse arguments @@ -439,16 +413,14 @@ impl MessageProcessor { Err(e) => { tracing::error!("Failed to parse Codex tool call reply parameters: {e}"); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse configuration for Codex tool: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse configuration for Codex tool: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }, @@ -457,16 +429,14 @@ impl MessageProcessor { "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required." ); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required.".to_owned(), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text( + "Missing arguments for codex-reply tool-call; the `thread_id` and `prompt` fields are required.", + )], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }; @@ -476,16 +446,14 @@ impl MessageProcessor { Err(e) => { tracing::error!("Failed to parse thread_id: {e}"); let result = CallToolResult { - content: vec![ContentBlock::TextContent(TextContent { - r#type: "text".to_owned(), - text: format!("Failed to parse thread_id: {e}"), - annotations: None, - })], - is_error: Some(true), + content: vec![rmcp::model::Content::text(format!( + "Failed to parse thread_id: {e}" + ))], structured_content: None, + is_error: Some(true), + meta: None, }; - self.send_response::(request_id, result) - .await; + self.outgoing.send_response(request_id, result).await; return; } }; @@ -528,17 +496,11 @@ impl MessageProcessor { }); } - fn handle_set_level( - &self, - params: ::Params, - ) { + fn handle_set_level(&self, params: rmcp::model::SetLevelRequestParam) { tracing::info!("logging/setLevel -> params: {:?}", params); } - fn handle_complete( - &self, - params: ::Params, - ) { + fn handle_complete(&self, params: rmcp::model::CompleteRequestParam) { tracing::info!("completion/complete -> params: {:?}", params); } @@ -546,16 +508,10 @@ impl MessageProcessor { // Notification handlers // --------------------------------------------------------------------- - async fn handle_cancelled_notification( - &self, - params: ::Params, - ) { + async fn handle_cancelled_notification(&self, params: rmcp::model::CancelledNotificationParam) { let request_id = params.request_id; // Create a stable string form early for logging and submission id. - let request_id_string = match &request_id { - RequestId::String(s) => s.clone(), - RequestId::Integer(i) => i.to_string(), - }; + let request_id_string = request_id.to_string(); // Obtain the thread id while holding the first lock, then release. let thread_id = { @@ -563,7 +519,7 @@ impl MessageProcessor { match map_guard.get(&request_id) { Some(id) => *id, None => { - tracing::warn!("Session not found for request_id: {}", request_id_string); + tracing::warn!("Session not found for request_id: {request_id_string}"); return; } } @@ -580,13 +536,13 @@ impl MessageProcessor { }; // Submit interrupt to Codex. - let err = codex_arc + if let Err(e) = codex_arc .submit_with_id(Submission { id: request_id_string, op: codex_core::protocol::Op::Interrupt, }) - .await; - if let Err(e) = err { + .await + { tracing::error!("Failed to submit interrupt to Codex: {e}"); return; } @@ -597,48 +553,15 @@ impl MessageProcessor { .remove(&request_id); } - fn handle_progress_notification( - &self, - params: ::Params, - ) { + fn handle_progress_notification(&self, params: rmcp::model::ProgressNotificationParam) { tracing::info!("notifications/progress -> params: {:?}", params); } - fn handle_resource_list_changed( - &self, - params: ::Params, - ) { - tracing::info!( - "notifications/resources/list_changed -> params: {:?}", - params - ); + fn handle_roots_list_changed(&self) { + tracing::info!("notifications/roots/list_changed"); } - fn handle_resource_updated( - &self, - params: ::Params, - ) { - tracing::info!("notifications/resources/updated -> params: {:?}", params); - } - - fn handle_prompt_list_changed( - &self, - params: ::Params, - ) { - tracing::info!("notifications/prompts/list_changed -> params: {:?}", params); - } - - fn handle_tool_list_changed( - &self, - params: ::Params, - ) { - tracing::info!("notifications/tools/list_changed -> params: {:?}", params); - } - - fn handle_logging_message( - &self, - params: ::Params, - ) { - tracing::info!("notifications/message -> params: {:?}", params); + fn handle_initialized_notification(&self) { + tracing::info!("notifications/initialized"); } } diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index 954e50d9de..e512eedbd7 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -4,28 +4,30 @@ use std::sync::atomic::Ordering; use codex_core::protocol::Event; use codex_protocol::ThreadId; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCError; -use mcp_types::JSONRPCErrorError; -use mcp_types::JSONRPCMessage; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::RequestId; -use mcp_types::Result; +use rmcp::model::CustomNotification; +use rmcp::model::CustomRequest; +use rmcp::model::ErrorData; +use rmcp::model::JsonRpcError; +use rmcp::model::JsonRpcMessage; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::RequestId; use serde::Serialize; +use serde_json::Value; use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::oneshot; use tracing::warn; -use crate::error_code::INTERNAL_ERROR_CODE; +pub(crate) type OutgoingJsonRpcMessage = JsonRpcMessage; /// Sends messages to the client and manages request callbacks. pub(crate) struct OutgoingMessageSender { next_request_id: AtomicI64, sender: mpsc::UnboundedSender, - request_id_to_callback: Mutex>>, + request_id_to_callback: Mutex>>, } impl OutgoingMessageSender { @@ -41,8 +43,8 @@ impl OutgoingMessageSender { &self, method: &str, params: Option, - ) -> oneshot::Receiver { - let id = RequestId::Integer(self.next_request_id.fetch_add(1, Ordering::Relaxed)); + ) -> oneshot::Receiver { + let id = RequestId::Number(self.next_request_id.fetch_add(1, Ordering::Relaxed)); let outgoing_message_id = id.clone(); let (tx_approve, rx_approve) = oneshot::channel(); { @@ -59,7 +61,7 @@ impl OutgoingMessageSender { rx_approve } - pub(crate) async fn notify_client_response(&self, id: RequestId, result: Result) { + pub(crate) async fn notify_client_response(&self, id: RequestId, result: Value) { let entry = { let mut request_id_to_callback = self.request_id_to_callback.lock().await; request_id_to_callback.remove_entry(&id) @@ -78,23 +80,20 @@ impl OutgoingMessageSender { } pub(crate) async fn send_response(&self, id: RequestId, response: T) { - match serde_json::to_value(response) { - Ok(result) => { - let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); - let _ = self.sender.send(outgoing_message); - } + let result = match serde_json::to_value(response) { + Ok(result) => result, Err(err) => { self.send_error( id, - JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("failed to serialize response: {err}"), - data: None, - }, + ErrorData::internal_error(format!("failed to serialize response: {err}"), None), ) .await; + return; } - } + }; + + let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); + let _ = self.sender.send(outgoing_message); } /// This is used with the MCP server, but not the more general JSON-RPC app @@ -130,7 +129,7 @@ impl OutgoingMessageSender { let _ = self.sender.send(outgoing_message); } - pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) { + pub(crate) async fn send_error(&self, id: RequestId, error: ErrorData) { let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error }); let _ = self.sender.send(outgoing_message); } @@ -144,34 +143,32 @@ pub(crate) enum OutgoingMessage { Error(OutgoingError), } -impl From for JSONRPCMessage { +impl From for OutgoingJsonRpcMessage { fn from(val: OutgoingMessage) -> Self { use OutgoingMessage::*; match val { Request(OutgoingRequest { id, method, params }) => { - JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), + JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, id, - method, - params, + request: CustomRequest::new(method, params), }) } Notification(OutgoingNotification { method, params }) => { - JSONRPCMessage::Notification(JSONRPCNotification { - jsonrpc: JSONRPC_VERSION.into(), - method, - params, + JsonRpcMessage::Notification(JsonRpcNotification { + jsonrpc: JsonRpcVersion2_0, + notification: CustomNotification::new(method, params), }) } Response(OutgoingResponse { id, result }) => { - JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), + JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, id, result, }) } - Error(OutgoingError { id, error }) => JSONRPCMessage::Error(JSONRPCError { - jsonrpc: JSONRPC_VERSION.into(), + Error(OutgoingError { id, error }) => JsonRpcMessage::Error(JsonRpcError { + jsonrpc: JsonRpcVersion2_0, id, error, }), @@ -220,12 +217,12 @@ pub(crate) struct OutgoingNotificationMeta { #[derive(Debug, Clone, PartialEq, Serialize)] pub(crate) struct OutgoingResponse { pub id: RequestId, - pub result: Result, + pub result: Value, } #[derive(Debug, Clone, PartialEq, Serialize)] pub(crate) struct OutgoingError { - pub error: JSONRPCErrorError, + pub error: ErrorData, pub id: RequestId, } @@ -246,6 +243,48 @@ mod tests { use super::*; + #[test] + fn outgoing_request_serializes_as_jsonrpc_request() { + let msg: OutgoingJsonRpcMessage = OutgoingMessage::Request(OutgoingRequest { + id: RequestId::Number(1), + method: "elicitation/create".to_string(), + params: Some(json!({ "k": "v" })), + }) + .into(); + + let value = serde_json::to_value(msg).expect("message should serialize"); + let obj = value.as_object().expect("json object"); + + assert_eq!(obj.get("jsonrpc"), Some(&json!("2.0"))); + assert_eq!(obj.get("id"), Some(&json!(1))); + assert_eq!(obj.get("method"), Some(&json!("elicitation/create"))); + assert_eq!(obj.get("params"), Some(&json!({ "k": "v" }))); + assert!( + obj.get("request").is_none(), + "rmcp request must flatten to JSON-RPC method/params" + ); + } + + #[test] + fn outgoing_notification_serializes_as_jsonrpc_notification() { + let msg: OutgoingJsonRpcMessage = OutgoingMessage::Notification(OutgoingNotification { + method: "notifications/initialized".to_string(), + params: None, + }) + .into(); + + let value = serde_json::to_value(msg).expect("message should serialize"); + let obj = value.as_object().expect("json object"); + + assert_eq!(obj.get("jsonrpc"), Some(&json!("2.0"))); + assert_eq!(obj.get("method"), Some(&json!("notifications/initialized"))); + assert_eq!(obj.get("params"), Some(&serde_json::Value::Null)); + assert!( + obj.get("notification").is_none(), + "rmcp notification must flatten to JSON-RPC method/params" + ); + } + #[tokio::test] async fn test_send_event_as_notification() -> Result<()> { let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); @@ -316,7 +355,7 @@ mod tests { msg: EventMsg::SessionConfigured(session_configured_event.clone()), }; let meta = OutgoingNotificationMeta { - request_id: Some(RequestId::String("123".to_string())), + request_id: Some(RequestId::String("123".into())), thread_id: None, }; @@ -381,7 +420,7 @@ mod tests { msg: EventMsg::SessionConfigured(session_configured_event.clone()), }; let meta = OutgoingNotificationMeta { - request_id: Some(RequestId::String("123".to_string())), + request_id: Some(RequestId::String("123".into())), thread_id: Some(thread_id), }; diff --git a/codex-rs/mcp-server/src/patch_approval.rs b/codex-rs/mcp-server/src/patch_approval.rs index 5c3073959d..55938e257b 100644 --- a/codex-rs/mcp-server/src/patch_approval.rs +++ b/codex-rs/mcp-server/src/patch_approval.rs @@ -7,24 +7,21 @@ use codex_core::protocol::FileChange; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; use codex_protocol::ThreadId; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPCErrorError; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; +use rmcp::model::ErrorData; +use rmcp::model::RequestId; use serde::Deserialize; use serde::Serialize; +use serde_json::Value; use serde_json::json; use tracing::error; -use crate::codex_tool_runner::INVALID_PARAMS_ERROR_CODE; use crate::outgoing_message::OutgoingMessageSender; #[derive(Debug, Deserialize, Serialize)] pub struct PatchApprovalElicitRequestParams { pub message: String, #[serde(rename = "requestedSchema")] - pub requested_schema: ElicitRequestParamsRequestedSchema, + pub requested_schema: Value, #[serde(rename = "threadId")] pub thread_id: ThreadId, pub codex_elicitation: String, @@ -64,11 +61,7 @@ pub(crate) async fn handle_patch_approval_request( let params = PatchApprovalElicitRequestParams { message: message_lines.join("\n"), - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, + requested_schema: json!({"type":"object","properties":{}}), thread_id, codex_elicitation: "patch-approval".to_string(), codex_mcp_tool_call_id: tool_call_id.clone(), @@ -85,14 +78,7 @@ pub(crate) async fn handle_patch_approval_request( error!("{message}"); outgoing - .send_error( - request_id.clone(), - JSONRPCErrorError { - code: INVALID_PARAMS_ERROR_CODE, - message, - data: None, - }, - ) + .send_error(request_id.clone(), ErrorData::invalid_params(message, None)) .await; return; @@ -100,7 +86,7 @@ pub(crate) async fn handle_patch_approval_request( }; let on_response = outgoing - .send_request(ElicitRequest::METHOD, Some(params_json)) + .send_request("elicitation/create", Some(params_json)) .await; // Listen for the response on a separate task so we don't block the main agent loop. @@ -115,7 +101,7 @@ pub(crate) async fn handle_patch_approval_request( pub(crate) async fn on_patch_approval_response( event_id: String, - receiver: tokio::sync::oneshot::Receiver, + receiver: tokio::sync::oneshot::Receiver, codex: Arc, ) { let response = receiver.await; diff --git a/codex-rs/mcp-server/tests/common/Cargo.toml b/codex-rs/mcp-server/tests/common/Cargo.toml index aba984edab..1dec2d09ac 100644 --- a/codex-rs/mcp-server/tests/common/Cargo.toml +++ b/codex-rs/mcp-server/tests/common/Cargo.toml @@ -12,7 +12,7 @@ anyhow = { workspace = true } codex-core = { workspace = true } codex-mcp-server = { workspace = true } codex-utils-cargo-bin = { workspace = true } -mcp-types = { workspace = true } +rmcp = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } serde = { workspace = true } diff --git a/codex-rs/mcp-server/tests/common/lib.rs b/codex-rs/mcp-server/tests/common/lib.rs index 364c708651..c2c3757efc 100644 --- a/codex-rs/mcp-server/tests/common/lib.rs +++ b/codex-rs/mcp-server/tests/common/lib.rs @@ -6,14 +6,16 @@ pub use core_test_support::format_with_current_shell; pub use core_test_support::format_with_current_shell_display_non_login; pub use core_test_support::format_with_current_shell_non_login; pub use mcp_process::McpProcess; -use mcp_types::JSONRPCResponse; pub use mock_model_server::create_mock_chat_completions_server; pub use responses::create_apply_patch_sse_response; pub use responses::create_final_assistant_message_sse_response; pub use responses::create_shell_command_sse_response; +use rmcp::model::JsonRpcResponse; use serde::de::DeserializeOwned; -pub fn to_response(response: JSONRPCResponse) -> anyhow::Result { +pub fn to_response( + response: JsonRpcResponse, +) -> anyhow::Result { let value = serde_json::to_value(response.result)?; let codex_response = serde_json::from_value(value)?; Ok(codex_response) diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 9a3f076fb1..c019120282 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -12,19 +12,21 @@ use tokio::process::ChildStdout; use anyhow::Context; use codex_mcp_server::CodexToolCallParam; -use mcp_types::CallToolRequestParams; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::InitializeRequestParams; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCMessage; -use mcp_types::JSONRPCNotification; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ModelContextProtocolNotification; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; use pretty_assertions::assert_eq; +use rmcp::model::CallToolRequestParam; +use rmcp::model::ClientCapabilities; +use rmcp::model::CustomNotification; +use rmcp::model::CustomRequest; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::JsonRpcMessage; +use rmcp::model::JsonRpcNotification; +use rmcp::model::JsonRpcRequest; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::ProtocolVersion; +use rmcp::model::RequestId; use serde_json::json; use tokio::process::Command; @@ -110,9 +112,11 @@ impl McpProcess { pub async fn initialize(&mut self) -> anyhow::Result<()> { let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); - let params = InitializeRequestParams { + let params = InitializeRequestParam { capabilities: ClientCapabilities { - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), experimental: None, roots: None, sampling: None, @@ -121,17 +125,17 @@ impl McpProcess { name: "elicitation test".into(), title: Some("Elicitation Test".into()), version: "0.0.0".into(), - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.into(), + protocol_version: ProtocolVersion::V_2025_03_26, }; let params_value = serde_json::to_value(params)?; - self.send_jsonrpc_message(JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - method: mcp_types::InitializeRequest::METHOD.into(), - params: Some(params_value), + self.send_jsonrpc_message(JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(request_id), + request: CustomRequest::new("initialize", Some(params_value)), })) .await?; @@ -146,33 +150,38 @@ impl McpProcess { os_info.architecture().unwrap_or("unknown"), codex_core::terminal::user_agent() ); + let JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc, + id, + result, + }) = initialized + else { + anyhow::bail!("expected initialize response message, got: {initialized:?}") + }; + assert_eq!(jsonrpc, JsonRpcVersion2_0); + assert_eq!(id, RequestId::Number(request_id)); assert_eq!( - JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - result: json!({ - "capabilities": { - "tools": { - "listChanged": true - }, + result, + json!({ + "capabilities": { + "tools": { + "listChanged": true }, - "serverInfo": { - "name": "codex-mcp-server", - "title": "Codex", - "version": "0.0.0", - "user_agent": user_agent - }, - "protocolVersion": mcp_types::MCP_SCHEMA_VERSION - }) - }), - initialized + }, + "serverInfo": { + "name": "codex-mcp-server", + "title": "Codex", + "version": "0.0.0", + "user_agent": user_agent + }, + "protocolVersion": ProtocolVersion::V_2025_03_26 + }) ); // Send notifications/initialized to ack the response. - self.send_jsonrpc_message(JSONRPCMessage::Notification(JSONRPCNotification { - jsonrpc: JSONRPC_VERSION.into(), - method: mcp_types::InitializedNotification::METHOD.into(), - params: None, + self.send_jsonrpc_message(JsonRpcMessage::Notification(JsonRpcNotification { + jsonrpc: JsonRpcVersion2_0, + notification: CustomNotification::new("notifications/initialized", None), })) .await?; @@ -185,12 +194,15 @@ impl McpProcess { &mut self, params: CodexToolCallParam, ) -> anyhow::Result { - let codex_tool_call_params = CallToolRequestParams { - name: "codex".to_string(), - arguments: Some(serde_json::to_value(params)?), + let codex_tool_call_params = CallToolRequestParam { + name: "codex".into(), + arguments: Some(match serde_json::to_value(params)? { + serde_json::Value::Object(map) => map, + _ => unreachable!("params serialize to object"), + }), }; self.send_request( - mcp_types::CallToolRequest::METHOD, + "tools/call", Some(serde_json::to_value(codex_tool_call_params)?), ) .await @@ -203,11 +215,10 @@ impl McpProcess { ) -> anyhow::Result { let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); - let message = JSONRPCMessage::Request(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(request_id), - method: method.to_string(), - params, + let message = JsonRpcMessage::Request(JsonRpcRequest { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(request_id), + request: CustomRequest::new(method, params), }); self.send_jsonrpc_message(message).await?; Ok(request_id) @@ -218,15 +229,18 @@ impl McpProcess { id: RequestId, result: serde_json::Value, ) -> anyhow::Result<()> { - self.send_jsonrpc_message(JSONRPCMessage::Response(JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), + self.send_jsonrpc_message(JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, id, result, })) .await } - async fn send_jsonrpc_message(&mut self, message: JSONRPCMessage) -> anyhow::Result<()> { + async fn send_jsonrpc_message( + &mut self, + message: JsonRpcMessage, + ) -> anyhow::Result<()> { eprintln!("writing message to stdin: {message:?}"); let payload = serde_json::to_string(&message)?; self.stdin.write_all(payload.as_bytes()).await?; @@ -235,31 +249,37 @@ impl McpProcess { Ok(()) } - async fn read_jsonrpc_message(&mut self) -> anyhow::Result { + async fn read_jsonrpc_message( + &mut self, + ) -> anyhow::Result> { let mut line = String::new(); self.stdout.read_line(&mut line).await?; - let message = serde_json::from_str::(&line)?; + let message = serde_json::from_str::< + JsonRpcMessage, + >(&line)?; eprintln!("read message from stdout: {message:?}"); Ok(message) } - pub async fn read_stream_until_request_message(&mut self) -> anyhow::Result { + pub async fn read_stream_until_request_message( + &mut self, + ) -> anyhow::Result> { eprintln!("in read_stream_until_request_message()"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(_) => { + JsonRpcMessage::Notification(_) => { eprintln!("notification: {message:?}"); } - JSONRPCMessage::Request(jsonrpc_request) => { + JsonRpcMessage::Request(jsonrpc_request) => { return Ok(jsonrpc_request); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(_) => { + JsonRpcMessage::Response(_) => { anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}"); } } @@ -269,22 +289,22 @@ impl McpProcess { pub async fn read_stream_until_response_message( &mut self, request_id: RequestId, - ) -> anyhow::Result { + ) -> anyhow::Result> { eprintln!("in read_stream_until_response_message({request_id:?})"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(_) => { + JsonRpcMessage::Notification(_) => { eprintln!("notification: {message:?}"); } - JSONRPCMessage::Request(_) => { + JsonRpcMessage::Request(_) => { anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}"); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(jsonrpc_response) => { + JsonRpcMessage::Response(jsonrpc_response) => { if jsonrpc_response.id == request_id { return Ok(jsonrpc_response); } @@ -297,15 +317,15 @@ impl McpProcess { /// Method "codex/event" with params.msg.type == "task_complete". pub async fn read_stream_until_legacy_task_complete_notification( &mut self, - ) -> anyhow::Result { + ) -> anyhow::Result> { eprintln!("in read_stream_until_legacy_task_complete_notification()"); loop { let message = self.read_jsonrpc_message().await?; match message { - JSONRPCMessage::Notification(notification) => { - let is_match = if notification.method == "codex/event" { - if let Some(params) = ¬ification.params { + JsonRpcMessage::Notification(notification) => { + let is_match = if notification.notification.method == "codex/event" { + if let Some(params) = ¬ification.notification.params { params .get("msg") .and_then(|m| m.get("type")) @@ -324,13 +344,13 @@ impl McpProcess { eprintln!("ignoring notification: {notification:?}"); } } - JSONRPCMessage::Request(_) => { + JsonRpcMessage::Request(_) => { anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}"); } - JSONRPCMessage::Error(_) => { + JsonRpcMessage::Error(_) => { anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}"); } - JSONRPCMessage::Response(_) => { + JsonRpcMessage::Response(_) => { anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}"); } } diff --git a/codex-rs/mcp-server/tests/suite/codex_tool.rs b/codex-rs/mcp-server/tests/suite/codex_tool.rs index 31c451d24f..15836b9057 100644 --- a/codex-rs/mcp-server/tests/suite/codex_tool.rs +++ b/codex-rs/mcp-server/tests/suite/codex_tool.rs @@ -12,14 +12,10 @@ use codex_mcp_server::ExecApprovalElicitRequestParams; use codex_mcp_server::ExecApprovalResponse; use codex_mcp_server::PatchApprovalElicitRequestParams; use codex_mcp_server::PatchApprovalResponse; -use mcp_types::ElicitRequest; -use mcp_types::ElicitRequestParamsRequestedSchema; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCRequest; -use mcp_types::JSONRPCResponse; -use mcp_types::ModelContextProtocolRequest; -use mcp_types::RequestId; use pretty_assertions::assert_eq; +use rmcp::model::JsonRpcResponse; +use rmcp::model::JsonRpcVersion2_0; +use rmcp::model::RequestId; use serde_json::json; use tempfile::TempDir; use tokio::time::timeout; @@ -106,22 +102,27 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { ) .await??; + assert_eq!(elicitation_request.jsonrpc, JsonRpcVersion2_0); + assert_eq!(elicitation_request.request.method, "elicitation/create"); + let elicitation_request_id = elicitation_request.id.clone(); let params = serde_json::from_value::( elicitation_request + .request .params .clone() .ok_or_else(|| anyhow::anyhow!("elicitation_request.params must be set"))?, )?; - let expected_elicitation_request = create_expected_elicitation_request( - elicitation_request_id.clone(), - expected_shell_command, - workdir_for_shell_function_call.path(), - codex_request_id.to_string(), - params.codex_event_id.clone(), - params.thread_id, - )?; - assert_eq!(expected_elicitation_request, elicitation_request); + assert_eq!( + elicitation_request.request.params, + Some(create_expected_elicitation_request_params( + expected_shell_command, + workdir_for_shell_function_call.path(), + codex_request_id.to_string(), + params.codex_event_id.clone(), + params.thread_id, + )?) + ); // Accept the `git init` request by responding to the elicitation. mcp_process @@ -146,13 +147,13 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { // Verify the original `codex` tool call completes and that the file was created. let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; assert_eq!( - JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(codex_request_id), + JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(codex_request_id), result: json!({ "content": [ { @@ -174,41 +175,32 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> { Ok(()) } -fn create_expected_elicitation_request( - elicitation_request_id: RequestId, +fn create_expected_elicitation_request_params( command: Vec, workdir: &Path, codex_mcp_tool_call_id: String, codex_event_id: String, thread_id: codex_protocol::ThreadId, -) -> anyhow::Result { +) -> anyhow::Result { let expected_message = format!( "Allow Codex to run `{}` in `{}`?", shlex::try_join(command.iter().map(std::convert::AsRef::as_ref))?, workdir.to_string_lossy() ); let codex_parsed_cmd = parse_command::parse_command(&command); - Ok(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: elicitation_request_id, - method: ElicitRequest::METHOD.to_string(), - params: Some(serde_json::to_value(&ExecApprovalElicitRequestParams { - message: expected_message, - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, - thread_id, - codex_elicitation: "exec-approval".to_string(), - codex_mcp_tool_call_id, - codex_event_id, - codex_command: command, - codex_cwd: workdir.to_path_buf(), - codex_call_id: "call1234".to_string(), - codex_parsed_cmd, - })?), - }) + let params_json = serde_json::to_value(ExecApprovalElicitRequestParams { + message: expected_message, + requested_schema: json!({"type":"object","properties":{}}), + thread_id, + codex_elicitation: "exec-approval".to_string(), + codex_mcp_tool_call_id, + codex_event_id, + codex_command: command, + codex_cwd: workdir.to_path_buf(), + codex_call_id: "call1234".to_string(), + codex_parsed_cmd, + })?; + Ok(params_json) } /// Test that patch approval triggers an elicitation request to the MCP and that @@ -267,9 +259,13 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { ) .await??; + assert_eq!(elicitation_request.jsonrpc, JsonRpcVersion2_0); + assert_eq!(elicitation_request.request.method, "elicitation/create"); + let elicitation_request_id = elicitation_request.id.clone(); let params = serde_json::from_value::( elicitation_request + .request .params .clone() .ok_or_else(|| anyhow::anyhow!("elicitation_request.params must be set"))?, @@ -284,16 +280,17 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { }, ); - let expected_elicitation_request = create_expected_patch_approval_elicitation_request( - elicitation_request_id.clone(), - expected_changes, - None, // No grant_root expected - None, // No reason expected - codex_request_id.to_string(), - params.codex_event_id.clone(), - params.thread_id, - )?; - assert_eq!(expected_elicitation_request, elicitation_request); + assert_eq!( + elicitation_request.request.params, + Some(create_expected_patch_approval_elicitation_request_params( + expected_changes, + None, // No grant_root expected + None, // No reason expected + codex_request_id.to_string(), + params.codex_event_id.clone(), + params.thread_id, + )?) + ); // Accept the patch approval request by responding to the elicitation mcp_process @@ -308,13 +305,13 @@ async fn patch_approval_triggers_elicitation() -> anyhow::Result<()> { // Verify the original `codex` tool call completes let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; assert_eq!( - JSONRPCResponse { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(codex_request_id), + JsonRpcResponse { + jsonrpc: JsonRpcVersion2_0, + id: RequestId::Number(codex_request_id), result: json!({ "content": [ { @@ -375,11 +372,11 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> { let codex_response = timeout( DEFAULT_READ_TIMEOUT, - mcp_process.read_stream_until_response_message(RequestId::Integer(codex_request_id)), + mcp_process.read_stream_until_response_message(RequestId::Number(codex_request_id)), ) .await??; - assert_eq!(codex_response.jsonrpc, JSONRPC_VERSION); - assert_eq!(codex_response.id, RequestId::Integer(codex_request_id)); + assert_eq!(codex_response.jsonrpc, JsonRpcVersion2_0); + assert_eq!(codex_response.id, RequestId::Number(codex_request_id)); assert_eq!( codex_response.result, json!({ @@ -430,42 +427,33 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> { Ok(()) } -fn create_expected_patch_approval_elicitation_request( - elicitation_request_id: RequestId, +fn create_expected_patch_approval_elicitation_request_params( changes: HashMap, grant_root: Option, reason: Option, codex_mcp_tool_call_id: String, codex_event_id: String, thread_id: codex_protocol::ThreadId, -) -> anyhow::Result { +) -> anyhow::Result { let mut message_lines = Vec::new(); if let Some(r) = &reason { message_lines.push(r.clone()); } message_lines.push("Allow Codex to apply proposed code changes?".to_string()); + let params_json = serde_json::to_value(PatchApprovalElicitRequestParams { + message: message_lines.join("\n"), + requested_schema: json!({"type":"object","properties":{}}), + thread_id, + codex_elicitation: "patch-approval".to_string(), + codex_mcp_tool_call_id, + codex_event_id, + codex_reason: reason, + codex_grant_root: grant_root, + codex_changes: changes, + codex_call_id: "call1234".to_string(), + })?; - Ok(JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: elicitation_request_id, - method: ElicitRequest::METHOD.to_string(), - params: Some(serde_json::to_value(&PatchApprovalElicitRequestParams { - message: message_lines.join("\n"), - requested_schema: ElicitRequestParamsRequestedSchema { - r#type: "object".to_string(), - properties: json!({}), - required: None, - }, - thread_id, - codex_elicitation: "patch-approval".to_string(), - codex_mcp_tool_call_id, - codex_event_id, - codex_reason: reason, - codex_grant_root: grant_root, - codex_changes: changes, - codex_call_id: "call1234".to_string(), - })?), - }) + Ok(params_json) } /// This handle is used to ensure that the MockServer and TempDir are not dropped while diff --git a/codex-rs/protocol/Cargo.toml b/codex-rs/protocol/Cargo.toml index c3d9d30915..b58393936b 100644 --- a/codex-rs/protocol/Cargo.toml +++ b/codex-rs/protocol/Cargo.toml @@ -19,7 +19,6 @@ codex-utils-image = { workspace = true } icu_decimal = { workspace = true } icu_locale_core = { workspace = true } icu_provider = { workspace = true, features = ["sync"] } -mcp-types = { workspace = true } mime_guess = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/codex-rs/protocol/src/approvals.rs b/codex-rs/protocol/src/approvals.rs index 78050dfa86..635cd5223d 100644 --- a/codex-rs/protocol/src/approvals.rs +++ b/codex-rs/protocol/src/approvals.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::path::PathBuf; +use crate::mcp::RequestId; use crate::parse_command::ParsedCommand; use crate::protocol::FileChange; -use mcp_types::RequestId; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -62,6 +62,7 @@ pub struct ExecApprovalRequestEvent { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub struct ElicitationRequestEvent { pub server_name: String, + #[ts(type = "string | number")] pub id: RequestId, pub message: String, // TODO: MCP servers can request we fill out a schema for the elicitation. We don't support diff --git a/codex-rs/protocol/src/mcp.rs b/codex-rs/protocol/src/mcp.rs index 88a1f989c1..d2a8b0ccd8 100644 --- a/codex-rs/protocol/src/mcp.rs +++ b/codex-rs/protocol/src/mcp.rs @@ -1,14 +1,14 @@ +//! Types used when representing Model Context Protocol (MCP) values inside the +//! Codex protocol. +//! +//! We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and +//! `schemars`) so they can be embedded in Codex's own protocol structures. use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use ts_rs::TS; -/// Types used when representing Model Context Protocol (MCP) values inside the -/// Codex protocol. -/// -/// We intentionally keep these types TS/JSON-schema friendly (via `ts-rs` and -/// `schemars`) so they can be embedded in Codex's own protocol structures. - +/// ID of a request, which can be either a string or an integer. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)] #[serde(untagged)] pub enum RequestId { @@ -26,6 +26,7 @@ impl std::fmt::Display for RequestId { } } +/// Definition for a tool the client can call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct Tool { @@ -51,6 +52,7 @@ pub struct Tool { pub meta: Option, } +/// A known resource that the server is capable of reading. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct Resource { @@ -80,6 +82,7 @@ pub struct Resource { pub meta: Option, } +/// A template description for resources available on the server. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct ResourceTemplate { @@ -99,6 +102,7 @@ pub struct ResourceTemplate { pub mime_type: Option, } +/// The server's response to a tool call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct CallToolResult { diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 9dffb02565..07c8856fe6 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -2,8 +2,6 @@ use std::collections::HashMap; use std::path::Path; use codex_utils_image::load_and_resize_to_fit; -use mcp_types::CallToolResult; -use mcp_types::ContentBlock; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; @@ -24,6 +22,8 @@ use codex_git::GhostCommit; use codex_utils_image::error::ImageProcessingError; use schemars::JsonSchema; +use crate::mcp::CallToolResult; + /// Controls whether a command should use the session sandbox or bypass it. #[derive( Debug, Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS, @@ -833,6 +833,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { content, structured_content, is_error, + meta: _, } = call_tool_result; let is_success = is_error != &Some(true); @@ -869,7 +870,7 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { } }; - let content_items = convert_content_blocks_to_items(content); + let content_items = convert_mcp_content_to_items(content); FunctionCallOutputPayload { content: serialized_content, @@ -879,32 +880,45 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { } } -fn convert_content_blocks_to_items( - blocks: &[ContentBlock], +fn convert_mcp_content_to_items( + contents: &[serde_json::Value], ) -> Option> { + #[derive(serde::Deserialize)] + #[serde(tag = "type")] + enum McpContent { + #[serde(rename = "text")] + Text { text: String }, + #[serde(rename = "image")] + Image { + data: String, + #[serde(rename = "mimeType", alias = "mime_type")] + mime_type: Option, + }, + #[serde(other)] + Unknown, + } + let mut saw_image = false; - let mut items = Vec::with_capacity(blocks.len()); - tracing::warn!("Blocks: {:?}", blocks); - for block in blocks { - match block { - ContentBlock::TextContent(text) => { - items.push(FunctionCallOutputContentItem::InputText { - text: text.text.clone(), - }); - } - ContentBlock::ImageContent(image) => { + let mut items = Vec::with_capacity(contents.len()); + + for content in contents { + let item = match serde_json::from_value::(content.clone()) { + Ok(McpContent::Text { text }) => FunctionCallOutputContentItem::InputText { text }, + Ok(McpContent::Image { data, mime_type }) => { saw_image = true; - // Just in case the content doesn't include a data URL, add it. - let image_url = if image.data.starts_with("data:") { - image.data.clone() + let image_url = if data.starts_with("data:") { + data } else { - format!("data:{};base64,{}", image.mime_type, image.data) + let mime_type = mime_type.unwrap_or_else(|| "application/octet-stream".into()); + format!("data:{mime_type};base64,{data}") }; - items.push(FunctionCallOutputContentItem::InputImage { image_url }); + FunctionCallOutputContentItem::InputImage { image_url } } - // TODO: render audio, resource, and embedded resource content to the model. - _ => return None, - } + Ok(McpContent::Unknown) | Err(_) => FunctionCallOutputContentItem::InputText { + text: serde_json::to_string(content).unwrap_or_else(|_| "".to_string()), + }, + }; + items.push(item); } if saw_image { Some(items) } else { None } @@ -936,12 +950,54 @@ mod tests { use crate::protocol::AskForApproval; use anyhow::Result; use codex_execpolicy::Policy; - use mcp_types::ImageContent; - use mcp_types::TextContent; use pretty_assertions::assert_eq; use std::path::PathBuf; use tempfile::tempdir; + #[test] + fn convert_mcp_content_to_items_preserves_data_urls() { + let contents = vec![serde_json::json!({ + "type": "image", + "data": "data:image/png;base64,Zm9v", + "mimeType": "image/png", + })]; + + let items = convert_mcp_content_to_items(&contents).expect("expected image items"); + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,Zm9v".to_string(), + }] + ); + } + + #[test] + fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix() { + let contents = vec![serde_json::json!({ + "type": "image", + "data": "Zm9v", + "mimeType": "image/png", + })]; + + let items = convert_mcp_content_to_items(&contents).expect("expected image items"); + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,Zm9v".to_string(), + }] + ); + } + + #[test] + fn convert_mcp_content_to_items_returns_none_without_images() { + let contents = vec![serde_json::json!({ + "type": "text", + "text": "hello", + })]; + + assert_eq!(convert_mcp_content_to_items(&contents), None); + } + #[test] fn converts_sandbox_mode_into_developer_instructions() { let workspace_write: DeveloperInstructions = SandboxMode::WorkspaceWrite.into(); @@ -1124,20 +1180,12 @@ mod tests { fn serializes_image_outputs_as_array() -> Result<()> { let call_tool_result = CallToolResult { content: vec![ - ContentBlock::TextContent(TextContent { - annotations: None, - text: "caption".into(), - r#type: "text".into(), - }), - ContentBlock::ImageContent(ImageContent { - annotations: None, - data: "BASE64".into(), - mime_type: "image/png".into(), - r#type: "image".into(), - }), + serde_json::json!({"type":"text","text":"caption"}), + serde_json::json!({"type":"image","data":"BASE64","mimeType":"image/png"}), ], - is_error: None, structured_content: None, + is_error: Some(false), + meta: None, }; let payload = FunctionCallOutputPayload::from(&call_tool_result); @@ -1169,6 +1217,33 @@ mod tests { Ok(()) } + #[test] + fn preserves_existing_image_data_urls() -> Result<()> { + let call_tool_result = CallToolResult { + content: vec![serde_json::json!({ + "type": "image", + "data": "data:image/png;base64,BASE64", + "mimeType": "image/png" + })], + structured_content: None, + is_error: Some(false), + meta: None, + }; + + let payload = FunctionCallOutputPayload::from(&call_tool_result); + let Some(items) = payload.content_items else { + panic!("expected content items"); + }; + assert_eq!( + items, + vec![FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,BASE64".into(), + }] + ); + + Ok(()) + } + #[test] fn deserializes_array_payload_into_items() -> Result<()> { let json = r#"[ diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 6a6939620f..71a7973c6d 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -23,6 +23,11 @@ use crate::dynamic_tools::DynamicToolCallRequest; use crate::dynamic_tools::DynamicToolResponse; use crate::dynamic_tools::DynamicToolSpec; use crate::items::TurnItem; +use crate::mcp::CallToolResult; +use crate::mcp::RequestId; +use crate::mcp::Resource as McpResource; +use crate::mcp::ResourceTemplate as McpResourceTemplate; +use crate::mcp::Tool as McpTool; use crate::message_history::HistoryEntry; use crate::models::BaseInstructions; use crate::models::ContentItem; @@ -35,11 +40,6 @@ use crate::plan_tool::UpdatePlanArgs; use crate::request_user_input::RequestUserInputResponse; use crate::user_input::UserInput; use codex_utils_absolute_path::AbsolutePathBuf; -use mcp_types::CallToolResult; -use mcp_types::RequestId; -use mcp_types::Resource as McpResource; -use mcp_types::ResourceTemplate as McpResourceTemplate; -use mcp_types::Tool as McpTool; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index 4b2d6964f4..2b8752fcea 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -18,7 +18,6 @@ codex-protocol = { workspace = true } codex-utils-home-dir = { workspace = true } futures = { workspace = true, default-features = false, features = ["std"] } keyring = { workspace = true, features = ["crypto-rust"] } -mcp-types = { path = "../mcp-types" } oauth2 = "5" reqwest = { version = "0.12", default-features = false, features = [ "json", diff --git a/codex-rs/rmcp-client/src/logging_client_handler.rs b/codex-rs/rmcp-client/src/logging_client_handler.rs index 0d2c3aaa97..8db730df06 100644 --- a/codex-rs/rmcp-client/src/logging_client_handler.rs +++ b/codex-rs/rmcp-client/src/logging_client_handler.rs @@ -9,7 +9,6 @@ use rmcp::model::CreateElicitationResult; use rmcp::model::LoggingLevel; use rmcp::model::LoggingMessageNotificationParam; use rmcp::model::ProgressNotificationParam; -use rmcp::model::RequestId; use rmcp::model::ResourceUpdatedNotificationParam; use rmcp::service::NotificationContext; use rmcp::service::RequestContext; @@ -41,11 +40,7 @@ impl ClientHandler for LoggingClientHandler { request: CreateElicitationRequestParam, context: RequestContext, ) -> Result { - let id = match context.id { - RequestId::String(id) => mcp_types::RequestId::String(id.to_string()), - RequestId::Number(id) => mcp_types::RequestId::Integer(id), - }; - (self.send_elicitation)(id, request) + (self.send_elicitation)(context.id, request) .await .map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None)) } diff --git a/codex-rs/rmcp-client/src/rmcp_client.rs b/codex-rs/rmcp-client/src/rmcp_client.rs index c1bf6d39d3..12b4b52b91 100644 --- a/codex-rs/rmcp-client/src/rmcp_client.rs +++ b/codex-rs/rmcp-client/src/rmcp_client.rs @@ -10,22 +10,9 @@ use anyhow::Result; use anyhow::anyhow; use futures::FutureExt; use futures::future::BoxFuture; -use mcp_types::CallToolRequestParams; -use mcp_types::CallToolResult; -use mcp_types::InitializeRequestParams; -use mcp_types::InitializeResult; -use mcp_types::ListResourceTemplatesRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ListResourcesRequestParams; -use mcp_types::ListResourcesResult; -use mcp_types::ListToolsRequestParams; -use mcp_types::ListToolsResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResult; -use mcp_types::RequestId; -use mcp_types::Tool; use reqwest::header::HeaderMap; use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolResult; use rmcp::model::ClientNotification; use rmcp::model::ClientRequest; use rmcp::model::CreateElicitationRequestParam; @@ -34,9 +21,16 @@ use rmcp::model::CustomNotification; use rmcp::model::CustomRequest; use rmcp::model::Extensions; use rmcp::model::InitializeRequestParam; +use rmcp::model::InitializeResult; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ListResourcesResult; +use rmcp::model::ListToolsResult; use rmcp::model::PaginatedRequestParam; use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceResult; +use rmcp::model::RequestId; use rmcp::model::ServerResult; +use rmcp::model::Tool; use rmcp::service::RoleClient; use rmcp::service::RunningService; use rmcp::service::{self}; @@ -62,9 +56,6 @@ use crate::oauth::StoredOAuthTokens; use crate::program_resolver; use crate::utils::apply_default_headers; use crate::utils::build_default_headers; -use crate::utils::convert_call_tool_result; -use crate::utils::convert_to_mcp; -use crate::utils::convert_to_rmcp; use crate::utils::create_env_for_mcp_server; use crate::utils::run_with_timeout; @@ -229,12 +220,11 @@ impl RmcpClient { /// https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization pub async fn initialize( &self, - params: InitializeRequestParams, + params: InitializeRequestParam, timeout: Option, send_elicitation: SendElicitation, ) -> Result { - let rmcp_params: InitializeRequestParam = convert_to_rmcp(params.clone())?; - let client_handler = LoggingClientHandler::new(rmcp_params, send_elicitation); + let client_handler = LoggingClientHandler::new(params.clone(), send_elicitation); let (transport, oauth_persistor) = { let mut guard = self.state.lock().await; @@ -275,7 +265,7 @@ impl RmcpClient { .peer() .peer_info() .ok_or_else(|| anyhow!("handshake succeeded but server info was missing"))?; - let initialize_result = convert_to_mcp(initialize_result_rmcp)?; + let initialize_result = initialize_result_rmcp.clone(); { let mut guard = self.state.lock().await; @@ -296,28 +286,26 @@ impl RmcpClient { pub async fn list_tools( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { - let result = self.list_tools_with_connector_ids(params, timeout).await?; - Ok(ListToolsResult { - next_cursor: result.next_cursor, - tools: result.tools.into_iter().map(|tool| tool.tool).collect(), - }) + self.refresh_oauth_if_needed().await; + let service = self.service().await?; + let fut = service.list_tools(params); + let result = run_with_timeout(fut, timeout, "tools/list").await?; + self.persist_oauth_tokens().await; + Ok(result) } pub async fn list_tools_with_connector_ids( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_tools(rmcp_params); + let fut = service.list_tools(params); let result = run_with_timeout(fut, timeout, "tools/list").await?; let tools = result .tools @@ -327,7 +315,6 @@ impl RmcpClient { let connector_id = Self::meta_string(meta, "connector_id"); let connector_name = Self::meta_string(meta, "connector_name") .or_else(|| Self::meta_string(meta, "connector_display_name")); - let tool = convert_to_mcp(tool)?; Ok(ToolWithConnectorId { tool, connector_id, @@ -352,53 +339,43 @@ impl RmcpClient { pub async fn list_resources( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_resources(rmcp_params); + let fut = service.list_resources(params); let result = run_with_timeout(fut, timeout, "resources/list").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn list_resource_templates( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params = params - .map(convert_to_rmcp::<_, PaginatedRequestParam>) - .transpose()?; - let fut = service.list_resource_templates(rmcp_params); + let fut = service.list_resource_templates(params); let result = run_with_timeout(fut, timeout, "resources/templates/list").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn read_resource( &self, - params: ReadResourceRequestParams, + params: ReadResourceRequestParam, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let rmcp_params: ReadResourceRequestParam = convert_to_rmcp(params)?; - let fut = service.read_resource(rmcp_params); + let fut = service.read_resource(params); let result = run_with_timeout(fut, timeout, "resources/read").await?; - let converted = convert_to_mcp(result)?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn call_tool( @@ -409,13 +386,23 @@ impl RmcpClient { ) -> Result { self.refresh_oauth_if_needed().await; let service = self.service().await?; - let params = CallToolRequestParams { arguments, name }; - let rmcp_params: CallToolRequestParam = convert_to_rmcp(params)?; + let arguments = match arguments { + Some(Value::Object(map)) => Some(map), + Some(other) => { + return Err(anyhow!( + "MCP tool arguments must be a JSON object, got {other}" + )); + } + None => None, + }; + let rmcp_params = CallToolRequestParam { + name: name.into(), + arguments, + }; let fut = service.call_tool(rmcp_params); - let rmcp_result = run_with_timeout(fut, timeout, "tools/call").await?; - let converted = convert_call_tool_result(rmcp_result)?; + let result = run_with_timeout(fut, timeout, "tools/call").await?; self.persist_oauth_tokens().await; - Ok(converted) + Ok(result) } pub async fn send_custom_notification( diff --git a/codex-rs/rmcp-client/src/utils.rs b/codex-rs/rmcp-client/src/utils.rs index 8deb4d402b..e47c1d14b6 100644 --- a/codex-rs/rmcp-client/src/utils.rs +++ b/codex-rs/rmcp-client/src/utils.rs @@ -5,14 +5,11 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; -use mcp_types::CallToolResult; use reqwest::ClientBuilder; use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; -use rmcp::model::CallToolResult as RmcpCallToolResult; use rmcp::service::ServiceError; -use serde_json::Value; use tokio::time; pub(crate) async fn run_with_timeout( @@ -33,45 +30,6 @@ where } } -pub(crate) fn convert_call_tool_result(result: RmcpCallToolResult) -> Result { - let mut value = serde_json::to_value(result)?; - if let Some(obj) = value.as_object_mut() - && (obj.get("content").is_none() - || obj.get("content").is_some_and(serde_json::Value::is_null)) - { - obj.insert("content".to_string(), Value::Array(Vec::new())); - } - serde_json::from_value(value).context("failed to convert call tool result") -} - -/// Convert from mcp-types to Rust SDK types. -/// -/// The Rust SDK types are the same as our mcp-types crate because they are both -/// derived from the same MCP specification. -/// As a result, it should be safe to convert directly from one to the other. -pub(crate) fn convert_to_rmcp(value: T) -> Result -where - T: serde::Serialize, - U: serde::de::DeserializeOwned, -{ - let json = serde_json::to_value(value)?; - serde_json::from_value(json).map_err(|err| anyhow!(err)) -} - -/// Convert from Rust SDK types to mcp-types. -/// -/// The Rust SDK types are the same as our mcp-types crate because they are both -/// derived from the same MCP specification. -/// As a result, it should be safe to convert directly from one to the other. -pub(crate) fn convert_to_mcp(value: T) -> Result -where - T: serde::Serialize, - U: serde::de::DeserializeOwned, -{ - let json = serde_json::to_value(value)?; - serde_json::from_value(json).map_err(|err| anyhow!(err)) -} - pub(crate) fn create_env_for_mcp_server( extra_env: Option>, env_vars: &[String], @@ -203,10 +161,7 @@ pub(crate) const DEFAULT_ENV_VARS: &[&str] = &[ #[cfg(test)] mod tests { use super::*; - use mcp_types::ContentBlock; use pretty_assertions::assert_eq; - use rmcp::model::CallToolResult as RmcpCallToolResult; - use serde_json::json; use serial_test::serial; use std::ffi::OsString; @@ -260,43 +215,4 @@ mod tests { let env = create_env_for_mcp_server(None, &[custom_var.to_string()]); assert_eq!(env.get(custom_var), Some(&value.to_string())); } - - #[test] - fn convert_call_tool_result_defaults_missing_content() -> Result<()> { - let structured_content = json!({ "key": "value" }); - let rmcp_result = RmcpCallToolResult { - content: vec![], - structured_content: Some(structured_content.clone()), - is_error: Some(true), - meta: None, - }; - - let result = convert_call_tool_result(rmcp_result)?; - - assert!(result.content.is_empty()); - assert_eq!(result.structured_content, Some(structured_content)); - assert_eq!(result.is_error, Some(true)); - - Ok(()) - } - - #[test] - fn convert_call_tool_result_preserves_existing_content() -> Result<()> { - let rmcp_result = RmcpCallToolResult::success(vec![rmcp::model::Content::text("hello")]); - - let result = convert_call_tool_result(rmcp_result)?; - - assert_eq!(result.content.len(), 1); - match &result.content[0] { - ContentBlock::TextContent(text_content) => { - assert_eq!(text_content.text, "hello"); - assert_eq!(text_content.r#type, "text"); - } - other => panic!("expected text content got {other:?}"), - } - assert_eq!(result.structured_content, None); - assert_eq!(result.is_error, Some(false)); - - Ok(()) - } } diff --git a/codex-rs/rmcp-client/tests/resources.rs b/codex-rs/rmcp-client/tests/resources.rs index 3d627ebbf4..9bfd77c183 100644 --- a/codex-rs/rmcp-client/tests/resources.rs +++ b/codex-rs/rmcp-client/tests/resources.rs @@ -7,15 +7,15 @@ use codex_rmcp_client::ElicitationResponse; use codex_rmcp_client::RmcpClient; use codex_utils_cargo_bin::CargoBinError; use futures::FutureExt as _; -use mcp_types::ClientCapabilities; -use mcp_types::Implementation; -use mcp_types::InitializeRequestParams; -use mcp_types::ListResourceTemplatesResult; -use mcp_types::ReadResourceRequestParams; -use mcp_types::ReadResourceResultContents; -use mcp_types::Resource; -use mcp_types::ResourceTemplate; -use mcp_types::TextResourceContents; +use rmcp::model::AnnotateAble; +use rmcp::model::ClientCapabilities; +use rmcp::model::ElicitationCapability; +use rmcp::model::Implementation; +use rmcp::model::InitializeRequestParam; +use rmcp::model::ListResourceTemplatesResult; +use rmcp::model::ProtocolVersion; +use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ResourceContents; use serde_json::json; const RESOURCE_URI: &str = "memo://codex/example-note"; @@ -24,21 +24,24 @@ fn stdio_server_bin() -> Result { codex_utils_cargo_bin::cargo_bin("test_stdio_server") } -fn init_params() -> InitializeRequestParams { - InitializeRequestParams { +fn init_params() -> InitializeRequestParam { + InitializeRequestParam { capabilities: ClientCapabilities { experimental: None, roots: None, sampling: None, - elicitation: Some(json!({})), + elicitation: Some(ElicitationCapability { + schema_validation: None, + }), }, client_info: Implementation { name: "codex-test".into(), version: "0.0.0-test".into(), title: Some("Codex rmcp resource test".into()), - user_agent: None, + icons: None, + website_url: None, }, - protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_string(), + protocol_version: ProtocolVersion::V_2025_06_18, } } @@ -79,15 +82,17 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { .expect("memo resource present"); assert_eq!( memo, - &Resource { - annotations: None, + &rmcp::model::RawResource { + uri: RESOURCE_URI.to_string(), + name: "example-note".to_string(), + title: Some("Example Note".to_string()), description: Some("A sample MCP resource exposed for integration tests.".to_string()), mime_type: Some("text/plain".to_string()), - name: "example-note".to_string(), size: None, - title: Some("Example Note".to_string()), - uri: RESOURCE_URI.to_string(), + icons: None, + meta: None, } + .no_annotation() ); let templates = client .list_resource_templates(None, Some(Duration::from_secs(5))) @@ -95,39 +100,39 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { assert_eq!( templates, ListResourceTemplatesResult { + meta: None, next_cursor: None, - resource_templates: vec![ResourceTemplate { - annotations: None, - description: Some( - "Template for memo://codex/{slug} resources used in tests.".to_string() - ), - mime_type: Some("text/plain".to_string()), - name: "codex-memo".to_string(), - title: Some("Codex Memo".to_string()), - uri_template: "memo://codex/{slug}".to_string(), - }], + resource_templates: vec![ + rmcp::model::RawResourceTemplate { + uri_template: "memo://codex/{slug}".to_string(), + name: "codex-memo".to_string(), + title: Some("Codex Memo".to_string()), + description: Some( + "Template for memo://codex/{slug} resources used in tests.".to_string(), + ), + mime_type: Some("text/plain".to_string()), + } + .no_annotation() + ], } ); let read = client .read_resource( - ReadResourceRequestParams { + ReadResourceRequestParam { uri: RESOURCE_URI.to_string(), }, Some(Duration::from_secs(5)), ) .await?; - let ReadResourceResultContents::TextResourceContents(text) = - read.contents.first().expect("resource contents present") - else { - panic!("expected text resource"); - }; + let text = read.contents.first().expect("resource contents present"); assert_eq!( text, - &TextResourceContents { - text: "This is a sample MCP resource served by the rmcp test server.".to_string(), + &ResourceContents::TextResourceContents { uri: RESOURCE_URI.to_string(), mime_type: Some("text/plain".to_string()), + text: "This is a sample MCP resource served by the rmcp test server.".to_string(), + meta: None, } ); diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index d859aa945e..8bdd473220 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -54,7 +54,6 @@ dunce = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } itertools = { workspace = true } lazy_static = { workspace = true } -mcp-types = { workspace = true } pathdiff = { workspace = true } pulldown-cmark = { workspace = true } rand = { workspace = true } @@ -67,6 +66,7 @@ ratatui = { workspace = true, features = [ ratatui-macros = { workspace = true } regex-lite = { workspace = true } reqwest = { version = "0.12", features = ["json"] } +rmcp = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } shlex = { workspace = true } diff --git a/codex-rs/tui/src/bottom_pane/approval_overlay.rs b/codex-rs/tui/src/bottom_pane/approval_overlay.rs index 0f0445fee8..15f252e86f 100644 --- a/codex-rs/tui/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/approval_overlay.rs @@ -23,11 +23,11 @@ use codex_core::protocol::ExecPolicyAmendment; use codex_core::protocol::FileChange; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; +use codex_protocol::mcp::RequestId; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; use crossterm::event::KeyModifiers; -use mcp_types::RequestId; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Stylize; diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap index 77738439a1..38fb05e28d 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap @@ -27,7 +27,7 @@ expression: "lines[start_idx..].join(\"\\n\")" exec, linux-sandbox, tui, login, ollama, and mcp. • Ran for d in ansi-escape apply-patch arg0 cli common core exec execpolicy - │ file-search linux-sandbox login mcp-client mcp-server mcp-types ollama + │ file-search linux-sandbox login mcp-client mcp-server ollama │ tui; do echo "--- $d/Cargo.toml"; sed -n '1,200p' $d/Cargo.toml; echo; │ … +1 lines └ --- ansi-escape/Cargo.toml diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index eb1de452a9..566c9b9f51 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -47,6 +47,8 @@ use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; use codex_otel::RuntimeMetricsSummary; use codex_protocol::account::PlanType; +use codex_protocol::mcp::Resource; +use codex_protocol::mcp::ResourceTemplate; use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::PlanItemArg; @@ -55,10 +57,6 @@ use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::user_input::TextElement; use image::DynamicImage; use image::ImageReader; -use mcp_types::EmbeddedResourceResource; -use mcp_types::Resource; -use mcp_types::ResourceLink; -use mcp_types::ResourceTemplate; use ratatui::prelude::*; use ratatui::style::Color; use ratatui::style::Modifier; @@ -1201,7 +1199,7 @@ pub(crate) struct McpToolCallCell { invocation: McpInvocation, start_time: Instant, duration: Option, - result: Option>, + result: Option>, animations_enabled: bool, } @@ -1228,7 +1226,7 @@ impl McpToolCallCell { pub(crate) fn complete( &mut self, duration: Duration, - result: Result, + result: Result, ) -> Option> { let image_cell = try_new_completed_mcp_tool_call_with_image_output(&result) .map(|cell| Box::new(cell) as Box); @@ -1251,23 +1249,32 @@ impl McpToolCallCell { self.result = Some(Err("interrupted".to_string())); } - fn render_content_block(block: &mcp_types::ContentBlock, width: usize) -> String { - match block { - mcp_types::ContentBlock::TextContent(text) => { + fn render_content_block(block: &serde_json::Value, width: usize) -> String { + let content = match serde_json::from_value::(block.clone()) { + Ok(content) => content, + Err(_) => { + return format_and_truncate_tool_result( + &block.to_string(), + TOOL_CALL_MAX_LINES, + width, + ); + } + }; + + match content.raw { + rmcp::model::RawContent::Text(text) => { format_and_truncate_tool_result(&text.text, TOOL_CALL_MAX_LINES, width) } - mcp_types::ContentBlock::ImageContent(_) => "".to_string(), - mcp_types::ContentBlock::AudioContent(_) => "