fix: mcp-types serialization wasn't quite working (#791)

While creating a basic MCP server in
https://github.com/openai/codex/pull/792, I discovered a number of bugs
with the initial `mcp-types` crate that I needed to fix in order to
implement the server.

For example, I discovered that when serializing a message, `"jsonrpc":
"2.0"` was not being included.

I changed the codegen so that the field is added as:

```rust
    #[serde(rename = "jsonrpc", default = "default_jsonrpc")]
    pub jsonrpc: String,
```

This ensures that the field is serialized as `"2.0"`, though the field
still has to be assigned, which is tedious. I may experiment with
`Default` or something else in the future. (I also considered creating a
custom serializer, but I'm not sure it's worth the trouble.)

While here, I also added `MCP_SCHEMA_VERSION` and `JSONRPC_VERSION` as
`pub const`s for the crate.

I also discovered that MCP rejects sending `null` for optional fields,
so I had to add `#[serde(skip_serializing_if = "Option::is_none")]` on
`Option` fields.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/791).
* #792
* __->__ #791
This commit is contained in:
Michael Bolin
2025-05-02 16:38:05 -07:00
committed by GitHub
parent 83961e0299
commit 865e518771
3 changed files with 316 additions and 47 deletions

View File

@@ -13,6 +13,8 @@ from pathlib import Path
# Helper first so it is defined when other functions call it. # Helper first so it is defined when other functions call it.
from typing import Any, Literal from typing import Any, Literal
SCHEMA_VERSION = "2025-03-26"
JSONRPC_VERSION = "2.0"
STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n" STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n"
@@ -30,7 +32,7 @@ def main() -> int:
num_args = len(sys.argv) num_args = len(sys.argv)
if num_args == 1: if num_args == 1:
schema_file = ( 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: elif num_args == 2:
schema_file = Path(sys.argv[1]) schema_file = Path(sys.argv[1])
@@ -48,7 +50,7 @@ def main() -> int:
DEFINITIONS = schema_json["definitions"] DEFINITIONS = schema_json["definitions"]
out = [ out = [
""" f"""
// @generated // @generated
// DO NOT EDIT THIS FILE DIRECTLY. // DO NOT EDIT THIS FILE DIRECTLY.
// Run the following in the crate root to regenerate this file: // Run the following in the crate root to regenerate this file:
@@ -61,18 +63,23 @@ use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::convert::TryFrom; 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). /// Paired request/response types for the Model Context Protocol (MCP).
pub trait ModelContextProtocolRequest { pub trait ModelContextProtocolRequest {{
const METHOD: &'static str; const METHOD: &'static str;
type Params: DeserializeOwned + Serialize + Send + Sync + 'static; type Params: DeserializeOwned + Serialize + Send + Sync + 'static;
type Result: DeserializeOwned + Serialize + Send + Sync + 'static; type Result: DeserializeOwned + Serialize + Send + Sync + 'static;
} }}
/// One-way message in the Model Context Protocol (MCP). /// One-way message in the Model Context Protocol (MCP).
pub trait ModelContextProtocolNotification { pub trait ModelContextProtocolNotification {{
const METHOD: &'static str; const METHOD: &'static str;
type Params: DeserializeOwned + Serialize + Send + Sync + 'static; 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: 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 # Capture description
description = definition.get("description") description = definition.get("description")
@@ -181,6 +192,14 @@ def add_definition(name: str, definition: dict[str, Any], out: list[str]) -> Non
if properties: if properties:
required_props = set(definition.get("required", [])) required_props = set(definition.get("required", []))
out.extend(define_struct(name, properties, required_props, description)) 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 return
enum_values = definition.get("enum", []) enum_values = definition.get("enum", [])
@@ -245,10 +264,6 @@ class StructField:
serde: str | None = None serde: str | None = None
def append(self, out: list[str], supports_const: bool) -> None: def append(self, out: list[str], supports_const: bool) -> None:
# Omit these for now.
if self.name == "jsonrpc":
return
if self.serde: if self.serde:
out.append(f" {self.serde}\n") out.append(f" {self.serde}\n")
if self.viz == "const": if self.viz == "const":
@@ -273,11 +288,22 @@ def define_struct(
if prop_name == "_meta": if prop_name == "_meta":
# TODO? # TODO?
continue 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) prop_type = map_type(prop, prop_name, name)
if prop_name not in required_props: is_optional = prop_name not in required_props
if is_optional:
prop_type = f"Option<{prop_type}>" prop_type = f"Option<{prop_type}>"
rs_prop = rust_prop_name(prop_name) rs_prop = rust_prop_name(prop_name, is_optional)
if prop_type.startswith("&'static str"): if prop_type.startswith("&'static str"):
fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde)) fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde))
else: else:
@@ -565,16 +591,32 @@ class RustProp:
serde: str | None = None serde: str | None = None
def rust_prop_name(name: str) -> RustProp: def rust_prop_name(name: str, is_optional: bool) -> RustProp:
"""Convert a JSON property name to a Rust property name.""" """Convert a JSON property name to a Rust property name."""
prop_name: str
is_rename = False
if name == "type": if name == "type":
return RustProp("r#type", None) prop_name = "r#type"
elif name == "ref": elif name == "ref":
return RustProp("r#ref", None) prop_name = "r#ref"
elif snake_case := to_snake_case(name): elif snake_case := to_snake_case(name):
return RustProp(snake_case, f'#[serde(rename = "{name}")]') prop_name = snake_case
is_rename = True
else: else:
return RustProp(name, None) prop_name = name
serde_annotations = []
if is_rename:
serde_annotations.append(f'rename = "{name}"')
if is_optional:
serde_annotations.append("default")
serde_annotations.append('skip_serializing_if = "Option::is_none"')
if serde_annotations:
serde_str = f'#[serde({", ".join(serde_annotations)})]'
else:
serde_str = None
return RustProp(prop_name, serde_str)
def to_snake_case(name: str) -> str: def to_snake_case(name: str) -> str:

View File

@@ -10,6 +10,9 @@ use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::convert::TryFrom; 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). /// Paired request/response types for the Model Context Protocol (MCP).
pub trait ModelContextProtocolRequest { pub trait ModelContextProtocolRequest {
const METHOD: &'static str; const METHOD: &'static str;
@@ -23,16 +26,23 @@ pub trait ModelContextProtocolNotification {
type Params: DeserializeOwned + Serialize + Send + Sync + 'static; 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 /// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Annotations { pub struct Annotations {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>, pub audience: Option<Vec<Role>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>, pub priority: Option<f64>,
} }
/// Audio provided to or from an LLM. /// Audio provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct AudioContent { pub struct AudioContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
pub data: String, pub data: String,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType")]
@@ -43,7 +53,7 @@ pub struct AudioContent {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct BlobResourceContents { pub struct BlobResourceContents {
pub blob: String, pub blob: String,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
pub uri: String, pub uri: String,
} }
@@ -59,6 +69,7 @@ impl ModelContextProtocolRequest for CallToolRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CallToolRequestParams { pub struct CallToolRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>, pub arguments: Option<serde_json::Value>,
pub name: String, pub name: String,
} }
@@ -76,7 +87,7 @@ pub struct CallToolRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CallToolResult { pub struct CallToolResult {
pub content: Vec<CallToolResultContent>, pub content: Vec<CallToolResultContent>,
#[serde(rename = "isError")] #[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>, pub is_error: Option<bool>,
} }
@@ -88,6 +99,12 @@ pub enum CallToolResultContent {
EmbeddedResource(EmbeddedResource), 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum CancelledNotification {} pub enum CancelledNotification {}
@@ -98,6 +115,7 @@ impl ModelContextProtocolNotification for CancelledNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CancelledNotificationParams { pub struct CancelledNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>, pub reason: Option<String>,
#[serde(rename = "requestId")] #[serde(rename = "requestId")]
pub request_id: RequestId, pub request_id: RequestId,
@@ -106,15 +124,22 @@ pub struct CancelledNotificationParams {
/// Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. /// Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ClientCapabilities { pub struct ClientCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub experimental: Option<serde_json::Value>, pub experimental: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub roots: Option<ClientCapabilitiesRoots>, pub roots: Option<ClientCapabilitiesRoots>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sampling: Option<serde_json::Value>, pub sampling: Option<serde_json::Value>,
} }
/// Present if the client supports listing roots. /// Present if the client supports listing roots.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ClientCapabilitiesRoots { pub struct ClientCapabilitiesRoots {
#[serde(rename = "listChanged")] #[serde(
rename = "listChanged",
default,
skip_serializing_if = "Option::is_none"
)]
pub list_changed: Option<bool>, pub list_changed: Option<bool>,
} }
@@ -202,12 +227,19 @@ pub struct CompleteResult {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CompleteResultCompletion { pub struct CompleteResultCompletion {
#[serde(rename = "hasMore")] #[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>, pub has_more: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total: Option<i64>, pub total: Option<i64>,
pub values: Vec<String>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum CreateMessageRequest {} pub enum CreateMessageRequest {}
@@ -219,18 +251,36 @@ impl ModelContextProtocolRequest for CreateMessageRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CreateMessageRequestParams { pub struct CreateMessageRequestParams {
#[serde(rename = "includeContext")] #[serde(
rename = "includeContext",
default,
skip_serializing_if = "Option::is_none"
)]
pub include_context: Option<String>, pub include_context: Option<String>,
#[serde(rename = "maxTokens")] #[serde(rename = "maxTokens")]
pub max_tokens: i64, pub max_tokens: i64,
pub messages: Vec<SamplingMessage>, pub messages: Vec<SamplingMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>, pub metadata: Option<serde_json::Value>,
#[serde(rename = "modelPreferences")] #[serde(
rename = "modelPreferences",
default,
skip_serializing_if = "Option::is_none"
)]
pub model_preferences: Option<ModelPreferences>, pub model_preferences: Option<ModelPreferences>,
#[serde(rename = "stopSequences")] #[serde(
rename = "stopSequences",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_sequences: Option<Vec<String>>, pub stop_sequences: Option<Vec<String>>,
#[serde(rename = "systemPrompt")] #[serde(
rename = "systemPrompt",
default,
skip_serializing_if = "Option::is_none"
)]
pub system_prompt: Option<String>, pub system_prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>, pub temperature: Option<f64>,
} }
@@ -240,7 +290,11 @@ pub struct CreateMessageResult {
pub content: CreateMessageResultContent, pub content: CreateMessageResultContent,
pub model: String, pub model: String,
pub role: Role, pub role: Role,
#[serde(rename = "stopReason")] #[serde(
rename = "stopReason",
default,
skip_serializing_if = "Option::is_none"
)]
pub stop_reason: Option<String>, pub stop_reason: Option<String>,
} }
@@ -251,6 +305,12 @@ pub enum CreateMessageResultContent {
AudioContent(AudioContent), 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Cursor(String); pub struct Cursor(String);
@@ -260,6 +320,7 @@ pub struct Cursor(String);
/// of the LLM and/or the user. /// of the LLM and/or the user.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct EmbeddedResource { pub struct EmbeddedResource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
pub resource: EmbeddedResourceResource, pub resource: EmbeddedResourceResource,
pub r#type: String, // &'static str = "resource" pub r#type: String, // &'static str = "resource"
@@ -284,6 +345,7 @@ impl ModelContextProtocolRequest for GetPromptRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct GetPromptRequestParams { pub struct GetPromptRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>, pub arguments: Option<serde_json::Value>,
pub name: String, pub name: String,
} }
@@ -291,13 +353,21 @@ pub struct GetPromptRequestParams {
/// The server's response to a prompts/get request from the client. /// The server's response to a prompts/get request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct GetPromptResult { pub struct GetPromptResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
pub messages: Vec<PromptMessage>, 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. /// An image provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ImageContent { pub struct ImageContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
pub data: String, pub data: String,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType")]
@@ -334,6 +404,7 @@ pub struct InitializeRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct InitializeResult { pub struct InitializeResult {
pub capabilities: ServerCapabilities, pub capabilities: ServerCapabilities,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>, pub instructions: Option<String>,
#[serde(rename = "protocolVersion")] #[serde(rename = "protocolVersion")]
pub protocol_version: String, pub protocol_version: String,
@@ -341,6 +412,12 @@ pub struct InitializeResult {
pub server_info: Implementation, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum InitializedNotification {} pub enum InitializedNotification {}
@@ -370,11 +447,14 @@ pub type JSONRPCBatchResponse = Vec<JSONRPCBatchResponseItem>;
pub struct JSONRPCError { pub struct JSONRPCError {
pub error: JSONRPCErrorError, pub error: JSONRPCErrorError,
pub id: RequestId, pub id: RequestId,
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
pub jsonrpc: String,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct JSONRPCErrorError { pub struct JSONRPCErrorError {
pub code: i64, pub code: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>, pub data: Option<serde_json::Value>,
pub message: String, pub message: String,
} }
@@ -394,7 +474,10 @@ pub enum JSONRPCMessage {
/// A notification which does not expect a response. /// A notification which does not expect a response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct JSONRPCNotification { pub struct JSONRPCNotification {
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
pub jsonrpc: String,
pub method: String, pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>, pub params: Option<serde_json::Value>,
} }
@@ -402,7 +485,10 @@ pub struct JSONRPCNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct JSONRPCRequest { pub struct JSONRPCRequest {
pub id: RequestId, pub id: RequestId,
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
pub jsonrpc: String,
pub method: String, pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>, pub params: Option<serde_json::Value>,
} }
@@ -410,6 +496,8 @@ pub struct JSONRPCRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct JSONRPCResponse { pub struct JSONRPCResponse {
pub id: RequestId, pub id: RequestId,
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
pub jsonrpc: String,
pub result: Result, pub result: Result,
} }
@@ -424,17 +512,28 @@ impl ModelContextProtocolRequest for ListPromptsRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListPromptsRequestParams { pub struct ListPromptsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>, pub cursor: Option<String>,
} }
/// The server's response to a prompts/list request from the client. /// The server's response to a prompts/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListPromptsResult { pub struct ListPromptsResult {
#[serde(rename = "nextCursor")] #[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>, pub next_cursor: Option<String>,
pub prompts: Vec<Prompt>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ListResourceTemplatesRequest {} pub enum ListResourceTemplatesRequest {}
@@ -446,18 +545,29 @@ impl ModelContextProtocolRequest for ListResourceTemplatesRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListResourceTemplatesRequestParams { pub struct ListResourceTemplatesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>, pub cursor: Option<String>,
} }
/// The server's response to a resources/templates/list request from the client. /// The server's response to a resources/templates/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListResourceTemplatesResult { pub struct ListResourceTemplatesResult {
#[serde(rename = "nextCursor")] #[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>, pub next_cursor: Option<String>,
#[serde(rename = "resourceTemplates")] #[serde(rename = "resourceTemplates")]
pub resource_templates: Vec<ResourceTemplate>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ListResourcesRequest {} pub enum ListResourcesRequest {}
@@ -469,17 +579,28 @@ impl ModelContextProtocolRequest for ListResourcesRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListResourcesRequestParams { pub struct ListResourcesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>, pub cursor: Option<String>,
} }
/// The server's response to a resources/list request from the client. /// The server's response to a resources/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListResourcesResult { pub struct ListResourcesResult {
#[serde(rename = "nextCursor")] #[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>, pub next_cursor: Option<String>,
pub resources: Vec<Resource>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ListRootsRequest {} pub enum ListRootsRequest {}
@@ -497,6 +618,12 @@ pub struct ListRootsResult {
pub roots: Vec<Root>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ListToolsRequest {} pub enum ListToolsRequest {}
@@ -508,17 +635,28 @@ impl ModelContextProtocolRequest for ListToolsRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListToolsRequestParams { pub struct ListToolsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>, pub cursor: Option<String>,
} }
/// The server's response to a tools/list request from the client. /// The server's response to a tools/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ListToolsResult { pub struct ListToolsResult {
#[serde(rename = "nextCursor")] #[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>, pub next_cursor: Option<String>,
pub tools: Vec<Tool>, 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. /// The severity of a log message.
/// ///
/// These map to syslog message severities, as specified in RFC-5424: /// These map to syslog message severities, as specified in RFC-5424:
@@ -555,6 +693,7 @@ impl ModelContextProtocolNotification for LoggingMessageNotification {
pub struct LoggingMessageNotificationParams { pub struct LoggingMessageNotificationParams {
pub data: serde_json::Value, pub data: serde_json::Value,
pub level: LoggingLevel, pub level: LoggingLevel,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logger: Option<String>, pub logger: Option<String>,
} }
@@ -564,6 +703,7 @@ pub struct LoggingMessageNotificationParams {
/// to the client to interpret. /// to the client to interpret.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ModelHint { pub struct ModelHint {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
} }
@@ -580,38 +720,64 @@ pub struct ModelHint {
/// balance them against other considerations. /// balance them against other considerations.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ModelPreferences { pub struct ModelPreferences {
#[serde(rename = "costPriority")] #[serde(
rename = "costPriority",
default,
skip_serializing_if = "Option::is_none"
)]
pub cost_priority: Option<f64>, pub cost_priority: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>, pub hints: Option<Vec<ModelHint>>,
#[serde(rename = "intelligencePriority")] #[serde(
rename = "intelligencePriority",
default,
skip_serializing_if = "Option::is_none"
)]
pub intelligence_priority: Option<f64>, pub intelligence_priority: Option<f64>,
#[serde(rename = "speedPriority")] #[serde(
rename = "speedPriority",
default,
skip_serializing_if = "Option::is_none"
)]
pub speed_priority: Option<f64>, pub speed_priority: Option<f64>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Notification { pub struct Notification {
pub method: String, pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>, pub params: Option<serde_json::Value>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PaginatedRequest { pub struct PaginatedRequest {
pub method: String, pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<PaginatedRequestParams>, pub params: Option<PaginatedRequestParams>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PaginatedRequestParams { pub struct PaginatedRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>, pub cursor: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PaginatedResult { pub struct PaginatedResult {
#[serde(rename = "nextCursor")] #[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>, 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum PingRequest {} pub enum PingRequest {}
@@ -631,10 +797,12 @@ impl ModelContextProtocolNotification for ProgressNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ProgressNotificationParams { pub struct ProgressNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>, pub message: Option<String>,
pub progress: f64, pub progress: f64,
#[serde(rename = "progressToken")] #[serde(rename = "progressToken")]
pub progress_token: ProgressToken, pub progress_token: ProgressToken,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total: Option<f64>, pub total: Option<f64>,
} }
@@ -648,7 +816,9 @@ pub enum ProgressToken {
/// A prompt or prompt template that the server offers. /// A prompt or prompt template that the server offers.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Prompt { pub struct Prompt {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<PromptArgument>>, pub arguments: Option<Vec<PromptArgument>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
pub name: String, pub name: String,
} }
@@ -656,8 +826,10 @@ pub struct Prompt {
/// Describes an argument that a prompt can accept. /// Describes an argument that a prompt can accept.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PromptArgument { pub struct PromptArgument {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
pub name: String, pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<bool>, pub required: Option<bool>,
} }
@@ -720,9 +892,16 @@ pub enum ReadResourceResultContents {
BlobResourceContents(BlobResourceContents), 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)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Request { pub struct Request {
pub method: String, pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>, pub params: Option<serde_json::Value>,
} }
@@ -736,11 +915,14 @@ pub enum RequestId {
/// A known resource that the server is capable of reading. /// A known resource that the server is capable of reading.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Resource { pub struct Resource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
pub name: String, pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<i64>, pub size: Option<i64>,
pub uri: String, pub uri: String,
} }
@@ -748,7 +930,7 @@ pub struct Resource {
/// The contents of a specific resource or sub-resource. /// The contents of a specific resource or sub-resource.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ResourceContents { pub struct ResourceContents {
#[serde(rename = "mimeType")] #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
pub uri: String, pub uri: String,
} }
@@ -771,9 +953,11 @@ pub struct ResourceReference {
/// A template description for resources available on the server. /// A template description for resources available on the server.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ResourceTemplate { pub struct ResourceTemplate {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
pub name: String, pub name: String,
#[serde(rename = "uriTemplate")] #[serde(rename = "uriTemplate")]
@@ -793,8 +977,7 @@ pub struct ResourceUpdatedNotificationParams {
pub uri: String, pub uri: String,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub type Result = serde_json::Value;
pub struct Result {}
/// The sender or recipient of messages and data in a conversation. /// The sender or recipient of messages and data in a conversation.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@@ -808,6 +991,7 @@ pub enum Role {
/// Represents a root directory or file that the server can operate on. /// Represents a root directory or file that the server can operate on.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Root { pub struct Root {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
pub uri: String, pub uri: String,
} }
@@ -837,33 +1021,52 @@ pub enum SamplingMessageContent {
/// Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. /// Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ServerCapabilities { pub struct ServerCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completions: Option<serde_json::Value>, pub completions: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub experimental: Option<serde_json::Value>, pub experimental: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logging: Option<serde_json::Value>, pub logging: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompts: Option<ServerCapabilitiesPrompts>, pub prompts: Option<ServerCapabilitiesPrompts>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resources: Option<ServerCapabilitiesResources>, pub resources: Option<ServerCapabilitiesResources>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<ServerCapabilitiesTools>, pub tools: Option<ServerCapabilitiesTools>,
} }
/// Present if the server offers any tools to call. /// Present if the server offers any tools to call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ServerCapabilitiesTools { pub struct ServerCapabilitiesTools {
#[serde(rename = "listChanged")] #[serde(
rename = "listChanged",
default,
skip_serializing_if = "Option::is_none"
)]
pub list_changed: Option<bool>, pub list_changed: Option<bool>,
} }
/// Present if the server offers any resources to read. /// Present if the server offers any resources to read.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ServerCapabilitiesResources { pub struct ServerCapabilitiesResources {
#[serde(rename = "listChanged")] #[serde(
rename = "listChanged",
default,
skip_serializing_if = "Option::is_none"
)]
pub list_changed: Option<bool>, pub list_changed: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subscribe: Option<bool>, pub subscribe: Option<bool>,
} }
/// Present if the server offers any prompt templates. /// Present if the server offers any prompt templates.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ServerCapabilitiesPrompts { pub struct ServerCapabilitiesPrompts {
#[serde(rename = "listChanged")] #[serde(
rename = "listChanged",
default,
skip_serializing_if = "Option::is_none"
)]
pub list_changed: Option<bool>, pub list_changed: Option<bool>,
} }
@@ -948,6 +1151,7 @@ pub struct SubscribeRequestParams {
/// Text provided to or from an LLM. /// Text provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct TextContent { pub struct TextContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>, pub annotations: Option<Annotations>,
pub text: String, pub text: String,
pub r#type: String, // &'static str = "text" pub r#type: String, // &'static str = "text"
@@ -955,7 +1159,7 @@ pub struct TextContent {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct TextResourceContents { pub struct TextResourceContents {
#[serde(rename = "mimeType")] #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
pub text: String, pub text: String,
pub uri: String, pub uri: String,
@@ -964,7 +1168,9 @@ pub struct TextResourceContents {
/// Definition for a tool the client can call. /// Definition for a tool the client can call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Tool { pub struct Tool {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>, pub annotations: Option<ToolAnnotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
#[serde(rename = "inputSchema")] #[serde(rename = "inputSchema")]
pub input_schema: ToolInputSchema, pub input_schema: ToolInputSchema,
@@ -974,7 +1180,9 @@ pub struct Tool {
/// A JSON Schema object defining the expected parameters for the tool. /// A JSON Schema object defining the expected parameters for the tool.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ToolInputSchema { pub struct ToolInputSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>, pub properties: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>, pub required: Option<Vec<String>>,
pub r#type: String, // &'static str = "object" pub r#type: String, // &'static str = "object"
} }
@@ -989,14 +1197,31 @@ pub struct ToolInputSchema {
/// received from untrusted servers. /// received from untrusted servers.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ToolAnnotations { pub struct ToolAnnotations {
#[serde(rename = "destructiveHint")] #[serde(
rename = "destructiveHint",
default,
skip_serializing_if = "Option::is_none"
)]
pub destructive_hint: Option<bool>, pub destructive_hint: Option<bool>,
#[serde(rename = "idempotentHint")] #[serde(
rename = "idempotentHint",
default,
skip_serializing_if = "Option::is_none"
)]
pub idempotent_hint: Option<bool>, pub idempotent_hint: Option<bool>,
#[serde(rename = "openWorldHint")] #[serde(
rename = "openWorldHint",
default,
skip_serializing_if = "Option::is_none"
)]
pub open_world_hint: Option<bool>, pub open_world_hint: Option<bool>,
#[serde(rename = "readOnlyHint")] #[serde(
rename = "readOnlyHint",
default,
skip_serializing_if = "Option::is_none"
)]
pub read_only_hint: Option<bool>, pub read_only_hint: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>, pub title: Option<String>,
} }

View File

@@ -5,6 +5,7 @@ use mcp_types::InitializeRequestParams;
use mcp_types::JSONRPCMessage; use mcp_types::JSONRPCMessage;
use mcp_types::JSONRPCRequest; use mcp_types::JSONRPCRequest;
use mcp_types::RequestId; use mcp_types::RequestId;
use mcp_types::JSONRPC_VERSION;
use serde_json::json; use serde_json::json;
#[test] #[test]
@@ -30,6 +31,7 @@ fn deserialize_initialize_request() {
}; };
let expected_req = JSONRPCRequest { let expected_req = JSONRPCRequest {
jsonrpc: JSONRPC_VERSION.into(),
id: RequestId::Integer(1), id: RequestId::Integer(1),
method: "initialize".into(), method: "initialize".into(),
params: Some(json!({ params: Some(json!({