feat: add meta to elicitation results (#792)
This commit is contained in:
parent
be321a4abe
commit
5f432834a1
8 changed files with 80 additions and 5 deletions
|
|
@ -122,6 +122,7 @@ impl ClientHandler for ElicitationDefaultsClientHandler {
|
||||||
Ok(CreateElicitationResult {
|
Ok(CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content,
|
content,
|
||||||
|
meta: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +175,7 @@ impl ClientHandler for FullClientHandler {
|
||||||
Ok(CreateElicitationResult {
|
Ok(CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content: Some(json!({"username": "testuser", "email": "test@example.com"})),
|
content: Some(json!({"username": "testuser", "email": "test@example.com"})),
|
||||||
|
meta: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
|
||||||
/// Ok(CreateElicitationResult {
|
/// Ok(CreateElicitationResult {
|
||||||
/// action: ElicitationAction::Accept,
|
/// action: ElicitationAction::Accept,
|
||||||
/// content: Some(user_input),
|
/// content: Some(user_input),
|
||||||
|
/// meta: None,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// CreateElicitationRequestParam::UrlElicitationParam {meta, message, url, elicitation_id,} => {
|
/// CreateElicitationRequestParam::UrlElicitationParam {meta, message, url, elicitation_id,} => {
|
||||||
|
|
@ -154,6 +155,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
|
||||||
/// Ok(CreateElicitationResult {
|
/// Ok(CreateElicitationResult {
|
||||||
/// action: ElicitationAction::Accept,
|
/// action: ElicitationAction::Accept,
|
||||||
/// content: None,
|
/// content: None,
|
||||||
|
/// meta: None,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
|
@ -171,6 +173,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
|
||||||
std::future::ready(Ok(CreateElicitationResult {
|
std::future::ready(Ok(CreateElicitationResult {
|
||||||
action: ElicitationAction::Decline,
|
action: ElicitationAction::Decline,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2699,6 +2699,10 @@ pub struct CreateElicitationResult {
|
||||||
/// Only present when action is Accept.
|
/// Only present when action is Accept.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub content: Option<Value>,
|
pub content: Option<Value>,
|
||||||
|
|
||||||
|
/// Optional protocol-level metadata for this result.
|
||||||
|
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub meta: Option<Meta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateElicitationResult {
|
impl CreateElicitationResult {
|
||||||
|
|
@ -2707,6 +2711,7 @@ impl CreateElicitationResult {
|
||||||
Self {
|
Self {
|
||||||
action,
|
action,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2715,6 +2720,12 @@ impl CreateElicitationResult {
|
||||||
self.content = Some(content);
|
self.content = Some(content);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the metadata on this result.
|
||||||
|
pub fn with_meta(mut self, meta: Meta) -> Self {
|
||||||
|
self.meta = Some(meta);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request type for creating an elicitation to gather user input
|
/// Request type for creating an elicitation to gather user input
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//cargo test --test test_elicitation --features "client server"
|
//cargo test --test test_elicitation --features "client server"
|
||||||
|
|
||||||
use rmcp::{model::*, service::*};
|
use rmcp::{model::*, object, service::*};
|
||||||
// For typed elicitation tests
|
// For typed elicitation tests
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
|
@ -98,6 +98,7 @@ async fn test_elicitation_result_serialization() {
|
||||||
let accept_result = CreateElicitationResult {
|
let accept_result = CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content: Some(json!({"email": "user@example.com"})),
|
content: Some(json!({"email": "user@example.com"})),
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&accept_result).unwrap();
|
let json = serde_json::to_value(&accept_result).unwrap();
|
||||||
|
|
@ -111,6 +112,7 @@ async fn test_elicitation_result_serialization() {
|
||||||
let decline_result = CreateElicitationResult {
|
let decline_result = CreateElicitationResult {
|
||||||
action: ElicitationAction::Decline,
|
action: ElicitationAction::Decline,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&decline_result).unwrap();
|
let json = serde_json::to_value(&decline_result).unwrap();
|
||||||
|
|
@ -124,6 +126,26 @@ async fn test_elicitation_result_serialization() {
|
||||||
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
|
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
|
||||||
assert_eq!(deserialized.action, ElicitationAction::Decline);
|
assert_eq!(deserialized.action, ElicitationAction::Decline);
|
||||||
assert_eq!(deserialized.content, None);
|
assert_eq!(deserialized.content, None);
|
||||||
|
assert_eq!(deserialized.meta, None);
|
||||||
|
|
||||||
|
// Test protocol-level metadata round-trips as _meta.
|
||||||
|
let meta_result =
|
||||||
|
CreateElicitationResult::new(ElicitationAction::Accept).with_meta(Meta(object!({
|
||||||
|
"traceId": "elicitation-123"
|
||||||
|
})));
|
||||||
|
|
||||||
|
let json = serde_json::to_value(&meta_result).unwrap();
|
||||||
|
let expected = json!({
|
||||||
|
"action": "accept",
|
||||||
|
"_meta": {"traceId": "elicitation-123"}
|
||||||
|
});
|
||||||
|
assert_eq!(json, expected);
|
||||||
|
|
||||||
|
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
deserialized.meta,
|
||||||
|
Some(Meta(object!({ "traceId": "elicitation-123" })))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that elicitation requests can be created and handled through the JSON-RPC protocol
|
/// Test that elicitation requests can be created and handled through the JSON-RPC protocol
|
||||||
|
|
@ -843,6 +865,7 @@ async fn test_elicitation_direction_server_to_client() {
|
||||||
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
|
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content: Some(json!("John Doe")),
|
content: Some(json!("John Doe")),
|
||||||
|
meta: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify client result can be serialized
|
// Verify client result can be serialized
|
||||||
|
|
@ -893,6 +916,7 @@ async fn test_elicitation_json_rpc_direction() {
|
||||||
ClientResult::CreateElicitationResult(CreateElicitationResult {
|
ClientResult::CreateElicitationResult(CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content: Some(json!(true)),
|
content: Some(json!(true)),
|
||||||
|
meta: None,
|
||||||
}),
|
}),
|
||||||
RequestId::Number(1),
|
RequestId::Number(1),
|
||||||
);
|
);
|
||||||
|
|
@ -928,6 +952,7 @@ async fn test_elicitation_actions_compliance() {
|
||||||
ElicitationAction::Accept => Some(serde_json::json!("some data")),
|
ElicitationAction::Accept => Some(serde_json::json!("some data")),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&result).unwrap();
|
let json = serde_json::to_value(&result).unwrap();
|
||||||
|
|
@ -958,6 +983,7 @@ async fn test_elicitation_result_in_client_result() {
|
||||||
let result = ClientResult::CreateElicitationResult(CreateElicitationResult {
|
let result = ClientResult::CreateElicitationResult(CreateElicitationResult {
|
||||||
action: ElicitationAction::Decline,
|
action: ElicitationAction::Decline,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
|
@ -2161,6 +2187,7 @@ async fn test_url_elicitation_action_workflow() {
|
||||||
let accept_result = CreateElicitationResult {
|
let accept_result = CreateElicitationResult {
|
||||||
action: ElicitationAction::Accept,
|
action: ElicitationAction::Accept,
|
||||||
content: None, // URL elicitation doesn't return content, just confirmation
|
content: None, // URL elicitation doesn't return content, just confirmation
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&accept_result).unwrap();
|
let json = serde_json::to_value(&accept_result).unwrap();
|
||||||
|
|
@ -2172,6 +2199,7 @@ async fn test_url_elicitation_action_workflow() {
|
||||||
let decline_result = CreateElicitationResult {
|
let decline_result = CreateElicitationResult {
|
||||||
action: ElicitationAction::Decline,
|
action: ElicitationAction::Decline,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&decline_result).unwrap();
|
let json = serde_json::to_value(&decline_result).unwrap();
|
||||||
|
|
@ -2181,6 +2209,7 @@ async fn test_url_elicitation_action_workflow() {
|
||||||
let cancel_result = CreateElicitationResult {
|
let cancel_result = CreateElicitationResult {
|
||||||
action: ElicitationAction::Cancel,
|
action: ElicitationAction::Cancel,
|
||||||
content: None,
|
content: None,
|
||||||
|
meta: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&cancel_result).unwrap();
|
let json = serde_json::to_value(&cancel_result).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,14 @@
|
||||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"_meta": {
|
||||||
|
"description": "Optional protocol-level metadata for this result.",
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"description": "The user's decision on how to handle the elicitation request",
|
"description": "The user's decision on how to handle the elicitation request",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,14 @@
|
||||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"_meta": {
|
||||||
|
"description": "Optional protocol-level metadata for this result.",
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"description": "The user's decision on how to handle the elicitation request",
|
"description": "The user's decision on how to handle the elicitation request",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
|
|
@ -655,6 +655,14 @@
|
||||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"_meta": {
|
||||||
|
"description": "Optional protocol-level metadata for this result.",
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"description": "The user's decision on how to handle the elicitation request",
|
"description": "The user's decision on how to handle the elicitation request",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"description": "The content returned by the tool (text, images, etc.)",
|
"description": "The content returned by the tool (text, images, etc.)",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Annotated"
|
"$ref": "#/definitions/Annotated"
|
||||||
}
|
}
|
||||||
|
|
@ -402,10 +403,7 @@
|
||||||
"structuredContent": {
|
"structuredContent": {
|
||||||
"description": "An optional JSON object that represents the structured result of the tool call"
|
"description": "An optional JSON object that represents the structured result of the tool call"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": [
|
|
||||||
"content"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"CancelTaskResult": {
|
"CancelTaskResult": {
|
||||||
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
|
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
|
||||||
|
|
@ -657,6 +655,14 @@
|
||||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"_meta": {
|
||||||
|
"description": "Optional protocol-level metadata for this result.",
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"description": "The user's decision on how to handle the elicitation request",
|
"description": "The user's decision on how to handle the elicitation request",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue