Skip to content

Commit 81411fc

Browse files
authored
feat(meta): add _meta field to prompts, resources and paginated result (#558)
1 parent eb5a7f7 commit 81411fc

File tree

14 files changed

+195
-8
lines changed

14 files changed

+195
-8
lines changed

crates/rmcp-macros/src/prompt.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub struct PromptAttribute {
1818
pub arguments: Option<Expr>,
1919
/// Optional icons for the prompt
2020
pub icons: Option<Expr>,
21+
/// Optional metadata for the prompt
22+
pub meta: Option<Expr>,
2123
}
2224

2325
pub struct ResolvedPromptAttribute {
@@ -26,6 +28,7 @@ pub struct ResolvedPromptAttribute {
2628
pub description: Option<Expr>,
2729
pub arguments: Expr,
2830
pub icons: Option<Expr>,
31+
pub meta: Option<Expr>,
2932
}
3033

3134
impl ResolvedPromptAttribute {
@@ -36,6 +39,7 @@ impl ResolvedPromptAttribute {
3639
arguments,
3740
title,
3841
icons,
42+
meta,
3943
} = self;
4044
let description = if let Some(description) = description {
4145
quote! { Some(#description.into()) }
@@ -52,6 +56,11 @@ impl ResolvedPromptAttribute {
5256
} else {
5357
quote! { None }
5458
};
59+
let meta = if let Some(meta) = meta {
60+
quote! { Some(#meta) }
61+
} else {
62+
quote! { None }
63+
};
5564
let tokens = quote! {
5665
pub fn #fn_ident() -> rmcp::model::Prompt {
5766
rmcp::model::Prompt {
@@ -60,6 +69,7 @@ impl ResolvedPromptAttribute {
6069
arguments: #arguments,
6170
title: #title,
6271
icons: #icons,
72+
meta: #meta,
6373
}
6474
}
6575
};
@@ -114,6 +124,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
114124
arguments: arguments.clone(),
115125
title: attribute.title,
116126
icons: attribute.icons,
127+
meta: attribute.meta,
117128
};
118129
let prompt_attr_fn = resolved_prompt_attr.into_fn(prompt_attr_fn_ident.clone())?;
119130

crates/rmcp-macros/src/prompt_handler.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use syn::{Expr, ImplItem, ItemImpl, parse_quote};
77
#[darling(default)]
88
pub struct PromptHandlerAttribute {
99
pub router: Option<Expr>,
10+
pub meta: Option<Expr>,
1011
}
1112

1213
pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
@@ -40,6 +41,12 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<Toke
4041
}
4142
};
4243

44+
let meta = if let Some(meta) = attribute.meta {
45+
quote! { Some(#meta) }
46+
} else {
47+
quote! { None }
48+
};
49+
4350
// Add list_prompts implementation
4451
let list_prompts_impl: ImplItem = parse_quote! {
4552
async fn list_prompts(
@@ -50,6 +57,7 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<Toke
5057
let prompts = #router_expr.list_all();
5158
Ok(ListPromptsResult {
5259
prompts,
60+
meta: #meta,
5361
next_cursor: None,
5462
})
5563
}

crates/rmcp-macros/src/tool_handler.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use syn::{Expr, ImplItem, ItemImpl};
77
#[darling(default)]
88
pub struct ToolHandlerAttribute {
99
pub router: Expr,
10+
pub meta: Option<Expr>,
1011
}
1112

1213
impl Default for ToolHandlerAttribute {
@@ -16,13 +17,14 @@ impl Default for ToolHandlerAttribute {
1617
self.tool_router
1718
})
1819
.unwrap(),
20+
meta: None,
1921
}
2022
}
2123
}
2224

2325
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
2426
let attr_args = NestedMeta::parse_meta_list(attr)?;
25-
let ToolHandlerAttribute { router } = ToolHandlerAttribute::from_list(&attr_args)?;
27+
let ToolHandlerAttribute { router, meta } = ToolHandlerAttribute::from_list(&attr_args)?;
2628
let mut item_impl = syn::parse2::<ItemImpl>(input.clone())?;
2729
let tool_call_fn = quote! {
2830
async fn call_tool(
@@ -34,13 +36,24 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
3436
#router.call(tcc).await
3537
}
3638
};
39+
40+
let result_meta = if let Some(meta) = meta {
41+
quote! { Some(#meta) }
42+
} else {
43+
quote! { None }
44+
};
45+
3746
let tool_list_fn = quote! {
3847
async fn list_tools(
3948
&self,
4049
_request: Option<rmcp::model::PaginatedRequestParam>,
4150
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
4251
) -> Result<rmcp::model::ListToolsResult, rmcp::ErrorData> {
43-
Ok(rmcp::model::ListToolsResult::with_all_items(#router.list_all()))
52+
Ok(rmcp::model::ListToolsResult{
53+
tools: #router.list_all(),
54+
meta: #result_meta,
55+
next_cursor: None,
56+
})
4457
}
4558
};
4659
let tool_call_fn = syn::parse2::<ImplItem>(tool_call_fn)?;

crates/rmcp/src/handler/server/router.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ where
102102
let tools = self.tool_router.list_all();
103103
Ok(ServerResult::ListToolsResult(ListToolsResult {
104104
tools,
105-
next_cursor: None,
105+
..Default::default()
106106
}))
107107
}
108108
ClientRequest::GetPromptRequest(request) => {
@@ -125,7 +125,7 @@ where
125125
let prompts = self.prompt_router.list_all();
126126
Ok(ServerResult::ListPromptsResult(ListPromptsResult {
127127
prompts,
128-
next_cursor: None,
128+
..Default::default()
129129
}))
130130
}
131131
rest => self.service.handle_request(rest, context).await,

crates/rmcp/src/model.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,8 @@ macro_rules! paginated_result {
828828
#[serde(rename_all = "camelCase")]
829829
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
830830
pub struct $t {
831+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
832+
pub meta: Option<Meta>,
831833
#[serde(skip_serializing_if = "Option::is_none")]
832834
pub next_cursor: Option<Cursor>,
833835
pub $i_item: $t_item,
@@ -838,6 +840,7 @@ macro_rules! paginated_result {
838840
items: $t_item,
839841
) -> Self {
840842
Self {
843+
meta: None,
841844
next_cursor: None,
842845
$i_item: items,
843846
}

crates/rmcp/src/model/content.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ mod tests {
258258
mime_type: Some("text/plain".to_string()),
259259
size: Some(100),
260260
icons: None,
261+
meta: None,
261262
});
262263

263264
let json = serde_json::to_string(&resource_link).unwrap();

crates/rmcp/src/model/prompt.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use base64::engine::{Engine, general_purpose::STANDARD as BASE64_STANDARD};
22
use serde::{Deserialize, Serialize};
33

44
use super::{
5-
AnnotateAble, Annotations, Icon, RawEmbeddedResource, RawImageContent,
5+
AnnotateAble, Annotations, Icon, Meta, RawEmbeddedResource, RawImageContent,
66
content::{EmbeddedResource, ImageContent},
77
resource::ResourceContents,
88
};
@@ -25,6 +25,9 @@ pub struct Prompt {
2525
/// Optional list of icons for the prompt
2626
#[serde(skip_serializing_if = "Option::is_none")]
2727
pub icons: Option<Vec<Icon>>,
28+
/// Optional additional metadata for this prompt
29+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
30+
pub meta: Option<Meta>,
2831
}
2932

3033
impl Prompt {
@@ -44,6 +47,7 @@ impl Prompt {
4447
description: description.map(Into::into),
4548
arguments,
4649
icons: None,
50+
meta: None,
4751
}
4852
}
4953
}

crates/rmcp/src/model/resource.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ pub struct RawResource {
2929
/// Optional list of icons for the resource
3030
#[serde(skip_serializing_if = "Option::is_none")]
3131
pub icons: Option<Vec<Icon>>,
32+
/// Optional additional metadata for this resource
33+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
34+
pub meta: Option<Meta>,
3235
}
3336

3437
pub type Resource = Annotated<RawResource>;
@@ -95,6 +98,7 @@ impl RawResource {
9598
mime_type: None,
9699
size: None,
97100
icons: None,
101+
meta: None,
98102
}
99103
}
100104
}
@@ -115,6 +119,7 @@ mod tests {
115119
mime_type: Some("text/plain".to_string()),
116120
size: Some(100),
117121
icons: None,
122+
meta: None,
118123
};
119124

120125
let json = serde_json::to_string(&resource).unwrap();

crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,14 @@
992992
"description": "Represents a resource in the extension with metadata",
993993
"type": "object",
994994
"properties": {
995+
"_meta": {
996+
"description": "Optional additional metadata for this resource",
997+
"type": [
998+
"object",
999+
"null"
1000+
],
1001+
"additionalProperties": true
1002+
},
9951003
"description": {
9961004
"description": "Optional description of the resource",
9971005
"type": [

crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,14 @@
992992
"description": "Represents a resource in the extension with metadata",
993993
"type": "object",
994994
"properties": {
995+
"_meta": {
996+
"description": "Optional additional metadata for this resource",
997+
"type": [
998+
"object",
999+
"null"
1000+
],
1001+
"additionalProperties": true
1002+
},
9951003
"description": {
9961004
"description": "Optional description of the resource",
9971005
"type": [

0 commit comments

Comments
 (0)