Skip to content

Commit 43170ea

Browse files
committed
fix: emit cache hints from handler macros
1 parent 684ff5a commit 43170ea

3 files changed

Lines changed: 120 additions & 6 deletions

File tree

crates/rmcp-macros/src/prompt_handler.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,20 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<Toke
5757
async fn list_prompts(
5858
&self,
5959
_request: Option<rmcp::model::PaginatedRequestParams>,
60-
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
60+
context: rmcp::service::RequestContext<rmcp::RoleServer>,
6161
) -> Result<rmcp::model::ListPromptsResult, rmcp::ErrorData> {
6262
let prompts = #router_expr.list_all();
63+
let supports_cache_hints = context.protocol_version().is_some_and(|version| {
64+
version >= rmcp::model::ProtocolVersion::V_2026_07_28
65+
});
6366
Ok(rmcp::model::ListPromptsResult {
6467
result_type: Some(rmcp::model::ResultType::COMPLETE),
6568
prompts,
6669
meta: #meta,
6770
next_cursor: None,
68-
ttl_ms: None,
69-
cache_scope: None,
71+
ttl_ms: supports_cache_hints.then_some(0),
72+
cache_scope: supports_cache_hints
73+
.then_some(rmcp::model::CacheScope::Public),
7074
})
7175
}
7276
};

crates/rmcp-macros/src/tool_handler.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,19 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
6666
async fn list_tools(
6767
&self,
6868
_request: Option<rmcp::model::PaginatedRequestParams>,
69-
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
69+
context: rmcp::service::RequestContext<rmcp::RoleServer>,
7070
) -> Result<rmcp::model::ListToolsResult, rmcp::ErrorData> {
71+
let supports_cache_hints = context.protocol_version().is_some_and(|version| {
72+
version >= rmcp::model::ProtocolVersion::V_2026_07_28
73+
});
7174
Ok(rmcp::model::ListToolsResult{
7275
result_type: Some(rmcp::model::ResultType::COMPLETE),
7376
tools: #router.list_all(),
7477
meta: #result_meta,
7578
next_cursor: None,
76-
ttl_ms: None,
77-
cache_scope: None,
79+
ttl_ms: supports_cache_hints.then_some(0),
80+
cache_scope: supports_cache_hints
81+
.then_some(rmcp::model::CacheScope::Public),
7882
})
7983
}
8084
})?;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#![cfg(not(feature = "local"))]
2+
#![cfg(feature = "client")]
3+
4+
use rmcp::{
5+
ClientHandler, ServerHandler, ServiceExt,
6+
handler::server::router::{prompt::PromptRouter, tool::ToolRouter},
7+
model::{CacheScope, ClientInfo, ListPromptsResult, ListToolsResult, ProtocolVersion},
8+
prompt_handler, tool_handler,
9+
};
10+
11+
#[derive(Debug, Clone)]
12+
struct CacheHintServer {
13+
tool_router: ToolRouter<Self>,
14+
prompt_router: PromptRouter<Self>,
15+
}
16+
17+
impl CacheHintServer {
18+
fn new() -> Self {
19+
Self {
20+
tool_router: ToolRouter::new(),
21+
prompt_router: PromptRouter::new(),
22+
}
23+
}
24+
}
25+
26+
#[tool_handler(router = self.tool_router)]
27+
#[prompt_handler(router = self.prompt_router)]
28+
impl ServerHandler for CacheHintServer {}
29+
30+
#[derive(Debug, Clone)]
31+
struct VersionedClient {
32+
protocol_version: ProtocolVersion,
33+
}
34+
35+
impl ClientHandler for VersionedClient {
36+
fn get_info(&self) -> ClientInfo {
37+
let mut info = ClientInfo::default();
38+
info.protocol_version = self.protocol_version.clone();
39+
info
40+
}
41+
}
42+
43+
async fn list_results(protocol_version: ProtocolVersion) -> (ListToolsResult, ListPromptsResult) {
44+
let (server_transport, client_transport) = tokio::io::duplex(4096);
45+
46+
let server_handle = tokio::spawn(async move {
47+
CacheHintServer::new()
48+
.serve(server_transport)
49+
.await?
50+
.waiting()
51+
.await?;
52+
anyhow::Ok(())
53+
});
54+
55+
let client = VersionedClient { protocol_version }
56+
.serve(client_transport)
57+
.await
58+
.expect("client should connect");
59+
let tools = client
60+
.list_tools(None)
61+
.await
62+
.expect("tools/list should succeed");
63+
let prompts = client
64+
.list_prompts(None)
65+
.await
66+
.expect("prompts/list should succeed");
67+
68+
client.cancel().await.expect("client should cancel");
69+
server_handle.await.expect("server task").expect("server");
70+
(tools, prompts)
71+
}
72+
73+
#[tokio::test]
74+
async fn handler_macros_should_emit_required_cache_hints_for_2026_07_28() {
75+
let (tools, prompts) = list_results(ProtocolVersion::V_2026_07_28).await;
76+
77+
assert_eq!(
78+
(
79+
tools.ttl_ms,
80+
tools.cache_scope,
81+
prompts.ttl_ms,
82+
prompts.cache_scope,
83+
),
84+
(
85+
Some(0),
86+
Some(CacheScope::Public),
87+
Some(0),
88+
Some(CacheScope::Public),
89+
)
90+
);
91+
}
92+
93+
#[tokio::test]
94+
async fn handler_macros_should_omit_cache_hints_for_legacy_versions() {
95+
let (tools, prompts) = list_results(ProtocolVersion::V_2025_11_25).await;
96+
97+
assert_eq!(
98+
(
99+
tools.ttl_ms,
100+
tools.cache_scope,
101+
prompts.ttl_ms,
102+
prompts.cache_scope,
103+
),
104+
(None, None, None, None)
105+
);
106+
}

0 commit comments

Comments
 (0)