Skip to content

Commit 5f43283

Browse files
authored
feat: add meta to elicitation results (#792)
1 parent be321a4 commit 5f43283

File tree

8 files changed

+80
-5
lines changed

8 files changed

+80
-5
lines changed

conformance/src/bin/client.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ impl ClientHandler for ElicitationDefaultsClientHandler {
122122
Ok(CreateElicitationResult {
123123
action: ElicitationAction::Accept,
124124
content,
125+
meta: None,
125126
})
126127
}
127128
}
@@ -174,6 +175,7 @@ impl ClientHandler for FullClientHandler {
174175
Ok(CreateElicitationResult {
175176
action: ElicitationAction::Accept,
176177
content: Some(json!({"username": "testuser", "email": "test@example.com"})),
178+
meta: None,
177179
})
178180
}
179181
}

crates/rmcp/src/handler/client.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
146146
/// Ok(CreateElicitationResult {
147147
/// action: ElicitationAction::Accept,
148148
/// content: Some(user_input),
149+
/// meta: None,
149150
/// })
150151
/// }
151152
/// CreateElicitationRequestParam::UrlElicitationParam {meta, message, url, elicitation_id,} => {
@@ -154,6 +155,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
154155
/// Ok(CreateElicitationResult {
155156
/// action: ElicitationAction::Accept,
156157
/// content: None,
158+
/// meta: None,
157159
/// })
158160
/// }
159161
/// }
@@ -171,6 +173,7 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
171173
std::future::ready(Ok(CreateElicitationResult {
172174
action: ElicitationAction::Decline,
173175
content: None,
176+
meta: None,
174177
}))
175178
}
176179

crates/rmcp/src/model.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2699,6 +2699,10 @@ pub struct CreateElicitationResult {
26992699
/// Only present when action is Accept.
27002700
#[serde(skip_serializing_if = "Option::is_none")]
27012701
pub content: Option<Value>,
2702+
2703+
/// Optional protocol-level metadata for this result.
2704+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
2705+
pub meta: Option<Meta>,
27022706
}
27032707

27042708
impl CreateElicitationResult {
@@ -2707,6 +2711,7 @@ impl CreateElicitationResult {
27072711
Self {
27082712
action,
27092713
content: None,
2714+
meta: None,
27102715
}
27112716
}
27122717

@@ -2715,6 +2720,12 @@ impl CreateElicitationResult {
27152720
self.content = Some(content);
27162721
self
27172722
}
2723+
2724+
/// Set the metadata on this result.
2725+
pub fn with_meta(mut self, meta: Meta) -> Self {
2726+
self.meta = Some(meta);
2727+
self
2728+
}
27182729
}
27192730

27202731
/// Request type for creating an elicitation to gather user input

crates/rmcp/tests/test_elicitation.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//cargo test --test test_elicitation --features "client server"
22

3-
use rmcp::{model::*, service::*};
3+
use rmcp::{model::*, object, service::*};
44
// For typed elicitation tests
55
#[cfg(feature = "schemars")]
66
use schemars::JsonSchema;
@@ -98,6 +98,7 @@ async fn test_elicitation_result_serialization() {
9898
let accept_result = CreateElicitationResult {
9999
action: ElicitationAction::Accept,
100100
content: Some(json!({"email": "user@example.com"})),
101+
meta: None,
101102
};
102103

103104
let json = serde_json::to_value(&accept_result).unwrap();
@@ -111,6 +112,7 @@ async fn test_elicitation_result_serialization() {
111112
let decline_result = CreateElicitationResult {
112113
action: ElicitationAction::Decline,
113114
content: None,
115+
meta: None,
114116
};
115117

116118
let json = serde_json::to_value(&decline_result).unwrap();
@@ -124,6 +126,26 @@ async fn test_elicitation_result_serialization() {
124126
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
125127
assert_eq!(deserialized.action, ElicitationAction::Decline);
126128
assert_eq!(deserialized.content, None);
129+
assert_eq!(deserialized.meta, None);
130+
131+
// Test protocol-level metadata round-trips as _meta.
132+
let meta_result =
133+
CreateElicitationResult::new(ElicitationAction::Accept).with_meta(Meta(object!({
134+
"traceId": "elicitation-123"
135+
})));
136+
137+
let json = serde_json::to_value(&meta_result).unwrap();
138+
let expected = json!({
139+
"action": "accept",
140+
"_meta": {"traceId": "elicitation-123"}
141+
});
142+
assert_eq!(json, expected);
143+
144+
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
145+
assert_eq!(
146+
deserialized.meta,
147+
Some(Meta(object!({ "traceId": "elicitation-123" })))
148+
);
127149
}
128150

129151
/// 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() {
843865
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
844866
action: ElicitationAction::Accept,
845867
content: Some(json!("John Doe")),
868+
meta: None,
846869
});
847870

