diff --git a/conformance/src/bin/client.rs b/conformance/src/bin/client.rs index b9c8cea9d..253451729 100644 --- a/conformance/src/bin/client.rs +++ b/conformance/src/bin/client.rs @@ -252,12 +252,8 @@ async fn perform_oauth_flow_preregistered( manager.set_metadata(metadata); // Configure with pre-registered credentials - let config = rmcp::transport::auth::OAuthClientConfig { - client_id: client_id.to_string(), - client_secret: Some(client_secret.to_string()), - scopes: vec![], - redirect_uri: REDIRECT_URI.to_string(), - }; + let config = rmcp::transport::auth::OAuthClientConfig::new(client_id, REDIRECT_URI) + .with_client_secret(client_secret); manager.configure_client(config)?; let scopes = manager.select_scopes(None, &[]); diff --git a/conformance/src/bin/server.rs b/conformance/src/bin/server.rs index bfa98f42c..5ca4b5922 100644 --- a/conformance/src/bin/server.rs +++ b/conformance/src/bin/server.rs @@ -818,10 +818,7 @@ async fn main() -> anyhow::Result<()> { tracing::info!("Starting conformance server on {}", bind_addr); let server = ConformanceServer::new(); - let config = StreamableHttpServerConfig { - stateful_mode: true, - ..Default::default() - }; + let config = StreamableHttpServerConfig::default(); let service = StreamableHttpService::new( move || Ok(server.clone()), LocalSessionManager::default().into(), diff --git a/crates/rmcp/Cargo.toml b/crates/rmcp/Cargo.toml index 9c6133989..87940df1b 100644 --- a/crates/rmcp/Cargo.toml +++ b/crates/rmcp/Cargo.toml @@ -9,6 +9,10 @@ readme = { workspace = true } description = "Rust SDK for Model Context Protocol" documentation = "https://docs.rs/rmcp" +[lints.clippy] +exhaustive_structs = "warn" +exhaustive_enums = "warn" + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/rmcp/src/handler/server/common.rs b/crates/rmcp/src/handler/server/common.rs index 2ecab6386..0496f6183 100644 --- a/crates/rmcp/src/handler/server/common.rs +++ b/crates/rmcp/src/handler/server/common.rs @@ -138,6 +138,7 @@ where } } +#[allow(clippy::exhaustive_structs)] pub struct Extension(pub T); impl FromContextPart for Extension @@ -182,6 +183,7 @@ where } } +#[allow(clippy::exhaustive_structs)] pub struct RequestId(pub crate::model::RequestId); impl FromContextPart for RequestId diff --git a/crates/rmcp/src/handler/server/prompt.rs b/crates/rmcp/src/handler/server/prompt.rs index 27a03e835..f312d6d80 100644 --- a/crates/rmcp/src/handler/server/prompt.rs +++ b/crates/rmcp/src/handler/server/prompt.rs @@ -20,6 +20,7 @@ use crate::{ }; /// Context for prompt retrieval operations +#[non_exhaustive] pub struct PromptContext<'a, S> { pub server: &'a S, pub name: String, @@ -117,6 +118,7 @@ impl IntoGetPromptResult for Result // Future wrapper that automatically handles IntoGetPromptResult conversion pin_project_lite::pin_project! { #[project = IntoGetPromptResultFutProj] + #[non_exhaustive] pub enum IntoGetPromptResultFut { Pending { #[pin] @@ -151,6 +153,7 @@ where } // Prompt-specific extractor for prompt name +#[allow(clippy::exhaustive_structs)] pub struct PromptName(pub String); impl FromContextPart> for PromptName { diff --git a/crates/rmcp/src/handler/server/router.rs b/crates/rmcp/src/handler/server/router.rs index 1f34ba5b2..08beb61d2 100644 --- a/crates/rmcp/src/handler/server/router.rs +++ b/crates/rmcp/src/handler/server/router.rs @@ -13,6 +13,7 @@ use crate::{ pub mod prompt; pub mod tool; +#[non_exhaustive] pub struct Router { pub tool_router: tool::ToolRouter, pub prompt_router: prompt::PromptRouter, diff --git a/crates/rmcp/src/handler/server/router/prompt.rs b/crates/rmcp/src/handler/server/router/prompt.rs index b5ea4a47f..36a1f8ed2 100644 --- a/crates/rmcp/src/handler/server/router/prompt.rs +++ b/crates/rmcp/src/handler/server/router/prompt.rs @@ -6,6 +6,7 @@ use crate::{ service::{MaybeBoxFuture, MaybeSend}, }; +#[non_exhaustive] pub struct PromptRoute { #[allow(clippy::type_complexity)] pub get: Arc>, @@ -90,6 +91,7 @@ where } /// Adapter for functions generated by the #\[prompt\] macro +#[allow(clippy::exhaustive_structs)] pub struct PromptAttrGenerateFunctionAdapter; impl IntoPromptRoute for F @@ -103,6 +105,7 @@ where } #[derive(Debug)] +#[non_exhaustive] pub struct PromptRouter { #[allow(clippy::type_complexity)] pub map: std::collections::HashMap, PromptRoute>, diff --git a/crates/rmcp/src/handler/server/router/tool.rs b/crates/rmcp/src/handler/server/router/tool.rs index 42c582c40..6a412154f 100644 --- a/crates/rmcp/src/handler/server/router/tool.rs +++ b/crates/rmcp/src/handler/server/router/tool.rs @@ -136,6 +136,7 @@ use crate::{ service::{MaybeBoxFuture, MaybeSend}, }; +#[non_exhaustive] pub struct ToolRoute { #[allow(clippy::type_complexity)] pub call: Arc>, @@ -216,6 +217,7 @@ where } } +#[allow(clippy::exhaustive_structs)] pub struct ToolAttrGenerateFunctionAdapter; impl IntoToolRoute for F where @@ -251,6 +253,7 @@ where } } +#[non_exhaustive] pub struct WithToolAttr where C: CallToolHandler + MaybeSend + Clone + 'static, @@ -292,6 +295,7 @@ where } } #[derive(Debug)] +#[non_exhaustive] pub struct ToolRouter { #[allow(clippy::type_complexity)] pub map: std::collections::HashMap, ToolRoute>, diff --git a/crates/rmcp/src/handler/server/tool.rs b/crates/rmcp/src/handler/server/tool.rs index 0ad8ce61a..a0e884512 100644 --- a/crates/rmcp/src/handler/server/tool.rs +++ b/crates/rmcp/src/handler/server/tool.rs @@ -29,6 +29,7 @@ pub fn parse_json_object(input: JsonObject) -> Result { pub request_context: RequestContext, pub service: &'s S, @@ -104,6 +105,7 @@ impl IntoCallToolResult for Result { pin_project_lite::pin_project! { #[project = IntoCallToolResultFutProj] + #[non_exhaustive] pub enum IntoCallToolResultFut { Pending { #[pin] @@ -163,6 +165,7 @@ pub type DynCallToolHandler = -> futures::future::LocalBoxFuture<'s, Result>; // Tool-specific extractor for tool name +#[allow(clippy::exhaustive_structs)] pub struct ToolName(pub Cow<'static, str>); impl FromContextPart> for ToolName { diff --git a/crates/rmcp/src/handler/server/wrapper/json.rs b/crates/rmcp/src/handler/server/wrapper/json.rs index 8eae30268..82ed1a07c 100644 --- a/crates/rmcp/src/handler/server/wrapper/json.rs +++ b/crates/rmcp/src/handler/server/wrapper/json.rs @@ -14,6 +14,7 @@ use crate::{ /// serialized as structured JSON content with an associated schema. /// The framework will place the JSON in the `structured_content` field /// of the tool result rather than the regular `content` field. +#[allow(clippy::exhaustive_structs)] pub struct Json(pub T); // Implement JsonSchema for Json to delegate to T's schema diff --git a/crates/rmcp/src/handler/server/wrapper/parameters.rs b/crates/rmcp/src/handler/server/wrapper/parameters.rs index 9de73dd66..ed5331a67 100644 --- a/crates/rmcp/src/handler/server/wrapper/parameters.rs +++ b/crates/rmcp/src/handler/server/wrapper/parameters.rs @@ -42,6 +42,7 @@ use schemars::JsonSchema; /// - Returns appropriate error responses if deserialization fails #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(transparent)] +#[allow(clippy::exhaustive_structs)] pub struct Parameters

(pub P); impl JsonSchema for Parameters

{ diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index fa47ea788..b440ea7a1 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -58,6 +58,7 @@ macro_rules! object { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy, Eq)] #[serde(deny_unknown_fields)] #[cfg_attr(feature = "server", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct EmptyObject {} pub trait ConstString: Default { @@ -70,6 +71,7 @@ pub trait ConstString: Default { macro_rules! const_string { ($name:ident = $value:literal) => { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] + #[allow(clippy::exhaustive_structs)] pub struct $name; impl ConstString for $name { @@ -196,6 +198,7 @@ impl<'de> Deserialize<'de> for ProtocolVersion { /// This is commonly used for request IDs and other identifiers in JSON-RPC /// where the specification allows both numeric and string values. #[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[allow(clippy::exhaustive_enums)] pub enum NumberOrString { /// A numeric identifier Number(i64), @@ -292,6 +295,7 @@ pub type RequestId = NumberOrString; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Hash, Eq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ProgressToken(pub NumberOrString); // ============================================================================= @@ -338,6 +342,7 @@ impl GetExtensions for Request { #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RequestOptionalParam { pub method: M, // #[serde(skip_serializing_if = "Option::is_none")] @@ -361,6 +366,7 @@ impl RequestOptionalParam { #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RequestNoParam { pub method: M, /// extensions will carry anything possible in the context, including [`Meta`] @@ -403,6 +409,7 @@ impl Notification { #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct NotificationNoParam { pub method: M, /// extensions will carry anything possible in the context, including [`Meta`] @@ -414,6 +421,7 @@ pub struct NotificationNoParam { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct JsonRpcRequest { pub jsonrpc: JsonRpcVersion2_0, pub id: RequestId, @@ -435,6 +443,7 @@ impl JsonRpcRequest { type DefaultResponse = JsonObject; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct JsonRpcResponse { pub jsonrpc: JsonRpcVersion2_0, pub id: RequestId, @@ -443,6 +452,7 @@ pub struct JsonRpcResponse { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct JsonRpcError { pub jsonrpc: JsonRpcVersion2_0, pub id: RequestId, @@ -462,6 +472,7 @@ impl JsonRpcError { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct JsonRpcNotification { pub jsonrpc: JsonRpcVersion2_0, #[serde(flatten)] @@ -475,6 +486,7 @@ pub struct JsonRpcNotification { #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ErrorCode(pub i32); impl ErrorCode { @@ -493,6 +505,7 @@ impl ErrorCode { /// providing a standardized way to communicate errors between clients and servers. #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ErrorData { /// The error type that occurred (using standard JSON-RPC error codes) pub code: ErrorCode, @@ -552,6 +565,7 @@ impl ErrorData { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum JsonRpcMessage { /// A single request expecting a response Request(JsonRpcRequest), @@ -651,6 +665,7 @@ impl From for () { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CustomResult(pub Value); impl CustomResult { @@ -667,6 +682,7 @@ impl CustomResult { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CancelledNotificationParam { pub request_id: RequestId, pub reason: Option, @@ -691,6 +707,7 @@ pub type CancelledNotification = /// deserialize them into domain-specific types. #[derive(Debug, Clone)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CustomNotification { pub method: String, pub params: Option, @@ -725,6 +742,7 @@ impl CustomNotification { /// deserialize them into domain-specific types. #[derive(Debug, Clone)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CustomRequest { pub method: String, pub params: Option, @@ -1050,6 +1068,7 @@ const_string!(ProgressNotificationMethod = "notifications/progress"); #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ProgressNotificationParam { pub progress_token: ProgressToken, /// The progress thus far. This should increase every time progress is made, even if the total is unknown. @@ -1097,6 +1116,7 @@ macro_rules! paginated_result { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] + #[allow(clippy::exhaustive_structs)] pub struct $t { #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, @@ -1293,6 +1313,7 @@ const_string!(ResourceUpdatedNotificationMethod = "notifications/resources/updat #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ResourceUpdatedNotificationParam { /// The URI of the resource that was updated pub uri: String, @@ -1392,6 +1413,7 @@ pub type ToolListChangedNotification = NotificationNoParam { Single(T), Multiple(Vec), @@ -1665,6 +1691,7 @@ pub struct SamplingMessage { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum SamplingMessageContent { Text(RawTextContent), Image(RawImageContent), @@ -1792,6 +1819,7 @@ impl TryFrom for SamplingContent { /// should be provided to the LLM when processing sampling requests. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum ContextInclusion { /// Include context from all connected MCP servers #[serde(rename = "allServers")] @@ -2119,6 +2147,7 @@ impl ModelHint { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CompletionContext { /// Previously resolved argument values that can inform completion suggestions #[serde(skip_serializing_if = "Option::is_none")] @@ -2209,6 +2238,7 @@ pub type CompleteRequest = Request #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CompletionInfo { pub values: Vec, #[serde(skip_serializing_if = "Option::is_none")] @@ -2302,6 +2332,7 @@ impl CompleteResult { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(tag = "type")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum Reference { #[serde(rename = "ref/resource")] Resource(ResourceReference), @@ -2353,6 +2384,7 @@ impl Reference { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ResourceReference { pub uri: String, } @@ -2386,6 +2418,7 @@ const_string!(CompleteRequestMethod = "completion/complete"); #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ArgumentInfo { pub name: String, pub value: String, @@ -2460,6 +2493,7 @@ const_string!(ElicitationCompletionNotificationMethod = "notifications/elicitati #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum ElicitationAction { /// User accepts the request and provides the requested information Accept, @@ -2571,6 +2605,7 @@ impl TryFrom for CreateElicitati try_from = "CreateElicitationRequestParamDeserializeHelper" )] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum CreateElicitationRequestParams { #[serde(rename = "form", rename_all = "camelCase")] FormElicitationParams { @@ -2631,6 +2666,7 @@ pub type CreateElicitationRequestParam = CreateElicitationRequestParams; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CreateElicitationResult { /// The user's decision on how to handle the elicitation request pub action: ElicitationAction, @@ -2666,6 +2702,7 @@ pub type CreateElicitationRequest = #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ElicitationResponseNotificationParam { pub elicitation_id: String, } @@ -2691,7 +2728,7 @@ pub type ElicitationCompletionNotification = /// /// Contains the content returned by the tool execution and an optional /// flag indicating whether the operation resulted in an error. -#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Default, Debug, Serialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] @@ -2710,6 +2747,48 @@ pub struct CallToolResult { pub meta: Option, } +// Custom Deserialize implementation that: +// 1. Defaults `content` to `[]` when the field is missing (lenient per Postel's law) +// 2. Requires at least one known field to be present, so that `CallToolResult` doesn't +// greedily match arbitrary JSON objects when used inside `#[serde(untagged)]` enums +// (e.g. `ServerResult`), which would shadow `CustomResult`. +impl<'de> Deserialize<'de> for CallToolResult { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Helper { + content: Option>, + structured_content: Option, + is_error: Option, + #[serde(rename = "_meta")] + meta: Option, + } + + let helper = Helper::deserialize(deserializer)?; + + if helper.content.is_none() + && helper.structured_content.is_none() + && helper.is_error.is_none() + && helper.meta.is_none() + { + return Err(serde::de::Error::custom( + "expected at least one known CallToolResult field \ + (content, structuredContent, isError, or _meta)", + )); + } + + Ok(CallToolResult { + content: helper.content.unwrap_or_default(), + structured_content: helper.structured_content, + is_error: helper.is_error, + meta: helper.meta, + }) + } +} + impl CallToolResult { /// Create a successful tool result with unstructured content pub fn success(content: Vec) -> Self { @@ -2990,6 +3069,7 @@ pub type GetTaskInfoRequest = Request; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct GetTaskInfoParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] @@ -3019,6 +3099,7 @@ pub type GetTaskResultRequest = Request; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CancelTaskParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] @@ -3119,6 +3201,7 @@ macro_rules! ts_union { #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] + #[allow(clippy::exhaustive_enums)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum $U { $($declared)* diff --git a/crates/rmcp/src/model/annotated.rs b/crates/rmcp/src/model/annotated.rs index 9158e10be..b6e4e1220 100644 --- a/crates/rmcp/src/model/annotated.rs +++ b/crates/rmcp/src/model/annotated.rs @@ -39,6 +39,7 @@ impl Annotations { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct Annotated { #[serde(flatten)] pub raw: T, diff --git a/crates/rmcp/src/model/capabilities.rs b/crates/rmcp/src/model/capabilities.rs index b47a8a849..5feff5c41 100644 --- a/crates/rmcp/src/model/capabilities.rs +++ b/crates/rmcp/src/model/capabilities.rs @@ -34,6 +34,7 @@ pub type ExtensionCapabilities = BTreeMap; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct PromptsCapability { #[serde(skip_serializing_if = "Option::is_none")] pub list_changed: Option, @@ -42,6 +43,7 @@ pub struct PromptsCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ResourcesCapability { #[serde(skip_serializing_if = "Option::is_none")] pub subscribe: Option, @@ -52,6 +54,7 @@ pub struct ResourcesCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ToolsCapability { #[serde(skip_serializing_if = "Option::is_none")] pub list_changed: Option, @@ -60,6 +63,7 @@ pub struct ToolsCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RootsCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub list_changed: Option, @@ -69,6 +73,7 @@ pub struct RootsCapabilities { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct TasksCapability { #[serde(skip_serializing_if = "Option::is_none")] pub requests: Option, @@ -82,6 +87,7 @@ pub struct TasksCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct TaskRequestsCapability { #[serde(skip_serializing_if = "Option::is_none")] pub sampling: Option, @@ -94,6 +100,7 @@ pub struct TaskRequestsCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct SamplingTaskCapability { #[serde(skip_serializing_if = "Option::is_none")] pub create_message: Option, @@ -102,6 +109,7 @@ pub struct SamplingTaskCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ElicitationTaskCapability { #[serde(skip_serializing_if = "Option::is_none")] pub create: Option, @@ -110,6 +118,7 @@ pub struct ElicitationTaskCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ToolsTaskCapability { #[serde(skip_serializing_if = "Option::is_none")] pub call: Option, @@ -190,6 +199,7 @@ impl TasksCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct FormElicitationCapability { /// Whether the client supports JSON Schema validation for elicitation responses. /// When true, the client will validate user input against the requested_schema @@ -201,6 +211,7 @@ pub struct FormElicitationCapability { /// Capability for URL mode elicitation. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct UrlElicitationCapability {} /// Elicitation allows servers to request interactive input from users during tool execution. @@ -209,6 +220,7 @@ pub struct UrlElicitationCapability {} #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ElicitationCapability { /// Whether client supports form-based elicitation. #[serde(skip_serializing_if = "Option::is_none")] @@ -222,6 +234,7 @@ pub struct ElicitationCapability { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct SamplingCapability { /// Support for `tools` and `toolChoice` parameters #[serde(skip_serializing_if = "Option::is_none")] @@ -310,10 +323,12 @@ macro_rules! builder { ($Target: ident {$($f: ident: $T: ty),* $(,)?}) => { paste! { #[derive(Default, Clone, Copy, Debug)] + #[allow(clippy::exhaustive_structs)] pub struct [<$Target BuilderState>]< $(const [<$f:upper>]: bool = false,)* >; #[derive(Debug, Default)] + #[allow(clippy::exhaustive_structs)] pub struct [<$Target Builder>]]> { $(pub $f: Option<$T>,)* pub state: PhantomData diff --git a/crates/rmcp/src/model/content.rs b/crates/rmcp/src/model/content.rs index 83658b023..281f5e2a9 100644 --- a/crates/rmcp/src/model/content.rs +++ b/crates/rmcp/src/model/content.rs @@ -9,6 +9,7 @@ use super::{AnnotateAble, Annotated, resource::ResourceContents}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawTextContent { pub text: String, /// Optional protocol-level metadata for this content block @@ -19,6 +20,7 @@ pub type TextContent = Annotated; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawImageContent { /// The base64-encoded image pub data: String, @@ -32,6 +34,7 @@ pub type ImageContent = Annotated; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawEmbeddedResource { /// Optional protocol-level metadata for this content block #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] @@ -63,6 +66,7 @@ impl EmbeddedResource { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawAudioContent { pub data: String, pub mime_type: String, @@ -145,6 +149,7 @@ impl ToolResultContent { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum RawContent { Text(RawTextContent), Image(RawImageContent), diff --git a/crates/rmcp/src/model/elicitation_schema.rs b/crates/rmcp/src/model/elicitation_schema.rs index cdbb87d6d..b782d9519 100644 --- a/crates/rmcp/src/model/elicitation_schema.rs +++ b/crates/rmcp/src/model/elicitation_schema.rs @@ -49,6 +49,7 @@ const_string!(ArrayTypeConst = "array"); #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(untagged)] +#[allow(clippy::exhaustive_enums)] pub enum PrimitiveSchema { /// Enum property (explicit enum schema) Enum(EnumSchema), @@ -70,6 +71,7 @@ pub enum PrimitiveSchema { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "kebab-case")] +#[allow(clippy::exhaustive_enums)] pub enum StringFormat { /// Email address format Email, @@ -344,6 +346,7 @@ impl NumberSchema { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] +#[allow(clippy::exhaustive_structs)] pub struct IntegerSchema { /// Type discriminator #[serde(rename = "type")] @@ -510,6 +513,7 @@ impl BooleanSchema { /// Represent single entry for titled item #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct ConstTitle { #[serde(rename = "const")] pub const_: String, @@ -530,6 +534,7 @@ impl ConstTitle { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct LegacyEnumSchema { #[serde(rename = "type")] pub type_: StringTypeConst, @@ -595,6 +600,7 @@ impl TitledSingleSelectEnumSchema { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(untagged)] +#[allow(clippy::exhaustive_enums)] pub enum SingleSelectEnumSchema { Untitled(UntitledSingleSelectEnumSchema), Titled(TitledSingleSelectEnumSchema), @@ -603,6 +609,7 @@ pub enum SingleSelectEnumSchema { /// Items for untitled multi-select options #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct UntitledItems { #[serde(rename = "type")] pub type_: StringTypeConst, @@ -613,6 +620,7 @@ pub struct UntitledItems { /// Items for titled multi-select options #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct TitledItems { // MCP spec requires "anyOf" for multi-select enums (allows any combination) // Alias "oneOf" for compatibility with schemars @@ -718,6 +726,7 @@ impl TitledMultiSelectEnumSchema { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(untagged)] +#[allow(clippy::exhaustive_enums)] pub enum MultiSelectEnumSchema { Untitled(UntitledMultiSelectEnumSchema), Titled(TitledMultiSelectEnumSchema), @@ -741,6 +750,7 @@ pub enum MultiSelectEnumSchema { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(untagged)] +#[allow(clippy::exhaustive_enums)] pub enum EnumSchema { Single(SingleSelectEnumSchema), Multi(MultiSelectEnumSchema), @@ -749,9 +759,11 @@ pub enum EnumSchema { /// Marker type for single-select enum builder #[derive(Debug)] +#[allow(clippy::exhaustive_structs)] pub struct SingleSelect; /// Marker type for multi-select enum builder #[derive(Debug)] +#[allow(clippy::exhaustive_structs)] pub struct MultiSelect; /// Builder for EnumSchema /// Allows to create various enum schema types (single/multi select, titled/untitled) @@ -1077,6 +1089,7 @@ impl EnumSchema { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] +#[allow(clippy::exhaustive_structs)] pub struct ElicitationSchema { /// Always "object" for elicitation schemas #[serde(rename = "type")] @@ -1221,6 +1234,7 @@ impl ElicitationSchema { /// .build(); /// ``` #[derive(Debug, Default)] +#[allow(clippy::exhaustive_structs)] pub struct ElicitationSchemaBuilder { pub properties: BTreeMap, pub required: Vec, diff --git a/crates/rmcp/src/model/meta.rs b/crates/rmcp/src/model/meta.rs index c60762a35..1690cce2d 100644 --- a/crates/rmcp/src/model/meta.rs +++ b/crates/rmcp/src/model/meta.rs @@ -195,6 +195,7 @@ variant_extension! { #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(transparent)] +#[allow(clippy::exhaustive_structs)] pub struct Meta(pub JsonObject); const PROGRESS_TOKEN_FIELD: &str = "progressToken"; impl Meta { diff --git a/crates/rmcp/src/model/prompt.rs b/crates/rmcp/src/model/prompt.rs index 531a86d25..19fbdd0fb 100644 --- a/crates/rmcp/src/model/prompt.rs +++ b/crates/rmcp/src/model/prompt.rs @@ -138,6 +138,7 @@ impl PromptArgument { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum PromptMessageRole { User, Assistant, @@ -147,6 +148,7 @@ pub enum PromptMessageRole { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum PromptMessageContent { /// Plain text content Text { text: String }, diff --git a/crates/rmcp/src/model/resource.rs b/crates/rmcp/src/model/resource.rs index 8a25e25ba..9b43e7e14 100644 --- a/crates/rmcp/src/model/resource.rs +++ b/crates/rmcp/src/model/resource.rs @@ -6,6 +6,7 @@ use super::{Annotated, Icon, Meta}; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawResource { /// URI representing the resource location (e.g., "file:///path/to/file" or "str:///content") pub uri: String, @@ -39,6 +40,7 @@ pub type Resource = Annotated; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct RawResourceTemplate { pub uri_template: String, pub name: String, @@ -58,6 +60,7 @@ pub type ResourceTemplate = Annotated; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum ResourceContents { #[serde(rename_all = "camelCase")] TextResourceContents { diff --git a/crates/rmcp/src/model/task.rs b/crates/rmcp/src/model/task.rs index 8373aa243..699b47eb5 100644 --- a/crates/rmcp/src/model/task.rs +++ b/crates/rmcp/src/model/task.rs @@ -7,6 +7,7 @@ use super::Meta; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum TaskStatus { /// The receiver accepted the request and is currently working on it. #[default] @@ -110,6 +111,7 @@ impl CreateTaskResult { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct GetTaskResult { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, @@ -123,7 +125,7 @@ pub struct GetTaskResult { /// (e.g., `CallToolResult` for `tools/call`). This is represented as /// an open object. The payload is the original request's result /// serialized as a JSON value. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct GetTaskPayloadResult(pub Value); @@ -135,12 +137,32 @@ impl GetTaskPayloadResult { } } +// Custom Deserialize that always fails, so that `GetTaskPayloadResult` is skipped +// during `#[serde(untagged)]` enum deserialization (e.g. `ServerResult`). +// The payload has the same JSON shape as `CustomResult(Value)`, so they are +// indistinguishable. `CustomResult` acts as the catch-all instead. +// `GetTaskPayloadResult` should be constructed programmatically via `::new()`. +impl<'de> serde::Deserialize<'de> for GetTaskPayloadResult { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Consume the value so the deserializer state stays consistent. + serde::de::IgnoredAny::deserialize(deserializer)?; + Err(serde::de::Error::custom( + "GetTaskPayloadResult cannot be deserialized directly; \ + use CustomResult as the catch-all", + )) + } +} + /// Response to a `tasks/cancel` request. /// /// Per spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct CancelTaskResult { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, @@ -152,6 +174,7 @@ pub struct CancelTaskResult { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_structs)] pub struct TaskList { pub tasks: Vec, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/rmcp/src/model/tool.rs b/crates/rmcp/src/model/tool.rs index ca6e56915..288f8c7c1 100644 --- a/crates/rmcp/src/model/tool.rs +++ b/crates/rmcp/src/model/tool.rs @@ -51,6 +51,7 @@ pub struct Tool { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[allow(clippy::exhaustive_enums)] pub enum TaskSupport { /// Clients MUST NOT invoke this tool as a task (default behavior). #[default] diff --git a/crates/rmcp/src/service.rs b/crates/rmcp/src/service.rs index 3bad42519..65b5ee719 100644 --- a/crates/rmcp/src/service.rs +++ b/crates/rmcp/src/service.rs @@ -307,6 +307,7 @@ type Responder = tokio::sync::oneshot::Sender; /// /// or wait for response by call [`RequestHandle::await_response`] #[derive(Debug)] +#[non_exhaustive] pub struct RequestHandle { pub rx: tokio::sync::oneshot::Receiver>, pub options: PeerRequestOptions, @@ -398,6 +399,7 @@ impl std::fmt::Debug for Peer { type ProxyOutbound = mpsc::Receiver>; #[derive(Debug, Default)] +#[non_exhaustive] pub struct PeerRequestOptions { pub timeout: Option, pub meta: Option, @@ -648,6 +650,7 @@ pub enum QuitReason { /// Request execution context #[derive(Debug, Clone)] +#[non_exhaustive] pub struct RequestContext { /// this token will be cancelled when the [`CancelledNotification`] is received. pub ct: CancellationToken, @@ -673,6 +676,7 @@ impl RequestContext { /// Request execution context #[derive(Debug, Clone)] +#[non_exhaustive] pub struct NotificationContext { pub meta: Meta, pub extensions: Extensions, diff --git a/crates/rmcp/src/service/client.rs b/crates/rmcp/src/service/client.rs index 8b49606e4..ba88a84c0 100644 --- a/crates/rmcp/src/service/client.rs +++ b/crates/rmcp/src/service/client.rs @@ -140,6 +140,7 @@ where } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[allow(clippy::exhaustive_structs)] pub struct RoleClient; impl ServiceRole for RoleClient { diff --git a/crates/rmcp/src/service/server.rs b/crates/rmcp/src/service/server.rs index 5946d23a4..a92d51019 100644 --- a/crates/rmcp/src/service/server.rs +++ b/crates/rmcp/src/service/server.rs @@ -27,6 +27,7 @@ use crate::{ }; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[allow(clippy::exhaustive_structs)] pub struct RoleServer; impl ServiceRole for RoleServer { @@ -571,6 +572,7 @@ macro_rules! elicit_safe { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] pub enum ElicitationMode { Form, Url, diff --git a/crates/rmcp/src/task_manager.rs b/crates/rmcp/src/task_manager.rs index 774c542f8..32bcf8f0e 100644 --- a/crates/rmcp/src/task_manager.rs +++ b/crates/rmcp/src/task_manager.rs @@ -19,6 +19,7 @@ pub type OperationFuture = /// Describes metadata associated with an enqueued task. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct OperationDescriptor { pub operation_id: String, pub name: String, @@ -55,6 +56,7 @@ impl OperationDescriptor { } /// Operation message describing a unit of asynchronous work. +#[non_exhaustive] pub struct OperationMessage { pub descriptor: OperationDescriptor, pub future: OperationFuture, @@ -91,6 +93,7 @@ struct RunningTask { descriptor: OperationDescriptor, } +#[non_exhaustive] pub struct TaskResult { pub descriptor: OperationDescriptor, pub result: Result, Error>, diff --git a/crates/rmcp/src/transport.rs b/crates/rmcp/src/transport.rs index 8a90542d8..26376f8cf 100644 --- a/crates/rmcp/src/transport.rs +++ b/crates/rmcp/src/transport.rs @@ -151,6 +151,7 @@ where fn into_transport(self) -> impl Transport + 'static; } +#[non_exhaustive] pub enum TransportAdapterIdentity {} impl IntoTransport for T where @@ -231,6 +232,7 @@ where #[derive(Debug, thiserror::Error)] #[error("Transport [{transport_name}] error: {error}")] +#[non_exhaustive] pub struct DynamicTransportError { pub transport_name: Cow<'static, str>, pub transport_type_id: std::any::TypeId, diff --git a/crates/rmcp/src/transport/async_rw.rs b/crates/rmcp/src/transport/async_rw.rs index ff4ecc65b..b14d94c33 100644 --- a/crates/rmcp/src/transport/async_rw.rs +++ b/crates/rmcp/src/transport/async_rw.rs @@ -16,6 +16,7 @@ use tokio_util::{ use super::{IntoTransport, Transport}; use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage}; +#[non_exhaustive] pub enum TransportAdapterAsyncRW {} impl IntoTransport for (R, W) @@ -29,6 +30,7 @@ where } } +#[non_exhaustive] pub enum TransportAdapterAsyncCombinedRW {} impl IntoTransport for S where @@ -277,6 +279,7 @@ fn try_parse_with_compatibility( } #[derive(Debug, Error)] +#[non_exhaustive] pub enum JsonRpcMessageCodecError { #[error("max line length exceeded")] MaxLineLengthExceeded, diff --git a/crates/rmcp/src/transport/auth.rs b/crates/rmcp/src/transport/auth.rs index 9e048b1a2..68a9e7235 100644 --- a/crates/rmcp/src/transport/auth.rs +++ b/crates/rmcp/src/transport/auth.rs @@ -60,6 +60,7 @@ const DEFAULT_EXCHANGE_URL: &str = "http://localhost"; /// Stored credentials for OAuth2 authorization #[derive(Clone, Serialize, Deserialize)] +#[non_exhaustive] pub struct StoredCredentials { pub client_id: String, pub token_response: Option, @@ -134,6 +135,7 @@ impl CredentialStore for InMemoryCredentialStore { /// Stored authorization state for OAuth2 PKCE flow #[derive(Clone, Serialize, Deserialize)] +#[non_exhaustive] pub struct StoredAuthorizationState { pub pkce_verifier: String, pub csrf_token: String, @@ -172,6 +174,7 @@ impl std::fmt::Debug for StoredAuthorizationState { /// } /// ``` #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[non_exhaustive] pub struct VendorExtraTokenFields(pub HashMap); impl ExtraTokenFields for VendorExtraTokenFields {} @@ -257,6 +260,7 @@ impl StateStore for InMemoryStateStore { /// HTTP client with OAuth 2.0 authorization #[derive(Clone)] +#[non_exhaustive] pub struct AuthClient { pub http_client: C, pub auth_manager: Arc>, @@ -350,6 +354,7 @@ pub enum AuthError { /// oauth2 metadata #[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[non_exhaustive] pub struct AuthorizationMetadata { pub authorization_endpoint: String, pub token_endpoint: String, @@ -373,6 +378,7 @@ struct ResourceServerMetadata { /// Parameters extracted from WWW-Authenticate header #[derive(Debug, Clone, Default)] +#[non_exhaustive] pub struct WWWAuthenticateParams { pub resource_metadata_url: Option, pub scope: Option, @@ -394,6 +400,7 @@ impl WWWAuthenticateParams { /// oauth2 client config #[derive(Debug, Clone)] +#[non_exhaustive] pub struct OAuthClientConfig { pub client_id: String, pub client_secret: Option, @@ -401,6 +408,27 @@ pub struct OAuthClientConfig { pub redirect_uri: String, } +impl OAuthClientConfig { + pub fn new(client_id: impl Into, redirect_uri: impl Into) -> Self { + Self { + client_id: client_id.into(), + client_secret: None, + scopes: Vec::new(), + redirect_uri: redirect_uri.into(), + } + } + + pub fn with_client_secret(mut self, secret: impl Into) -> Self { + self.client_secret = Some(secret.into()); + self + } + + pub fn with_scopes(mut self, scopes: Vec) -> Self { + self.scopes = scopes; + self + } +} + // add type aliases for oauth2 types type OAuthErrorResponse = oauth2::StandardErrorResponse; @@ -440,6 +468,7 @@ pub const EXTENSION_OAUTH_CLIENT_CREDENTIALS: &str = /// JWT signing algorithm for private_key_jwt authentication (SEP-1046) #[cfg(feature = "auth-client-credentials-jwt")] #[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum JwtSigningAlgorithm { RS256, RS384, @@ -477,6 +506,7 @@ impl JwtSigningAlgorithm { /// - `ClientSecret`: credentials sent in the request body /// - `PrivateKeyJwt`: RFC 7523 signed JWT assertion (requires `auth-client-credentials-jwt` feature) #[derive(Debug, Clone)] +#[non_exhaustive] pub enum ClientCredentialsConfig { /// Client secret authentication (credentials in request body) ClientSecret { @@ -534,6 +564,7 @@ impl ClientCredentialsConfig { /// Configuration for scope upgrade behavior #[derive(Debug, Clone)] +#[non_exhaustive] pub struct ScopeUpgradeConfig { /// Maximum number of scope upgrade attempts before giving up pub max_upgrade_attempts: u32, @@ -579,6 +610,7 @@ pub(crate) struct ClientRegistrationRequest { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[non_exhaustive] pub struct ClientRegistrationResponse { pub client_id: String, pub client_secret: Option, @@ -589,6 +621,18 @@ pub struct ClientRegistrationResponse { pub additional_fields: HashMap, } +impl ClientRegistrationResponse { + pub fn new(client_id: impl Into, redirect_uris: Vec) -> Self { + Self { + client_id: client_id.into(), + client_secret: None, + client_name: None, + redirect_uris, + additional_fields: HashMap::new(), + } + } +} + /// SEP-991: URL-based Client IDs /// Validate that the client_id is a valid URL with https scheme and non-root pathname fn is_https_url(value: &str) -> bool { @@ -2015,6 +2059,7 @@ impl AuthorizationManager { } /// oauth2 authorization session, for guiding user to complete the authorization process +#[non_exhaustive] pub struct AuthorizationSession { pub auth_manager: AuthorizationManager, pub auth_url: String, @@ -2167,6 +2212,7 @@ impl AuthorizedHttpClient { /// OAuth state machine /// Use the OAuthState to manage the OAuth client is more recommend /// But also you can use the AuthorizationManager,AuthorizationSession,AuthorizedHttpClient directly +#[non_exhaustive] pub enum OAuthState { /// the AuthorizationManager Unauthorized(AuthorizationManager), diff --git a/crates/rmcp/src/transport/common/client_side_sse.rs b/crates/rmcp/src/transport/common/client_side_sse.rs index b826b12d4..fc9e15eb7 100644 --- a/crates/rmcp/src/transport/common/client_side_sse.rs +++ b/crates/rmcp/src/transport/common/client_side_sse.rs @@ -17,6 +17,7 @@ pub trait SseRetryPolicy: std::fmt::Debug + Send + Sync { } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct FixedInterval { pub max_times: Option, pub duration: Duration, @@ -47,6 +48,7 @@ impl Default for FixedInterval { } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct ExponentialBackoff { pub max_times: Option, pub base_duration: Duration, @@ -77,6 +79,7 @@ impl SseRetryPolicy for ExponentialBackoff { } #[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] pub struct NeverRetry; impl SseRetryPolicy for NeverRetry { @@ -169,6 +172,7 @@ impl SseAutoReconnectStream> { pin_project_lite::pin_project! { #[project = SseAutoReconnectStreamStateProj] + #[non_exhaustive] pub enum SseAutoReconnectStreamState { Connected { #[pin] diff --git a/crates/rmcp/src/transport/common/server_side_http.rs b/crates/rmcp/src/transport/common/server_side_http.rs index efa50fd09..d24b19af6 100644 --- a/crates/rmcp/src/transport/common/server_side_http.rs +++ b/crates/rmcp/src/transport/common/server_side_http.rs @@ -58,6 +58,7 @@ impl sse_stream::Timer for TokioTimer { } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct ServerSseMessage { /// The event ID for this message. When set, clients can use this ID /// with the `Last-Event-ID` header to resume the stream from this point. diff --git a/crates/rmcp/src/transport/sink_stream.rs b/crates/rmcp/src/transport/sink_stream.rs index f31743922..6286b53b3 100644 --- a/crates/rmcp/src/transport/sink_stream.rs +++ b/crates/rmcp/src/transport/sink_stream.rs @@ -50,6 +50,7 @@ where } } +#[non_exhaustive] pub enum TransportAdapterSinkStream {} impl IntoTransport for (Si, St) @@ -64,6 +65,7 @@ where } } +#[non_exhaustive] pub enum TransportAdapterAsyncCombinedRW {} impl IntoTransport for S where diff --git a/crates/rmcp/src/transport/streamable_http_client.rs b/crates/rmcp/src/transport/streamable_http_client.rs index bbb98bf38..aea1f1a1e 100644 --- a/crates/rmcp/src/transport/streamable_http_client.rs +++ b/crates/rmcp/src/transport/streamable_http_client.rs @@ -24,11 +24,13 @@ use crate::{ type BoxedSseStream = BoxStream<'static, Result>; #[derive(Debug)] +#[non_exhaustive] pub struct AuthRequiredError { pub www_authenticate_header: String, } #[derive(Debug)] +#[non_exhaustive] pub struct InsufficientScopeError { pub www_authenticate_header: String, pub required_scope: Option, @@ -212,6 +214,7 @@ pub trait StreamableHttpClient: Clone + Send + 'static { + '_; } +#[non_exhaustive] pub struct RetryConfig { pub max_times: Option, pub min_duration: Duration, @@ -253,6 +256,7 @@ struct SessionCleanupInfo { } #[derive(Debug, Clone, Default)] +#[non_exhaustive] pub struct StreamableHttpClientWorker { pub client: C, pub config: StreamableHttpClientTransportConfig, @@ -1041,6 +1045,7 @@ impl StreamableHttpClientTransport { } } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct StreamableHttpClientTransportConfig { pub uri: Arc, pub retry_config: Arc, diff --git a/crates/rmcp/src/transport/streamable_http_server/session/local.rs b/crates/rmcp/src/transport/streamable_http_server/session/local.rs index cad533802..2d2059c59 100644 --- a/crates/rmcp/src/transport/streamable_http_server/session/local.rs +++ b/crates/rmcp/src/transport/streamable_http_server/session/local.rs @@ -29,12 +29,14 @@ use crate::{ }; #[derive(Debug, Default)] +#[non_exhaustive] pub struct LocalSessionManager { pub sessions: tokio::sync::RwLock>, pub session_config: SessionConfig, } #[derive(Debug, Error)] +#[non_exhaustive] pub enum LocalSessionManagerError { #[error("Session not found: {0}")] SessionNotFound(SessionId), @@ -148,6 +150,7 @@ impl std::fmt::Display for EventId { } #[derive(Debug, Clone, Error)] +#[non_exhaustive] pub enum EventIdParseError { #[error("Invalid index: {0}")] InvalidIndex(ParseIntError), @@ -310,6 +313,7 @@ impl LocalSessionWorker { } #[derive(Debug, Error)] +#[non_exhaustive] pub enum SessionError { #[error("Invalid request id: {0}")] DuplicatedRequestId(HttpRequestId), @@ -339,6 +343,7 @@ enum OutboundChannel { Common, } #[derive(Debug)] +#[non_exhaustive] pub struct StreamableHttpMessageReceiver { pub http_request_id: Option, pub inner: Receiver, @@ -657,6 +662,7 @@ impl LocalSessionWorker { } #[derive(Debug)] +#[non_exhaustive] pub enum SessionEvent { ClientMessage { message: ClientJsonRpcMessage, @@ -1059,6 +1065,7 @@ impl Worker for LocalSessionWorker { } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct SessionConfig { /// the capacity of the channel for the session. Default is 16. pub channel_capacity: usize, diff --git a/crates/rmcp/src/transport/streamable_http_server/session/never.rs b/crates/rmcp/src/transport/streamable_http_server/session/never.rs index 436d4cfce..a2f72d820 100644 --- a/crates/rmcp/src/transport/streamable_http_server/session/never.rs +++ b/crates/rmcp/src/transport/streamable_http_server/session/never.rs @@ -10,9 +10,12 @@ use crate::{ #[derive(Debug, Clone, Error)] #[error("Session management is not supported")] +#[non_exhaustive] pub struct ErrorSessionManagementNotSupported; #[derive(Debug, Clone, Default)] +#[non_exhaustive] pub struct NeverSessionManager {} +#[non_exhaustive] pub enum NeverTransport {} impl Transport for NeverTransport { type Error = ErrorSessionManagementNotSupported; diff --git a/crates/rmcp/src/transport/streamable_http_server/tower.rs b/crates/rmcp/src/transport/streamable_http_server/tower.rs index 0130467df..de09b9527 100644 --- a/crates/rmcp/src/transport/streamable_http_server/tower.rs +++ b/crates/rmcp/src/transport/streamable_http_server/tower.rs @@ -30,6 +30,7 @@ use crate::{ }; #[derive(Debug, Clone)] +#[non_exhaustive] pub struct StreamableHttpServerConfig { /// The ping message duration for SSE connections. pub sse_keep_alive: Option, @@ -62,6 +63,13 @@ impl Default for StreamableHttpServerConfig { } } +impl StreamableHttpServerConfig { + pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self { + self.cancellation_token = token; + self + } +} + #[expect( clippy::result_large_err, reason = "BoxResponse is intentionally large; matches other handlers in this file" diff --git a/crates/rmcp/src/transport/worker.rs b/crates/rmcp/src/transport/worker.rs index d7c53afd4..a5d722d44 100644 --- a/crates/rmcp/src/transport/worker.rs +++ b/crates/rmcp/src/transport/worker.rs @@ -53,6 +53,7 @@ pub trait Worker: Sized + Send + 'static { } } +#[non_exhaustive] pub struct WorkerSendRequest { pub message: TxJsonRpcMessage, pub responder: tokio::sync::oneshot::Sender>, @@ -66,6 +67,7 @@ pub struct WorkerTransport { ct: CancellationToken, } +#[non_exhaustive] pub struct WorkerConfig { pub name: Option, pub channel_buffer_capacity: usize, @@ -79,6 +81,7 @@ impl Default for WorkerConfig { } } } +#[non_exhaustive] pub enum WorkerAdapter {} impl IntoTransport for W { @@ -143,11 +146,13 @@ impl WorkerTransport { } } +#[non_exhaustive] pub struct SendRequest { pub message: TxJsonRpcMessage, pub responder: tokio::sync::oneshot::Sender>, } +#[non_exhaustive] pub struct WorkerContext { pub to_handler_tx: tokio::sync::mpsc::Sender>, pub from_handler_rx: tokio::sync::mpsc::Receiver>, diff --git a/crates/rmcp/tests/test_complex_schema.rs b/crates/rmcp/tests/test_complex_schema.rs index a9c41a3c5..adfb7cd02 100644 --- a/crates/rmcp/tests/test_complex_schema.rs +++ b/crates/rmcp/tests/test_complex_schema.rs @@ -5,6 +5,7 @@ use rmcp::{ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)] +#[allow(clippy::exhaustive_enums)] pub enum ChatRole { System, User, @@ -13,18 +14,21 @@ pub enum ChatRole { } #[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct ChatMessage { pub role: ChatRole, pub content: String, } #[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct ChatRequest { pub system: Option, pub messages: Vec, } #[derive(Clone, Default)] +#[allow(clippy::exhaustive_structs)] pub struct Demo; #[tool_router] diff --git a/crates/rmcp/tests/test_elicitation.rs b/crates/rmcp/tests/test_elicitation.rs index 7d946a2bf..bfa8cc493 100644 --- a/crates/rmcp/tests/test_elicitation.rs +++ b/crates/rmcp/tests/test_elicitation.rs @@ -1458,20 +1458,15 @@ async fn test_peer_request_options_timeout() { let timeout = Some(Duration::from_secs(15)); - let options = PeerRequestOptions { - timeout, - meta: None, - }; + let mut options = PeerRequestOptions::default(); + options.timeout = timeout; // Verify timeout is properly stored assert_eq!(options.timeout, timeout); assert!(options.meta.is_none()); // Test with no timeout - let options_no_timeout = PeerRequestOptions { - timeout: None, - meta: None, - }; + let options_no_timeout = PeerRequestOptions::default(); assert!(options_no_timeout.timeout.is_none()); } diff --git a/crates/rmcp/tests/test_json_schema_detection.rs b/crates/rmcp/tests/test_json_schema_detection.rs index af587319d..6b07cc6d6 100644 --- a/crates/rmcp/tests/test_json_schema_detection.rs +++ b/crates/rmcp/tests/test_json_schema_detection.rs @@ -6,6 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct TestData { pub value: String, } diff --git a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json index bd8f744b0..c1aa13dea 100644 --- a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json +++ b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json @@ -388,7 +388,6 @@ "content": { "description": "The content returned by the tool (text, images, etc.)", "type": "array", - "default": [], "items": { "$ref": "#/definitions/Annotated" } @@ -403,7 +402,10 @@ "structuredContent": { "description": "An optional JSON object that represents the structured result of the tool call" } - } + }, + "required": [ + "content" + ] }, "CancelTaskResult": { "description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.", diff --git a/crates/rmcp/tests/test_structured_output.rs b/crates/rmcp/tests/test_structured_output.rs index b498d3120..6f397d876 100644 --- a/crates/rmcp/tests/test_structured_output.rs +++ b/crates/rmcp/tests/test_structured_output.rs @@ -10,18 +10,21 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct CalculationRequest { pub a: i32, pub b: i32, } #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct CalculationResult { pub sum: i32, pub product: i32, } #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct UserInfo { pub name: String, pub age: u32, diff --git a/crates/rmcp/tests/test_tool_builder_methods.rs b/crates/rmcp/tests/test_tool_builder_methods.rs index f93c05462..d47002f07 100644 --- a/crates/rmcp/tests/test_tool_builder_methods.rs +++ b/crates/rmcp/tests/test_tool_builder_methods.rs @@ -4,12 +4,14 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct InputData { pub name: String, pub age: u32, } #[derive(Serialize, Deserialize, JsonSchema)] +#[allow(clippy::exhaustive_structs)] pub struct OutputData { pub greeting: String, pub is_adult: bool, diff --git a/examples/servers/src/complex_auth_streamhttp.rs b/examples/servers/src/complex_auth_streamhttp.rs index 4afacf8d2..34c4b1584 100644 --- a/examples/servers/src/complex_auth_streamhttp.rs +++ b/examples/servers/src/complex_auth_streamhttp.rs @@ -51,12 +51,9 @@ impl McpOAuthStore { let mut clients = HashMap::new(); clients.insert( "mcp-client".to_string(), - OAuthClientConfig { - client_id: "mcp-client".to_string(), - client_secret: Some("mcp-client-secret".to_string()), - scopes: vec!["profile".to_string(), "email".to_string()], - redirect_uri: "http://localhost:8080/callback".to_string(), - }, + OAuthClientConfig::new("mcp-client", "http://localhost:8080/callback") + .with_client_secret("mcp-client-secret") + .with_scopes(vec!["profile".to_string(), "email".to_string()]), ); Self { @@ -520,17 +517,16 @@ async fn oauth_authorization_server() -> impl IntoResponse { "response_types_supported".into(), Value::Array(vec![Value::String("code".into())]), ); - let metadata = AuthorizationMetadata { - authorization_endpoint: format!("http://{}/oauth/authorize", BIND_ADDRESS), - token_endpoint: format!("http://{}/oauth/token", BIND_ADDRESS), - scopes_supported: Some(vec!["profile".to_string(), "email".to_string()]), - registration_endpoint: Some(format!("http://{}/oauth/register", BIND_ADDRESS)), - response_types_supported: Some(vec!["code".to_string()]), - code_challenge_methods_supported: Some(vec!["S256".to_string()]), - issuer: Some(BIND_ADDRESS.to_string()), - jwks_uri: Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)), - additional_fields, - }; + let mut metadata = AuthorizationMetadata::default(); + metadata.authorization_endpoint = format!("http://{}/oauth/authorize", BIND_ADDRESS); + metadata.token_endpoint = format!("http://{}/oauth/token", BIND_ADDRESS); + metadata.scopes_supported = Some(vec!["profile".to_string(), "email".to_string()]); + metadata.registration_endpoint = Some(format!("http://{}/oauth/register", BIND_ADDRESS)); + metadata.response_types_supported = Some(vec!["code".to_string()]); + metadata.code_challenge_methods_supported = Some(vec!["S256".to_string()]); + metadata.issuer = Some(BIND_ADDRESS.to_string()); + metadata.jwks_uri = Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)); + metadata.additional_fields = additional_fields; debug!("metadata: {:?}", metadata); (StatusCode::OK, Json(metadata)) } @@ -556,12 +552,8 @@ async fn oauth_register( let client_id = format!("client-{}", Uuid::new_v4()); let client_secret = generate_random_string(32); - let client = OAuthClientConfig { - client_id: client_id.clone(), - client_secret: Some(client_secret.clone()), - redirect_uri: req.redirect_uris[0].clone(), - scopes: vec![], - }; + let client = OAuthClientConfig::new(client_id.clone(), req.redirect_uris[0].clone()) + .with_client_secret(client_secret.clone()); state .clients @@ -570,13 +562,9 @@ async fn oauth_register( .insert(client_id.clone(), client); // return client information - let response = ClientRegistrationResponse { - client_id, - client_secret: Some(client_secret), - client_name: Some(req.client_name), - redirect_uris: req.redirect_uris, - additional_fields: HashMap::new(), - }; + let mut response = ClientRegistrationResponse::new(client_id, req.redirect_uris); + response.client_secret = Some(client_secret); + response.client_name = Some(req.client_name); (StatusCode::CREATED, Json(response)).into_response() } diff --git a/examples/servers/src/counter_streamhttp.rs b/examples/servers/src/counter_streamhttp.rs index db9b9df18..3811c09f4 100644 --- a/examples/servers/src/counter_streamhttp.rs +++ b/examples/servers/src/counter_streamhttp.rs @@ -25,10 +25,7 @@ async fn main() -> anyhow::Result<()> { let service = StreamableHttpService::new( || Ok(Counter::new()), LocalSessionManager::default().into(), - StreamableHttpServerConfig { - cancellation_token: ct.child_token(), - ..Default::default() - }, + StreamableHttpServerConfig::default().with_cancellation_token(ct.child_token()), ); let router = axum::Router::new().nest_service("/mcp", service);