Skip to main content

adk_mcp_sdk/
lib.rs

1//! # ADK MCP SDK
2//!
3//! Enterprise traits for MCP servers targeting the ADK-Rust Enterprise registry.
4//!
5//! Servers implement these traits to declare their capabilities, tools, risk
6//! levels, and health checks — enabling automatic onboarding into the registry.
7
8pub 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/// Generate a tools-only `ServerHandler` with the MCP 2026 stateless features
23/// used across Zavora servers.
24///
25/// The server's tool impl must use `#[rmcp::tool_router]` (without
26/// `server_handler`). Protected tools are held behind an integrity-protected
27/// MRTR approval, and selected tools become protocol-native MCP tasks when the client
28/// advertises task support. Legacy clients continue to execute ordinary tools,
29/// but protected calls fail closed because they cannot complete MRTR approval.
30#[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}