848871
// Verify client result can be serialized
@@ -893,6 +916,7 @@ async fn test_elicitation_json_rpc_direction() {
893916
ClientResult::CreateElicitationResult(CreateElicitationResult {
894917
action: ElicitationAction::Accept,
895918
content: Some(json!(true)),
919+
meta: None,
896920
}),
897921
RequestId::Number(1),
898922
);
@@ -928,6 +952,7 @@ async fn test_elicitation_actions_compliance() {
928952
ElicitationAction::Accept => Some(serde_json::json!("some data")),
929953
_ => None,
930954
},
955+
meta: None,
931956
};
932957

933958
let json = serde_json::to_value(&result).unwrap();
@@ -958,6 +983,7 @@ async fn test_elicitation_result_in_client_result() {
958983
let result = ClientResult::CreateElicitationResult(CreateElicitationResult {
959984
action: ElicitationAction::Decline,
960985
content: None,
986+
meta: None,
961987
});
962988

963989
match result {
@@ -2161,6 +2187,7 @@ async fn test_url_elicitation_action_workflow() {
21612187
let accept_result = CreateElicitationResult {
21622188
action: ElicitationAction::Accept,
21632189
content: None, // URL elicitation doesn't return content, just confirmation
2190+
meta: None,
21642191
};
21652192

21662193
let json = serde_json::to_value(&accept_result).unwrap();
@@ -2172,6 +2199,7 @@ async fn test_url_elicitation_action_workflow() {
21722199
let decline_result = CreateElicitationResult {
21732200
action: ElicitationAction::Decline,
21742201
content: None,
2202+
meta: None,
21752203
};
21762204

21772205
let json = serde_json::to_value(&decline_result).unwrap();
@@ -2181,6 +2209,7 @@ async fn test_url_elicitation_action_workflow() {
21812209
let cancel_result = CreateElicitationResult {
21822210
action: ElicitationAction::Cancel,
21832211
content: None,
2212+
meta: None,
21842213
};
21852214

21862215
let json = serde_json::to_value(&cancel_result).unwrap();

crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,14 @@
418418
"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.",
419419
"type": "object",
420420
"properties": {
421+
"_meta": {
422+
"description": "Optional protocol-level metadata for this result.",
423+
"type": [
424+
"object",
425+
"null"
426+
],
427+
"additionalProperties": true
428+
},
421429
"action": {
422430
"description": "The user's decision on how to handle the elicitation request",
423431
"allOf": [

crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,14 @@
418418
"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.",
419419
"type": "object",
420420
"properties": {
421+
"_meta": {
422+
"description": "Optional protocol-level metadata for this result.",
423+
"type": [
424+
"object",
425+
"null"
426+
],
427+
"additionalProperties": true
428+
},
421429
"action": {
422430
"description": "The user's decision on how to handle the elicitation request",
423431
"allOf": [

crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,14 @@
655655
"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.",
656656
"type": "object",
657657
"properties": {
658+
"_meta": {
659+
"description": "Optional protocol-level metadata for this result.",
660+
"type": [
661+
"object",
662+
"null"
663+
],
664+
"additionalProperties": true
665+
},
658666
"action": {
659667
"description": "The user's decision on how to handle the elicitation request",
660668
"allOf": [

crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@
388388
"content": {
389389
"description": "The content returned by the tool (text, images, etc.)",
390390
"type": "array",
391+
"default": [],
391392
"items": {
392393
"$ref": "#/definitions/Annotated"
393394
}
@@ -402,10 +403,7 @@
402403
"structuredContent": {
403404
"description": "An optional JSON object that represents the structured result of the tool call"
404405
}
405-
},
406-
"required": [
407-
"content"
408-
]
406+
}
409407
},
410408
"CancelTaskResult": {
411409
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
@@ -657,6 +655,14 @@
657655
"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.",
658656
"type": "object",
659657
"properties": {
658+
"_meta": {
659+
"description": "Optional protocol-level metadata for this result.",
660+
"type": [
661+
"object",
662+
"null"
663+
],
664+
"additionalProperties": true
665+
},
660666
"action": {
661667
"description": "The user's decision on how to handle the elicitation request",
662668
"allOf": [

0 commit comments

Comments
 (0)