From 423ac7c7e936fc3bfadd52ba9e4371d281c22692 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 2 May 2025 15:05:18 -0700 Subject: [PATCH] fix: ensure jsonrpc field is serialized as "2.0" --- codex-rs/mcp-types/generate_mcp_types.py | 45 +++++++++--- codex-rs/mcp-types/src/lib.rs | 90 +++++++++++++++++++++++- codex-rs/mcp-types/tests/initialize.rs | 2 + 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/codex-rs/mcp-types/generate_mcp_types.py b/codex-rs/mcp-types/generate_mcp_types.py index f613aa74eb..e604ee9a2c 100755 --- a/codex-rs/mcp-types/generate_mcp_types.py +++ b/codex-rs/mcp-types/generate_mcp_types.py @@ -13,6 +13,8 @@ from pathlib import Path # Helper first so it is defined when other functions call it. from typing import Any, Literal +SCHEMA_VERSION = "2025-03-26" +JSONRPC_VERSION = "2.0" STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n" @@ -30,7 +32,7 @@ def main() -> int: num_args = len(sys.argv) if num_args == 1: schema_file = ( - Path(__file__).resolve().parent / "schema" / "2025-03-26" / "schema.json" + Path(__file__).resolve().parent / "schema" / SCHEMA_VERSION / "schema.json" ) elif num_args == 2: schema_file = Path(sys.argv[1]) @@ -48,7 +50,7 @@ def main() -> int: DEFINITIONS = schema_json["definitions"] out = [ - """ + f""" // @generated // DO NOT EDIT THIS FILE DIRECTLY. // Run the following in the crate root to regenerate this file: @@ -61,18 +63,23 @@ use serde::Serialize; use serde::de::DeserializeOwned; use std::convert::TryFrom; +pub const MCP_SCHEMA_VERSION: &str = "{SCHEMA_VERSION}"; +pub const JSONRPC_VERSION: &str = "{JSONRPC_VERSION}"; + /// Paired request/response types for the Model Context Protocol (MCP). -pub trait ModelContextProtocolRequest { +pub trait ModelContextProtocolRequest {{ const METHOD: &'static str; type Params: DeserializeOwned + Serialize + Send + Sync + 'static; type Result: DeserializeOwned + Serialize + Send + Sync + 'static; -} +}} /// One-way message in the Model Context Protocol (MCP). -pub trait ModelContextProtocolNotification { +pub trait ModelContextProtocolNotification {{ const METHOD: &'static str; type Params: DeserializeOwned + Serialize + Send + Sync + 'static; -} +}} + +fn default_jsonrpc() -> String {{ JSONRPC_VERSION.to_owned() }} """ ] @@ -174,6 +181,10 @@ pub trait ModelContextProtocolNotification { def add_definition(name: str, definition: dict[str, Any], out: list[str]) -> None: + if name == "Result": + out.append("pub type Result = serde_json::Value;\n\n") + return + # Capture description description = definition.get("description") @@ -181,6 +192,14 @@ def add_definition(name: str, definition: dict[str, Any], out: list[str]) -> Non if properties: required_props = set(definition.get("required", [])) out.extend(define_struct(name, properties, required_props, description)) + + # Special carve-out for Result types: + if name.endswith("Result"): + out.extend(f"impl From<{name}> for serde_json::Value {{\n") + out.append(f" fn from(value: {name}) -> Self {{\n") + out.append(" serde_json::to_value(value).unwrap()\n") + out.append(" }\n") + out.append("}\n\n") return enum_values = definition.get("enum", []) @@ -245,10 +264,6 @@ class StructField: serde: str | None = None def append(self, out: list[str], supports_const: bool) -> None: - # Omit these for now. - if self.name == "jsonrpc": - return - if self.serde: out.append(f" {self.serde}\n") if self.viz == "const": @@ -273,6 +288,16 @@ def define_struct( if prop_name == "_meta": # TODO? continue + elif prop_name == "jsonrpc": + fields.append( + StructField( + "pub", + "jsonrpc", + "String", # cannot use `&'static str` because of Deserialize + '#[serde(rename = "jsonrpc", default = "default_jsonrpc")]', + ) + ) + continue prop_type = map_type(prop, prop_name, name) if prop_name not in required_props: diff --git a/codex-rs/mcp-types/src/lib.rs b/codex-rs/mcp-types/src/lib.rs index 4ae0fa09cf..6aa3a93246 100644 --- a/codex-rs/mcp-types/src/lib.rs +++ b/codex-rs/mcp-types/src/lib.rs @@ -10,6 +10,9 @@ use serde::Deserialize; use serde::Serialize; use std::convert::TryFrom; +pub const MCP_SCHEMA_VERSION: &str = "2025-03-26"; +pub const JSONRPC_VERSION: &str = "2.0"; + /// Paired request/response types for the Model Context Protocol (MCP). pub trait ModelContextProtocolRequest { const METHOD: &'static str; @@ -23,6 +26,10 @@ pub trait ModelContextProtocolNotification { type Params: DeserializeOwned + Serialize + Send + Sync + 'static; } +fn default_jsonrpc() -> String { + JSONRPC_VERSION.to_owned() +} + /// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Annotations { @@ -88,6 +95,12 @@ pub enum CallToolResultContent { EmbeddedResource(EmbeddedResource), } +impl From for serde_json::Value { + fn from(value: CallToolResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum CancelledNotification {} @@ -208,6 +221,12 @@ pub struct CompleteResultCompletion { pub values: Vec, } +impl From for serde_json::Value { + fn from(value: CompleteResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum CreateMessageRequest {} @@ -251,6 +270,12 @@ pub enum CreateMessageResultContent { AudioContent(AudioContent), } +impl From for serde_json::Value { + fn from(value: CreateMessageResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Cursor(String); @@ -295,6 +320,12 @@ pub struct GetPromptResult { pub messages: Vec, } +impl From for serde_json::Value { + fn from(value: GetPromptResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + /// An image provided to or from an LLM. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct ImageContent { @@ -341,6 +372,12 @@ pub struct InitializeResult { pub server_info: Implementation, } +impl From for serde_json::Value { + fn from(value: InitializeResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum InitializedNotification {} @@ -370,6 +407,8 @@ pub type JSONRPCBatchResponse = Vec; pub struct JSONRPCError { pub error: JSONRPCErrorError, pub id: RequestId, + #[serde(rename = "jsonrpc", default = "default_jsonrpc")] + pub jsonrpc: String, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -394,6 +433,8 @@ pub enum JSONRPCMessage { /// A notification which does not expect a response. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct JSONRPCNotification { + #[serde(rename = "jsonrpc", default = "default_jsonrpc")] + pub jsonrpc: String, pub method: String, pub params: Option, } @@ -402,6 +443,8 @@ pub struct JSONRPCNotification { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct JSONRPCRequest { pub id: RequestId, + #[serde(rename = "jsonrpc", default = "default_jsonrpc")] + pub jsonrpc: String, pub method: String, pub params: Option, } @@ -410,6 +453,8 @@ pub struct JSONRPCRequest { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct JSONRPCResponse { pub id: RequestId, + #[serde(rename = "jsonrpc", default = "default_jsonrpc")] + pub jsonrpc: String, pub result: Result, } @@ -435,6 +480,12 @@ pub struct ListPromptsResult { pub prompts: Vec, } +impl From for serde_json::Value { + fn from(value: ListPromptsResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ListResourceTemplatesRequest {} @@ -458,6 +509,12 @@ pub struct ListResourceTemplatesResult { pub resource_templates: Vec, } +impl From for serde_json::Value { + fn from(value: ListResourceTemplatesResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ListResourcesRequest {} @@ -480,6 +537,12 @@ pub struct ListResourcesResult { pub resources: Vec, } +impl From for serde_json::Value { + fn from(value: ListResourcesResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ListRootsRequest {} @@ -497,6 +560,12 @@ pub struct ListRootsResult { pub roots: Vec, } +impl From for serde_json::Value { + fn from(value: ListRootsResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ListToolsRequest {} @@ -519,6 +588,12 @@ pub struct ListToolsResult { pub tools: Vec, } +impl From for serde_json::Value { + fn from(value: ListToolsResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + /// The severity of a log message. /// /// These map to syslog message severities, as specified in RFC-5424: @@ -612,6 +687,12 @@ pub struct PaginatedResult { pub next_cursor: Option, } +impl From for serde_json::Value { + fn from(value: PaginatedResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum PingRequest {} @@ -720,6 +801,12 @@ pub enum ReadResourceResultContents { BlobResourceContents(BlobResourceContents), } +impl From for serde_json::Value { + fn from(value: ReadResourceResult) -> Self { + serde_json::to_value(value).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Request { pub method: String, @@ -793,8 +880,7 @@ pub struct ResourceUpdatedNotificationParams { pub uri: String, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Result {} +pub type Result = serde_json::Value; /// The sender or recipient of messages and data in a conversation. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] diff --git a/codex-rs/mcp-types/tests/initialize.rs b/codex-rs/mcp-types/tests/initialize.rs index e857f8db3e..12e7f0f936 100644 --- a/codex-rs/mcp-types/tests/initialize.rs +++ b/codex-rs/mcp-types/tests/initialize.rs @@ -5,6 +5,7 @@ use mcp_types::InitializeRequestParams; use mcp_types::JSONRPCMessage; use mcp_types::JSONRPCRequest; use mcp_types::RequestId; +use mcp_types::JSONRPC_VERSION; use serde_json::json; #[test] @@ -30,6 +31,7 @@ fn deserialize_initialize_request() { }; let expected_req = JSONRPCRequest { + jsonrpc: JSONRPC_VERSION.into(), id: RequestId::Integer(1), method: "initialize".into(), params: Some(json!({