feat(rmcp): add optional _meta to CallToolResult, EmbeddedResource, and ResourceContents (#386)
* feat(rmcp): support optional _meta on EmbeddedResource and ResourceContents - Add optional _meta to RawEmbeddedResource and ResourceContents (text/blob) - Update constructors/usages to set meta: None by default - Add tests to lock behavior (embedded text/blob + CallToolResult) - Update JSON Schemas to include optional _meta fields * style(rmcp): rustfmt after _meta changes and tests
This commit is contained in:
parent
1a20f8a0d1
commit
7d46b39b49
5 changed files with 311 additions and 3 deletions
|
|
@ -1267,6 +1267,9 @@ pub struct CallToolResult {
|
|||
/// Whether this result represents an error condition
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_error: Option<bool>,
|
||||
/// Optional protocol-level metadata for this result
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<Meta>,
|
||||
}
|
||||
|
||||
impl CallToolResult {
|
||||
|
|
@ -1276,6 +1279,7 @@ impl CallToolResult {
|
|||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
/// Create an error tool result with unstructured content
|
||||
|
|
@ -1284,6 +1288,7 @@ impl CallToolResult {
|
|||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(true),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
/// Create a successful tool result with structured content
|
||||
|
|
@ -1305,6 +1310,7 @@ impl CallToolResult {
|
|||
content: vec![Content::text(value.to_string())],
|
||||
structured_content: Some(value),
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
/// Create an error tool result with structured content
|
||||
|
|
@ -1330,6 +1336,7 @@ impl CallToolResult {
|
|||
content: vec![Content::text(value.to_string())],
|
||||
structured_content: Some(value),
|
||||
is_error: Some(true),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1377,6 +1384,9 @@ impl<'de> Deserialize<'de> for CallToolResult {
|
|||
structured_content: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
is_error: Option<bool>,
|
||||
/// Accept `_meta` during deserialization
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
meta: Option<Meta>,
|
||||
}
|
||||
|
||||
let helper = CallToolResultHelper::deserialize(deserializer)?;
|
||||
|
|
@ -1384,6 +1394,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
|
|||
content: helper.content.unwrap_or_default(),
|
||||
structured_content: helper.structured_content,
|
||||
is_error: helper.is_error,
|
||||
meta: helper.meta,
|
||||
};
|
||||
|
||||
// Validate mutual exclusivity
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ pub type ImageContent = Annotated<RawImageContent>;
|
|||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawEmbeddedResource {
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<super::Meta>,
|
||||
pub resource: ResourceContents,
|
||||
}
|
||||
pub type EmbeddedResource = Annotated<RawEmbeddedResource>;
|
||||
|
|
@ -88,15 +90,20 @@ impl RawContent {
|
|||
}
|
||||
|
||||
pub fn resource(resource: ResourceContents) -> Self {
|
||||
RawContent::Resource(RawEmbeddedResource { resource })
|
||||
RawContent::Resource(RawEmbeddedResource {
|
||||
meta: None,
|
||||
resource,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn embedded_text<S: Into<String>, T: Into<String>>(uri: S, content: T) -> Self {
|
||||
RawContent::Resource(RawEmbeddedResource {
|
||||
meta: None,
|
||||
resource: ResourceContents::TextResourceContents {
|
||||
uri: uri.into(),
|
||||
mime_type: Some("text".to_string()),
|
||||
text: content.into(),
|
||||
meta: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ variant_extension! {
|
|||
PromptListChangedNotification
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(transparent)]
|
||||
pub struct Meta(pub JsonObject);
|
||||
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
|
||||
|
|
|
|||
|
|
@ -149,12 +149,14 @@ impl PromptMessage {
|
|||
uri,
|
||||
mime_type: Some(mime_type),
|
||||
text: text.unwrap_or_default(),
|
||||
meta: None,
|
||||
};
|
||||
|
||||
Self {
|
||||
role,
|
||||
content: PromptMessageContent::Resource {
|
||||
resource: RawEmbeddedResource {
|
||||
meta: None,
|
||||
resource: resource_contents,
|
||||
}
|
||||
.optional_annotate(annotations),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Annotated;
|
||||
use super::{Annotated, Meta};
|
||||
|
||||
/// Represents a resource in the extension with metadata
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
|
|
@ -51,6 +51,8 @@ pub enum ResourceContents {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mime_type: Option<String>,
|
||||
text: String,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
meta: Option<Meta>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
BlobResourceContents {
|
||||
|
|
@ -58,6 +60,8 @@ pub enum ResourceContents {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mime_type: Option<String>,
|
||||
blob: String,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
meta: Option<Meta>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +71,7 @@ impl ResourceContents {
|
|||
uri: uri.into(),
|
||||
mime_type: Some("text".into()),
|
||||
text: text.into(),
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +119,7 @@ mod tests {
|
|||
uri: "file:///test.txt".to_string(),
|
||||
mime_type: Some("text/plain".to_string()),
|
||||
text: "Hello world".to_string(),
|
||||
meta: None,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&text_contents).unwrap();
|
||||
|
|
|
|||
Loading…
Reference in a new issue