feat: add meta to elicitation results (#792)

This commit is contained in:
Matthew Zeng 2026-04-08 12:07:04 -07:00 committed by GitHub
parent be321a4abe
commit 5f432834a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 80 additions and 5 deletions

View file

@ -122,6 +122,7 @@ impl ClientHandler for ElicitationDefaultsClientHandler {
Ok(CreateElicitationResult {
action: ElicitationAction::Accept,
content,
meta: None,
})
}
}
@ -174,6 +175,7 @@ impl ClientHandler for FullClientHandler {
Ok(CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!({"username": "testuser", "email": "test@example.com"})),
meta: None,
})
}
}

View file

@ -146,6 +146,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
/// Ok(CreateElicitationResult {
/// action: ElicitationAction::Accept,
/// content: Some(user_input),
/// meta: None,
/// })
/// }
/// CreateElicitationRequestParam::UrlElicitationParam {meta, message, url, elicitation_id,} => {
@ -154,6 +155,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
/// Ok(CreateElicitationResult {
/// action: ElicitationAction::Accept,
/// content: None,
/// meta: None,
/// })
/// }
/// }
@ -171,6 +173,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
std::future::ready(Ok(CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
}))
}

View file

@ -2699,6 +2699,10 @@ pub struct CreateElicitationResult {
/// Only present when action is Accept.
#[serde(skip_serializing_if = "Option::is_none")]
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 {
@ -2707,6 +2711,7 @@ impl CreateElicitationResult {
Self {
action,
content: None,
meta: None,
}
}
@ -2715,6 +2720,12 @@ impl CreateElicitationResult {
self.content = Some(content);
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

View file

@ -1,6 +1,6 @@
//cargo test --test test_elicitation --features "client server"
use rmcp::{model::*, service::*};
use rmcp::{model::*, object, service::*};
// For typed elicitation tests
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
@ -98,6 +98,7 @@ async fn test_elicitation_result_serialization() {
let accept_result = CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!({"email": "user@example.com"})),
meta: None,
};
let json = serde_json::to_value(&accept_result).unwrap();
@ -111,6 +112,7 @@ async fn test_elicitation_result_serialization() {
let decline_result = CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
};
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();
assert_eq!(deserialized.action, ElicitationAction::Decline);
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
@ -843,6 +865,7 @@ async fn test_elicitation_direction_server_to_client() {
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!("John Doe")),
meta: None,
});
// Verify client result can be serialized
@ -893,6 +916,7 @@ async fn test_elicitation_json_rpc_direction() {
ClientResult::CreateElicitationResult(CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!(true)),
meta: None,
}),
RequestId::Number(1),
);
@ -928,6 +952,7 @@ async fn test_elicitation_actions_compliance() {
ElicitationAction::Accept => Some(serde_json::json!("some data")),
_ => None,
},
meta: None,
};
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 {
action: ElicitationAction::Decline,
content: None,
meta: None,
});
match result {
@ -2161,6 +2187,7 @@ async fn test_url_elicitation_action_workflow() {
let accept_result = CreateElicitationResult {
action: ElicitationAction::Accept,
content: None, // URL elicitation doesn't return content, just confirmation
meta: None,
};
let json = serde_json::to_value(&accept_result).unwrap();
@ -2172,6 +2199,7 @@ async fn test_url_elicitation_action_workflow() {
let decline_result = CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
};
let json = serde_json::to_value(&decline_result).unwrap();
@ -2181,6 +2209,7 @@ async fn test_url_elicitation_action_workflow() {
let cancel_result = CreateElicitationResult {
action: ElicitationAction::Cancel,
content: None,
meta: None,
};
let json = serde_json::to_value(&cancel_result).unwrap();

View file

@ -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.",
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
"allOf": [

View file

@ -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.",
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
"allOf": [

View file

@ -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.",
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
"allOf": [

View file

@ -388,6 +388,7 @@
"content": {
"description": "The content returned by the tool (text, images, etc.)",
"type": "array",
"default": [],
"items": {
"$ref": "#/definitions/Annotated"
}
@ -402,10 +403,7 @@
"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`.",
@ -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.",
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
"allOf": [