feat!: add MRTR model types (SEP-2322) (#915)
* feat!: add SEP-2322 MRTR model types * feat!: remove URLElicitationRequiredError (SEP-2322)
This commit is contained in:
parent
519577601d
commit
f4ff56b81d
22 changed files with 969 additions and 255 deletions
|
|
@ -204,9 +204,8 @@ impl ServerHandler for ConformanceServer {
|
|||
),
|
||||
];
|
||||
Ok(ListToolsResult {
|
||||
meta: None,
|
||||
tools,
|
||||
next_cursor: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +539,6 @@ impl ServerHandler for ConformanceServer {
|
|||
_cx: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourcesResult, ErrorData> {
|
||||
Ok(ListResourcesResult {
|
||||
meta: None,
|
||||
resources: vec![
|
||||
Resource::new("test://static-text", "Static Text Resource")
|
||||
.with_description("A static text resource for testing")
|
||||
|
|
@ -549,7 +547,7 @@ impl ServerHandler for ConformanceServer {
|
|||
.with_description("A static binary/blob resource for testing")
|
||||
.with_mime_type("image/png"),
|
||||
],
|
||||
next_cursor: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -609,13 +607,12 @@ impl ServerHandler for ConformanceServer {
|
|||
_cx: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourceTemplatesResult, ErrorData> {
|
||||
Ok(ListResourceTemplatesResult {
|
||||
meta: None,
|
||||
resource_templates: vec![
|
||||
ResourceTemplate::new("test://template/{id}/data", "Dynamic Resource")
|
||||
.with_description("A dynamic resource with parameter substitution")
|
||||
.with_mime_type("application/json"),
|
||||
],
|
||||
next_cursor: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +642,6 @@ impl ServerHandler for ConformanceServer {
|
|||
_cx: RequestContext<RoleServer>,
|
||||
) -> Result<ListPromptsResult, ErrorData> {
|
||||
Ok(ListPromptsResult {
|
||||
meta: None,
|
||||
prompts: vec![
|
||||
Prompt::new(
|
||||
"test_simple_prompt",
|
||||
|
|
@ -675,7 +671,7 @@ impl ServerHandler for ConformanceServer {
|
|||
None,
|
||||
),
|
||||
],
|
||||
next_cursor: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<Toke
|
|||
) -> Result<rmcp::model::ListPromptsResult, rmcp::ErrorData> {
|
||||
let prompts = #router_expr.list_all();
|
||||
Ok(rmcp::model::ListPromptsResult {
|
||||
result_type: Default::default(),
|
||||
prompts,
|
||||
meta: #meta,
|
||||
next_cursor: None,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
|
|||
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
|
||||
) -> Result<rmcp::model::ListToolsResult, rmcp::ErrorData> {
|
||||
Ok(rmcp::model::ListToolsResult{
|
||||
result_type: Default::default(),
|
||||
tools: #router.list_all(),
|
||||
meta: #result_meta,
|
||||
next_cursor: None,
|
||||
|
|
|
|||
|
|
@ -66,10 +66,6 @@ impl<H: ClientHandler> Service<RoleClient> for H {
|
|||
ServerNotification::PromptListChangedNotification(_notification_no_param) => {
|
||||
self.on_prompt_list_changed(context).await
|
||||
}
|
||||
ServerNotification::ElicitationCompleteNotification(notification) => {
|
||||
self.on_url_elicitation_notification_complete(notification.params, context)
|
||||
.await
|
||||
}
|
||||
ServerNotification::TaskStatusNotification(notification) => {
|
||||
self.on_task_status(notification.params, context).await
|
||||
}
|
||||
|
|
@ -242,13 +238,6 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
|
|||
std::future::ready(())
|
||||
}
|
||||
|
||||
fn on_url_elicitation_notification_complete(
|
||||
&self,
|
||||
params: ElicitationResponseNotificationParam,
|
||||
context: NotificationContext<RoleClient>,
|
||||
) -> impl Future<Output = ()> + MaybeSendFuture + '_ {
|
||||
std::future::ready(())
|
||||
}
|
||||
fn on_task_status(
|
||||
&self,
|
||||
params: TaskStatusNotificationParam,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ impl IntoGetPromptResult for GetPromptResult {
|
|||
impl IntoGetPromptResult for Vec<PromptMessage> {
|
||||
fn into_get_prompt_result(self) -> Result<GetPromptResult, crate::ErrorData> {
|
||||
Ok(GetPromptResult {
|
||||
result_type: Default::default(),
|
||||
description: None,
|
||||
messages: self,
|
||||
meta: None,
|
||||
|
|
|
|||
|
|
@ -669,6 +669,8 @@ mod tests {
|
|||
name: Cow::Borrowed("requires_params"),
|
||||
arguments: Some(Default::default()),
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), peer),
|
||||
);
|
||||
|
|
@ -708,6 +710,8 @@ mod tests {
|
|||
name: Cow::Borrowed("test_tool"),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
RequestContext::new(NumberOrString::Number(1), peer),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ impl<'s, S> ToolCallContext<'s, S> {
|
|||
name,
|
||||
arguments,
|
||||
task,
|
||||
..
|
||||
}: CallToolRequestParams,
|
||||
request_context: RequestContext<RoleServer>,
|
||||
) -> Self {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ mod content;
|
|||
mod elicitation_schema;
|
||||
mod extension;
|
||||
mod meta;
|
||||
mod mrtr;
|
||||
mod prompt;
|
||||
mod resource;
|
||||
mod serde_impl;
|
||||
|
|
@ -23,6 +24,7 @@ pub use content::*;
|
|||
pub use elicitation_schema::*;
|
||||
pub use extension::*;
|
||||
pub use meta::*;
|
||||
pub use mrtr::*;
|
||||
pub use prompt::*;
|
||||
pub use resource::*;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
|
|
@ -516,7 +518,6 @@ impl ErrorCode {
|
|||
pub const INVALID_PARAMS: Self = Self(-32602);
|
||||
pub const INTERNAL_ERROR: Self = Self(-32603);
|
||||
pub const PARSE_ERROR: Self = Self(-32700);
|
||||
pub const URL_ELICITATION_REQUIRED: Self = Self(-32042);
|
||||
}
|
||||
|
||||
/// Error information for JSON-RPC error responses.
|
||||
|
|
@ -572,12 +573,6 @@ impl ErrorData {
|
|||
pub fn internal_error(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
|
||||
Self::new(ErrorCode::INTERNAL_ERROR, message, data)
|
||||
}
|
||||
pub fn url_elicitation_required(
|
||||
message: impl Into<Cow<'static, str>>,
|
||||
data: Option<Value>,
|
||||
) -> Self {
|
||||
Self::new(ErrorCode::URL_ELICITATION_REQUIRED, message, data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents any JSON-RPC message that can be sent or received.
|
||||
|
|
@ -684,6 +679,71 @@ impl From<EmptyResult> for () {
|
|||
fn from(_value: EmptyResult) {}
|
||||
}
|
||||
|
||||
/// Indicates the type of a result object, allowing the client to
|
||||
/// determine how to parse the response.
|
||||
///
|
||||
/// The spec defines this as an open string (`"complete" | "input_required" | string`),
|
||||
/// so unknown values are preserved rather than rejected. Servers implementing this
|
||||
/// protocol version MUST include `resultType` in every result. For backward
|
||||
/// compatibility, clients MUST treat an absent field as `"complete"`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ResultType(Cow<'static, str>);
|
||||
|
||||
impl ResultType {
|
||||
pub const COMPLETE: Self = Self(Cow::Borrowed("complete"));
|
||||
pub const INPUT_REQUIRED: Self = Self(Cow::Borrowed("input_required"));
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Returns `true` if this is `"input_required"`.
|
||||
pub fn is_input_required(&self) -> bool {
|
||||
self.0 == "input_required"
|
||||
}
|
||||
|
||||
/// Returns `true` if this is `"complete"`.
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.0 == "complete"
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ResultType {
|
||||
fn default() -> Self {
|
||||
Self::COMPLETE
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ResultType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ResultType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
"complete" => Ok(Self::COMPLETE),
|
||||
"input_required" => Ok(Self::INPUT_REQUIRED),
|
||||
_ => Ok(Self(Cow::Owned(s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ResultType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A catch-all response either side can use for custom requests.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
|
|
@ -1185,6 +1245,9 @@ macro_rules! paginated_result {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
|
||||
pub struct $t {
|
||||
/// Result type discriminator. Absent values deserialize as `"complete"`.
|
||||
#[serde(default)]
|
||||
pub result_type: ResultType,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -1197,6 +1260,7 @@ macro_rules! paginated_result {
|
|||
items: $t_item,
|
||||
) -> Self {
|
||||
Self {
|
||||
result_type: ResultType::default(),
|
||||
meta: None,
|
||||
next_cursor: None,
|
||||
$i_item: items,
|
||||
|
|
@ -1240,6 +1304,13 @@ pub struct ReadResourceRequestParams {
|
|||
pub meta: Option<Meta>,
|
||||
/// The URI of the resource to read
|
||||
pub uri: String,
|
||||
/// Client responses to server-initiated input requests from a previous
|
||||
/// [`InputRequiredResult`].
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_responses: Option<InputResponses>,
|
||||
/// Opaque request state echoed back from a previous [`InputRequiredResult`].
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_state: Option<String>,
|
||||
}
|
||||
|
||||
impl ReadResourceRequestParams {
|
||||
|
|
@ -1248,6 +1319,8 @@ impl ReadResourceRequestParams {
|
|||
Self {
|
||||
meta: None,
|
||||
uri: uri.into(),
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1256,6 +1329,18 @@ impl ReadResourceRequestParams {
|
|||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the input responses for an MRTR retry.
|
||||
pub fn with_input_responses(mut self, input_responses: InputResponses) -> Self {
|
||||
self.input_responses = Some(input_responses);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the request state for an MRTR retry.
|
||||
pub fn with_request_state(mut self, request_state: impl Into<String>) -> Self {
|
||||
self.request_state = Some(request_state.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestParamsMeta for ReadResourceRequestParams {
|
||||
|
|
@ -1276,6 +1361,9 @@ pub type ReadResourceRequestParam = ReadResourceRequestParams;
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct ReadResourceResult {
|
||||
/// Result type discriminator. Absent values deserialize as `"complete"`.
|
||||
#[serde(default)]
|
||||
pub result_type: ResultType,
|
||||
/// The actual content of the resource
|
||||
pub contents: Vec<ResourceContents>,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -1286,6 +1374,7 @@ impl ReadResourceResult {
|
|||
/// Create a new ReadResourceResult with the given contents.
|
||||
pub fn new(contents: Vec<ResourceContents>) -> Self {
|
||||
Self {
|
||||
result_type: ResultType::default(),
|
||||
contents,
|
||||
meta: None,
|
||||
}
|
||||
|
|
@ -1433,6 +1522,13 @@ pub struct GetPromptRequestParams {
|
|||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<JsonObject>,
|
||||
/// Client responses to server-initiated input requests from a previous
|
||||
/// [`InputRequiredResult`].
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_responses: Option<InputResponses>,
|
||||
/// Opaque request state echoed back from a previous [`InputRequiredResult`].
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_state: Option<String>,
|
||||
}
|
||||
|
||||
impl GetPromptRequestParams {
|
||||
|
|
@ -1442,6 +1538,8 @@ impl GetPromptRequestParams {
|
|||
meta: None,
|
||||
name: name.into(),
|
||||
arguments: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1456,6 +1554,18 @@ impl GetPromptRequestParams {
|
|||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the input responses for an MRTR retry.
|
||||
pub fn with_input_responses(mut self, input_responses: InputResponses) -> Self {
|
||||
self.input_responses = Some(input_responses);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the request state for an MRTR retry.
|
||||
pub fn with_request_state(mut self, request_state: impl Into<String>) -> Self {
|
||||
self.request_state = Some(request_state.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestParamsMeta for GetPromptRequestParams {
|
||||
|
|
@ -2438,6 +2548,9 @@ impl CompletionInfo {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct CompleteResult {
|
||||
/// Result type discriminator. Absent values deserialize as `"complete"`.
|
||||
#[serde(default)]
|
||||
pub result_type: ResultType,
|
||||
pub completion: CompletionInfo,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
|
|
@ -2447,6 +2560,7 @@ impl CompleteResult {
|
|||
/// Create a new CompleteResult with the given completion info.
|
||||
pub fn new(completion: CompletionInfo) -> Self {
|
||||
Self {
|
||||
result_type: ResultType::default(),
|
||||
completion,
|
||||
meta: None,
|
||||
}
|
||||
|
|
@ -2653,7 +2767,6 @@ pub type RootsListChangedNotification = NotificationNoParam<RootsListChangedNoti
|
|||
// Elicitation allows servers to request interactive input from users during tool execution.
|
||||
const_string!(ElicitationCreateRequestMethod = "elicitation/create");
|
||||
const_string!(ElicitationResponseNotificationMethod = "notifications/elicitation/response");
|
||||
const_string!(ElicitationCompletionNotificationMethod = "notifications/elicitation/complete");
|
||||
|
||||
/// Represents the possible actions a user can take in response to an elicitation request.
|
||||
///
|
||||
|
|
@ -2888,34 +3001,6 @@ pub type ElicitRequest = Request<ElicitationCreateRequestMethod, ElicitRequestPa
|
|||
#[deprecated(since = "2.0.0", note = "Renamed to ElicitRequest")]
|
||||
pub type CreateElicitationRequest = ElicitRequest;
|
||||
|
||||
/// Notification parameters for an url elicitation completion notification.
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct ElicitationResponseNotificationParam {
|
||||
pub elicitation_id: String,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
}
|
||||
|
||||
impl ElicitationResponseNotificationParam {
|
||||
/// Create a new ElicitationResponseNotificationParam.
|
||||
pub fn new(elicitation_id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
elicitation_id: elicitation_id.into(),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Notification sent when an url elicitation process is completed.
|
||||
pub type ElicitationCompleteNotification =
|
||||
Notification<ElicitationCompletionNotificationMethod, ElicitationResponseNotificationParam>;
|
||||
|
||||
#[deprecated(since = "2.0.0", note = "Renamed to ElicitationCompleteNotification")]
|
||||
pub type ElicitationCompletionNotification = ElicitationCompleteNotification;
|
||||
|
||||
// =============================================================================
|
||||
// TOOL EXECUTION RESULTS
|
||||
// =============================================================================
|
||||
|
|
@ -2929,6 +3014,9 @@ pub type ElicitationCompletionNotification = ElicitationCompleteNotification;
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct CallToolResult {
|
||||
/// Result type discriminator. Absent values deserialize as `"complete"`.
|
||||
#[serde(default)]
|
||||
pub result_type: ResultType,
|
||||
/// The content returned by the tool (text, images, etc.)
|
||||
#[serde(default)]
|
||||
pub content: Vec<ContentBlock>,
|
||||
|
|
@ -2956,6 +3044,8 @@ impl<'de> Deserialize<'de> for CallToolResult {
|
|||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Helper {
|
||||
#[serde(default)]
|
||||
result_type: ResultType,
|
||||
content: Option<Vec<ContentBlock>>,
|
||||
structured_content: Option<Value>,
|
||||
is_error: Option<bool>,
|
||||
|
|
@ -2977,6 +3067,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
|
|||
}
|
||||
|
||||
Ok(CallToolResult {
|
||||
result_type: helper.result_type,
|
||||
content: helper.content.unwrap_or_default(),
|
||||
structured_content: helper.structured_content,
|
||||
is_error: helper.is_error,
|
||||
|
|
@ -2989,6 +3080,7 @@ impl CallToolResult {
|
|||
/// Create a successful tool result with unstructured content
|
||||
pub fn success(content: Vec<ContentBlock>) -> Self {
|
||||
CallToolResult {
|
||||
result_type: ResultType::default(),
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
|
|
@ -3046,6 +3138,7 @@ impl CallToolResult {
|
|||
/// ```
|
||||
pub fn error(content: Vec<ContentBlock>) -> Self {
|
||||
CallToolResult {
|
||||
result_type: ResultType::default(),
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(true),
|
||||
|
|
@ -3068,6 +3161,7 @@ impl CallToolResult {
|
|||
/// ```
|
||||
pub fn structured(value: Value) -> Self {
|
||||
CallToolResult {
|
||||
result_type: ResultType::default(),
|
||||
content: vec![ContentBlock::text(value.to_string())],
|
||||
structured_content: Some(value),
|
||||
is_error: Some(false),
|
||||
|
|
@ -3094,6 +3188,7 @@ impl CallToolResult {
|
|||
/// ```
|
||||
pub fn structured_error(value: Value) -> Self {
|
||||
CallToolResult {
|
||||
result_type: ResultType::default(),
|
||||
content: vec![ContentBlock::text(value.to_string())],
|
||||
structured_content: Some(value),
|
||||
is_error: Some(true),
|
||||
|
|
@ -3170,6 +3265,14 @@ pub struct CallToolRequestParams {
|
|||
/// Task metadata for async task management (SEP-1319)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub task: Option<TaskMetadata>,
|
||||
/// Client responses to server-initiated input requests from a previous
|
||||
/// [`InputRequiredResult`]. Present only when retrying after an incomplete result.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_responses: Option<InputResponses>,
|
||||
/// Opaque request state echoed back from a previous [`InputRequiredResult`].
|
||||
/// Clients MUST return this value exactly as received.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_state: Option<String>,
|
||||
}
|
||||
|
||||
impl CallToolRequestParams {
|
||||
|
|
@ -3180,6 +3283,8 @@ impl CallToolRequestParams {
|
|||
name: name.into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3194,6 +3299,18 @@ impl CallToolRequestParams {
|
|||
self.task = Some(task);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the input responses for an MRTR retry.
|
||||
pub fn with_input_responses(mut self, input_responses: InputResponses) -> Self {
|
||||
self.input_responses = Some(input_responses);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the request state for an MRTR retry.
|
||||
pub fn with_request_state(mut self, request_state: impl Into<String>) -> Self {
|
||||
self.request_state = Some(request_state.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestParamsMeta for CallToolRequestParams {
|
||||
|
|
@ -3286,6 +3403,9 @@ impl CreateMessageResult {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct GetPromptResult {
|
||||
/// Result type discriminator. Absent values deserialize as `"complete"`.
|
||||
#[serde(default)]
|
||||
pub result_type: ResultType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub messages: Vec<PromptMessage>,
|
||||
|
|
@ -3297,6 +3417,7 @@ impl GetPromptResult {
|
|||
/// Create a new GetPromptResult with required fields.
|
||||
pub fn new(messages: Vec<PromptMessage>) -> Self {
|
||||
Self {
|
||||
result_type: ResultType::default(),
|
||||
description: None,
|
||||
messages,
|
||||
meta: None,
|
||||
|
|
@ -3662,7 +3783,6 @@ ts_union!(
|
|||
| ResourceListChangedNotification
|
||||
| ToolListChangedNotification
|
||||
| PromptListChangedNotification
|
||||
| ElicitationCompleteNotification
|
||||
| TaskStatusNotification
|
||||
| CustomNotification;
|
||||
);
|
||||
|
|
@ -3683,6 +3803,7 @@ ts_union!(
|
|||
| GetTaskResult
|
||||
| CancelTaskResult
|
||||
| CallToolResult
|
||||
| InputRequiredResult
|
||||
| GetTaskPayloadResult
|
||||
| EmptyResult
|
||||
| CustomResult
|
||||
|
|
|
|||
|
|
@ -218,7 +218,6 @@ variant_extension! {
|
|||
ResourceListChangedNotification
|
||||
ToolListChangedNotification
|
||||
PromptListChangedNotification
|
||||
ElicitationCompleteNotification
|
||||
TaskStatusNotification
|
||||
CustomNotification
|
||||
}
|
||||
|
|
|
|||
388
crates/rmcp/src/model/mrtr.rs
Normal file
388
crates/rmcp/src/model/mrtr.rs
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
//! Multi Round-Trip Request (MRTR) types for SEP-2322.
|
||||
//!
|
||||
//! Provides [`InputRequiredResult`], [`InputRequests`], and [`InputResponses`]
|
||||
//! for the stateless multi round-trip request pattern defined in the MCP spec.
|
||||
//! [`ResultType`] lives in the parent [`super`] module alongside other base result types.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! A server may respond to `tools/call`, `prompts/get`, or `resources/read` with an
|
||||
//! [`InputRequiredResult`] instead of the normal result. The client fulfills the
|
||||
//! [`InputRequests`], then retries the original request with [`InputResponses`] and
|
||||
//! the echoed `requestState`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{CreateMessageRequest, ElicitRequest, ListRootsRequest, Meta, ResultType};
|
||||
|
||||
/// A server-initiated request that can appear inside [`InputRequests`].
|
||||
///
|
||||
/// Per the MCP spec, only `CreateMessageRequest` (sampling),
|
||||
/// `ElicitRequest` (elicitation), and `ListRootsRequest` (roots)
|
||||
/// are allowed. This is modeled as an untagged enum rather than a
|
||||
/// `ServerRequest` alias to prevent `PingRequest` or `CustomRequest` from
|
||||
/// being included.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub enum InputRequest {
|
||||
/// A `sampling/createMessage` request.
|
||||
CreateMessage(CreateMessageRequest),
|
||||
/// An `elicitation/create` request.
|
||||
Elicitation(ElicitRequest),
|
||||
/// A `roots/list` request.
|
||||
ListRoots(ListRootsRequest),
|
||||
}
|
||||
|
||||
/// A map of server-initiated requests that the client must fulfill.
|
||||
///
|
||||
/// Keys are server-assigned string identifiers; values are request objects
|
||||
/// (`ElicitRequest`, `CreateMessageRequest`, or `ListRootsRequest`).
|
||||
pub type InputRequests = BTreeMap<String, InputRequest>;
|
||||
|
||||
/// A map of client responses to server-initiated requests.
|
||||
///
|
||||
/// Keys correspond to the keys in the [`InputRequests`] map; values are the
|
||||
/// client's result for each request (`ElicitResult`, `CreateMessageResult`,
|
||||
/// or `ListRootsResult`), represented as opaque JSON because the
|
||||
/// heterogeneous `ClientResult` union does not derive the traits required
|
||||
/// for use as a `BTreeMap` value.
|
||||
pub type InputResponses = BTreeMap<String, Value>;
|
||||
|
||||
/// A result indicating that additional input is needed before the request
|
||||
/// can be completed.
|
||||
///
|
||||
/// At least one of [`input_requests`](Self::input_requests) or
|
||||
/// [`request_state`](Self::request_state) MUST be present.
|
||||
///
|
||||
/// Servers MAY send this in response to `tools/call`, `prompts/get`, or
|
||||
/// `resources/read`. Servers MUST NOT send this for any other request.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rmcp::model::InputRequiredResult;
|
||||
///
|
||||
/// let result = InputRequiredResult::from_request_state("opaque-server-state");
|
||||
/// assert!(result.input_requests.is_none());
|
||||
/// assert_eq!(result.request_state.as_deref(), Some("opaque-server-state"));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct InputRequiredResult {
|
||||
/// Always `"input_required"` for this result type.
|
||||
pub result_type: ResultType,
|
||||
|
||||
/// Server-initiated requests that the client must fulfill before retrying.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_requests: Option<InputRequests>,
|
||||
|
||||
/// Opaque request state to be echoed back by the client on retry.
|
||||
/// Clients MUST NOT inspect, parse, modify, or make any assumptions
|
||||
/// about the contents.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_state: Option<String>,
|
||||
|
||||
/// Optional protocol-level metadata.
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
}
|
||||
|
||||
/// Custom deserializer that requires `resultType: "input_required"` to prevent
|
||||
/// greedy matching in the untagged `ServerResult` enum (which would otherwise
|
||||
/// swallow empty objects or unknown shapes).
|
||||
impl<'de> Deserialize<'de> for InputRequiredResult {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Helper {
|
||||
result_type: Option<ResultType>,
|
||||
input_requests: Option<InputRequests>,
|
||||
request_state: Option<String>,
|
||||
#[serde(rename = "_meta")]
|
||||
meta: Option<Meta>,
|
||||
}
|
||||
|
||||
let helper = Helper::deserialize(deserializer)?;
|
||||
|
||||
match &helper.result_type {
|
||||
Some(rt) if rt.is_input_required() => {}
|
||||
_ => {
|
||||
return Err(serde::de::Error::custom(
|
||||
"InputRequiredResult requires resultType to be \"input_required\"",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InputRequiredResult {
|
||||
result_type: ResultType::INPUT_REQUIRED,
|
||||
input_requests: helper.input_requests,
|
||||
request_state: helper.request_state,
|
||||
meta: helper.meta,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputRequiredResult {
|
||||
/// Creates a new `InputRequiredResult` with both input requests and request state.
|
||||
pub fn new(input_requests: Option<InputRequests>, request_state: Option<String>) -> Self {
|
||||
Self {
|
||||
result_type: ResultType::INPUT_REQUIRED,
|
||||
input_requests,
|
||||
request_state,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates from input requests only.
|
||||
pub fn from_input_requests(input_requests: InputRequests) -> Self {
|
||||
Self::new(Some(input_requests), None)
|
||||
}
|
||||
|
||||
/// Creates from request state only (e.g. for load shedding).
|
||||
pub fn from_request_state(request_state: impl Into<String>) -> Self {
|
||||
Self::new(None, Some(request_state.into()))
|
||||
}
|
||||
|
||||
/// Sets optional metadata.
|
||||
pub fn with_meta(mut self, meta: Meta) -> Self {
|
||||
self.meta = Some(meta);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod result_type {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_is_complete() {
|
||||
assert_eq!(ResultType::default(), ResultType::COMPLETE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_complete() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ResultType::COMPLETE).unwrap(),
|
||||
serde_json::json!("complete")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_input_required() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ResultType::INPUT_REQUIRED).unwrap(),
|
||||
serde_json::json!("input_required")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_known_values() {
|
||||
let complete: ResultType =
|
||||
serde_json::from_value(serde_json::json!("complete")).unwrap();
|
||||
assert_eq!(complete, ResultType::COMPLETE);
|
||||
|
||||
let input_required: ResultType =
|
||||
serde_json::from_value(serde_json::json!("input_required")).unwrap();
|
||||
assert_eq!(input_required, ResultType::INPUT_REQUIRED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_unknown_extension_values() {
|
||||
let custom: ResultType =
|
||||
serde_json::from_value(serde_json::json!("streaming")).unwrap();
|
||||
assert_eq!(custom.as_str(), "streaming");
|
||||
assert!(!custom.is_complete());
|
||||
assert!(!custom.is_input_required());
|
||||
|
||||
let reserialized = serde_json::to_value(&custom).unwrap();
|
||||
assert_eq!(reserialized, serde_json::json!("streaming"));
|
||||
}
|
||||
}
|
||||
|
||||
mod input_required_result {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserializes_with_requests_and_state() {
|
||||
let json = serde_json::json!({
|
||||
"resultType": "input_required",
|
||||
"inputRequests": {
|
||||
"github_login": {
|
||||
"method": "elicitation/create",
|
||||
"params": {
|
||||
"message": "Please provide your GitHub username",
|
||||
"requestedSchema": {
|
||||
"type": "object",
|
||||
"properties": { "name": { "type": "string" } },
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"capital_of_france": {
|
||||
"method": "sampling/createMessage",
|
||||
"params": {
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": { "type": "text", "text": "What is the capital of France?" }
|
||||
}],
|
||||
"maxTokens": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0"
|
||||
});
|
||||
|
||||
let result: InputRequiredResult = serde_json::from_value(json).unwrap();
|
||||
|
||||
let requests = result
|
||||
.input_requests
|
||||
.as_ref()
|
||||
.expect("should have input_requests");
|
||||
assert_eq!(requests.len(), 2);
|
||||
assert!(requests.contains_key("github_login"));
|
||||
assert!(requests.contains_key("capital_of_france"));
|
||||
assert_eq!(
|
||||
result.request_state.as_deref(),
|
||||
Some("eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_preserves_all_fields() {
|
||||
let json = serde_json::json!({
|
||||
"resultType": "input_required",
|
||||
"inputRequests": {
|
||||
"key": {
|
||||
"method": "elicitation/create",
|
||||
"params": {
|
||||
"message": "test",
|
||||
"requestedSchema": { "type": "object", "properties": {} }
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestState": "abc123"
|
||||
});
|
||||
|
||||
let result: InputRequiredResult = serde_json::from_value(json).unwrap();
|
||||
let reserialized = serde_json::to_value(&result).unwrap();
|
||||
|
||||
assert_eq!(reserialized["resultType"], "input_required");
|
||||
assert!(reserialized["inputRequests"].is_object());
|
||||
assert_eq!(reserialized["requestState"], "abc123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_with_request_state_only() {
|
||||
let json = serde_json::json!({
|
||||
"resultType": "input_required",
|
||||
"requestState": "eyJwcm9ncmVzcyI6IjUwJSJ9"
|
||||
});
|
||||
|
||||
let result: InputRequiredResult = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert!(result.input_requests.is_none());
|
||||
assert_eq!(
|
||||
result.request_state.as_deref(),
|
||||
Some("eyJwcm9ncmVzcyI6IjUwJSJ9")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_missing_result_type() {
|
||||
let json = serde_json::json!({
|
||||
"requestState": "some-state"
|
||||
});
|
||||
let err = serde_json::from_value::<InputRequiredResult>(json).unwrap_err();
|
||||
assert!(
|
||||
err.to_string().contains("input_required"),
|
||||
"error should mention the required resultType, got: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_wrong_result_type() {
|
||||
let json = serde_json::json!({
|
||||
"resultType": "complete",
|
||||
"requestState": "some-state"
|
||||
});
|
||||
let err = serde_json::from_value::<InputRequiredResult>(json).unwrap_err();
|
||||
assert!(
|
||||
err.to_string().contains("input_required"),
|
||||
"error should mention the required resultType, got: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod input_responses {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserializes_heterogeneous_results() {
|
||||
let json = serde_json::json!({
|
||||
"github_login": {
|
||||
"action": "accept",
|
||||
"content": { "name": "octocat" }
|
||||
},
|
||||
"capital_of_france": {
|
||||
"role": "assistant",
|
||||
"content": { "type": "text", "text": "Paris." },
|
||||
"model": "claude-3-sonnet-20240307",
|
||||
"stopReason": "endTurn"
|
||||
}
|
||||
});
|
||||
|
||||
let responses: InputResponses = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert_eq!(responses.len(), 2);
|
||||
assert!(responses.contains_key("github_login"));
|
||||
assert!(responses.contains_key("capital_of_france"));
|
||||
}
|
||||
}
|
||||
|
||||
mod constructors {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_request_state_sets_state_only() {
|
||||
let result = InputRequiredResult::from_request_state("opaque");
|
||||
|
||||
assert_eq!(result.result_type, ResultType::INPUT_REQUIRED);
|
||||
assert!(result.input_requests.is_none());
|
||||
assert_eq!(result.request_state.as_deref(), Some("opaque"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_input_requests_sets_requests_only() {
|
||||
let mut requests = InputRequests::new();
|
||||
requests.insert(
|
||||
"key".to_string(),
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"method": "elicitation/create",
|
||||
"params": {
|
||||
"message": "test",
|
||||
"requestedSchema": { "type": "object", "properties": {} }
|
||||
}
|
||||
}))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let result = InputRequiredResult::from_input_requests(requests);
|
||||
|
||||
assert!(result.input_requests.is_some());
|
||||
assert!(result.request_state.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,6 +451,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -489,6 +491,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -510,6 +514,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -528,6 +534,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -564,6 +572,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: None,
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -590,6 +600,8 @@ mod test {
|
|||
name: "my_tool".into(),
|
||||
arguments: Some(serde_json::Map::from_iter([("x".to_string(), json!(1))])),
|
||||
task: None,
|
||||
input_responses: None,
|
||||
request_state: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ use url::Url;
|
|||
|
||||
use super::*;
|
||||
#[cfg(feature = "elicitation")]
|
||||
use crate::model::{
|
||||
ElicitRequest, ElicitRequestParams, ElicitResult, ElicitationAction,
|
||||
ElicitationCompleteNotification, ElicitationResponseNotificationParam,
|
||||
};
|
||||
use crate::model::{ElicitRequest, ElicitRequestParams, ElicitResult, ElicitationAction};
|
||||
use crate::{
|
||||
model::{
|
||||
CancelledNotification, CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage,
|
||||
|
|
@ -474,8 +471,6 @@ impl Peer<RoleServer> {
|
|||
method!(peer_req create_elicitation ElicitRequest(ElicitRequestParams) => ElicitResult);
|
||||
#[cfg(feature = "elicitation")]
|
||||
method!(peer_req_with_timeout create_elicitation_with_timeout ElicitRequest(ElicitRequestParams) => ElicitResult);
|
||||
#[cfg(feature = "elicitation")]
|
||||
method!(peer_not notify_url_elicitation_completed ElicitationCompleteNotification(ElicitationResponseNotificationParam));
|
||||
|
||||
method!(peer_not notify_cancelled CancelledNotification(CancelledNotificationParam));
|
||||
method!(peer_not notify_progress ProgressNotification(ProgressNotificationParam));
|
||||
|
|
|
|||
|
|
@ -1882,31 +1882,6 @@ async fn test_url_elicitation_json_rpc_protocol() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Test ElicitationCompleteNotification serialization/deserialization
|
||||
#[tokio::test]
|
||||
async fn test_elicitation_completion_notification() {
|
||||
let notification_params = ElicitationResponseNotificationParam::new("elicit-789");
|
||||
|
||||
// Test serialization
|
||||
let json = serde_json::to_value(¬ification_params).unwrap();
|
||||
let expected = json!({
|
||||
"elicitationId": "elicit-789"
|
||||
});
|
||||
assert_eq!(json, expected);
|
||||
|
||||
// Test deserialization
|
||||
let deserialized: ElicitationResponseNotificationParam =
|
||||
serde_json::from_value(expected).unwrap();
|
||||
assert_eq!(deserialized.elicitation_id, "elicit-789");
|
||||
|
||||
// Test complete notification structure
|
||||
let notification = ElicitationCompleteNotification::new(notification_params);
|
||||
|
||||
let json = serde_json::to_value(¬ification).unwrap();
|
||||
assert_eq!(json["method"], "notifications/elicitation/complete");
|
||||
assert_eq!(json["params"]["elicitationId"], "elicit-789");
|
||||
}
|
||||
|
||||
/// Test UrlElicitationCapability structure and serialization
|
||||
#[tokio::test]
|
||||
async fn test_url_elicitation_capability() {
|
||||
|
|
@ -2020,39 +1995,6 @@ async fn test_elicitation_both_modes() {
|
|||
assert!(url_json.get("requestedSchema").is_none());
|
||||
}
|
||||
|
||||
/// Test URL_ELICITATION_REQUIRED error code
|
||||
#[tokio::test]
|
||||
async fn test_url_elicitation_required_error_code() {
|
||||
// Test the error code constant
|
||||
assert_eq!(ErrorCode::URL_ELICITATION_REQUIRED.0, -32042);
|
||||
|
||||
// Test creating error data with URL_ELICITATION_REQUIRED
|
||||
let error_data = ErrorData::url_elicitation_required(
|
||||
"URL elicitation is required for this operation",
|
||||
Some(json!({
|
||||
"url": "https://example.com/complete",
|
||||
"elicitationId": "elicit-999"
|
||||
})),
|
||||
);
|
||||
|
||||
assert_eq!(error_data.code, ErrorCode::URL_ELICITATION_REQUIRED);
|
||||
assert_eq!(
|
||||
error_data.message,
|
||||
"URL elicitation is required for this operation"
|
||||
);
|
||||
assert!(error_data.data.is_some());
|
||||
|
||||
// Test serialization
|
||||
let json = serde_json::to_value(&error_data).unwrap();
|
||||
assert_eq!(json["code"], -32042);
|
||||
assert_eq!(
|
||||
json["message"],
|
||||
"URL elicitation is required for this operation"
|
||||
);
|
||||
assert_eq!(json["data"]["url"], "https://example.com/complete");
|
||||
assert_eq!(json["data"]["elicitationId"], "elicit-999");
|
||||
}
|
||||
|
||||
/// Test ClientCapabilities with different elicitation mode combinations
|
||||
#[tokio::test]
|
||||
async fn test_client_capabilities_elicitation_modes() {
|
||||
|
|
@ -2102,32 +2044,6 @@ async fn test_client_capabilities_elicitation_modes() {
|
|||
assert!(json["elicitation"]["url"].is_object());
|
||||
}
|
||||
|
||||
/// Test ElicitationCompleteNotification in ServerNotification enum
|
||||
#[tokio::test]
|
||||
async fn test_elicitation_completion_in_server_notification() {
|
||||
let notification_param = ElicitationResponseNotificationParam::new("notify-123");
|
||||
|
||||
let completion_notification = ElicitationCompleteNotification::new(notification_param.clone());
|
||||
|
||||
// Test that it's part of ServerNotification
|
||||
let server_notification =
|
||||
ServerNotification::ElicitationCompleteNotification(completion_notification);
|
||||
|
||||
// Test serialization
|
||||
let json = serde_json::to_value(&server_notification).unwrap();
|
||||
assert_eq!(json["method"], "notifications/elicitation/complete");
|
||||
assert_eq!(json["params"]["elicitationId"], "notify-123");
|
||||
|
||||
// Test deserialization
|
||||
let deserialized: ServerNotification = serde_json::from_value(json).unwrap();
|
||||
match deserialized {
|
||||
ServerNotification::ElicitationCompleteNotification(notif) => {
|
||||
assert_eq!(notif.params.elicitation_id, "notify-123");
|
||||
}
|
||||
_ => panic!("Expected ElicitationCompleteNotification variant"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test ElicitationAction with URL elicitation workflow
|
||||
#[tokio::test]
|
||||
async fn test_url_elicitation_action_workflow() {
|
||||
|
|
@ -2161,10 +2077,4 @@ async fn test_elicitation_method_constants() {
|
|||
ElicitationResponseNotificationMethod::VALUE,
|
||||
"notifications/elicitation/response"
|
||||
);
|
||||
|
||||
// Test new completion notification method
|
||||
assert_eq!(
|
||||
ElicitationCompletionNotificationMethod::VALUE,
|
||||
"notifications/elicitation/complete"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"result": {
|
||||
"resultType": "complete",
|
||||
"tools": [
|
||||
{
|
||||
"name": "add",
|
||||
|
|
|
|||
|
|
@ -143,10 +143,25 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`]. Present only when retrying after an incomplete result.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the tool to call",
|
||||
"type": "string"
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].\nClients MUST return this value exactly as received.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"task": {
|
||||
"description": "Task metadata for async task management (SEP-1319)",
|
||||
"anyOf": [
|
||||
|
|
@ -726,8 +741,23 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1374,6 +1404,21 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"description": "The URI of the resource to read",
|
||||
"type": "string"
|
||||
|
|
|
|||
|
|
@ -143,10 +143,25 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`]. Present only when retrying after an incomplete result.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the tool to call",
|
||||
"type": "string"
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].\nClients MUST return this value exactly as received.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"task": {
|
||||
"description": "Task metadata for async task management (SEP-1319)",
|
||||
"anyOf": [
|
||||
|
|
@ -726,8 +741,23 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1374,6 +1404,21 @@
|
|||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputResponses": {
|
||||
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state echoed back from a previous [`InputRequiredResult`].",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"description": "The URI of the resource to read",
|
||||
"type": "string"
|
||||
|
|
|
|||
|
|
@ -178,6 +178,15 @@
|
|||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
},
|
||||
"structuredContent": {
|
||||
"description": "An optional JSON object that represents the structured result of the tool call"
|
||||
}
|
||||
|
|
@ -292,6 +301,15 @@
|
|||
},
|
||||
"completion": {
|
||||
"$ref": "#/definitions/CompletionInfo"
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -752,35 +770,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"ElicitationCompletionNotificationMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
"const": "notifications/elicitation/complete"
|
||||
},
|
||||
"ElicitationCreateRequestMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
"const": "elicitation/create"
|
||||
},
|
||||
"ElicitationResponseNotificationParam": {
|
||||
"description": "Notification parameters for an url elicitation completion notification.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"elicitationId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitationId"
|
||||
]
|
||||
},
|
||||
"ElicitationSchema": {
|
||||
"description": "Type-safe elicitation schema for requesting structured user input.\n\nThis enforces the MCP 2025-06-18 specification that elicitation schemas\nmust be objects with primitive-typed properties.\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet schema = ElicitationSchema::builder()\n .required_email(\"email\")\n .required_integer(\"age\", 0, 150)\n .optional_bool(\"newsletter\", false)\n .build();\n```",
|
||||
"type": "object",
|
||||
|
|
@ -948,6 +942,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/PromptMessage"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1205,6 +1208,77 @@
|
|||
"serverInfo"
|
||||
]
|
||||
},
|
||||
"InputRequest": {
|
||||
"description": "A server-initiated request that can appear inside [`InputRequests`].\n\nPer the MCP spec, only `CreateMessageRequest` (sampling),\n`ElicitRequest` (elicitation), and `ListRootsRequest` (roots)\nare allowed. This is modeled as an untagged enum rather than a\n`ServerRequest` alias to prevent `PingRequest` or `CustomRequest` from\nbeing included.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A `sampling/createMessage` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Request"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "An `elicitation/create` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Request2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A `roots/list` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RequestNoParam2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"InputRequiredResult": {
|
||||
"description": "A result indicating that additional input is needed before the request\ncan be completed.\n\nAt least one of [`input_requests`](Self::input_requests) or\n[`request_state`](Self::request_state) MUST be present.\n\nServers MAY send this in response to `tools/call`, `prompts/get`, or\n`resources/read`. Servers MUST NOT send this for any other request.\n\n# Examples\n\n```\nuse rmcp::model::InputRequiredResult;\n\nlet result = InputRequiredResult::from_request_state(\"opaque-server-state\");\nassert!(result.input_requests.is_none());\nassert_eq!(result.request_state.as_deref(), Some(\"opaque-server-state\"));\n```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"description": "Optional protocol-level metadata.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputRequests": {
|
||||
"description": "Server-initiated requests that the client must fulfill before retrying.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/InputRequest"
|
||||
}
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state to be echoed back by the client on retry.\nClients MUST NOT inspect, parse, modify, or make any assumptions\nabout the contents.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Always `\"input_required\"` for this result type.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"resultType"
|
||||
]
|
||||
},
|
||||
"IntegerSchema": {
|
||||
"description": "Schema definition for integer properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
|
||||
"type": "object",
|
||||
|
|
@ -1322,9 +1396,6 @@
|
|||
{
|
||||
"$ref": "#/definitions/Notification5"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/Notification6"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/CustomNotification"
|
||||
}
|
||||
|
|
@ -1456,6 +1527,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/Prompt"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1483,6 +1563,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/ResourceTemplate"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1510,6 +1599,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/Resource"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1564,6 +1662,15 @@
|
|||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
},
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
@ -1758,21 +1865,6 @@
|
|||
]
|
||||
},
|
||||
"Notification5": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"method": {
|
||||
"$ref": "#/definitions/ElicitationCompletionNotificationMethod"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ElicitationResponseNotificationParam"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
]
|
||||
},
|
||||
"Notification6": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"method": {
|
||||
|
|
@ -2129,6 +2221,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/ResourceContents"
|
||||
}
|
||||
},
|
||||
"result_type": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -2442,6 +2543,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ResultType": {
|
||||
"description": "Indicates the type of a result object, allowing the client to\ndetermine how to parse the response.\n\nThe spec defines this as an open string (`\"complete\" | \"input_required\" | string`),\nso unknown values are preserved rather than rejected. Servers implementing this\nprotocol version MUST include `resultType` in every result. For backward\ncompatibility, clients MUST treat an absent field as `\"complete\"`.",
|
||||
"type": "string"
|
||||
},
|
||||
"Role": {
|
||||
"description": "Represents the role of a participant in a conversation or message exchange.\n\nUsed in sampling and chat contexts to distinguish between different\ntypes of message senders in the conversation flow.",
|
||||
"oneOf": [
|
||||
|
|
@ -2738,6 +2843,9 @@
|
|||
{
|
||||
"$ref": "#/definitions/CallToolResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/InputRequiredResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/GetTaskPayloadResult"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -178,6 +178,15 @@
|
|||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
},
|
||||
"structuredContent": {
|
||||
"description": "An optional JSON object that represents the structured result of the tool call"
|
||||
}
|
||||
|
|
@ -292,6 +301,15 @@
|
|||
},
|
||||
"completion": {
|
||||
"$ref": "#/definitions/CompletionInfo"
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -752,35 +770,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"ElicitationCompletionNotificationMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
"const": "notifications/elicitation/complete"
|
||||
},
|
||||
"ElicitationCreateRequestMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
"const": "elicitation/create"
|
||||
},
|
||||
"ElicitationResponseNotificationParam": {
|
||||
"description": "Notification parameters for an url elicitation completion notification.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"elicitationId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitationId"
|
||||
]
|
||||
},
|
||||
"ElicitationSchema": {
|
||||
"description": "Type-safe elicitation schema for requesting structured user input.\n\nThis enforces the MCP 2025-06-18 specification that elicitation schemas\nmust be objects with primitive-typed properties.\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet schema = ElicitationSchema::builder()\n .required_email(\"email\")\n .required_integer(\"age\", 0, 150)\n .optional_bool(\"newsletter\", false)\n .build();\n```",
|
||||
"type": "object",
|
||||
|
|
@ -948,6 +942,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/PromptMessage"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1205,6 +1208,77 @@
|
|||
"serverInfo"
|
||||
]
|
||||
},
|
||||
"InputRequest": {
|
||||
"description": "A server-initiated request that can appear inside [`InputRequests`].\n\nPer the MCP spec, only `CreateMessageRequest` (sampling),\n`ElicitRequest` (elicitation), and `ListRootsRequest` (roots)\nare allowed. This is modeled as an untagged enum rather than a\n`ServerRequest` alias to prevent `PingRequest` or `CustomRequest` from\nbeing included.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A `sampling/createMessage` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Request"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "An `elicitation/create` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Request2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A `roots/list` request.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RequestNoParam2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"InputRequiredResult": {
|
||||
"description": "A result indicating that additional input is needed before the request\ncan be completed.\n\nAt least one of [`input_requests`](Self::input_requests) or\n[`request_state`](Self::request_state) MUST be present.\n\nServers MAY send this in response to `tools/call`, `prompts/get`, or\n`resources/read`. Servers MUST NOT send this for any other request.\n\n# Examples\n\n```\nuse rmcp::model::InputRequiredResult;\n\nlet result = InputRequiredResult::from_request_state(\"opaque-server-state\");\nassert!(result.input_requests.is_none());\nassert_eq!(result.request_state.as_deref(), Some(\"opaque-server-state\"));\n```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"description": "Optional protocol-level metadata.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputRequests": {
|
||||
"description": "Server-initiated requests that the client must fulfill before retrying.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/InputRequest"
|
||||
}
|
||||
},
|
||||
"requestState": {
|
||||
"description": "Opaque request state to be echoed back by the client on retry.\nClients MUST NOT inspect, parse, modify, or make any assumptions\nabout the contents.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Always `\"input_required\"` for this result type.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"resultType"
|
||||
]
|
||||
},
|
||||
"IntegerSchema": {
|
||||
"description": "Schema definition for integer properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
|
||||
"type": "object",
|
||||
|
|
@ -1322,9 +1396,6 @@
|
|||
{
|
||||
"$ref": "#/definitions/Notification5"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/Notification6"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/CustomNotification"
|
||||
}
|
||||
|
|
@ -1456,6 +1527,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/Prompt"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1483,6 +1563,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/ResourceTemplate"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1510,6 +1599,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/Resource"
|
||||
}
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -1564,6 +1662,15 @@
|
|||
"null"
|
||||
]
|
||||
},
|
||||
"resultType": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
},
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
@ -1758,21 +1865,6 @@
|
|||
]
|
||||
},
|
||||
"Notification5": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"method": {
|
||||
"$ref": "#/definitions/ElicitationCompletionNotificationMethod"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ElicitationResponseNotificationParam"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
]
|
||||
},
|
||||
"Notification6": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"method": {
|
||||
|
|
@ -2129,6 +2221,15 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/ResourceContents"
|
||||
}
|
||||
},
|
||||
"result_type": {
|
||||
"description": "Result type discriminator. Absent values deserialize as `\"complete\"`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ResultType"
|
||||
}
|
||||
],
|
||||
"default": "complete"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -2442,6 +2543,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ResultType": {
|
||||
"description": "Indicates the type of a result object, allowing the client to\ndetermine how to parse the response.\n\nThe spec defines this as an open string (`\"complete\" | \"input_required\" | string`),\nso unknown values are preserved rather than rejected. Servers implementing this\nprotocol version MUST include `resultType` in every result. For backward\ncompatibility, clients MUST treat an absent field as `\"complete\"`.",
|
||||
"type": "string"
|
||||
},
|
||||
"Role": {
|
||||
"description": "Represents the role of a participant in a conversation or message exchange.\n\nUsed in sampling and chat contexts to distinguish between different\ntypes of message senders in the conversation flow.",
|
||||
"oneOf": [
|
||||
|
|
@ -2738,6 +2843,9 @@
|
|||
{
|
||||
"$ref": "#/definitions/CallToolResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/InputRequiredResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/GetTaskPayloadResult"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ fn serialize_tool_result_with_meta() {
|
|||
let result = CallToolResult::success(content).with_meta(Some(meta));
|
||||
let v = serde_json::to_value(&result).unwrap();
|
||||
let expected = json!({
|
||||
"resultType": "complete",
|
||||
"content": [{"type":"text","text":"ok"}],
|
||||
"isError": false,
|
||||
"_meta": {"foo":"bar"}
|
||||
|
|
|
|||
|
|
@ -252,8 +252,7 @@ impl ServerHandler for Counter {
|
|||
self._create_resource_text("str:////Users/to/some/path/", "cwd"),
|
||||
self._create_resource_text("memo://insights", "memo-name"),
|
||||
],
|
||||
next_cursor: None,
|
||||
meta: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -293,9 +292,8 @@ impl ServerHandler for Counter {
|
|||
_: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourceTemplatesResult, McpError> {
|
||||
Ok(ListResourceTemplatesResult {
|
||||
next_cursor: None,
|
||||
resource_templates: Vec::new(),
|
||||
meta: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,18 +129,9 @@ impl ElicitationServer {
|
|||
)
|
||||
})?;
|
||||
match elicit_result {
|
||||
ElicitationAction::Accept => {
|
||||
// Mock notifying completion
|
||||
let _ = context
|
||||
.peer
|
||||
.notify_url_elicitation_completed(ElicitationResponseNotificationParam::new(
|
||||
"elicit_123",
|
||||
))
|
||||
.await;
|
||||
Ok(CallToolResult::success(vec![ContentBlock::text(
|
||||
ElicitationAction::Accept => Ok(CallToolResult::success(vec![ContentBlock::text(
|
||||
"Elicitation via URL successful".to_string(),
|
||||
)]))
|
||||
}
|
||||
)])),
|
||||
ElicitationAction::Cancel => Ok(CallToolResult::success(vec![ContentBlock::text(
|
||||
"Elicitation via URL cancelled by user".to_string(),
|
||||
)])),
|
||||
|
|
|
|||
|
|
@ -113,8 +113,7 @@ impl ServerHandler for SamplingDemoServer {
|
|||
.unwrap(),
|
||||
),
|
||||
)],
|
||||
meta: None,
|
||||
next_cursor: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue