Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/rmcp-macros/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::common::{extract_doc_line, none_expr};
pub struct PromptAttribute {
/// The name of the prompt
pub name: Option<String>,
/// Human readable title of prompt
pub title: Option<String>,
/// Optional description of what the prompt does
pub description: Option<String>,
/// Arguments that can be passed to the prompt
Expand All @@ -18,6 +20,7 @@ pub struct PromptAttribute {

pub struct ResolvedPromptAttribute {
pub name: String,
pub title: Option<String>,
pub description: Option<String>,
pub arguments: Expr,
}
Expand All @@ -28,18 +31,25 @@ impl ResolvedPromptAttribute {
name,
description,
arguments,
title,
} = self;
let description = if let Some(description) = description {
quote! { Some(#description.into()) }
} else {
quote! { None }
};
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Prompt {
rmcp::model::Prompt {
name: #name.into(),
description: #description,
arguments: #arguments,
title: #title,
}
}
};
Expand Down Expand Up @@ -87,6 +97,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
name: name.clone(),
description: description.clone(),
arguments: arguments.clone(),
title: attribute.title,
};
let prompt_attr_fn = resolved_prompt_attr.into_fn(prompt_attr_fn_ident.clone())?;

Expand Down
11 changes: 11 additions & 0 deletions crates/rmcp-macros/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ fn extract_schema_from_return_type(ret_type: &syn::Type) -> Option<Expr> {
pub struct ToolAttribute {
/// The name of the tool
pub name: Option<String>,
/// Human readable title of tool
pub title: Option<String>,
pub description: Option<String>,
/// A JSON Schema object defining the expected parameters for the tool
pub input_schema: Option<Expr>,
Expand All @@ -77,6 +79,7 @@ pub struct ToolAttribute {

pub struct ResolvedToolAttribute {
pub name: String,
pub title: Option<String>,
pub description: Option<String>,
pub input_schema: Expr,
pub output_schema: Option<Expr>,
Expand All @@ -88,6 +91,7 @@ impl ResolvedToolAttribute {
let Self {
name,
description,
title,
input_schema,
output_schema,
annotations,
Expand All @@ -102,10 +106,16 @@ impl ResolvedToolAttribute {
} else {
quote! { None }
};
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Tool {
rmcp::model::Tool {
name: #name.into(),
title: #title,
description: #description,
input_schema: #input_schema,
output_schema: #output_schema,
Expand Down Expand Up @@ -229,6 +239,7 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
input_schema: input_schema_expr,
output_schema: output_schema_expr,
annotations: annotations_expr,
title: attribute.title,
};
let tool_attr_fn = resolved_tool_attr.into_fn(tool_attr_fn_ident)?;
// modify the the input function
Expand Down
1 change: 1 addition & 0 deletions crates/rmcp/src/handler/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl<H: ClientHandler> Service<RoleClient> for H {
ServerRequest::CreateMessageRequest(request) => self
.create_message(request.params, context)
.await
.map(Box::new)
.map(ClientResult::CreateMessageResult),
ServerRequest::ListRootsRequest(_) => self
.list_roots(context)
Expand Down
1 change: 1 addition & 0 deletions crates/rmcp/src/handler/server/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ pub fn cached_arguments_from_schema<T: schemars::JsonSchema + std::any::Any>()

arguments.push(crate::model::PromptArgument {
name: name.clone(),
title: None,
description,
required: Some(required.contains(name.as_str())),
});
Expand Down
55 changes: 43 additions & 12 deletions crates/rmcp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ impl ErrorData {
/// Represents any JSON-RPC message that can be sent or received.
///
/// 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.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
Expand Down Expand Up @@ -686,6 +686,8 @@ impl Default for ClientInfo {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Implementation {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String,
}

Expand All @@ -699,6 +701,7 @@ impl Implementation {
pub fn from_build_env() -> Self {
Implementation {
name: env!("CARGO_CRATE_NAME").to_owned(),
title: None,
version: env!("CARGO_PKG_VERSION").to_owned(),
}
}
Expand Down Expand Up @@ -1104,6 +1107,8 @@ pub struct ResourceReference {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PromptReference {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}

const_string!(CompleteRequestMethod = "completion/complete");
Expand Down Expand Up @@ -1438,24 +1443,50 @@ pub struct GetPromptResult {

macro_rules! ts_union {
(
export type $U: ident =
$(|)?$($V: ident)|*;
export type $U: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)]
#[serde(untagged)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
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!(
Expand Down Expand Up @@ -1504,7 +1535,7 @@ ts_union!(
);

ts_union!(
export type ClientResult = CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult;
export type ClientResult = box CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult;
);

impl ClientResult {
Expand Down
3 changes: 2 additions & 1 deletion crates/rmcp/src/model/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub enum RawContent {
Text(RawTextContent),
Image(RawImageContent),
Resource(RawEmbeddedResource),
Audio(AudioContent),
Audio(RawAudioContent),
ResourceLink(super::resource::RawResource),
}

Expand Down Expand Up @@ -241,6 +241,7 @@ mod tests {
let resource_link = RawContent::ResourceLink(RawResource {
uri: "file:///test.txt".to_string(),
name: "test.txt".to_string(),
title: None,
description: Some("A test file".to_string()),
mime_type: Some("text/plain".to_string()),
size: Some(100),
Expand Down
6 changes: 6 additions & 0 deletions crates/rmcp/src/model/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use super::{
pub struct Prompt {
/// The name of the prompt
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
/// Optional description of what the prompt does
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
Expand All @@ -35,6 +37,7 @@ impl Prompt {
{
Prompt {
name: name.into(),
title: None,
description: description.map(Into::into),
arguments,
}
Expand All @@ -47,6 +50,9 @@ impl Prompt {
pub struct PromptArgument {
/// The name of the argument
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
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
Expand Down
7 changes: 7 additions & 0 deletions crates/rmcp/src/model/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub struct RawResource {
pub uri: String,
/// Name of the resource
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
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
Expand All @@ -34,6 +37,8 @@ pub struct RawResourceTemplate {
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
Expand Down Expand Up @@ -82,6 +87,7 @@ impl RawResource {
Self {
uri: uri.into(),
name: name.into(),
title: None,
description: None,
mime_type: None,
size: None,
Expand All @@ -99,6 +105,7 @@ mod tests {
fn test_resource_serialization() {
let resource = RawResource {
uri: "file:///test.txt".to_string(),
title: None,
name: "test".to_string(),
description: Some("Test resource".to_string()),
mime_type: Some("text/plain".to_string()),
Expand Down
4 changes: 4 additions & 0 deletions crates/rmcp/src/model/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use super::JsonObject;
pub struct Tool {
/// The name of the tool
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
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<Cow<'static, str>>,
Expand Down Expand Up @@ -138,6 +141,7 @@ impl Tool {
{
Tool {
name: name.into(),
title: None,
description: Some(description.into()),
input_schema: input_schema.into(),
output_schema: None,
Expand Down
17 changes: 16 additions & 1 deletion crates/rmcp/src/service/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,22 @@ macro_rules! method {
}

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);
#[cfg(feature = "elicitation")]
method!(peer_req create_elicitation CreateElicitationRequest(CreateElicitationRequestParam) => CreateElicitationResult);
Expand Down
3 changes: 3 additions & 0 deletions crates/rmcp/tests/test_elicitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ async fn test_initialize_request_with_elicitation() {
client_info: Implementation {
name: "test-client".to_string(),
version: "1.0.0".to_string(),
title: None,
},
};

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

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

Expand Down
Loading