fix: ensure jsonrpc field is serialized as "2.0"

This commit is contained in:
Michael Bolin
2025-05-02 15:05:18 -07:00
parent 83961e0299
commit 423ac7c7e9
3 changed files with 125 additions and 12 deletions

View File

@@ -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:

View File

@@ -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<CallToolResult> 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<String>,
}
impl From<CompleteResult> 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<CreateMessageResult> 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<PromptMessage>,
}
impl From<GetPromptResult> 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<InitializeResult> 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<JSONRPCBatchResponseItem>;
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<serde_json::Value>,
}
@@ -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<serde_json::Value>,
}
@@ -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<Prompt>,
}
impl From<ListPromptsResult> 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<ResourceTemplate>,
}
impl From<ListResourceTemplatesResult> 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<Resource>,
}
impl From<ListResourcesResult> 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<Root>,
}
impl From<ListRootsResult> 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<Tool>,
}
impl From<ListToolsResult> 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<String>,
}
impl From<PaginatedResult> 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<ReadResourceResult> 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)]

View File

@@ -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!({