Skip to main content

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}