feat: add title field for data types (#410)

* feat(model): add title field to various structs

* fix(test): fix test json schema

* fix(model): allow boxed type  in `ts_union` macro
This commit is contained in:
4t145 2025-09-08 09:15:22 +08:00 • committed by GitHub
commit 1b70f5b032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 219 additions and 73 deletions

View file

@ -10,6 +10,8 @@ use crate::common::{extract_doc_line, none_expr};
pub struct PromptAttribute { pub struct PromptAttribute {
/// The name of the prompt /// The name of the prompt
pub name: Option<String>, pub name: Option<String>,
/// Human readable title of prompt
pub title: Option<String>,
/// Optional description of what the prompt does /// Optional description of what the prompt does
pub description: Option<String>, pub description: Option<String>,
/// Arguments that can be passed to the prompt /// Arguments that can be passed to the prompt
@ -18,6 +20,7 @@ pub struct PromptAttribute {
pub struct ResolvedPromptAttribute { pub struct ResolvedPromptAttribute {
pub name: String, pub name: String,
pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub arguments: Expr, pub arguments: Expr,
} }
@ -28,18 +31,25 @@ impl ResolvedPromptAttribute {
name, name,
description, description,
arguments, arguments,
title,
} = self; } = self;
let description = if let Some(description) = description { let description = if let Some(description) = description {
quote! { Some(#description.into()) } quote! { Some(#description.into()) }
} else { } else {
quote! { None } quote! { None }
}; };
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let tokens = quote! { let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Prompt { pub fn #fn_ident() -> rmcp::model::Prompt {
rmcp::model::Prompt { rmcp::model::Prompt {
name: #name.into(), name: #name.into(),
description: #description, description: #description,
arguments: #arguments, arguments: #arguments,
title: #title,
} }
} }
}; };
@ -87,6 +97,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
name: name.clone(), name: name.clone(),
description: description.clone(), description: description.clone(),
arguments: arguments.clone(), arguments: arguments.clone(),
title: attribute.title,
}; };
let prompt_attr_fn = resolved_prompt_attr.into_fn(prompt_attr_fn_ident.clone())?; let prompt_attr_fn = resolved_prompt_attr.into_fn(prompt_attr_fn_ident.clone())?;

View file

@ -66,6 +66,8 @@ fn extract_schema_from_return_type(ret_type: &syn::Type) -> Option<Expr> {
pub struct ToolAttribute { pub struct ToolAttribute {
/// The name of the tool /// The name of the tool
pub name: Option<String>, pub name: Option<String>,
/// Human readable title of tool
pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
/// A JSON Schema object defining the expected parameters for the tool /// A JSON Schema object defining the expected parameters for the tool
pub input_schema: Option<Expr>, pub input_schema: Option<Expr>,
@ -77,6 +79,7 @@ pub struct ToolAttribute {
pub struct ResolvedToolAttribute { pub struct ResolvedToolAttribute {
pub name: String, pub name: String,
pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub input_schema: Expr, pub input_schema: Expr,
pub output_schema: Option<Expr>, pub output_schema: Option<Expr>,
@ -88,6 +91,7 @@ impl ResolvedToolAttribute {
let Self { let Self {
name, name,
description, description,
title,
input_schema, input_schema,
output_schema, output_schema,
annotations, annotations,
@ -102,10 +106,16 @@ impl ResolvedToolAttribute {
} else { } else {
quote! { None } quote! { None }
}; };
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let tokens = quote! { let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Tool { pub fn #fn_ident() -> rmcp::model::Tool {
rmcp::model::Tool { rmcp::model::Tool {
name: #name.into(), name: #name.into(),
title: #title,
description: #description, description: #description,
input_schema: #input_schema, input_schema: #input_schema,
output_schema: #output_schema, output_schema: #output_schema,
@ -229,6 +239,7 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
input_schema: input_schema_expr, input_schema: input_schema_expr,
output_schema: output_schema_expr, output_schema: output_schema_expr,
annotations: annotations_expr, annotations: annotations_expr,
title: attribute.title,
}; };
let tool_attr_fn = resolved_tool_attr.into_fn(tool_attr_fn_ident)?; let tool_attr_fn = resolved_tool_attr.into_fn(tool_attr_fn_ident)?;
// modify the the input function // modify the the input function

View file

@ -16,6 +16,7 @@ impl<H: ClientHandler> Service<RoleClient> for H {
ServerRequest::CreateMessageRequest(request) => self ServerRequest::CreateMessageRequest(request) => self
.create_message(request.params, context) .create_message(request.params, context)
.await .await
.map(Box::new)
.map(ClientResult::CreateMessageResult), .map(ClientResult::CreateMessageResult),
ServerRequest::ListRootsRequest(_) => self ServerRequest::ListRootsRequest(_) => self
.list_roots(context) .list_roots(context)

View file

@ -350,6 +350,7 @@ pub fn cached_arguments_from_schema<T: schemars::JsonSchema + std::any::Any>()
arguments.push(crate::model::PromptArgument { arguments.push(crate::model::PromptArgument {
name: name.clone(), name: name.clone(),
title: None,
description, description,
required: Some(required.contains(name.as_str())), required: Some(required.contains(name.as_str())),
}); });

View file

@ -504,7 +504,7 @@ impl ErrorData {
/// Represents any JSON-RPC message that can be sent or received. /// Represents any JSON-RPC message that can be sent or received.
/// ///
/// This enum covers all possible message types in the JSON-RPC protocol: /// This enum covers all possible message types in the JSON-RPC protocol:
/// individual requests/responses, notifications, batch operations, and errors. /// individual requests/responses, notifications, and errors.
/// It serves as the top-level message container for MCP communication. /// It serves as the top-level message container for MCP communication.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
@ -697,6 +697,8 @@ impl Default for ClientInfo {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Implementation { pub struct Implementation {
pub name: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String, pub version: String,
} }
@ -710,6 +712,7 @@ impl Implementation {
pub fn from_build_env() -> Self { pub fn from_build_env() -> Self {
Implementation { Implementation {
name: env!("CARGO_CRATE_NAME").to_owned(), name: env!("CARGO_CRATE_NAME").to_owned(),
title: None,
version: env!("CARGO_PKG_VERSION").to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(),
} }
} }
@ -1115,6 +1118,8 @@ pub struct ResourceReference {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PromptReference { pub struct PromptReference {
pub name: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
} }
const_string!(CompleteRequestMethod = "completion/complete"); const_string!(CompleteRequestMethod = "completion/complete");
@ -1449,24 +1454,50 @@ pub struct GetPromptResult {
macro_rules! ts_union { macro_rules! ts_union {
( (
export type $U: ident = export type $U:ident =
$(|)?$($V: ident)|*; $($rest:tt)*
) => { ) => {
ts_union!(@declare $U { $($rest)* });
ts_union!(@impl_from $U { $($rest)* });
};
(@declare $U:ident { $($variant:tt)* }) => {
ts_union!(@declare_variant $U { } {$($variant)*} );
};
(@declare_variant $U:ident { $($declared:tt)* } {$(|)? box $V:ident $($rest:tt)*}) => {
ts_union!(@declare_variant $U { $($declared)* $V(Box<$V>), } {$($rest)*});
};
(@declare_variant $U:ident { $($declared:tt)* } {$(|)? $V:ident $($rest:tt)*}) => {
ts_union!(@declare_variant $U { $($declared)* $V($V), } {$($rest)*});
};
(@declare_variant $U:ident { $($declared:tt)* } { ; }) => {
ts_union!(@declare_end $U { $($declared)* } );
};
(@declare_end $U:ident { $($declared:tt)* }) => {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)] #[serde(untagged)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum $U { pub enum $U {
$($V($V),)* $($declared)*
} }
$(
impl From<$V> for $U {
fn from(value: $V) -> Self {
$U::$V(value)
}
}
)*
}; };
(@impl_from $U: ident {$(|)? box $V:ident $($rest:tt)*}) => {
impl From<$V> for $U {
fn from(value: $V) -> Self {
$U::$V(Box::new(value))
}
}
ts_union!(@impl_from $U {$($rest)*});
};
(@impl_from $U: ident {$(|)? $V:ident $($rest:tt)*}) => {
impl From<$V> for $U {
fn from(value: $V) -> Self {
$U::$V(value)
}
}
ts_union!(@impl_from $U {$($rest)*});
};
(@impl_from $U: ident { ; }) => {};
(@impl_from $U: ident { }) => {};
} }
ts_union!( ts_union!(
@ -1515,7 +1546,7 @@ ts_union!(
); );
ts_union!( ts_union!(
export type ClientResult = CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult; export type ClientResult = box CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult;
); );
impl ClientResult { impl ClientResult {

View file

@ -66,7 +66,7 @@ pub enum RawContent {
Text(RawTextContent), Text(RawTextContent),
Image(RawImageContent), Image(RawImageContent),
Resource(RawEmbeddedResource), Resource(RawEmbeddedResource),
Audio(AudioContent), Audio(RawAudioContent),
ResourceLink(super::resource::RawResource), ResourceLink(super::resource::RawResource),
} }
@ -253,6 +253,7 @@ mod tests {
let resource_link = RawContent::ResourceLink(RawResource { let resource_link = RawContent::ResourceLink(RawResource {
uri: "file:///test.txt".to_string(), uri: "file:///test.txt".to_string(),
name: "test.txt".to_string(), name: "test.txt".to_string(),
title: None,
description: Some("A test file".to_string()), description: Some("A test file".to_string()),
mime_type: Some("text/plain".to_string()), mime_type: Some("text/plain".to_string()),
size: Some(100), size: Some(100),

View file

@ -14,6 +14,8 @@ use super::{
pub struct Prompt { pub struct Prompt {
/// The name of the prompt /// The name of the prompt
pub name: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// Optional description of what the prompt does /// Optional description of what the prompt does
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
@ -35,6 +37,7 @@ impl Prompt {
{ {
Prompt { Prompt {
name: name.into(), name: name.into(),
title: None,
description: description.map(Into::into), description: description.map(Into::into),
arguments, arguments,
} }
@ -47,6 +50,9 @@ impl Prompt {
pub struct PromptArgument { pub struct PromptArgument {
/// The name of the argument /// The name of the argument
pub name: String, pub name: String,
/// A human-readable title for the argument
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// A description of what the argument is used for /// A description of what the argument is used for
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,

View file

@ -11,6 +11,9 @@ pub struct RawResource {
pub uri: String, pub uri: String,
/// Name of the resource /// Name of the resource
pub name: String, pub name: String,
/// Human-readable title of the resource
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// Optional description of the resource /// Optional description of the resource
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
@ -34,6 +37,8 @@ pub struct RawResourceTemplate {
pub uri_template: String, pub uri_template: String,
pub name: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>, pub mime_type: Option<String>,
@ -82,6 +87,7 @@ impl RawResource {
Self { Self {
uri: uri.into(), uri: uri.into(),
name: name.into(), name: name.into(),
title: None,
description: None, description: None,
mime_type: None, mime_type: None,
size: None, size: None,
@ -99,6 +105,7 @@ mod tests {
fn test_resource_serialization() { fn test_resource_serialization() {
let resource = RawResource { let resource = RawResource {
uri: "file:///test.txt".to_string(), uri: "file:///test.txt".to_string(),
title: None,
name: "test".to_string(), name: "test".to_string(),
description: Some("Test resource".to_string()), description: Some("Test resource".to_string()),
mime_type: Some("text/plain".to_string()), mime_type: Some("text/plain".to_string()),

View file

@ -15,6 +15,9 @@ use super::JsonObject;
pub struct Tool { pub struct Tool {
/// The name of the tool /// The name of the tool
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
/// A human-readable title for the tool
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// A description of what the tool does /// A description of what the tool does
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<Cow<'static, str>>, pub description: Option<Cow<'static, str>>,
@ -138,6 +141,7 @@ impl Tool {
{ {
Tool { Tool {
name: name.into(), name: name.into(),
title: None,
description: Some(description.into()), description: Some(description.into()),
input_schema: input_schema.into(), input_schema: input_schema.into(),
output_schema: None, output_schema: None,

View file

@ -384,7 +384,22 @@ macro_rules! method {
} }
impl Peer<RoleServer> { impl Peer<RoleServer> {
method!(peer_req create_message CreateMessageRequest(CreateMessageRequestParam) => CreateMessageResult); pub async fn create_message(
&self,
params: CreateMessageRequestParam,
) -> Result<CreateMessageResult, ServiceError> {
let result = self
.send_request(ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params,
extensions: Default::default(),
}))
.await?;
match result {
ClientResult::CreateMessageResult(result) => Ok(*result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
method!(peer_req list_roots ListRootsRequest() => ListRootsResult); method!(peer_req list_roots ListRootsRequest() => ListRootsResult);
#[cfg(feature = "elicitation")] #[cfg(feature = "elicitation")]
method!(peer_req create_elicitation CreateElicitationRequest(CreateElicitationRequestParam) => CreateElicitationResult); method!(peer_req create_elicitation CreateElicitationRequest(CreateElicitationRequestParam) => CreateElicitationResult);

View file

@ -892,6 +892,7 @@ async fn test_initialize_request_with_elicitation() {
client_info: Implementation { client_info: Implementation {
name: "test-client".to_string(), name: "test-client".to_string(),
version: "1.0.0".to_string(), version: "1.0.0".to_string(),
title: None,
}, },
}; };
@ -933,6 +934,7 @@ async fn test_capability_checking_logic() {
client_info: Implementation { client_info: Implementation {
name: "test-client".to_string(), name: "test-client".to_string(),
version: "1.0.0".to_string(), version: "1.0.0".to_string(),
title: None,
}, },
}; };
@ -950,6 +952,7 @@ async fn test_capability_checking_logic() {
client_info: Implementation { client_info: Implementation {
name: "test-client".to_string(), name: "test-client".to_string(),
version: "1.0.0".to_string(), version: "1.0.0".to_string(),
title: None,
}, },
}; };

View file

@ -1,7 +1,7 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "JsonRpcMessage", "title": "JsonRpcMessage",
"description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, batch operations, and errors.\nIt serves as the top-level message container for MCP communication.", "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, and errors.\nIt serves as the top-level message container for MCP communication.",
"anyOf": [ "anyOf": [
{ {
"description": "A single request expecting a response", "description": "A single request expecting a response",
@ -113,7 +113,7 @@
}, },
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Annotated2" "$ref": "#/definitions/RawAudioContent"
} }
], ],
"required": [ "required": [
@ -139,31 +139,6 @@
} }
] ]
}, },
"Annotated2": {
"type": "object",
"properties": {
"annotations": {
"anyOf": [
{
"$ref": "#/definitions/Annotations"
},
{
"type": "null"
}
]
},
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
}
},
"required": [
"data",
"mimeType"
]
},
"Annotations": { "Annotations": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -491,6 +466,12 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"title": {
"type": [
"string",
"null"
]
},
"version": { "version": {
"type": "string" "type": "string"
} }
@ -845,6 +826,12 @@
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string"
},
"title": {
"type": [
"string",
"null"
]
} }
}, },
"required": [ "required": [
@ -855,6 +842,21 @@
"description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.", "description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.",
"type": "string" "type": "string"
}, },
"RawAudioContent": {
"type": "object",
"properties": {
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
}
},
"required": [
"data",
"mimeType"
]
},
"RawEmbeddedResource": { "RawEmbeddedResource": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -929,6 +931,13 @@
"format": "uint32", "format": "uint32",
"minimum": 0 "minimum": 0
}, },
"title": {
"description": "Human-readable title of the resource",
"type": [
"string",
"null"
]
},
"uri": { "uri": {
"description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")",
"type": "string" "type": "string"

View file

@ -1,7 +1,7 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "JsonRpcMessage", "title": "JsonRpcMessage",
"description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, batch operations, and errors.\nIt serves as the top-level message container for MCP communication.", "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, and errors.\nIt serves as the top-level message container for MCP communication.",
"anyOf": [ "anyOf": [
{ {
"description": "A single request expecting a response", "description": "A single request expecting a response",
@ -113,7 +113,7 @@
}, },
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Annotated2" "$ref": "#/definitions/RawAudioContent"
} }
], ],
"required": [ "required": [
@ -140,31 +140,6 @@
] ]
}, },
"Annotated2": { "Annotated2": {
"type": "object",
"properties": {
"annotations": {
"anyOf": [
{
"$ref": "#/definitions/Annotations"
},
{
"type": "null"
}
]
},
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
}
},
"required": [
"data",
"mimeType"
]
},
"Annotated3": {
"type": "object", "type": "object",
"properties": { "properties": {
"_meta": { "_meta": {
@ -193,7 +168,7 @@
"resource" "resource"
] ]
}, },
"Annotated4": { "Annotated3": {
"description": "Represents a resource in the extension with metadata", "description": "Represents a resource in the extension with metadata",
"type": "object", "type": "object",
"properties": { "properties": {
@ -234,6 +209,13 @@
"format": "uint32", "format": "uint32",
"minimum": 0 "minimum": 0
}, },
"title": {
"description": "Human-readable title of the resource",
"type": [
"string",
"null"
]
},
"uri": { "uri": {
"description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")",
"type": "string" "type": "string"
@ -244,7 +226,7 @@
"name" "name"
] ]
}, },
"Annotated5": { "Annotated4": {
"type": "object", "type": "object",
"properties": { "properties": {
"annotations": { "annotations": {
@ -272,6 +254,12 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"title": {
"type": [
"string",
"null"
]
},
"uriTemplate": { "uriTemplate": {
"type": "string" "type": "string"
} }
@ -626,6 +614,12 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"title": {
"type": [
"string",
"null"
]
},
"version": { "version": {
"type": "string" "type": "string"
} }
@ -815,7 +809,7 @@
"resourceTemplates": { "resourceTemplates": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/Annotated5" "$ref": "#/definitions/Annotated4"
} }
} }
}, },
@ -835,7 +829,7 @@
"resources": { "resources": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/Annotated4" "$ref": "#/definitions/Annotated3"
} }
} }
}, },
@ -1145,6 +1139,12 @@
"name": { "name": {
"description": "The name of the prompt", "description": "The name of the prompt",
"type": "string" "type": "string"
},
"title": {
"type": [
"string",
"null"
]
} }
}, },
"required": [ "required": [
@ -1172,6 +1172,13 @@
"boolean", "boolean",
"null" "null"
] ]
},
"title": {
"description": "A human-readable title for the argument",
"type": [
"string",
"null"
]
} }
}, },
"required": [ "required": [
@ -1274,7 +1281,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"resource": { "resource": {
"$ref": "#/definitions/Annotated3" "$ref": "#/definitions/Annotated2"
}, },
"type": { "type": {
"type": "string", "type": "string",
@ -1327,6 +1334,13 @@
"format": "uint32", "format": "uint32",
"minimum": 0 "minimum": 0
}, },
"title": {
"description": "Human-readable title of the resource",
"type": [
"string",
"null"
]
},
"type": { "type": {
"type": "string", "type": "string",
"const": "resource_link" "const": "resource_link"
@ -1367,6 +1381,21 @@
"description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.", "description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.",
"type": "string" "type": "string"
}, },
"RawAudioContent": {
"type": "object",
"properties": {
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
}
},
"required": [
"data",
"mimeType"
]
},
"RawEmbeddedResource": { "RawEmbeddedResource": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1441,6 +1470,13 @@
"format": "uint32", "format": "uint32",
"minimum": 0 "minimum": 0
}, },
"title": {
"description": "Human-readable title of the resource",
"type": [
"string",
"null"
]
},
"uri": { "uri": {
"description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")",
"type": "string" "type": "string"
@ -1817,6 +1853,13 @@
"null" "null"
], ],
"additionalProperties": true "additionalProperties": true
},
"title": {
"description": "A human-readable title for the tool",
"type": [
"string",
"null"
]
} }
}, },
"required": [ "required": [

View file

@ -22,6 +22,7 @@ async fn main() -> Result<()> {
capabilities: ClientCapabilities::default(), capabilities: ClientCapabilities::default(),
client_info: Implementation { client_info: Implementation {
name: "test sse client".to_string(), name: "test sse client".to_string(),
title: None,
version: "0.0.1".to_string(), version: "0.0.1".to_string(),
}, },
}; };

View file

@ -22,6 +22,7 @@ async fn main() -> Result<()> {
capabilities: ClientCapabilities::default(), capabilities: ClientCapabilities::default(),
client_info: Implementation { client_info: Implementation {
name: "test sse client".to_string(), name: "test sse client".to_string(),
title: None,
version: "0.0.1".to_string(), version: "0.0.1".to_string(),
}, },
}; };

View file

@ -105,6 +105,7 @@ impl ServerHandler for SamplingDemoServer {
Ok(ListToolsResult { Ok(ListToolsResult {
tools: vec![Tool { tools: vec![Tool {
name: "ask_llm".into(), name: "ask_llm".into(),
title: None,
description: Some("Ask a question to the LLM through sampling".into()), description: Some("Ask a question to the LLM through sampling".into()),
input_schema: Arc::new( input_schema: Arc::new(
serde_json::from_value(serde_json::json!({ serde_json::from_value(serde_json::json!({