# PR #1684: Changing method in MCP notifications - URL: https://github.com/openai/codex/pull/1684 - Author: aibrahim-oai - Created: 2025-07-26 00:11:20 UTC - Updated: 2025-07-27 03:43:38 UTC - Changes: +50/-12, Files changed: 3, Commits: 12 ## Description - Changing the codex/event type ## Full Diff ```diff diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index cf6e8b5191..1a6313db92 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -278,8 +278,9 @@ pub struct Event { } /// Response event from the agent -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Display)] #[serde(tag = "type", rename_all = "snake_case")] +#[strum(serialize_all = "lowercase")] pub enum EventMsg { /// Error while executing a submission Error(ErrorEvent), diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index a1eea65f25..e4af1f78cd 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -83,11 +83,26 @@ impl OutgoingMessageSender { let params = Some(serde_json::to_value(event).expect("Event must serialize")); let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { method: "codex/event".to_string(), + params: params.clone(), + }); + let _ = self.sender.send(outgoing_message).await; + + self.send_event_as_notification_new_schema(event, params) + .await; + } + // should be backwards compatible. + // it will replace send_event_as_notification eventually. + async fn send_event_as_notification_new_schema( + &self, + event: &Event, + params: Option, + ) { + let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { + method: event.msg.to_string(), params, }); let _ = self.sender.send(outgoing_message).await; } - pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) { let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error }); let _ = self.sender.send(outgoing_message).await; diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index b27a96eb89..528a40152f 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -270,27 +270,49 @@ impl McpProcess { pub async fn read_stream_until_configured_response_message( &mut self, ) -> anyhow::Result { + let mut sid_old: Option = None; + let mut sid_new: Option = None; loop { let message = self.read_jsonrpc_message().await?; eprint!("message: {message:?}"); match message { JSONRPCMessage::Notification(notification) => { - if notification.method == "codex/event" { - if let Some(params) = notification.params { + if let Some(params) = notification.params { + // Back-compat schema: method == "codex/event" and msg.type == "session_configured" + if notification.method == "codex/event" { if let Some(msg) = params.get("msg") { - if let Some(msg_type) = msg.get("type") { - if msg_type == "session_configured" { - if let Some(session_id) = msg.get("session_id") { - return Ok(session_id - .to_string() - .trim_matches('"') - .to_string()); - } + if msg.get("type").and_then(|v| v.as_str()) + == Some("session_configured") + { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_old = Some(session_id.to_string()); } } } } + // New schema: method is the Display of EventMsg::SessionConfigured => "SessionConfigured" + if notification.method == "sessionconfigured" { + if let Some(msg) = params.get("msg") { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_new = Some(session_id.to_string()); + } + } + } + } + + if sid_old.is_some() && sid_new.is_some() { + // Both seen, they must match + assert_eq!( + sid_old.as_ref().unwrap(), + sid_new.as_ref().unwrap(), + "session_id mismatch between old and new schema" + ); + return Ok(sid_old.unwrap()); } } JSONRPCMessage::Request(_) => { ``` ## Review Comments ### codex-rs/core/src/protocol.rs - Created: 2025-07-26 03:09:43 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2232408024 ```diff @@ -338,6 +338,34 @@ pub enum EventMsg { ShutdownComplete, } +impl fmt::Display for EventMsg { ``` > Please use strum macros instead and use lowercase? - Created: 2025-07-27 03:03:59 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233616677 ```diff @@ -278,8 +278,9 @@ pub struct Event { } /// Response event from the agent -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Display)] #[serde(tag = "type", rename_all = "snake_case")] +#[strum(serialize_all = "lowercase")] ``` > I misspoke, I think this should not be plain `lowercase`, but likely one of `snake_case` or `camelCase`, whatever we decide. While Rust prefers `snake_case`, MCP prefers `camelCase`, so perhaps we should adopt that here since the primary serialization use case is MCP? ### codex-rs/mcp-server/src/outgoing_message.rs - Created: 2025-07-27 03:04:47 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233616831 ```diff @@ -83,11 +83,26 @@ impl OutgoingMessageSender { let params = Some(serde_json::to_value(event).expect("Event must serialize")); let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { method: "codex/event".to_string(), + params: params.clone(), + }); + let _ = self.sender.send(outgoing_message).await; + + self.send_event_as_notification_new_schema(event, params) + .await; + } + // should be backwards compatible. ``` > I believe this is a net-new set of notifications. The old ones all have `codex/event` as the method whereas the new ones use the specific notification type. It should be fine to disambiguate. - Created: 2025-07-27 03:05:20 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233617072 ```diff @@ -83,11 +83,26 @@ impl OutgoingMessageSender { let params = Some(serde_json::to_value(event).expect("Event must serialize")); let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { method: "codex/event".to_string(), + params: params.clone(), + }); + let _ = self.sender.send(outgoing_message).await; + + self.send_event_as_notification_new_schema(event, params) + .await; + } + // should be backwards compatible. + // it will replace send_event_as_notification eventually. + async fn send_event_as_notification_new_schema( + &self, + event: &Event, + params: Option, ``` > Though we also plan to change the `params` in the new schema, but that is not obvious from this general datatype... - Created: 2025-07-27 03:05:31 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233617124 ```diff @@ -83,11 +83,26 @@ impl OutgoingMessageSender { let params = Some(serde_json::to_value(event).expect("Event must serialize")); let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { method: "codex/event".to_string(), + params: params.clone(), + }); + let _ = self.sender.send(outgoing_message).await; + + self.send_event_as_notification_new_schema(event, params) + .await; + } + // should be backwards compatible. + // it will replace send_event_as_notification eventually. + async fn send_event_as_notification_new_schema( + &self, + event: &Event, + params: Option, + ) { + let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { + method: event.msg.to_string(), params, }); let _ = self.sender.send(outgoing_message).await; } - ``` > restore? ### codex-rs/mcp-server/tests/common/mcp_process.rs - Created: 2025-07-27 01:36:44 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233580571 ```diff @@ -270,27 +270,49 @@ impl McpProcess { pub async fn read_stream_until_configured_response_message( &mut self, ) -> anyhow::Result { + let mut sid_old: Option = None; + let mut sid_new: Option = None; loop { let message = self.read_jsonrpc_message().await?; eprint!("message: {message:?}"); match message { JSONRPCMessage::Notification(notification) => { - if notification.method == "codex/event" { - if let Some(params) = notification.params { + if let Some(params) = notification.params { + // Back-compat schema: method == "codex/event" and msg.type == "session_configured" + if notification.method == "codex/event" { if let Some(msg) = params.get("msg") { - if let Some(msg_type) = msg.get("type") { - if msg_type == "session_configured" { - if let Some(session_id) = msg.get("session_id") { - return Ok(session_id - .to_string() - .trim_matches('"') - .to_string()); - } + if msg.get("type").and_then(|v| v.as_str()) + == Some("session_configured") + { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_old = Some(session_id.to_string()); } } } } + // New schema: method is the Display of EventMsg::SessionConfigured => "SessionConfigured" + if notification.method == "sessionconfigured" { ``` > It is normal to do this if you use serde or strum macros or something. You can also use these to override the name for individual variants. - Created: 2025-07-27 03:08:24 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233618044 ```diff @@ -270,27 +270,49 @@ impl McpProcess { pub async fn read_stream_until_configured_response_message( &mut self, ) -> anyhow::Result { + let mut sid_old: Option = None; + let mut sid_new: Option = None; loop { let message = self.read_jsonrpc_message().await?; eprint!("message: {message:?}"); match message { JSONRPCMessage::Notification(notification) => { - if notification.method == "codex/event" { - if let Some(params) = notification.params { + if let Some(params) = notification.params { + // Back-compat schema: method == "codex/event" and msg.type == "session_configured" + if notification.method == "codex/event" { if let Some(msg) = params.get("msg") { - if let Some(msg_type) = msg.get("type") { - if msg_type == "session_configured" { - if let Some(session_id) = msg.get("session_id") { - return Ok(session_id - .to_string() - .trim_matches('"') - .to_string()); - } + if msg.get("type").and_then(|v| v.as_str()) + == Some("session_configured") + { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_old = Some(session_id.to_string()); } } } } + // New schema: method is the Display of EventMsg::SessionConfigured => "SessionConfigured" + if notification.method == "sessionconfigured" { ``` > Note we were also doing this before: it's just that it was on our custom `type` field instead of the `method` field. > > In practice, if you have good integration tests, it should catch the fact if someone renames the variant without thinking about the serialization implications. It's that or you end up with a lot of copy paste where ever `rename` line matches the variant name.