15 KiB
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 --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<serde_json::Value>,
+ ) {
+ 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<String> {
+ let mut sid_old: Option<String> = None;
+ let mut sid_new: Option<String> = 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
@@ -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
@@ -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 ofsnake_caseorcamelCase, whatever we decide. While Rust preferssnake_case, MCP preferscamelCase, 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
@@ -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/eventas 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
@@ -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<serde_json::Value>,
Though we also plan to change the
paramsin 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
@@ -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<serde_json::Value>,
+ ) {
+ 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
@@ -270,27 +270,49 @@ impl McpProcess {
pub async fn read_stream_until_configured_response_message(
&mut self,
) -> anyhow::Result<String> {
+ let mut sid_old: Option<String> = None;
+ let mut sid_new: Option<String> = 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
@@ -270,27 +270,49 @@ impl McpProcess {
pub async fn read_stream_until_configured_response_message(
&mut self,
) -> anyhow::Result<String> {
+ let mut sid_old: Option<String> = None;
+ let mut sid_new: Option<String> = 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
typefield instead of themethodfield.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
renameline matches the variant name.