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:
@@ -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,11 +288,22 @@ 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:
|
||||
is_optional = prop_name not in required_props
|
||||
if is_optional:
|
||||
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"):
|
||||
fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde))
|
||||
else:
|
||||
@@ -565,16 +591,32 @@ class RustProp:
|
||||
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."""
|
||||
prop_name: str
|
||||
is_rename = False
|
||||
if name == "type":
|
||||
return RustProp("r#type", None)
|
||||
prop_name = "r#type"
|
||||
elif name == "ref":
|
||||
return RustProp("r#ref", None)
|
||||
prop_name = "r#ref"
|
||||
elif snake_case := to_snake_case(name):
|
||||
return RustProp(snake_case, f'#[serde(rename = "{name}")]')
|
||||
prop_name = snake_case
|
||||
is_rename = True
|
||||
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:
|
||||
|
||||
@@ -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,16 +26,23 @@ 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 {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub audience: Option<Vec<Role>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<f64>,
|
||||
}
|
||||
|
||||
/// Audio provided to or from an LLM.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct AudioContent {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
pub data: String,
|
||||
#[serde(rename = "mimeType")]
|
||||
@@ -43,7 +53,7 @@ pub struct AudioContent {
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct BlobResourceContents {
|
||||
pub blob: String,
|
||||
#[serde(rename = "mimeType")]
|
||||
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
pub uri: String,
|
||||
}
|
||||
@@ -59,6 +69,7 @@ impl ModelContextProtocolRequest for CallToolRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CallToolRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<serde_json::Value>,
|
||||
pub name: String,
|
||||
}
|
||||
@@ -76,7 +87,7 @@ pub struct CallToolRequestParams {
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CallToolResult {
|
||||
pub content: Vec<CallToolResultContent>,
|
||||
#[serde(rename = "isError")]
|
||||
#[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")]
|
||||
pub is_error: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -88,6 +99,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 {}
|
||||
|
||||
@@ -98,6 +115,7 @@ impl ModelContextProtocolNotification for CancelledNotification {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CancelledNotificationParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
#[serde(rename = "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.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ClientCapabilities {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub experimental: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub roots: Option<ClientCapabilitiesRoots>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sampling: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Present if the client supports listing roots.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ClientCapabilitiesRoots {
|
||||
#[serde(rename = "listChanged")]
|
||||
#[serde(
|
||||
rename = "listChanged",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub list_changed: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -202,12 +227,19 @@ pub struct CompleteResult {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CompleteResultCompletion {
|
||||
#[serde(rename = "hasMore")]
|
||||
#[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
|
||||
pub has_more: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub total: Option<i64>,
|
||||
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 {}
|
||||
|
||||
@@ -219,18 +251,36 @@ impl ModelContextProtocolRequest for CreateMessageRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CreateMessageRequestParams {
|
||||
#[serde(rename = "includeContext")]
|
||||
#[serde(
|
||||
rename = "includeContext",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub include_context: Option<String>,
|
||||
#[serde(rename = "maxTokens")]
|
||||
pub max_tokens: i64,
|
||||
pub messages: Vec<SamplingMessage>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
#[serde(rename = "modelPreferences")]
|
||||
#[serde(
|
||||
rename = "modelPreferences",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub model_preferences: Option<ModelPreferences>,
|
||||
#[serde(rename = "stopSequences")]
|
||||
#[serde(
|
||||
rename = "stopSequences",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub stop_sequences: Option<Vec<String>>,
|
||||
#[serde(rename = "systemPrompt")]
|
||||
#[serde(
|
||||
rename = "systemPrompt",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub system_prompt: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub temperature: Option<f64>,
|
||||
}
|
||||
|
||||
@@ -240,7 +290,11 @@ pub struct CreateMessageResult {
|
||||
pub content: CreateMessageResultContent,
|
||||
pub model: String,
|
||||
pub role: Role,
|
||||
#[serde(rename = "stopReason")]
|
||||
#[serde(
|
||||
rename = "stopReason",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub stop_reason: Option<String>,
|
||||
}
|
||||
|
||||
@@ -251,6 +305,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);
|
||||
|
||||
@@ -260,6 +320,7 @@ pub struct Cursor(String);
|
||||
/// of the LLM and/or the user.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct EmbeddedResource {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
pub resource: EmbeddedResourceResource,
|
||||
pub r#type: String, // &'static str = "resource"
|
||||
@@ -284,6 +345,7 @@ impl ModelContextProtocolRequest for GetPromptRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct GetPromptRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<serde_json::Value>,
|
||||
pub name: String,
|
||||
}
|
||||
@@ -291,13 +353,21 @@ pub struct GetPromptRequestParams {
|
||||
/// The server's response to a prompts/get request from the client.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct GetPromptResult {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
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 {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
pub data: String,
|
||||
#[serde(rename = "mimeType")]
|
||||
@@ -334,6 +404,7 @@ pub struct InitializeRequestParams {
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct InitializeResult {
|
||||
pub capabilities: ServerCapabilities,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub instructions: Option<String>,
|
||||
#[serde(rename = "protocolVersion")]
|
||||
pub protocol_version: String,
|
||||
@@ -341,6 +412,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,11 +447,14 @@ 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)]
|
||||
pub struct JSONRPCErrorError {
|
||||
pub code: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub message: String,
|
||||
}
|
||||
@@ -394,7 +474,10 @@ 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,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -402,7 +485,10 @@ 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,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -410,6 +496,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,
|
||||
}
|
||||
|
||||
@@ -424,17 +512,28 @@ impl ModelContextProtocolRequest for ListPromptsRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListPromptsRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
/// The server's response to a prompts/list request from the client.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListPromptsResult {
|
||||
#[serde(rename = "nextCursor")]
|
||||
#[serde(
|
||||
rename = "nextCursor",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub next_cursor: Option<String>,
|
||||
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 {}
|
||||
|
||||
@@ -446,18 +545,29 @@ impl ModelContextProtocolRequest for ListResourceTemplatesRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListResourceTemplatesRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
/// The server's response to a resources/templates/list request from the client.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListResourceTemplatesResult {
|
||||
#[serde(rename = "nextCursor")]
|
||||
#[serde(
|
||||
rename = "nextCursor",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub next_cursor: Option<String>,
|
||||
#[serde(rename = "resourceTemplates")]
|
||||
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 {}
|
||||
|
||||
@@ -469,17 +579,28 @@ impl ModelContextProtocolRequest for ListResourcesRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListResourcesRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
/// The server's response to a resources/list request from the client.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListResourcesResult {
|
||||
#[serde(rename = "nextCursor")]
|
||||
#[serde(
|
||||
rename = "nextCursor",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub next_cursor: Option<String>,
|
||||
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 +618,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 {}
|
||||
|
||||
@@ -508,17 +635,28 @@ impl ModelContextProtocolRequest for ListToolsRequest {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListToolsRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
/// The server's response to a tools/list request from the client.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ListToolsResult {
|
||||
#[serde(rename = "nextCursor")]
|
||||
#[serde(
|
||||
rename = "nextCursor",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub next_cursor: Option<String>,
|
||||
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:
|
||||
@@ -555,6 +693,7 @@ impl ModelContextProtocolNotification for LoggingMessageNotification {
|
||||
pub struct LoggingMessageNotificationParams {
|
||||
pub data: serde_json::Value,
|
||||
pub level: LoggingLevel,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub logger: Option<String>,
|
||||
}
|
||||
|
||||
@@ -564,6 +703,7 @@ pub struct LoggingMessageNotificationParams {
|
||||
/// to the client to interpret.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModelHint {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
@@ -580,38 +720,64 @@ pub struct ModelHint {
|
||||
/// balance them against other considerations.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModelPreferences {
|
||||
#[serde(rename = "costPriority")]
|
||||
#[serde(
|
||||
rename = "costPriority",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub cost_priority: Option<f64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hints: Option<Vec<ModelHint>>,
|
||||
#[serde(rename = "intelligencePriority")]
|
||||
#[serde(
|
||||
rename = "intelligencePriority",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub intelligence_priority: Option<f64>,
|
||||
#[serde(rename = "speedPriority")]
|
||||
#[serde(
|
||||
rename = "speedPriority",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub speed_priority: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Notification {
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PaginatedRequest {
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<PaginatedRequestParams>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PaginatedRequestParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PaginatedResult {
|
||||
#[serde(rename = "nextCursor")]
|
||||
#[serde(
|
||||
rename = "nextCursor",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
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 {}
|
||||
|
||||
@@ -631,10 +797,12 @@ impl ModelContextProtocolNotification for ProgressNotification {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ProgressNotificationParams {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
pub progress: f64,
|
||||
#[serde(rename = "progressToken")]
|
||||
pub progress_token: ProgressToken,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub total: Option<f64>,
|
||||
}
|
||||
|
||||
@@ -648,7 +816,9 @@ pub enum ProgressToken {
|
||||
/// A prompt or prompt template that the server offers.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Prompt {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<Vec<PromptArgument>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
}
|
||||
@@ -656,8 +826,10 @@ pub struct Prompt {
|
||||
/// Describes an argument that a prompt can accept.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PromptArgument {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub required: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -720,9 +892,16 @@ 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,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -736,11 +915,14 @@ pub enum RequestId {
|
||||
/// A known resource that the server is capable of reading.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Resource {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "mimeType")]
|
||||
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub size: Option<i64>,
|
||||
pub uri: String,
|
||||
}
|
||||
@@ -748,7 +930,7 @@ pub struct Resource {
|
||||
/// The contents of a specific resource or sub-resource.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ResourceContents {
|
||||
#[serde(rename = "mimeType")]
|
||||
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
pub uri: String,
|
||||
}
|
||||
@@ -771,9 +953,11 @@ pub struct ResourceReference {
|
||||
/// A template description for resources available on the server.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ResourceTemplate {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "mimeType")]
|
||||
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
pub name: String,
|
||||
#[serde(rename = "uriTemplate")]
|
||||
@@ -793,8 +977,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)]
|
||||
@@ -808,6 +991,7 @@ pub enum Role {
|
||||
/// Represents a root directory or file that the server can operate on.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Root {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<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.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ServerCapabilities {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub completions: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub experimental: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub logging: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub prompts: Option<ServerCapabilitiesPrompts>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub resources: Option<ServerCapabilitiesResources>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tools: Option<ServerCapabilitiesTools>,
|
||||
}
|
||||
|
||||
/// Present if the server offers any tools to call.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ServerCapabilitiesTools {
|
||||
#[serde(rename = "listChanged")]
|
||||
#[serde(
|
||||
rename = "listChanged",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub list_changed: Option<bool>,
|
||||
}
|
||||
|
||||
/// Present if the server offers any resources to read.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ServerCapabilitiesResources {
|
||||
#[serde(rename = "listChanged")]
|
||||
#[serde(
|
||||
rename = "listChanged",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub list_changed: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub subscribe: Option<bool>,
|
||||
}
|
||||
|
||||
/// Present if the server offers any prompt templates.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ServerCapabilitiesPrompts {
|
||||
#[serde(rename = "listChanged")]
|
||||
#[serde(
|
||||
rename = "listChanged",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub list_changed: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -948,6 +1151,7 @@ pub struct SubscribeRequestParams {
|
||||
/// Text provided to or from an LLM.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct TextContent {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<Annotations>,
|
||||
pub text: String,
|
||||
pub r#type: String, // &'static str = "text"
|
||||
@@ -955,7 +1159,7 @@ pub struct TextContent {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct TextResourceContents {
|
||||
#[serde(rename = "mimeType")]
|
||||
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
pub text: String,
|
||||
pub uri: String,
|
||||
@@ -964,7 +1168,9 @@ pub struct TextResourceContents {
|
||||
/// Definition for a tool the client can call.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Tool {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub annotations: Option<ToolAnnotations>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "inputSchema")]
|
||||
pub input_schema: ToolInputSchema,
|
||||
@@ -974,7 +1180,9 @@ pub struct Tool {
|
||||
/// A JSON Schema object defining the expected parameters for the tool.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ToolInputSchema {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub properties: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub required: Option<Vec<String>>,
|
||||
pub r#type: String, // &'static str = "object"
|
||||
}
|
||||
@@ -989,14 +1197,31 @@ pub struct ToolInputSchema {
|
||||
/// received from untrusted servers.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ToolAnnotations {
|
||||
#[serde(rename = "destructiveHint")]
|
||||
#[serde(
|
||||
rename = "destructiveHint",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub destructive_hint: Option<bool>,
|
||||
#[serde(rename = "idempotentHint")]
|
||||
#[serde(
|
||||
rename = "idempotentHint",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub idempotent_hint: Option<bool>,
|
||||
#[serde(rename = "openWorldHint")]
|
||||
#[serde(
|
||||
rename = "openWorldHint",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
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>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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!({
|
||||
|
||||
Reference in New Issue
Block a user