1pub mod health;
9pub mod manifest;
10pub mod protocol;
11pub mod risk;
12pub mod tools;
13
14pub use health::{HealthCheck, HealthStatus};
15pub use manifest::ServerManifest;
16pub use protocol::{
17 Mcp2026Policy, approval_gate, caller_identity, current_caller_identity, scope_caller_identity,
18};
19pub use risk::RiskClass;
20pub use tools::ToolMeta;
21
22#[macro_export]
31macro_rules! mcp_2026_server {
32 (
33 server: $server:ty,
34 task_tools: [$($task_tool:literal),* $(,)?],
35 approval_tools: [$($approval_tool:literal),* $(,)?],
36 cache_ttl_ms: $cache_ttl_ms:expr $(,)?
37 ) => {
38 const _: () = {
39 fn __adk_tasks() -> &'static ::rmcp::task_manager::TaskManager {
40 static TASKS: ::std::sync::OnceLock<::rmcp::task_manager::TaskManager> =
41 ::std::sync::OnceLock::new();
42 TASKS.get_or_init(::rmcp::task_manager::TaskManager::new)
43 }
44
45 impl ::rmcp::ServerHandler for $server {
46 async fn call_tool(
47 &self,
48 request: ::rmcp::model::CallToolRequestParams,
49 context: ::rmcp::service::RequestContext<::rmcp::service::RoleServer>,
50 ) -> ::std::result::Result<
51 ::rmcp::model::CallToolResponse,
52 ::rmcp::ErrorData,
53 > {
54 let policy = $crate::Mcp2026Policy::new(
55 &[$($task_tool),*],
56 &[$($approval_tool),*],
57 $cache_ttl_ms,
58 );
59 let caller = context.client_info().map(|client| client.name);
60
61 if let Some(response) = $crate::approval_gate(&request, &context, &policy)? {
62 return Ok(response);
63 }
64
65 let client_supports_tasks = context
66 .client_capabilities()
67 .is_some_and(|capabilities| capabilities.supports_tasks());
68 if client_supports_tasks && policy.is_task_tool(request.name.as_ref()) {
69 let owned_server = self.clone();
70 let owned_context = context.clone();
71 let task = __adk_tasks().spawn(
72 ::rmcp::task_manager::TaskOptions::new()
73 .with_ttl_ms(policy.task_ttl_ms)
74 .with_poll_interval_ms(policy.task_poll_interval_ms)
75 .with_status_message(format!("Running {}", request.name)),
76 move |task_context| {
77 Box::pin(async move {
78 let router = <$server>::tool_router();
79 let call = router.call(
80 ::rmcp::handler::server::tool::ToolCallContext::new(
81 &owned_server,
82 request,
83 owned_context,
84 ),
85 );
86 $crate::scope_caller_identity(caller, async move { ::tokio::select! {
87 _ = task_context.cancelled() => {
88 Err(::rmcp::task_manager::TaskExit::Cancelled)
89 }
90 result = call => match result {
91 Ok(::rmcp::model::CallToolResponse::Complete(result)) => Ok(result),
92 Ok(_) => Err(::rmcp::task_manager::TaskExit::Error(
93 ::rmcp::ErrorData::internal_error(
94 "nested task or input-required response is not supported",
95 None,
96 ),
97 )),
98 Err(error) => Err(::rmcp::task_manager::TaskExit::Error(error)),
99 }
100 }}).await
101 })
102 },
103 );
104 return Ok(::rmcp::model::CallToolResponse::Task(
105 ::rmcp::model::CreateTaskResult::new(task),
106 ));
107 }
108
109 $crate::scope_caller_identity(caller, <$server>::tool_router()
110 .call(::rmcp::handler::server::tool::ToolCallContext::new(
111 self, request, context,
112 )))
113 .await
114 }
115
116 async fn list_tools(
117 &self,
118 _request: Option<::rmcp::model::PaginatedRequestParams>,
119 _context: ::rmcp::service::RequestContext<::rmcp::service::RoleServer>,
120 ) -> ::std::result::Result<::rmcp::model::ListToolsResult, ::rmcp::ErrorData> {
121 Ok(::rmcp::model::ListToolsResult::with_all_items(
122 <$server>::tool_router().list_all(),
123 )
124 .with_ttl_ms($cache_ttl_ms)
125 .with_cache_scope(::rmcp::model::CacheScope::Public))
126 }
127
128 async fn get_task(
129 &self,
130 request: ::rmcp::model::GetTaskParams,
131 _context: ::rmcp::service::RequestContext<::rmcp::service::RoleServer>,
132 ) -> ::std::result::Result<::rmcp::model::GetTaskResult, ::rmcp::ErrorData> {
133 Ok(::rmcp::model::GetTaskResult::new(
134 __adk_tasks().get_task(&request.task_id)?,
135 ))
136 }
137
138 async fn update_task(
139 &self,
140 request: ::rmcp::model::UpdateTaskParams,
141 _context: ::rmcp::service::RequestContext<::rmcp::service::RoleServer>,
142 ) -> ::std::result::Result<(), ::rmcp::ErrorData> {
143 __adk_tasks().update_task(&request.task_id, request.input_responses)
144 }
145
146 async fn cancel_task(
147 &self,
148 request: ::rmcp::model::CancelTaskParams,
149 _context: ::rmcp::service::RequestContext<::rmcp::service::RoleServer>,
150 ) -> ::std::result::Result<(), ::rmcp::ErrorData> {
151 __adk_tasks().cancel_task(&request.task_id)
152 }
153
154 fn get_info(&self) -> ::rmcp::model::ServerInfo {
155 ::rmcp::model::ServerInfo::new(
156 ::rmcp::model::ServerCapabilities::builder()
157 .enable_tools()
158 .enable_tasks()
159 .build(),
160 )
161 .with_server_info(::rmcp::model::Implementation::from_build_env())
162 .with_instructions(
163 "Stateless MCP 2026 server with Tasks, sealed MRTR approvals, per-request identity, and cache hints. Legacy initialization remains supported."
164 .to_string(),
165 )
166 }
167 }
168 };
169 };
170}