chore: add #[non_exhaustive] to remaining public structs (#768)
* chore: add #[non_exhaustive] to remaining public structs * chore: add #[non_exhaustive] to remaining public types * chore: enable exhaustive_structs/enums clippy lints * test: add untagged ServerResult deserialization regression tests
This commit is contained in:
parent
ee1c63c53f
commit
6a3b32d3ab
53 changed files with 430 additions and 211 deletions
|
|
@ -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, &[]);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct Extension<T>(pub T);
|
||||
|
||||
impl<C, T> FromContextPart<C> for Extension<T>
|
||||
|
|
@ -182,6 +183,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RequestId(pub crate::model::RequestId);
|
||||
|
||||
impl<C> FromContextPart<C> for RequestId
|
||||
|
|
|
|||
|
|
@ -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<T: IntoGetPromptResult> IntoGetPromptResult for Result<T, crate::ErrorData>
|
|||
// Future wrapper that automatically handles IntoGetPromptResult conversion
|
||||
pin_project_lite::pin_project! {
|
||||
#[project = IntoGetPromptResultFutProj]
|
||||
#[non_exhaustive]
|
||||
pub enum IntoGetPromptResultFut<F, R> {
|
||||
Pending {
|
||||
#[pin]
|
||||
|
|
@ -151,6 +153,7 @@ where
|
|||
}
|
||||
|
||||
// Prompt-specific extractor for prompt name
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct PromptName(pub String);
|
||||
|
||||
impl<S> FromContextPart<PromptContext<'_, S>> for PromptName {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use crate::{
|
|||
pub mod prompt;
|
||||
pub mod tool;
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Router<S> {
|
||||
pub tool_router: tool::ToolRouter<S>,
|
||||
pub prompt_router: prompt::PromptRouter<S>,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
service::{MaybeBoxFuture, MaybeSend},
|
||||
};
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct PromptRoute<S> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub get: Arc<DynGetPromptHandler<S>>,
|
||||
|
|
@ -90,6 +91,7 @@ where
|
|||
}
|
||||
|
||||
/// Adapter for functions generated by the #\[prompt\] macro
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct PromptAttrGenerateFunctionAdapter;
|
||||
|
||||
impl<S, F> IntoPromptRoute<S, PromptAttrGenerateFunctionAdapter> for F
|
||||
|
|
@ -103,6 +105,7 @@ where
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct PromptRouter<S> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub map: std::collections::HashMap<Cow<'static, str>, PromptRoute<S>>,
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ use crate::{
|
|||
service::{MaybeBoxFuture, MaybeSend},
|
||||
};
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct ToolRoute<S> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub call: Arc<DynCallToolHandler<S>>,
|
||||
|
|
@ -216,6 +217,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ToolAttrGenerateFunctionAdapter;
|
||||
impl<S, F> IntoToolRoute<S, ToolAttrGenerateFunctionAdapter> for F
|
||||
where
|
||||
|
|
@ -251,6 +253,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct WithToolAttr<C, S, A>
|
||||
where
|
||||
C: CallToolHandler<S, A> + MaybeSend + Clone + 'static,
|
||||
|
|
@ -292,6 +295,7 @@ where
|
|||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ToolRouter<S> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub map: std::collections::HashMap<Cow<'static, str>, ToolRoute<S>>,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub fn parse_json_object<T: DeserializeOwned>(input: JsonObject) -> Result<T, cr
|
|||
)
|
||||
})
|
||||
}
|
||||
#[non_exhaustive]
|
||||
pub struct ToolCallContext<'s, S> {
|
||||
pub request_context: RequestContext<RoleServer>,
|
||||
pub service: &'s S,
|
||||
|
|
@ -104,6 +105,7 @@ impl<T: IntoCallToolResult> IntoCallToolResult for Result<T, crate::ErrorData> {
|
|||
|
||||
pin_project_lite::pin_project! {
|
||||
#[project = IntoCallToolResultFutProj]
|
||||
#[non_exhaustive]
|
||||
pub enum IntoCallToolResultFut<F, R> {
|
||||
Pending {
|
||||
#[pin]
|
||||
|
|
@ -163,6 +165,7 @@ pub type DynCallToolHandler<S> =
|
|||
-> futures::future::LocalBoxFuture<'s, Result<CallToolResult, crate::ErrorData>>;
|
||||
|
||||
// Tool-specific extractor for tool name
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ToolName(pub Cow<'static, str>);
|
||||
|
||||
impl<S> FromContextPart<ToolCallContext<'_, S>> for ToolName {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
// Implement JsonSchema for Json<T> to delegate to T's schema
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ use schemars::JsonSchema;
|
|||
/// - Returns appropriate error responses if deserialization fails
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct Parameters<P>(pub P);
|
||||
|
||||
impl<P: JsonSchema> JsonSchema for Parameters<P> {
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ProgressToken(pub NumberOrString);
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -338,6 +342,7 @@ impl<M, P> GetExtensions for Request<M, P> {
|
|||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RequestOptionalParam<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -361,6 +366,7 @@ impl<M: Default, P> RequestOptionalParam<M, P> {
|
|||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RequestNoParam<M = String> {
|
||||
pub method: M,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
|
|
@ -403,6 +409,7 @@ impl<M: Default, P> Notification<M, P> {
|
|||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct NotificationNoParam<M = String> {
|
||||
pub method: M,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
|
|
@ -414,6 +421,7 @@ pub struct NotificationNoParam<M = String> {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct JsonRpcRequest<R = Request> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
|
|
@ -435,6 +443,7 @@ impl<R> JsonRpcRequest<R> {
|
|||
type DefaultResponse = JsonObject;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct JsonRpcResponse<R = JsonObject> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
|
|
@ -443,6 +452,7 @@ pub struct JsonRpcResponse<R = JsonObject> {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct JsonRpcNotification<N = Notification> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
#[serde(flatten)]
|
||||
|
|
@ -475,6 +486,7 @@ pub struct JsonRpcNotification<N = Notification> {
|
|||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
|
||||
/// A single request expecting a response
|
||||
Request(JsonRpcRequest<Req>),
|
||||
|
|
@ -651,6 +665,7 @@ impl From<EmptyResult> for () {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CancelledNotificationParam {
|
||||
pub request_id: RequestId,
|
||||
pub reason: Option<String>,
|
||||
|
|
@ -691,6 +707,7 @@ pub type CancelledNotification =
|
|||
/// deserialize them into domain-specific types.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CustomNotification {
|
||||
pub method: String,
|
||||
pub params: Option<Value>,
|
||||
|
|
@ -725,6 +742,7 @@ impl CustomNotification {
|
|||
/// deserialize them into domain-specific types.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CustomRequest {
|
||||
pub method: String,
|
||||
pub params: Option<Value>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct $t {
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ResourceUpdatedNotificationParam {
|
||||
/// The URI of the resource that was updated
|
||||
pub uri: String,
|
||||
|
|
@ -1392,6 +1413,7 @@ pub type ToolListChangedNotification = NotificationNoParam<ToolListChangedNotifi
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
|
||||
#[serde(rename_all = "lowercase")] //match spec
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum LoggingLevel {
|
||||
Debug,
|
||||
Info,
|
||||
|
|
@ -1445,6 +1467,7 @@ const_string!(LoggingMessageNotificationMethod = "notifications/message");
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct LoggingMessageNotificationParam {
|
||||
/// The severity level of this log message
|
||||
pub level: LoggingLevel,
|
||||
|
|
@ -1490,6 +1513,7 @@ pub type CreateMessageRequest = Request<CreateMessageRequestMethod, CreateMessag
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum Role {
|
||||
/// A human user or client making a request
|
||||
User,
|
||||
|
|
@ -1501,6 +1525,7 @@ pub enum Role {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum ToolChoiceMode {
|
||||
/// Model decides whether to use tools
|
||||
Auto,
|
||||
|
|
@ -1550,6 +1575,7 @@ impl ToolChoice {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum SamplingContent<T> {
|
||||
Single(T),
|
||||
Multiple(Vec<T>),
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum SamplingMessageContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
|
|
@ -1792,6 +1819,7 @@ impl TryFrom<Content> for SamplingContent<SamplingMessageContent> {
|
|||
/// should be provided to the LLM when processing sampling requests.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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<CompleteRequestMethod, CompleteRequestParams>
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CompletionInfo {
|
||||
pub values: Vec<String>,
|
||||
#[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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum ElicitationAction {
|
||||
/// User accepts the request and provides the requested information
|
||||
Accept,
|
||||
|
|
@ -2571,6 +2605,7 @@ impl TryFrom<CreateElicitationRequestParamDeserializeHelper> for CreateElicitati
|
|||
try_from = "CreateElicitationRequestParamDeserializeHelper"
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ElicitationResponseNotificationParam {
|
||||
pub elicitation_id: String,
|
||||
}
|
||||
|
|
@ -3032,6 +3069,7 @@ pub type GetTaskInfoRequest = Request<GetTaskInfoMethod, GetTaskInfoParams>;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct GetTaskInfoParams {
|
||||
/// Protocol-level metadata for this request (SEP-1319)
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -3061,6 +3099,7 @@ pub type GetTaskResultRequest = Request<GetTaskResultMethod, GetTaskResultParams
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct GetTaskResultParams {
|
||||
/// Protocol-level metadata for this request (SEP-1319)
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -3087,6 +3126,7 @@ pub type CancelTaskRequest = Request<CancelTaskMethod, CancelTaskParams>;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CancelTaskParams {
|
||||
/// Protocol-level metadata for this request (SEP-1319)
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -3161,6 +3201,7 @@ macro_rules! ts_union {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum $U {
|
||||
$($declared)*
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ impl Annotations {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct Annotated<T: AnnotateAble> {
|
||||
#[serde(flatten)]
|
||||
pub raw: T,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub type ExtensionCapabilities = BTreeMap<String, JsonObject>;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct PromptsCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub list_changed: Option<bool>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ResourcesCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub subscribe: Option<bool>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ToolsCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub list_changed: Option<bool>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RootsCapabilities {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub list_changed: Option<bool>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct TasksCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub requests: Option<TaskRequestsCapability>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct TaskRequestsCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sampling: Option<SamplingTaskCapability>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct SamplingTaskCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub create_message: Option<JsonObject>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ElicitationTaskCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub create: Option<JsonObject>,
|
||||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ToolsTaskCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub call: Option<JsonObject>,
|
||||
|
|
@ -190,6 +199,7 @@ impl TasksCapability {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct [<$Target BuilderState>]<
|
||||
$(const [<$f:upper>]: bool = false,)*
|
||||
>;
|
||||
#[derive(Debug, Default)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct [<$Target Builder>]<S = [<$Target BuilderState>]> {
|
||||
$(pub $f: Option<$T>,)*
|
||||
pub state: PhantomData<S>
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RawTextContent {
|
||||
pub text: String,
|
||||
/// Optional protocol-level metadata for this content block
|
||||
|
|
@ -19,6 +20,7 @@ pub type TextContent = Annotated<RawTextContent>;
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RawImageContent {
|
||||
/// The base64-encoded image
|
||||
pub data: String,
|
||||
|
|
@ -32,6 +34,7 @@ pub type ImageContent = Annotated<RawImageContent>;
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum RawContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const_string!(ArrayTypeConst = "array");
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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")]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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")]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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)]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum EnumSchema {
|
||||
Single(SingleSelectEnumSchema),
|
||||
Multi(MultiSelectEnumSchema),
|
||||
|
|
@ -749,9 +759,11 @@ pub enum EnumSchema {
|
|||
|
||||
/// Marker type for single-select enum builder
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct SingleSelect;
|
||||
/// Marker type for multi-select enum builder
|
||||
#[derive(Debug)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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")]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ElicitationSchema {
|
||||
/// Always "object" for elicitation schemas
|
||||
#[serde(rename = "type")]
|
||||
|
|
@ -1221,6 +1234,7 @@ impl ElicitationSchema {
|
|||
/// .build();
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct ElicitationSchemaBuilder {
|
||||
pub properties: BTreeMap<String, PrimitiveSchema>,
|
||||
pub required: Vec<String>,
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ variant_extension! {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(transparent)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct Meta(pub JsonObject);
|
||||
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
|
||||
impl Meta {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ impl PromptArgument {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum PromptMessageContent {
|
||||
/// Plain text content
|
||||
Text { text: String },
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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<RawResource>;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RawResourceTemplate {
|
||||
pub uri_template: String,
|
||||
pub name: String,
|
||||
|
|
@ -58,6 +60,7 @@ pub type ResourceTemplate = Annotated<RawResourceTemplate>;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum ResourceContents {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
TextResourceContents {
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
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))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct GetTaskResult {
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
|
|
@ -160,6 +162,7 @@ impl<'de> serde::Deserialize<'de> for GetTaskPayloadResult {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct CancelTaskResult {
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
|
|
@ -171,6 +174,7 @@ pub struct CancelTaskResult {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct TaskList {
|
||||
pub tasks: Vec<Task>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
|
||||
pub enum TaskSupport {
|
||||
/// Clients MUST NOT invoke this tool as a task (default behavior).
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ type Responder<T> = tokio::sync::oneshot::Sender<T>;
|
|||
///
|
||||
/// or wait for response by call [`RequestHandle::await_response`]
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct RequestHandle<R: ServiceRole> {
|
||||
pub rx: tokio::sync::oneshot::Receiver<Result<R::PeerResp, ServiceError>>,
|
||||
pub options: PeerRequestOptions,
|
||||
|
|
@ -398,6 +399,7 @@ impl<R: ServiceRole> std::fmt::Debug for Peer<R> {
|
|||
type ProxyOutbound<R> = mpsc::Receiver<PeerSinkMessage<R>>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct PeerRequestOptions {
|
||||
pub timeout: Option<Duration>,
|
||||
pub meta: Option<Meta>,
|
||||
|
|
@ -648,6 +650,7 @@ pub enum QuitReason {
|
|||
|
||||
/// Request execution context
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct RequestContext<R: ServiceRole> {
|
||||
/// this token will be cancelled when the [`CancelledNotification`] is received.
|
||||
pub ct: CancellationToken,
|
||||
|
|
@ -673,6 +676,7 @@ impl<R: ServiceRole> RequestContext<R> {
|
|||
|
||||
/// Request execution context
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct NotificationContext<R: ServiceRole> {
|
||||
pub meta: Meta,
|
||||
pub extensions: Extensions,
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ where
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct RoleClient;
|
||||
|
||||
impl ServiceRole for RoleClient {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<Box<dyn OperationResultTransport>, Error>,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ where
|
|||
fn into_transport(self) -> impl Transport<R, Error = E> + 'static;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum TransportAdapterIdentity {}
|
||||
impl<R, T, E> IntoTransport<R, E, TransportAdapterIdentity> for T
|
||||
where
|
||||
|
|
@ -233,6 +234,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,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use tokio_util::{
|
|||
use super::{IntoTransport, Transport};
|
||||
use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage};
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum TransportAdapterAsyncRW {}
|
||||
|
||||
impl<Role, R, W> IntoTransport<Role, std::io::Error, TransportAdapterAsyncRW> for (R, W)
|
||||
|
|
@ -29,6 +30,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum TransportAdapterAsyncCombinedRW {}
|
||||
impl<Role, S> IntoTransport<Role, std::io::Error, TransportAdapterAsyncCombinedRW> for S
|
||||
where
|
||||
|
|
@ -277,6 +279,7 @@ fn try_parse_with_compatibility<T: serde::de::DeserializeOwned>(
|
|||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum JsonRpcMessageCodecError {
|
||||
#[error("max line length exceeded")]
|
||||
MaxLineLengthExceeded,
|
||||
|
|
|
|||
|
|
@ -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<OAuthTokenResponse>,
|
||||
|
|
@ -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<String, Value>);
|
||||
|
||||
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<C> {
|
||||
pub http_client: C,
|
||||
pub auth_manager: Arc<Mutex<AuthorizationManager>>,
|
||||
|
|
@ -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<Url>,
|
||||
pub scope: Option<String>,
|
||||
|
|
@ -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<String>,
|
||||
|
|
@ -401,6 +408,27 @@ pub struct OAuthClientConfig {
|
|||
pub redirect_uri: String,
|
||||
}
|
||||
|
||||
impl OAuthClientConfig {
|
||||
pub fn new(client_id: impl Into<String>, redirect_uri: impl Into<String>) -> 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<String>) -> Self {
|
||||
self.client_secret = Some(secret.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
|
||||
self.scopes = scopes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// add type aliases for oauth2 types
|
||||
type OAuthErrorResponse = oauth2::StandardErrorResponse<oauth2::basic::BasicErrorResponseType>;
|
||||
|
||||
|
|
@ -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<String>,
|
||||
|
|
@ -589,6 +621,18 @@ pub struct ClientRegistrationResponse {
|
|||
pub additional_fields: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
impl ClientRegistrationResponse {
|
||||
pub fn new(client_id: impl Into<String>, redirect_uris: Vec<String>) -> 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 {
|
||||
|
|
@ -2045,6 +2089,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,
|
||||
|
|
@ -2197,6 +2242,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),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub trait SseRetryPolicy: std::fmt::Debug + Send + Sync {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct FixedInterval {
|
||||
pub max_times: Option<usize>,
|
||||
pub duration: Duration,
|
||||
|
|
@ -47,6 +48,7 @@ impl Default for FixedInterval {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct ExponentialBackoff {
|
||||
pub max_times: Option<usize>,
|
||||
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<E: std::error::Error + Send> SseAutoReconnectStream<NeverReconnect<E>> {
|
|||
|
||||
pin_project_lite::pin_project! {
|
||||
#[project = SseAutoReconnectStreamStateProj]
|
||||
#[non_exhaustive]
|
||||
pub enum SseAutoReconnectStreamState<F> {
|
||||
Connected {
|
||||
#[pin]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum UnixSocketError {
|
||||
#[error("hyper error: {0}")]
|
||||
Hyper(#[from] hyper::Error),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum TransportAdapterSinkStream {}
|
||||
|
||||
impl<Role, Si, St> IntoTransport<Role, Si::Error, TransportAdapterSinkStream> for (Si, St)
|
||||
|
|
@ -64,6 +65,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum TransportAdapterAsyncCombinedRW {}
|
||||
impl<Role, S> IntoTransport<Role, S::Error, TransportAdapterAsyncCombinedRW> for S
|
||||
where
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ use crate::{
|
|||
type BoxedSseStream = BoxStream<'static, Result<Sse, SseError>>;
|
||||
|
||||
#[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<String>,
|
||||
|
|
@ -212,6 +214,7 @@ pub trait StreamableHttpClient: Clone + Send + 'static {
|
|||
+ '_;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct RetryConfig {
|
||||
pub max_times: Option<usize>,
|
||||
pub min_duration: Duration,
|
||||
|
|
@ -253,6 +256,7 @@ struct SessionCleanupInfo<C> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct StreamableHttpClientWorker<C: StreamableHttpClient> {
|
||||
pub client: C,
|
||||
pub config: StreamableHttpClientTransportConfig,
|
||||
|
|
@ -1046,6 +1050,7 @@ impl<C: StreamableHttpClient> StreamableHttpClientTransport<C> {
|
|||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct StreamableHttpClientTransportConfig {
|
||||
pub uri: Arc<str>,
|
||||
pub retry_config: Arc<dyn SseRetryPolicy>,
|
||||
|
|
|
|||
|
|
@ -29,12 +29,14 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct LocalSessionManager {
|
||||
pub sessions: tokio::sync::RwLock<HashMap<SessionId, LocalSessionHandle>>,
|
||||
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<HttpRequestId>,
|
||||
pub inner: Receiver<ServerSseMessage>,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<RoleServer> for NeverTransport {
|
||||
type Error = ErrorSessionManagementNotSupported;
|
||||
|
|
|
|||
|
|
@ -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<Duration>,
|
||||
|
|
@ -62,6 +63,33 @@ impl Default for StreamableHttpServerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
impl StreamableHttpServerConfig {
|
||||
pub fn with_sse_keep_alive(mut self, duration: Option<Duration>) -> Self {
|
||||
self.sse_keep_alive = duration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sse_retry(mut self, duration: Option<Duration>) -> Self {
|
||||
self.sse_retry = duration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_stateful_mode(mut self, stateful: bool) -> Self {
|
||||
self.stateful_mode = stateful;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_json_response(mut self, json_response: bool) -> Self {
|
||||
self.json_response = json_response;
|
||||
self
|
||||
}
|
||||
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ pub trait Worker: Sized + Send + 'static {
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct WorkerSendRequest<W: Worker> {
|
||||
pub message: TxJsonRpcMessage<W::Role>,
|
||||
pub responder: tokio::sync::oneshot::Sender<Result<(), W::Error>>,
|
||||
|
|
@ -66,6 +67,7 @@ pub struct WorkerTransport<W: Worker> {
|
|||
ct: CancellationToken,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct WorkerConfig {
|
||||
pub name: Option<String>,
|
||||
pub channel_buffer_capacity: usize,
|
||||
|
|
@ -79,6 +81,7 @@ impl Default for WorkerConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[non_exhaustive]
|
||||
pub enum WorkerAdapter {}
|
||||
|
||||
impl<W: Worker> IntoTransport<W::Role, W::Error, WorkerAdapter> for W {
|
||||
|
|
@ -143,11 +146,13 @@ impl<W: Worker> WorkerTransport<W> {
|
|||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct SendRequest<W: Worker> {
|
||||
pub message: TxJsonRpcMessage<W::Role>,
|
||||
pub responder: tokio::sync::oneshot::Sender<RxJsonRpcMessage<W::Role>>,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct WorkerContext<W: Worker> {
|
||||
pub to_handler_tx: tokio::sync::mpsc::Sender<RxJsonRpcMessage<W::Role>>,
|
||||
pub from_handler_rx: tokio::sync::mpsc::Receiver<WorkerSendRequest<W>>,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
|
||||
|
||||
use rmcp::{
|
||||
ErrorData as McpError, handler::server::wrapper::Parameters, model::*, schemars, tool,
|
||||
tool_router,
|
||||
|
|
|
|||
|
|
@ -13,3 +13,122 @@ fn test_tool_list_result() {
|
|||
})
|
||||
));
|
||||
}
|
||||
|
||||
/// Regression tests for `#[serde(untagged)]` deserialization of `ServerResult`.
|
||||
///
|
||||
/// `ServerResult` is an untagged enum, so serde tries each variant in declaration
|
||||
/// order. `GetTaskPayloadResult` has a custom `Deserialize` impl that always fails
|
||||
/// so it is skipped, and `CustomResult(Value)` acts as the catch-all. If variant
|
||||
/// ordering changes or the custom impl is removed, these tests will catch the
|
||||
/// regression.
|
||||
mod untagged_server_result {
|
||||
use rmcp::model::{CallToolResult, JsonRpcResponse, ServerJsonRpcMessage, ServerResult};
|
||||
use serde_json::json;
|
||||
|
||||
/// Helper: wrap a result value in a JSON-RPC response envelope.
|
||||
fn wrap_response(result: serde_json::Value) -> serde_json::Value {
|
||||
json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": result
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a JSON-RPC response and return the inner `ServerResult`.
|
||||
fn parse_result(json: serde_json::Value) -> ServerResult {
|
||||
let msg: ServerJsonRpcMessage = serde_json::from_value(json).unwrap();
|
||||
match msg {
|
||||
ServerJsonRpcMessage::Response(JsonRpcResponse { result, .. }) => result,
|
||||
other => panic!("expected Response, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_result_deserializes_to_correct_variant() {
|
||||
let result = parse_result(wrap_response(json!({
|
||||
"protocolVersion": "2025-03-26",
|
||||
"capabilities": {},
|
||||
"serverInfo": {
|
||||
"name": "test-server",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
})));
|
||||
assert!(
|
||||
matches!(result, ServerResult::InitializeResult(_)),
|
||||
"expected InitializeResult, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_tool_result_deserializes_to_correct_variant() {
|
||||
let result = parse_result(wrap_response(json!({
|
||||
"content": [
|
||||
{ "type": "text", "text": "hello" }
|
||||
]
|
||||
})));
|
||||
assert!(
|
||||
matches!(result, ServerResult::CallToolResult(_)),
|
||||
"expected CallToolResult, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_object_deserializes_to_empty_result() {
|
||||
let result = parse_result(wrap_response(json!({})));
|
||||
assert!(
|
||||
matches!(result, ServerResult::EmptyResult(_)),
|
||||
"expected EmptyResult, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_shape_falls_through_to_custom_result() {
|
||||
// A value that doesn't match any known result type should land in
|
||||
// CustomResult, NOT GetTaskPayloadResult.
|
||||
let result = parse_result(wrap_response(json!({
|
||||
"some_unknown_field": "some_value",
|
||||
"number": 42
|
||||
})));
|
||||
assert!(
|
||||
matches!(result, ServerResult::CustomResult(_)),
|
||||
"expected CustomResult, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbitrary_json_value_does_not_deserialize_as_get_task_payload_result() {
|
||||
// GetTaskPayloadResult wraps a bare Value, but its custom Deserialize
|
||||
// always fails so serde skips it during untagged resolution.
|
||||
// Any JSON value must fall through to CustomResult instead.
|
||||
for value in [json!(42), json!("hello"), json!(null), json!([1, 2, 3])] {
|
||||
let result = parse_result(wrap_response(value.clone()));
|
||||
assert!(
|
||||
matches!(result, ServerResult::CustomResult(_)),
|
||||
"value {value} should deserialize as CustomResult, got {result:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_initialize_result_preserves_variant() {
|
||||
let json = json!({
|
||||
"protocolVersion": "2025-03-26",
|
||||
"capabilities": {},
|
||||
"serverInfo": { "name": "test", "version": "1.0" }
|
||||
});
|
||||
// Parse as ServerResult, serialize back, parse again — must stay InitializeResult.
|
||||
let result = parse_result(wrap_response(json.clone()));
|
||||
assert!(matches!(&result, ServerResult::InitializeResult(_)));
|
||||
let reserialized = serde_json::to_value(&result).unwrap();
|
||||
let result2 = parse_result(wrap_response(reserialized));
|
||||
assert!(matches!(result2, ServerResult::InitializeResult(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_call_tool_result_preserves_variant() {
|
||||
let original = CallToolResult::success(vec![rmcp::model::Content::text("hello world")]);
|
||||
let json = serde_json::to_value(&original).unwrap();
|
||||
let result = parse_result(wrap_response(json));
|
||||
assert!(matches!(result, ServerResult::CallToolResult(_)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(clippy::exhaustive_structs)]
|
||||
//cargo test --test test_json_schema_detection --features "client server macros"
|
||||
use rmcp::{
|
||||
Json, ServerHandler, handler::server::router::tool::ToolRouter, tool, tool_handler, tool_router,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use rmcp::{
|
|||
model::*,
|
||||
service::{RequestContext, Service},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
// Tests start here
|
||||
#[tokio::test]
|
||||
|
|
@ -48,13 +47,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -85,13 +78,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(2),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(2), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -122,13 +109,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(3),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(3), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -179,13 +160,7 @@ async fn test_context_inclusion_ignored_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -241,13 +216,7 @@ async fn test_message_sequence_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -307,13 +276,7 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -330,13 +293,7 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(2),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(2), client.peer().clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
@ -370,13 +327,7 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -405,13 +356,7 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(2),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(2), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -457,13 +402,7 @@ async fn test_context_inclusion() -> anyhow::Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Meta::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use rmcp::{
|
|||
model::*,
|
||||
service::{RequestContext, Service},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_basic_sampling_message_creation() -> Result<()> {
|
||||
|
|
@ -126,13 +125,7 @@ async fn test_sampling_integration_with_test_handlers() -> Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(1),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -189,13 +182,7 @@ async fn test_sampling_no_context_inclusion() -> Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(2),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(2), client.peer().clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -253,13 +240,7 @@ async fn test_sampling_error_invalid_message_sequence() -> Result<()> {
|
|||
let result = handler
|
||||
.handle_request(
|
||||
request.clone(),
|
||||
RequestContext {
|
||||
peer: client.peer().clone(),
|
||||
ct: CancellationToken::new(),
|
||||
id: NumberOrString::Number(3),
|
||||
meta: Default::default(),
|
||||
extensions: Default::default(),
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(3), client.peer().clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
|
|||
|
|
@ -74,13 +74,7 @@ async fn start_test_server(ct: CancellationToken, trigger: Arc<Notify>) -> Strin
|
|||
let service = StreamableHttpService::new(
|
||||
move || Ok(server.clone()),
|
||||
Arc::new(LocalSessionManager::default()),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: Some(Duration::from_secs(15)),
|
||||
sse_retry: Some(Duration::from_secs(3)),
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default().with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ async fn spawn_server(
|
|||
#[tokio::test]
|
||||
async fn stateless_json_response_returns_application_json() -> anyhow::Result<()> {
|
||||
let ct = CancellationToken::new();
|
||||
let (client, url, ct) = spawn_server(StreamableHttpServerConfig {
|
||||
stateful_mode: false,
|
||||
json_response: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
})
|
||||
let (client, url, ct) = spawn_server(
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_stateful_mode(false)
|
||||
.with_json_response(true)
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let response = client
|
||||
|
|
@ -79,13 +79,12 @@ async fn stateless_json_response_returns_application_json() -> anyhow::Result<()
|
|||
#[tokio::test]
|
||||
async fn stateless_sse_mode_default_unchanged() -> anyhow::Result<()> {
|
||||
let ct = CancellationToken::new();
|
||||
let (client, url, ct) = spawn_server(StreamableHttpServerConfig {
|
||||
stateful_mode: false,
|
||||
json_response: false,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
})
|
||||
let (client, url, ct) = spawn_server(
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_stateful_mode(false)
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let response = client
|
||||
|
|
@ -122,13 +121,12 @@ async fn stateless_sse_mode_default_unchanged() -> anyhow::Result<()> {
|
|||
async fn json_response_ignored_in_stateful_mode() -> anyhow::Result<()> {
|
||||
let ct = CancellationToken::new();
|
||||
// json_response: true has no effect when stateful_mode: true — server still uses SSE
|
||||
let (client, url, ct) = spawn_server(StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
json_response: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
})
|
||||
let (client, url, ct) = spawn_server(
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_json_response(true)
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let response = client
|
||||
|
|
|
|||
|
|
@ -18,12 +18,9 @@ async fn test_priming_on_stream_start() -> anyhow::Result<()> {
|
|||
StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
Default::default(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
@ -87,12 +84,9 @@ async fn test_priming_on_stream_close() -> anyhow::Result<()> {
|
|||
let service = StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
session_manager.clone(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
|
|||
|
|
@ -32,12 +32,9 @@ async fn test_stale_session_id_returns_status_aware_error() -> anyhow::Result<()
|
|||
StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
Default::default(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
@ -103,12 +100,9 @@ async fn test_transparent_reinitialization_on_session_expiry() -> anyhow::Result
|
|||
let service = StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
session_manager.clone(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
@ -183,12 +177,9 @@ async fn test_session_expired_error_when_reinit_disabled() -> anyhow::Result<()>
|
|||
let service = StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
session_manager.clone(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(clippy::exhaustive_structs)]
|
||||
//cargo test --test test_structured_output --features "client server macros"
|
||||
use rmcp::{
|
||||
Json, ServerHandler,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(clippy::exhaustive_structs)]
|
||||
//cargo test --test test_tool_builder_methods --features "client server macros"
|
||||
use rmcp::model::{JsonObject, Tool};
|
||||
use schemars::JsonSchema;
|
||||
|
|
|
|||
|
|
@ -69,12 +69,9 @@ async fn test_with_js_streamable_http_client() -> anyhow::Result<()> {
|
|||
StreamableHttpService::new(
|
||||
|| Ok(Calculator::new()),
|
||||
Default::default(),
|
||||
StreamableHttpServerConfig {
|
||||
stateful_mode: true,
|
||||
sse_keep_alive: None,
|
||||
cancellation_token: ct.child_token(),
|
||||
..Default::default()
|
||||
},
|
||||
StreamableHttpServerConfig::default()
|
||||
.with_sse_keep_alive(None)
|
||||
.with_cancellation_token(ct.child_token()),
|
||||
);
|
||||
let router = axum::Router::new().nest_service("/mcp", service);
|
||||
let tcp_listener = tokio::net::TcpListener::bind(STREAMABLE_HTTP_BIND_ADDRESS).await?;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue