diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index e2b13f91e..a93a7072f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -754,6 +754,7 @@ checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", + "form_urlencoded", "futures-util", "http 1.4.0", "http-body", @@ -769,11 +770,13 @@ dependencies = [ "serde_core", "serde_json", "serde_path_to_error", + "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -792,6 +795,7 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -7113,11 +7117,12 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa" +checksum = "0a621b37a548ff6ab6292d57841eb25785a7f146d89391a19c9f199414bd13da" dependencies = [ "async-trait", + "axum", "base64 0.22.1", "bytes", "chrono", @@ -7148,9 +7153,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59" +checksum = "6b79ed92303f9262db79575aa8c3652581668e9d136be6fd0b9ededa78954c95" dependencies = [ "darling 0.23.0", "proc-macro2", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 2cfcf59ef..c8453cb3b 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -194,7 +194,7 @@ ratatui-macros = "0.6.0" regex = "1.12.2" regex-lite = "0.1.8" reqwest = "0.12" -rmcp = { version = "0.12.0", default-features = false } +rmcp = { version = "0.14.0", default-features = false } rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" } schemars = "0.8.22" diff --git a/codex-rs/app-server/tests/suite/v2/app_list.rs b/codex-rs/app-server/tests/suite/v2/app_list.rs index 53221adae..c6b829f2e 100644 --- a/codex-rs/app-server/tests/suite/v2/app_list.rs +++ b/codex-rs/app-server/tests/suite/v2/app_list.rs @@ -288,7 +288,7 @@ impl ServerHandler for AppListMcpServer { fn list_tools( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 8280b5a9e..9775e4991 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -77,8 +77,8 @@ use futures::prelude::*; use futures::stream::FuturesOrdered; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ListResourcesResult; -use rmcp::model::PaginatedRequestParam; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::PaginatedRequestParams; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::RequestId; use serde_json; @@ -2501,7 +2501,7 @@ impl Session { pub async fn list_resources( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2514,7 +2514,7 @@ impl Session { pub async fn list_resource_templates( &self, server: &str, - params: Option, + params: Option, ) -> anyhow::Result { self.services .mcp_connection_manager @@ -2527,7 +2527,7 @@ impl Session { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParam, + params: ReadResourceRequestParams, ) -> anyhow::Result { self.services .mcp_connection_manager diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 06fd2406b..38e5bcb6d 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -45,12 +45,12 @@ use futures::future::Shared; use rmcp::model::ClientCapabilities; use rmcp::model::ElicitationCapability; use rmcp::model::Implementation; -use rmcp::model::InitializeRequestParam; +use rmcp::model::InitializeRequestParams; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ListResourcesResult; -use rmcp::model::PaginatedRequestParam; +use rmcp::model::PaginatedRequestParams; use rmcp::model::ProtocolVersion; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::RequestId; use rmcp::model::Resource; @@ -551,7 +551,8 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor.as_ref().map(|next| PaginatedRequestParam { + let params = cursor.as_ref().map(|next| PaginatedRequestParams { + meta: None, cursor: Some(next.clone()), }); let response = match client.list_resources(params, timeout).await { @@ -616,7 +617,8 @@ impl McpConnectionManager { let mut cursor: Option = None; loop { - let params = cursor.as_ref().map(|next| PaginatedRequestParam { + let params = cursor.as_ref().map(|next| PaginatedRequestParams { + meta: None, cursor: Some(next.clone()), }); let response = match client.list_resource_templates(params, timeout).await { @@ -706,7 +708,7 @@ impl McpConnectionManager { 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; @@ -722,7 +724,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(); @@ -738,7 +740,7 @@ impl McpConnectionManager { pub async fn read_resource( &self, server: &str, - params: ReadResourceRequestParam, + params: ReadResourceRequestParams, ) -> Result { let managed = self.client_by_name(server).await?; let client = managed.client.clone(); @@ -921,7 +923,8 @@ async fn start_server_task( tx_event: Sender, elicitation_requests: ElicitationRequestManager, ) -> Result { - let params = InitializeRequestParam { + let params = InitializeRequestParams { + meta: None, capabilities: ClientCapabilities { experimental: None, roots: None, @@ -931,6 +934,7 @@ async fn start_server_task( elicitation: Some(ElicitationCapability { schema_validation: None, }), + tasks: None, }, client_info: Implementation { name: "codex-mcp-client".to_owned(), diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index fe541197c..2a0aabcbc 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -8,8 +8,8 @@ use async_trait::async_trait; use codex_protocol::mcp::CallToolResult; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ListResourcesResult; -use rmcp::model::PaginatedRequestParam; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::PaginatedRequestParams; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::Resource; use rmcp::model::ResourceTemplate; @@ -262,7 +262,8 @@ async fn handle_list_resources( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor.clone().map(|value| PaginatedRequestParam { + let params = cursor.clone().map(|value| PaginatedRequestParams { + meta: None, cursor: Some(value), }); let result = session @@ -367,7 +368,8 @@ async fn handle_list_resource_templates( let payload_result: Result = async { if let Some(server_name) = server.clone() { - let params = cursor.clone().map(|value| PaginatedRequestParam { + let params = cursor.clone().map(|value| PaginatedRequestParams { + meta: None, cursor: Some(value), }); let result = session @@ -474,7 +476,13 @@ async fn handle_read_resource( let payload_result: Result = async { let result = session - .read_resource(&server, ReadResourceRequestParam { uri: uri.clone() }) + .read_resource( + &server, + ReadResourceRequestParams { + meta: None, + uri: uri.clone(), + }, + ) .await .map_err(|err| { FunctionCallError::RespondToModel(format!("resources/read failed: {err:#}")) @@ -689,6 +697,7 @@ mod tests { title: None, description: None, mime_type: None, + icons: None, } .no_annotation() } diff --git a/codex-rs/exec-server/src/posix/mcp.rs b/codex-rs/exec-server/src/posix/mcp.rs index e48b70a2a..4cfce9ff8 100644 --- a/codex-rs/exec-server/src/posix/mcp.rs +++ b/codex-rs/exec-server/src/posix/mcp.rs @@ -185,7 +185,7 @@ impl ServerHandler for ExecTool { async fn initialize( &self, - _request: InitializeRequestParam, + _request: InitializeRequestParams, _context: RequestContext, ) -> Result { Ok(self.get_info()) diff --git a/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs b/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs index 6d0c1bb33..5ef1e6017 100644 --- a/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs +++ b/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs @@ -4,7 +4,7 @@ use codex_core::sandboxing::SandboxPermissions; use codex_execpolicy::Policy; use rmcp::ErrorData as McpError; use rmcp::RoleServer; -use rmcp::model::CreateElicitationRequestParam; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::CreateElicitationResult; use rmcp::model::ElicitationAction; use rmcp::model::ElicitationSchema; @@ -69,7 +69,8 @@ impl McpEscalationPolicy { .pause_for(async { context .peer - .create_elicitation(CreateElicitationRequestParam { + .create_elicitation(CreateElicitationRequestParams { + meta: None, message: format!( "Allow agent to run `{command}` in `{}`?", workdir.display() diff --git a/codex-rs/exec-server/tests/common/lib.rs b/codex-rs/exec-server/tests/common/lib.rs index 66bb4b7f9..a51a589ad 100644 --- a/codex-rs/exec-server/tests/common/lib.rs +++ b/codex-rs/exec-server/tests/common/lib.rs @@ -9,7 +9,7 @@ use rmcp::Service; use rmcp::model::ClientCapabilities; use rmcp::model::ClientInfo; use rmcp::model::ClientRequest; -use rmcp::model::CreateElicitationRequestParam; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::CreateElicitationResult; use rmcp::model::CustomRequest; use rmcp::model::ElicitationAction; @@ -142,7 +142,7 @@ where pub struct InteractiveClient { pub elicitations_to_accept: HashSet, - pub elicitation_requests: Arc>>, + pub elicitation_requests: Arc>>, } impl ClientHandler for InteractiveClient { @@ -156,7 +156,7 @@ impl ClientHandler for InteractiveClient { fn create_elicitation( &self, - request: CreateElicitationRequestParam, + request: CreateElicitationRequestParams, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { diff --git a/codex-rs/exec-server/tests/suite/accept_elicitation.rs b/codex-rs/exec-server/tests/suite/accept_elicitation.rs index eade4f6e5..7cb7438a5 100644 --- a/codex-rs/exec-server/tests/suite/accept_elicitation.rs +++ b/codex-rs/exec-server/tests/suite/accept_elicitation.rs @@ -15,9 +15,9 @@ use exec_server_test_support::write_default_execpolicy; use maplit::hashset; use pretty_assertions::assert_eq; use rmcp::ServiceExt; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; -use rmcp::model::CreateElicitationRequestParam; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::EmptyResult; use rmcp::model::ServerResult; use rmcp::model::object; @@ -64,7 +64,7 @@ prefix_rule( git_path, project_root_path.display() ); - let elicitation_requests: Arc>> = Default::default(); + let elicitation_requests: Arc>> = Default::default(); let client = InteractiveClient { elicitations_to_accept: hashset! { expected_elicitation_message.clone() }, elicitation_requests: elicitation_requests.clone(), @@ -96,7 +96,8 @@ prefix_rule( let CallToolResult { content, is_error, .. } = service - .call_tool(CallToolRequestParam { + .call_tool(CallToolRequestParams { + meta: None, name: Cow::Borrowed("shell"), arguments: Some(object(json!( { @@ -105,6 +106,7 @@ prefix_rule( "workdir": project_root_path.to_string_lossy(), } ))), + task: None, }) .await?; let tool_call_content = content diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 1d9ab192a..c3449d50c 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -9,7 +9,7 @@ use codex_core::default_client::get_codex_user_agent; use codex_core::protocol::Submission; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::ClientNotification; use rmcp::model::ClientRequest; @@ -115,6 +115,22 @@ impl MessageProcessor { ClientRequest::CompleteRequest(params) => { self.handle_complete(params.params); } + ClientRequest::GetTaskInfoRequest(_) => { + self.handle_unsupported_request(request_id, "tasks/get_info") + .await; + } + ClientRequest::ListTasksRequest(_) => { + self.handle_unsupported_request(request_id, "tasks/list") + .await; + } + ClientRequest::GetTaskResultRequest(_) => { + self.handle_unsupported_request(request_id, "tasks/get_result") + .await; + } + ClientRequest::CancelTaskRequest(_) => { + self.handle_unsupported_request(request_id, "tasks/cancel") + .await; + } ClientRequest::CustomRequest(custom) => { let method = custom.method.clone(); self.outgoing @@ -167,7 +183,7 @@ impl MessageProcessor { async fn handle_initialize( &mut self, id: RequestId, - params: rmcp::model::InitializeRequestParam, + params: rmcp::model::InitializeRequestParams, ) { tracing::info!("initialize -> params: {:?}", params); @@ -256,38 +272,38 @@ impl MessageProcessor { self.outgoing.send_response(id, json!({})).await; } - fn handle_list_resources(&self, params: Option) { + fn handle_list_resources(&self, params: Option) { tracing::info!("resources/list -> params: {:?}", params); } - fn handle_list_resource_templates(&self, params: Option) { + fn handle_list_resource_templates(&self, params: Option) { tracing::info!("resources/templates/list -> params: {:?}", params); } - fn handle_read_resource(&self, params: rmcp::model::ReadResourceRequestParam) { + fn handle_read_resource(&self, params: rmcp::model::ReadResourceRequestParams) { tracing::info!("resources/read -> params: {:?}", params); } - fn handle_subscribe(&self, params: rmcp::model::SubscribeRequestParam) { + fn handle_subscribe(&self, params: rmcp::model::SubscribeRequestParams) { tracing::info!("resources/subscribe -> params: {:?}", params); } - fn handle_unsubscribe(&self, params: rmcp::model::UnsubscribeRequestParam) { + fn handle_unsubscribe(&self, params: rmcp::model::UnsubscribeRequestParams) { tracing::info!("resources/unsubscribe -> params: {:?}", params); } - fn handle_list_prompts(&self, params: Option) { + fn handle_list_prompts(&self, params: Option) { tracing::info!("prompts/list -> params: {:?}", params); } - fn handle_get_prompt(&self, params: rmcp::model::GetPromptRequestParam) { + fn handle_get_prompt(&self, params: rmcp::model::GetPromptRequestParams) { tracing::info!("prompts/get -> params: {:?}", params); } async fn handle_list_tools( &self, id: RequestId, - params: Option, + params: Option, ) { tracing::trace!("tools/list -> {params:?}"); let result = rmcp::model::ListToolsResult { @@ -302,9 +318,11 @@ impl MessageProcessor { self.outgoing.send_response(id, result).await; } - async fn handle_call_tool(&self, id: RequestId, params: CallToolRequestParam) { + async fn handle_call_tool(&self, id: RequestId, params: CallToolRequestParams) { tracing::info!("tools/call -> params: {:?}", params); - let CallToolRequestParam { name, arguments } = params; + let CallToolRequestParams { + name, arguments, .. + } = params; match name.as_ref() { "codex" => self.handle_tool_call_codex(id, arguments).await, @@ -496,14 +514,27 @@ impl MessageProcessor { }); } - fn handle_set_level(&self, params: rmcp::model::SetLevelRequestParam) { + fn handle_set_level(&self, params: rmcp::model::SetLevelRequestParams) { tracing::info!("logging/setLevel -> params: {:?}", params); } - fn handle_complete(&self, params: rmcp::model::CompleteRequestParam) { + fn handle_complete(&self, params: rmcp::model::CompleteRequestParams) { tracing::info!("completion/complete -> params: {:?}", params); } + async fn handle_unsupported_request(&self, id: RequestId, method: &str) { + self.outgoing + .send_error( + id, + ErrorData::new( + ErrorCode::METHOD_NOT_FOUND, + format!("method not found: {method}"), + Some(json!({ "method": method })), + ), + ) + .await; + } + // --------------------------------------------------------------------- // Notification handlers // --------------------------------------------------------------------- diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index c01912028..67ce598af 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -13,13 +13,13 @@ use anyhow::Context; use codex_mcp_server::CodexToolCallParam; use pretty_assertions::assert_eq; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; 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::InitializeRequestParams; use rmcp::model::JsonRpcMessage; use rmcp::model::JsonRpcNotification; use rmcp::model::JsonRpcRequest; @@ -112,7 +112,8 @@ impl McpProcess { pub async fn initialize(&mut self) -> anyhow::Result<()> { let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed); - let params = InitializeRequestParam { + let params = InitializeRequestParams { + meta: None, capabilities: ClientCapabilities { elicitation: Some(ElicitationCapability { schema_validation: None, @@ -120,6 +121,7 @@ impl McpProcess { experimental: None, roots: None, sampling: None, + tasks: None, }, client_info: Implementation { name: "elicitation test".into(), @@ -194,12 +196,14 @@ impl McpProcess { &mut self, params: CodexToolCallParam, ) -> anyhow::Result { - let codex_tool_call_params = CallToolRequestParam { + let codex_tool_call_params = CallToolRequestParams { + meta: None, name: "codex".into(), arguments: Some(match serde_json::to_value(params)? { serde_json::Value::Object(map) => map, _ => unreachable!("params serialize to object"), }), + task: None, }; self.send_request( "tools/call", diff --git a/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs b/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs index e609a657b..81898b0bc 100644 --- a/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs +++ b/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs @@ -5,11 +5,11 @@ use std::sync::Arc; use rmcp::ErrorData as McpError; use rmcp::ServiceExt; use rmcp::handler::server::ServerHandler; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; -use rmcp::model::PaginatedRequestParam; +use rmcp::model::PaginatedRequestParams; use rmcp::model::ServerCapabilities; use rmcp::model::ServerInfo; use rmcp::model::Tool; @@ -73,7 +73,7 @@ impl ServerHandler for TestToolServer { fn list_tools( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { let tools = self.tools.clone(); @@ -88,7 +88,7 @@ impl ServerHandler for TestToolServer { async fn call_tool( &self, - request: CallToolRequestParam, + request: CallToolRequestParams, _context: rmcp::service::RequestContext, ) -> Result { match request.name.as_ref() { diff --git a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs index 3719766ac..d7708bf5e 100644 --- a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs +++ b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs @@ -5,16 +5,16 @@ use std::sync::Arc; use rmcp::ErrorData as McpError; use rmcp::ServiceExt; use rmcp::handler::server::ServerHandler; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::JsonObject; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ListResourcesResult; use rmcp::model::ListToolsResult; -use rmcp::model::PaginatedRequestParam; +use rmcp::model::PaginatedRequestParams; use rmcp::model::RawResource; use rmcp::model::RawResourceTemplate; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::Resource; use rmcp::model::ResourceContents; @@ -171,6 +171,7 @@ impl TestToolServer { "Template for memo://codex/{slug} resources used in tests.".to_string(), ), mime_type: Some("text/plain".to_string()), + icons: None, }; ResourceTemplate::new(raw, None) } @@ -227,7 +228,7 @@ impl ServerHandler for TestToolServer { fn list_tools( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { let tools = self.tools.clone(); @@ -242,7 +243,7 @@ impl ServerHandler for TestToolServer { fn list_resources( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { let resources = self.resources.clone(); @@ -257,7 +258,7 @@ impl ServerHandler for TestToolServer { async fn list_resource_templates( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> Result { Ok(ListResourceTemplatesResult { @@ -269,7 +270,7 @@ impl ServerHandler for TestToolServer { async fn read_resource( &self, - ReadResourceRequestParam { uri }: ReadResourceRequestParam, + ReadResourceRequestParams { uri, .. }: ReadResourceRequestParams, _context: rmcp::service::RequestContext, ) -> Result { if uri == MEMO_URI { @@ -291,7 +292,7 @@ impl ServerHandler for TestToolServer { async fn call_tool( &self, - request: CallToolRequestParam, + request: CallToolRequestParams, _context: rmcp::service::RequestContext, ) -> Result { match request.name.as_ref() { @@ -357,7 +358,7 @@ impl ServerHandler for TestToolServer { impl TestToolServer { fn parse_call_args Deserialize<'de>>( - request: &CallToolRequestParam, + request: &CallToolRequestParams, tool_name: &'static str, ) -> Result { match request.arguments.as_ref() { diff --git a/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs b/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs index b1247968e..821850d2a 100644 --- a/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs +++ b/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs @@ -17,16 +17,16 @@ use axum::response::Response; use axum::routing::get; use rmcp::ErrorData as McpError; use rmcp::handler::server::ServerHandler; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::JsonObject; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ListResourcesResult; use rmcp::model::ListToolsResult; -use rmcp::model::PaginatedRequestParam; +use rmcp::model::PaginatedRequestParams; use rmcp::model::RawResource; use rmcp::model::RawResourceTemplate; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::Resource; use rmcp::model::ResourceContents; @@ -106,6 +106,7 @@ impl TestToolServer { "Template for memo://codex/{slug} resources used in tests.".to_string(), ), mime_type: Some("text/plain".to_string()), + icons: None, }; ResourceTemplate::new(raw, None) } @@ -136,7 +137,7 @@ impl ServerHandler for TestToolServer { fn list_tools( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { let tools = self.tools.clone(); @@ -151,7 +152,7 @@ impl ServerHandler for TestToolServer { fn list_resources( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> impl std::future::Future> + Send + '_ { let resources = self.resources.clone(); @@ -166,7 +167,7 @@ impl ServerHandler for TestToolServer { async fn list_resource_templates( &self, - _request: Option, + _request: Option, _context: rmcp::service::RequestContext, ) -> Result { Ok(ListResourceTemplatesResult { @@ -178,7 +179,7 @@ impl ServerHandler for TestToolServer { async fn read_resource( &self, - ReadResourceRequestParam { uri }: ReadResourceRequestParam, + ReadResourceRequestParams { uri, .. }: ReadResourceRequestParams, _context: rmcp::service::RequestContext, ) -> Result { if uri == MEMO_URI { @@ -200,7 +201,7 @@ impl ServerHandler for TestToolServer { async fn call_tool( &self, - request: CallToolRequestParam, + request: CallToolRequestParams, _context: rmcp::service::RequestContext, ) -> Result { match request.name.as_ref() { diff --git a/codex-rs/rmcp-client/src/logging_client_handler.rs b/codex-rs/rmcp-client/src/logging_client_handler.rs index 8db730df0..45a1031c7 100644 --- a/codex-rs/rmcp-client/src/logging_client_handler.rs +++ b/codex-rs/rmcp-client/src/logging_client_handler.rs @@ -4,7 +4,7 @@ use rmcp::ClientHandler; use rmcp::RoleClient; use rmcp::model::CancelledNotificationParam; use rmcp::model::ClientInfo; -use rmcp::model::CreateElicitationRequestParam; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::CreateElicitationResult; use rmcp::model::LoggingLevel; use rmcp::model::LoggingMessageNotificationParam; @@ -37,7 +37,7 @@ impl LoggingClientHandler { impl ClientHandler for LoggingClientHandler { async fn create_elicitation( &self, - request: CreateElicitationRequestParam, + request: CreateElicitationRequestParams, context: RequestContext, ) -> Result { (self.send_elicitation)(context.id, request) diff --git a/codex-rs/rmcp-client/src/rmcp_client.rs b/codex-rs/rmcp-client/src/rmcp_client.rs index a2dc42ad3..0deccf391 100644 --- a/codex-rs/rmcp-client/src/rmcp_client.rs +++ b/codex-rs/rmcp-client/src/rmcp_client.rs @@ -10,23 +10,24 @@ use anyhow::Result; use anyhow::anyhow; use futures::FutureExt; use futures::future::BoxFuture; +use oauth2::TokenResponse; use reqwest::header::HeaderMap; -use rmcp::model::CallToolRequestParam; +use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::ClientNotification; use rmcp::model::ClientRequest; -use rmcp::model::CreateElicitationRequestParam; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::CreateElicitationResult; use rmcp::model::CustomNotification; use rmcp::model::CustomRequest; use rmcp::model::Extensions; -use rmcp::model::InitializeRequestParam; +use rmcp::model::InitializeRequestParams; 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::PaginatedRequestParams; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ReadResourceResult; use rmcp::model::RequestId; use rmcp::model::ServerResult; @@ -36,6 +37,7 @@ use rmcp::service::RunningService; use rmcp::service::{self}; use rmcp::transport::StreamableHttpClientTransport; use rmcp::transport::auth::AuthClient; +use rmcp::transport::auth::AuthError; use rmcp::transport::auth::OAuthState; use rmcp::transport::child_process::TokioChildProcess; use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig; @@ -143,7 +145,7 @@ impl Drop for ProcessGroupGuard { } } -pub type Elicitation = CreateElicitationRequestParam; +pub type Elicitation = CreateElicitationRequestParams; pub type ElicitationResponse = CreateElicitationResult; /// Interface for sending elicitation requests to the UI and awaiting a response. @@ -254,17 +256,44 @@ impl RmcpClient { }; let transport = if let Some(initial_tokens) = initial_oauth_tokens.clone() { - let (transport, oauth_persistor) = create_oauth_transport_and_runtime( + match create_oauth_transport_and_runtime( server_name, url, - initial_tokens, + initial_tokens.clone(), store_mode, default_headers.clone(), ) - .await?; - PendingTransport::StreamableHttpWithOAuth { - transport, - oauth_persistor, + .await + { + Ok((transport, oauth_persistor)) => PendingTransport::StreamableHttpWithOAuth { + transport, + oauth_persistor, + }, + Err(err) + if err.downcast_ref::().is_some_and(|auth_err| { + matches!(auth_err, AuthError::NoAuthorizationSupport) + }) => + { + let access_token = initial_tokens + .token_response + .0 + .access_token() + .secret() + .to_string(); + warn!( + "OAuth metadata discovery is unavailable for MCP server `{server_name}`; falling back to stored bearer token authentication" + ); + let http_config = + StreamableHttpClientTransportConfig::with_uri(url.to_string()) + .auth_header(access_token); + let http_client = + apply_default_headers(reqwest::Client::builder(), &default_headers) + .build()?; + let transport = + StreamableHttpClientTransport::with_client(http_client, http_config); + PendingTransport::StreamableHttp { transport } + } + Err(err) => return Err(err), } } else { let mut http_config = StreamableHttpClientTransportConfig::with_uri(url.to_string()); @@ -289,7 +318,7 @@ impl RmcpClient { /// https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization pub async fn initialize( &self, - params: InitializeRequestParam, + params: InitializeRequestParams, timeout: Option, send_elicitation: SendElicitation, ) -> Result { @@ -362,7 +391,7 @@ impl RmcpClient { pub async fn list_tools( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; @@ -375,7 +404,7 @@ impl RmcpClient { pub async fn list_tools_with_connector_ids( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; @@ -415,7 +444,7 @@ impl RmcpClient { pub async fn list_resources( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; @@ -429,7 +458,7 @@ impl RmcpClient { pub async fn list_resource_templates( &self, - params: Option, + params: Option, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; @@ -443,7 +472,7 @@ impl RmcpClient { pub async fn read_resource( &self, - params: ReadResourceRequestParam, + params: ReadResourceRequestParams, timeout: Option, ) -> Result { self.refresh_oauth_if_needed().await; @@ -471,9 +500,11 @@ impl RmcpClient { } None => None, }; - let rmcp_params = CallToolRequestParam { + let rmcp_params = CallToolRequestParams { + meta: None, name: name.into(), arguments, + task: None, }; let fut = service.call_tool(rmcp_params); let result = run_with_timeout(fut, timeout, "tools/call").await?; diff --git a/codex-rs/rmcp-client/tests/resources.rs b/codex-rs/rmcp-client/tests/resources.rs index 9bfd77c18..efab352ff 100644 --- a/codex-rs/rmcp-client/tests/resources.rs +++ b/codex-rs/rmcp-client/tests/resources.rs @@ -11,10 +11,10 @@ use rmcp::model::AnnotateAble; use rmcp::model::ClientCapabilities; use rmcp::model::ElicitationCapability; use rmcp::model::Implementation; -use rmcp::model::InitializeRequestParam; +use rmcp::model::InitializeRequestParams; use rmcp::model::ListResourceTemplatesResult; use rmcp::model::ProtocolVersion; -use rmcp::model::ReadResourceRequestParam; +use rmcp::model::ReadResourceRequestParams; use rmcp::model::ResourceContents; use serde_json::json; @@ -24,8 +24,9 @@ fn stdio_server_bin() -> Result { codex_utils_cargo_bin::cargo_bin("test_stdio_server") } -fn init_params() -> InitializeRequestParam { - InitializeRequestParam { +fn init_params() -> InitializeRequestParams { + InitializeRequestParams { + meta: None, capabilities: ClientCapabilities { experimental: None, roots: None, @@ -33,6 +34,7 @@ fn init_params() -> InitializeRequestParam { elicitation: Some(ElicitationCapability { schema_validation: None, }), + tasks: None, }, client_info: Implementation { name: "codex-test".into(), @@ -111,6 +113,7 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { "Template for memo://codex/{slug} resources used in tests.".to_string(), ), mime_type: Some("text/plain".to_string()), + icons: None, } .no_annotation() ], @@ -119,7 +122,8 @@ async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()> { let read = client .read_resource( - ReadResourceRequestParam { + ReadResourceRequestParams { + meta: None, uri: RESOURCE_URI.to_string(), }, Some(Duration::from_secs(5)),