rust_mcp_sdk/mcp_traits/request_context.rs
1use rust_mcp_schema::{
2 schema_utils::RpcErrorCodes, ClientCapabilities, Implementation, JsonObject, LoggingLevel,
3 ProgressToken, ProtocolVersion, RequestMetaObject, RpcError,
4};
5
6/// A client capability that a server handler may require.
7///
8/// Before dispatching a request, the runtime checks the client's
9/// declared capabilities against the handler's requirements. If a
10/// required capability is missing the request is rejected with
11/// [`crate::schema::MISSING_REQUIRED_CLIENT_CAPABILITY`] (-32021).
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum RequiredClientCapability {
14 /// The client must support sampling (`sampling` in `ClientCapabilities`).
15 Sampling,
16 /// The client must support elicitation (`elicitation`).
17 Elicitation,
18 /// The client must support roots (`roots`).
19 Roots,
20 /// The client must declare support for a specific extension.
21 Extension(&'static str),
22}
23
24impl RequiredClientCapability {
25 /// Human-readable name shown in error messages.
26 pub fn as_str(&self) -> &'static str {
27 match self {
28 Self::Sampling => "sampling",
29 Self::Elicitation => "elicitation",
30 Self::Roots => "roots",
31 Self::Extension(key) => key,
32 }
33 }
34
35 /// Check whether the given client capabilities satisfy this requirement.
36 pub fn is_satisfied_by(&self, caps: &ClientCapabilities) -> bool {
37 match self {
38 Self::Sampling => caps.sampling.is_some(),
39 Self::Elicitation => caps.elicitation.is_some(),
40 Self::Roots => caps.roots.is_some(),
41 Self::Extension(key) => caps
42 .extensions
43 .as_ref()
44 .is_some_and(|ext| ext.contains_key(*key)),
45 }
46 }
47
48 /// The `ClientCapabilities`-shaped JSON fragment naming this capability,
49 /// used in `MissingRequiredClientCapabilityError.data.requiredCapabilities`
50 /// (the schema defines it as an object of capability objects, e.g.
51 /// `{ "sampling": {} }`, not an array of names).
52 fn as_capability_json(&self) -> (&'static str, serde_json::Value) {
53 match self {
54 Self::Extension(_) => (self.as_str(), serde_json::json!({})),
55 _ => (self.as_str(), serde_json::json!({})),
56 }
57 }
58}
59
60/// Per-request context extracted from `RequestMetaObject` and carried to every handler.
61///
62/// The 2026-07-28 protocol has no `initialize` handshake: protocol version, client identity,
63/// capabilities and optional progress/log-level hints are declared in `_meta` on every request.
64/// Servers MUST NOT infer these values from prior requests.
65pub struct RequestContext {
66 /// The negotiated protocol version for this request.
67 pub protocol_version: ProtocolVersion,
68 /// The client's capabilities (required per spec).
69 pub client_capabilities: ClientCapabilities,
70 /// The client's self-reported identity (SHOULD per spec).
71 pub client_info: Option<Implementation>,
72 /// Opaque token for progress notifications, if the caller requests them.
73 pub progress_token: Option<ProgressToken>,
74 /// Desired log level for this request (SEP-2577 deprecated; may be absent or ignored).
75 #[allow(deprecated)]
76 pub log_level: Option<LoggingLevel>,
77}
78
79impl RequestContext {
80 /// Builds a validated `RequestContext` from the wire `RequestMetaObject`.
81 ///
82 /// Returns `UnsupportedProtocolVersionError` (-32022) when the protocol
83 /// version is unknown to this implementation or is a known version the
84 /// server has chosen not to support (see
85 /// `crate::utils::supported_protocol_versions`). The error data carries
86 /// the `supported` versions and echoes the `requested` version.
87 pub fn from_request_meta(meta: &RequestMetaObject) -> Result<Self, RpcError> {
88 let unsupported = || {
89 RpcError::new(
90 RpcErrorCodes::UNSUPPORTED_PROTOCOL_VERSION,
91 format!("Unsupported protocol version '{}'", meta.protocol_version),
92 Some(serde_json::json!({
93 "supported": crate::utils::supported_protocol_versions(),
94 "requested": meta.protocol_version,
95 })),
96 )
97 };
98
99 let protocol_version =
100 ProtocolVersion::try_from(meta.protocol_version.as_str()).map_err(|_| unsupported())?;
101
102 if !crate::utils::supported_protocol_versions().contains(&meta.protocol_version) {
103 return Err(unsupported());
104 }
105
106 Ok(Self {
107 protocol_version,
108 client_capabilities: meta.client_capabilities.clone(),
109 client_info: meta.client_info.clone(),
110 progress_token: meta.progress_token.clone(),
111 #[allow(deprecated)]
112 log_level: meta.log_level.clone(),
113 })
114 }
115
116 /// Enforce that the client declared every required capability.
117 ///
118 /// Returns `Ok(())` when all requirements are met, otherwise an error
119 /// with code [`crate::schema::MISSING_REQUIRED_CLIENT_CAPABILITY`] (-32021) whose
120 /// `data.requiredCapabilities` is a `ClientCapabilities`-shaped object
121 /// keyed by each missing capability (e.g. `{ "sampling": {} }`).
122 pub fn ensure_capabilities(
123 &self,
124 method: &str,
125 required: &[RequiredClientCapability],
126 ) -> Result<(), RpcError> {
127 let missing: Vec<_> = required
128 .iter()
129 .filter(|c| !c.is_satisfied_by(&self.client_capabilities))
130 .collect();
131 if missing.is_empty() {
132 return Ok(());
133 }
134
135 let missing_names: Vec<_> = missing.iter().map(|c| c.as_str()).collect();
136 let required_capabilities: serde_json::Map<String, serde_json::Value> = missing
137 .iter()
138 .map(|c| {
139 let (key, value) = c.as_capability_json();
140 (key.to_string(), value)
141 })
142 .collect();
143
144 Err(RpcError::new(
145 RpcErrorCodes::MISSING_REQUIRED_CLIENT_CAPABILITY,
146 format!(
147 "Client must declare {missing_names:?} capability/capabilities to call '{method}'",
148 ),
149 Some(serde_json::json!({
150 "requiredCapabilities": required_capabilities,
151 })),
152 ))
153 }
154
155 /// Returns the client's declared extensions, if any.
156 pub fn client_extensions(&self) -> Option<&std::collections::BTreeMap<String, JsonObject>> {
157 self.client_capabilities.extensions.as_ref()
158 }
159
160 /// A minimal context for custom/non-standard requests that carry no
161 /// `RequestMetaObject`. Uses the current compiled protocol version
162 /// and empty capabilities.
163 pub fn empty() -> Self {
164 Self {
165 protocol_version: ProtocolVersion::latest(),
166 client_capabilities: ClientCapabilities::default(),
167 client_info: None,
168 progress_token: None,
169 #[allow(deprecated)]
170 log_level: None,
171 }
172 }
173}