feat!: align metadata models with draft schema (#993)

This commit is contained in:
Dale Seo 2026-07-15 08:58:46 -04:00 • committed by GitHub
commit 839922d8fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2539 additions and 1059 deletions

View file

@ -103,7 +103,7 @@ impl ConformanceServer {
async fn call_mrtr_tool(
&self,
request: CallToolRequestParams,
meta: &Meta,
meta: &RequestMetaObject,
) -> Result<CallToolResponse, ErrorData> {
let responses = request.input_responses.as_ref();
match request.name.as_ref() {

View file

@ -209,13 +209,13 @@ where
}
}
impl<C> FromContextPart<C> for crate::model::Meta
impl<C> FromContextPart<C> for crate::model::RequestMetaObject
where
C: AsRequestContext,
{
fn from_context_part(context: &mut C) -> Result<Self, crate::ErrorData> {
let request_context = context.as_request_context_mut();
let mut meta = crate::model::Meta::default();
let mut meta = crate::model::RequestMetaObject::default();
std::mem::swap(&mut meta, &mut request_context.meta);
Ok(meta)
}

View file

@ -9,7 +9,7 @@ use crate::{
tool::schema_for_output,
wrapper::{Json, Parameters},
},
model::{Icon, JsonObject, Meta, ToolAnnotations, ToolExecution},
model::{Icon, JsonObject, MetaObject, ToolAnnotations, ToolExecution},
schemars::JsonSchema,
service::{MaybeSend, MaybeSendFuture},
};
@ -77,7 +77,7 @@ pub trait ToolBase {
fn icons() -> Option<Vec<Icon>> {
None
}
fn meta() -> Option<Meta> {
fn meta() -> Option<MetaObject> {
None
}
}

View file

@ -339,7 +339,8 @@ pub struct ProgressToken(pub NumberOrString);
pub struct Request<M = String, P = JsonObject> {
pub method: M,
pub params: P,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -372,7 +373,8 @@ pub struct RequestOptionalParam<M = String, P = JsonObject> {
pub method: M,
// #[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<P>,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -394,7 +396,8 @@ impl<M: Default, P> RequestOptionalParam<M, P> {
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RequestNoParam<M = String> {
pub method: M,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -415,7 +418,8 @@ impl<M> GetExtensions for RequestNoParam<M> {
pub struct Notification<M = String, P = JsonObject> {
pub method: M,
pub params: P,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -437,7 +441,8 @@ impl<M: Default, P> Notification<M, P> {
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct NotificationNoParam<M = String> {
pub method: M,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -782,7 +787,7 @@ pub struct CancelledNotificationParam {
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<NotificationMetaObject>,
}
impl CancelledNotificationParam {
@ -818,7 +823,8 @@ pub type CancelledNotification =
pub struct CustomNotification {
pub method: String,
pub params: Option<Value>,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -853,7 +859,8 @@ impl CustomNotification {
pub struct CustomRequest {
pub method: String,
pub params: Option<Value>,
/// extensions will carry anything possible in the context, including [`Meta`]
/// extensions will carry anything possible in the context, including the metadata
/// ([`RequestMetaObject`] for requests, [`NotificationMetaObject`] for notifications)
///
/// this is similar with the Extensions in `http` crate
#[cfg_attr(feature = "schemars", schemars(skip))]
@ -898,7 +905,7 @@ pub type InitializedNotification = NotificationNoParam<InitializedNotificationMe
pub struct InitializeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The MCP protocol version this client supports
pub protocol_version: ProtocolVersion,
/// The capabilities this client supports (sampling, roots, etc.)
@ -925,10 +932,10 @@ impl InitializeRequestParams {
}
impl RequestParamsMeta for InitializeRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -956,7 +963,7 @@ pub struct InitializeResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl InitializeResult {
@ -1167,7 +1174,7 @@ impl Implementation {
pub struct PaginatedRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
@ -1180,10 +1187,10 @@ impl PaginatedRequestParams {
}
impl RequestParamsMeta for PaginatedRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1214,7 +1221,7 @@ pub struct ProgressNotificationParam {
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<NotificationMetaObject>,
}
impl ProgressNotificationParam {
@ -1287,7 +1294,7 @@ macro_rules! paginated_result {
#[serde(default)]
pub result_type: ResultType,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<Cursor>,
/// Time, in milliseconds, that this result may be treated as fresh (SEP-2549).
@ -1365,7 +1372,7 @@ const_string!(ReadResourceRequestMethod = "resources/read");
pub struct ReadResourceRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The URI of the resource to read
pub uri: String,
/// Client responses to server-initiated input requests from a previous
@ -1389,7 +1396,7 @@ impl ReadResourceRequestParams {
}
/// Set the metadata for this request.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: RequestMetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -1408,10 +1415,10 @@ impl ReadResourceRequestParams {
}
impl RequestParamsMeta for ReadResourceRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1446,7 +1453,7 @@ pub struct ReadResourceResult {
/// The actual content of the resource
pub contents: Vec<ResourceContents>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl ReadResourceResult {
@ -1491,7 +1498,7 @@ const_string!(SubscribeRequestMethod = "resources/subscribe");
pub struct SubscribeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The URI of the resource to subscribe to
pub uri: String,
}
@ -1507,10 +1514,10 @@ impl SubscribeRequestParams {
}
impl RequestParamsMeta for SubscribeRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1531,7 +1538,7 @@ const_string!(UnsubscribeRequestMethod = "resources/unsubscribe");
pub struct UnsubscribeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The URI of the resource to unsubscribe from
pub uri: String,
}
@ -1547,10 +1554,10 @@ impl UnsubscribeRequestParams {
}
impl RequestParamsMeta for UnsubscribeRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1572,7 +1579,7 @@ pub struct ResourceUpdatedNotificationParam {
/// The URI of the resource that was updated
pub uri: String,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<NotificationMetaObject>,
}
impl ResourceUpdatedNotificationParam {
@ -1611,7 +1618,7 @@ const_string!(GetPromptRequestMethod = "prompts/get");
pub struct GetPromptRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<JsonObject>,
@ -1643,7 +1650,7 @@ impl GetPromptRequestParams {
}
/// Set the metadata for this request.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: RequestMetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -1662,10 +1669,10 @@ impl GetPromptRequestParams {
}
impl RequestParamsMeta for GetPromptRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1722,7 +1729,7 @@ const_string!(SetLevelRequestMethod = "logging/setLevel");
pub struct SetLevelRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The desired logging level
pub level: LoggingLevel,
}
@ -1735,10 +1742,10 @@ impl SetLevelRequestParams {
}
impl RequestParamsMeta for SetLevelRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -1773,7 +1780,7 @@ pub struct LoggingMessageNotificationParam {
/// The actual log data
pub data: Value,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<NotificationMetaObject>,
}
impl LoggingMessageNotificationParam {
@ -1994,7 +2001,7 @@ pub struct SamplingMessage {
/// The actual content of the message (text, image, audio, tool use, or tool result)
pub content: SamplingContent<SamplingMessageContentBlock>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
/// Content types for sampling messages (SEP-1577).
@ -2161,7 +2168,7 @@ pub enum ContextInclusion {
pub struct CreateMessageRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// Task metadata for async task management (SEP-1319)
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<TaskMetadata>,
@ -2196,10 +2203,10 @@ pub struct CreateMessageRequestParams {
}
impl RequestParamsMeta for CreateMessageRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -2516,7 +2523,7 @@ impl CompletionContext {
pub struct CompleteRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
pub r#ref: Reference,
pub argument: ArgumentInfo,
/// Optional context containing previously resolved argument values
@ -2543,10 +2550,10 @@ impl CompleteRequestParams {
}
impl RequestParamsMeta for CompleteRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -2646,7 +2653,7 @@ pub struct CompleteResult {
pub result_type: ResultType,
pub completion: CompletionInfo,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl CompleteResult {
@ -2789,7 +2796,7 @@ pub struct Root {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl Root {
@ -2809,7 +2816,7 @@ impl Root {
}
/// Sets the protocol-level metadata for this root.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -2833,7 +2840,7 @@ pub type ListRootsRequest = RequestNoParam<ListRootsRequestMethod>;
pub struct ListRootsResult {
pub roots: Vec<Root>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl ListRootsResult {
@ -2843,7 +2850,7 @@ impl ListRootsResult {
}
/// Sets the protocol-level metadata for this result.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -2889,14 +2896,14 @@ enum CreateElicitationRequestParamDeserializeHelper {
#[serde(rename = "form", rename_all = "camelCase")]
FormElicitationParam {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
message: String,
requested_schema: ElicitationSchema,
},
#[serde(rename = "url", rename_all = "camelCase")]
UrlElicitationParam {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
message: String,
url: String,
elicitation_id: String,
@ -2904,7 +2911,7 @@ enum CreateElicitationRequestParamDeserializeHelper {
#[serde(untagged, rename_all = "camelCase")]
FormElicitationParamBackwardsCompat {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
message: String,
requested_schema: ElicitationSchema,
},
@ -2988,7 +2995,7 @@ pub enum ElicitRequestParams {
FormElicitationParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
/// Human-readable message explaining what input is needed from the user.
/// This should be clear and provide sufficient context for the user to understand
/// what information they need to provide.
@ -3003,7 +3010,7 @@ pub enum ElicitRequestParams {
UrlElicitationParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
/// Human-readable message explaining what input is needed from the user.
/// This should be clear and provide sufficient context for the user to understand
/// what information they need to provide.
@ -3018,13 +3025,13 @@ pub enum ElicitRequestParams {
}
impl RequestParamsMeta for ElicitRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
match self {
ElicitRequestParams::FormElicitationParams { meta, .. } => meta.as_ref(),
ElicitRequestParams::UrlElicitationParams { meta, .. } => meta.as_ref(),
}
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
match self {
ElicitRequestParams::FormElicitationParams { meta, .. } => meta,
ElicitRequestParams::UrlElicitationParams { meta, .. } => meta,
@ -3059,7 +3066,7 @@ pub struct ElicitResult {
/// Optional protocol-level metadata for this result.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl ElicitResult {
@ -3079,7 +3086,7 @@ impl ElicitResult {
}
/// Set the metadata on this result.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -3121,7 +3128,7 @@ pub struct CallToolResult {
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>,
pub meta: Option<MetaObject>,
}
// Custom Deserialize implementation that:
@ -3143,7 +3150,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
structured_content: Option<Value>,
is_error: Option<bool>,
#[serde(rename = "_meta")]
meta: Option<Meta>,
meta: Option<MetaObject>,
}
let helper = Helper::deserialize(deserializer)?;
@ -3290,7 +3297,7 @@ impl CallToolResult {
}
/// Set the metadata on this result
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
pub fn with_meta(mut self, meta: Option<MetaObject>) -> Self {
self.meta = meta;
self
}
@ -3349,7 +3356,7 @@ const_string!(CallToolRequestMethod = "tools/call");
pub struct CallToolRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// The name of the tool to call
pub name: Cow<'static, str>,
/// Arguments to pass to the tool (must match the tool's input schema)
@ -3407,10 +3414,10 @@ impl CallToolRequestParams {
}
impl RequestParamsMeta for CallToolRequestParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -3503,7 +3510,7 @@ pub struct GetPromptResult {
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl GetPromptResult {
@ -3543,7 +3550,7 @@ pub type GetTaskInfoRequest = GetTaskRequest;
pub struct GetTaskParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
pub task_id: String,
}
@ -3557,10 +3564,10 @@ impl GetTaskParams {
}
impl RequestParamsMeta for GetTaskParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -3589,7 +3596,7 @@ pub type GetTaskResultRequest = GetTaskPayloadRequest;
pub struct GetTaskPayloadParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
pub task_id: String,
}
@ -3603,10 +3610,10 @@ impl GetTaskPayloadParams {
}
impl RequestParamsMeta for GetTaskPayloadParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -3626,7 +3633,7 @@ pub type CancelTaskRequest = Request<CancelTaskMethod, CancelTaskParams>;
pub struct CancelTaskParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
pub task_id: String,
}
@ -3640,10 +3647,10 @@ impl CancelTaskParams {
}
impl RequestParamsMeta for CancelTaskParams {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -3666,7 +3673,7 @@ const_string!(TaskStatusNotificationMethod = "notifications/tasks/status");
#[non_exhaustive]
pub struct TaskStatusNotificationParam {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<NotificationMetaObject>,
#[serde(flatten)]
pub task: crate::model::Task,
}
@ -3676,7 +3683,7 @@ impl TaskStatusNotificationParam {
Self { meta: None, task }
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: NotificationMetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -3717,7 +3724,7 @@ pub struct ListTasksResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl ListTasksResult {
@ -4487,7 +4494,9 @@ mod tests {
{
assert_eq!(
meta,
Some(Meta(object!({ "meta_form_key_1": "meta form value 1" })))
Some(RequestMetaObject(MetaObject(
object!({ "meta_form_key_1": "meta form value 1" })
)))
);
assert_eq!(message, "Please provide more details.");
assert_eq!(requested_schema.title, Some(Cow::from("User Details")));
@ -4514,7 +4523,9 @@ mod tests {
{
assert_eq!(
meta,
Some(Meta(object!({ "meta_url_key_1": "meta url value 1" })))
Some(RequestMetaObject(MetaObject(
object!({ "meta_url_key_1": "meta url value 1" })
)))
);
assert_eq!(message, "Please fill out the form at the following URL.");
assert_eq!(url, "https://example.com/form");
@ -4527,7 +4538,9 @@ mod tests {
#[test]
fn test_elicitation_serialization() {
let form_elicitation = ElicitRequestParams::FormElicitationParams {
meta: Some(Meta(object!({ "meta_form_key_1": "meta form value 1" }))),
meta: Some(RequestMetaObject(MetaObject(
object!({ "meta_form_key_1": "meta form value 1" }),
))),
message: "Please provide more details.".to_string(),
requested_schema: ElicitationSchema::builder()
.title("User Details")
@ -4551,7 +4564,9 @@ mod tests {
assert_eq!(json_form, expected_form_json);
let url_elicitation = ElicitRequestParams::UrlElicitationParams {
meta: Some(Meta(object!({ "meta_url_key_1": "meta url value 1" }))),
meta: Some(RequestMetaObject(MetaObject(
object!({ "meta_url_key_1": "meta url value 1" }),
))),
message: "Please fill out the form at the following URL.".to_string(),
url: "https://example.com/form".to_string(),
elicitation_id: "elicitation-123".to_string(),

View file

@ -12,7 +12,7 @@
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use super::{Annotations, Meta, resource::ResourceContents};
use super::{Annotations, MetaObject, resource::ResourceContents};
// ---------------------------------------------------------------------------
// Flat content structs
@ -28,7 +28,7 @@ pub struct TextContent {
pub text: String,
/// Optional protocol-level metadata for this content block.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this content.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -43,7 +43,7 @@ impl TextContent {
}
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -66,7 +66,7 @@ pub struct ImageContent {
pub mime_type: String,
/// Optional protocol-level metadata for this content block.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this content.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -82,7 +82,7 @@ impl ImageContent {
}
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -105,7 +105,7 @@ pub struct AudioContent {
pub mime_type: String,
/// Optional protocol-level metadata for this content block.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this content.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -121,7 +121,7 @@ impl AudioContent {
}
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -142,7 +142,7 @@ pub struct EmbeddedResource {
pub resource: ResourceContents,
/// Optional protocol-level metadata for this content block.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this content.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -164,7 +164,7 @@ impl EmbeddedResource {
}
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -189,7 +189,7 @@ pub struct ToolUseContent {
pub name: String,
pub input: super::JsonObject,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
/// Tool execution result in user message (SEP-1577).
@ -203,7 +203,7 @@ pub struct ToolUseContent {
)]
pub struct ToolResultContent {
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
pub tool_use_id: String,
pub content: Vec<ContentBlock>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -402,7 +402,7 @@ mod tests {
#[test]
fn test_audio_content_has_meta() {
let audio = AudioContent::new("data", "audio/wav").with_meta(Meta::default());
let audio = AudioContent::new("data", "audio/wav").with_meta(MetaObject::default());
let json = serde_json::to_value(&audio).unwrap();
assert!(json.get("_meta").is_some());
}

View file

@ -5,13 +5,29 @@ use serde_json::Value;
use super::{
ClientCapabilities, ClientNotification, ClientRequest, CustomNotification, CustomRequest,
Extensions, Implementation, JsonObject, JsonRpcMessage, LoggingLevel, NumberOrString,
ProgressToken, ProtocolVersion, ServerNotification, ServerRequest, TaskMetadata,
Extensions, Implementation, JsonObject, JsonRpcMessage, LoggingLevel, ProgressToken,
ProtocolVersion, RequestId, ServerNotification, ServerRequest, TaskMetadata,
};
/// Access to the metadata carried by a message envelope's [`Extensions`].
///
/// The metadata type differs by message kind: requests carry a
/// [`RequestMetaObject`] and notifications carry a [`NotificationMetaObject`].
///
/// The envelope extensions are the canonical runtime location for `_meta`:
/// deserialization strips the wire `params._meta` into the extensions (typed
/// params `meta` fields stay empty), and the service loop moves it into
/// [`RequestContext::meta`] / [`NotificationContext::meta`] before dispatch.
/// Typed params `meta` fields are honored when serializing outgoing messages;
/// on key conflicts the extensions-level metadata wins.
///
/// [`RequestContext::meta`]: crate::service::RequestContext
/// [`NotificationContext::meta`]: crate::service::NotificationContext
pub trait GetMeta {
fn get_meta_mut(&mut self) -> &mut Meta;
fn get_meta(&self) -> &Meta;
/// The metadata type for this message kind.
type Metadata: Default;
fn get_meta_mut(&mut self) -> &mut Self::Metadata;
fn get_meta(&self) -> &Self::Metadata;
}
pub trait GetExtensions {
@ -21,15 +37,16 @@ pub trait GetExtensions {
/// Trait for request params that contain the `_meta` field.
///
/// Per the MCP 2025-11-25 spec, all request params should have an optional `_meta`
/// field that can contain a `progressToken` for tracking long-running operations.
/// Per the MCP spec, all request params may have an optional `_meta`
/// field ([`RequestMetaObject`]) that can contain a `progressToken` for
/// tracking long-running operations.
pub trait RequestParamsMeta {
/// Get a reference to the meta field
fn meta(&self) -> Option<&Meta>;
fn meta(&self) -> Option<&RequestMetaObject>;
/// Get a mutable reference to the meta field
fn meta_mut(&mut self) -> &mut Option<Meta>;
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject>;
/// Set the meta field
fn set_meta(&mut self, meta: Meta) {
fn set_meta(&mut self, meta: RequestMetaObject) {
*self.meta_mut() = Some(meta);
}
/// Get the progress token from meta, if present
@ -41,7 +58,7 @@ pub trait RequestParamsMeta {
match self.meta_mut() {
Some(meta) => meta.set_progress_token(token),
none => {
let mut meta = Meta::new();
let mut meta = RequestMetaObject::new();
meta.set_progress_token(token);
*none = Some(meta);
}
@ -72,8 +89,8 @@ pub trait RequestParamsMeta {
self.meta_or_default().set_baggage(value);
}
/// Get a mutable reference to meta, inserting an empty one if absent.
fn meta_or_default(&mut self) -> &mut Meta {
self.meta_mut().get_or_insert_with(Meta::new)
fn meta_or_default(&mut self) -> &mut RequestMetaObject {
self.meta_mut().get_or_insert_with(RequestMetaObject::new)
}
}
@ -102,13 +119,14 @@ impl GetExtensions for CustomNotification {
}
impl GetMeta for CustomNotification {
fn get_meta_mut(&mut self) -> &mut Meta {
type Metadata = NotificationMetaObject;
fn get_meta_mut(&mut self) -> &mut NotificationMetaObject {
self.extensions_mut().get_or_insert_default()
}
fn get_meta(&self) -> &Meta {
fn get_meta(&self) -> &NotificationMetaObject {
self.extensions()
.get::<Meta>()
.unwrap_or(Meta::static_empty())
.get::<NotificationMetaObject>()
.unwrap_or(NotificationMetaObject::static_empty())
}
}
@ -122,19 +140,20 @@ impl GetExtensions for CustomRequest {
}
impl GetMeta for CustomRequest {
fn get_meta_mut(&mut self) -> &mut Meta {
type Metadata = RequestMetaObject;
fn get_meta_mut(&mut self) -> &mut RequestMetaObject {
self.extensions_mut().get_or_insert_default()
}
fn get_meta(&self) -> &Meta {
fn get_meta(&self) -> &RequestMetaObject {
self.extensions()
.get::<Meta>()
.unwrap_or(Meta::static_empty())
.get::<RequestMetaObject>()
.unwrap_or(RequestMetaObject::static_empty())
}
}
macro_rules! variant_extension {
(
$Enum: ident {
$Enum: ident: $Metadata: ident {
$($variant: ident)*
}
) => {
@ -155,18 +174,19 @@ macro_rules! variant_extension {
}
}
impl GetMeta for $Enum {
fn get_meta_mut(&mut self) -> &mut Meta {
type Metadata = $Metadata;
fn get_meta_mut(&mut self) -> &mut $Metadata {
self.extensions_mut().get_or_insert_default()
}
fn get_meta(&self) -> &Meta {
self.extensions().get::<Meta>().unwrap_or(Meta::static_empty())
fn get_meta(&self) -> &$Metadata {
self.extensions().get::<$Metadata>().unwrap_or($Metadata::static_empty())
}
}
};
}
variant_extension! {
ClientRequest {
ClientRequest: RequestMetaObject {
PingRequest
InitializeRequest
CompleteRequest
@ -189,7 +209,7 @@ variant_extension! {
}
variant_extension! {
ServerRequest {
ServerRequest: RequestMetaObject {
PingRequest
CreateMessageRequest
ListRootsRequest
@ -199,7 +219,7 @@ variant_extension! {
}
variant_extension! {
ClientNotification {
ClientNotification: NotificationMetaObject {
CancelledNotification
ProgressNotification
InitializedNotification
@ -210,7 +230,7 @@ variant_extension! {
}
variant_extension! {
ServerNotification {
ServerNotification: NotificationMetaObject {
CancelledNotification
ProgressNotification
LoggingMessageNotification
@ -222,18 +242,31 @@ variant_extension! {
CustomNotification
}
}
/// General-purpose `_meta` map (spec `MetaObject`).
///
/// This is the metadata shape used by results, content blocks, and catalog
/// descriptors (tools, prompts, resources, roots, ...). It preserves arbitrary
/// extension keys and offers helpers for the reserved W3C Trace Context keys
/// (SEP-414).
///
/// Request and notification `_meta` maps have additional reserved keys; see
/// [`RequestMetaObject`] and [`NotificationMetaObject`].
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(transparent)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct Meta(pub JsonObject);
pub struct MetaObject(pub JsonObject);
impl Meta {
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
const META_KEY_PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
const META_KEY_CLIENT_INFO: &str = "io.modelcontextprotocol/clientInfo";
const META_KEY_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
const META_KEY_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
/// Deprecated alias for [`MetaObject`].
///
/// This is a re-export rather than a type alias so the `Meta(...)` tuple
/// constructor keeps working. Request and notification metadata now have
/// dedicated types; use [`RequestMetaObject`] or [`NotificationMetaObject`]
/// where those are expected.
#[deprecated(note = "Use MetaObject (or RequestMetaObject / NotificationMetaObject)")]
pub use self::MetaObject as Meta;
impl MetaObject {
/// Reserved `_meta` key for the W3C Trace Context `traceparent` value (SEP-414).
const TRACEPARENT_FIELD: &str = "traceparent";
/// Reserved `_meta` key for the W3C Trace Context `tracestate` value (SEP-414).
@ -241,102 +274,11 @@ impl Meta {
/// Reserved `_meta` key for the W3C Baggage value (SEP-414).
const BAGGAGE_FIELD: &str = "baggage";
/// Create an empty metadata map.
pub fn new() -> Self {
Self(JsonObject::new())
}
/// Create a new Meta with a progress token set
pub fn with_progress_token(token: ProgressToken) -> Self {
let mut meta = Self::new();
meta.set_progress_token(token);
meta
}
pub(crate) fn static_empty() -> &'static Self {
static EMPTY: std::sync::OnceLock<Meta> = std::sync::OnceLock::new();
EMPTY.get_or_init(Default::default)
}
pub fn get_progress_token(&self) -> Option<ProgressToken> {
self.0
.get(Self::PROGRESS_TOKEN_FIELD)
.and_then(|v| match v {
Value::String(s) => {
Some(ProgressToken(NumberOrString::String(s.to_string().into())))
}
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Some(ProgressToken(NumberOrString::Number(i)))
} else if let Some(u) = n.as_u64() {
if u <= i64::MAX as u64 {
Some(ProgressToken(NumberOrString::Number(u as i64)))
} else {
None
}
} else {
None
}
}
_ => None,
})
}
pub fn set_progress_token(&mut self, token: ProgressToken) {
match token.0 {
NumberOrString::String(ref s) => self.0.insert(
Self::PROGRESS_TOKEN_FIELD.to_string(),
Value::String(s.to_string()),
),
NumberOrString::Number(n) => self.0.insert(
Self::PROGRESS_TOKEN_FIELD.to_string(),
Value::Number(n.into()),
),
};
}
/// Get the MCP protocol version carried in `_meta`, if present and valid.
pub fn protocol_version(&self) -> Option<ProtocolVersion> {
self.decode_value(Self::META_KEY_PROTOCOL_VERSION)
}
/// Set the MCP protocol version carried in `_meta`.
pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
self.0.insert(
Self::META_KEY_PROTOCOL_VERSION.to_string(),
Value::String(protocol_version.to_string()),
);
}
/// Get the client implementation identity carried in `_meta`, if present and valid.
pub fn client_info(&self) -> Option<Implementation> {
self.decode_value(Self::META_KEY_CLIENT_INFO)
}
/// Set the client implementation identity carried in `_meta`.
pub fn set_client_info(&mut self, client_info: Implementation) {
self.insert_serialized(Self::META_KEY_CLIENT_INFO, client_info);
}
/// Get the client capabilities carried in `_meta`, if present and valid.
pub fn client_capabilities(&self) -> Option<ClientCapabilities> {
self.decode_value(Self::META_KEY_CLIENT_CAPABILITIES)
}
/// Set the client capabilities carried in `_meta`.
pub fn set_client_capabilities(&mut self, client_capabilities: ClientCapabilities) {
self.insert_serialized(Self::META_KEY_CLIENT_CAPABILITIES, client_capabilities);
}
/// Get the requested per-request log level carried in `_meta`, if present and valid.
pub fn log_level(&self) -> Option<LoggingLevel> {
self.decode_value(Self::META_KEY_LOG_LEVEL)
}
/// Set the requested per-request log level carried in `_meta`.
pub fn set_log_level(&mut self, log_level: LoggingLevel) {
self.insert_serialized(Self::META_KEY_LOG_LEVEL, log_level);
}
/// Read a string-valued `_meta` field, or `None` if absent or not a string.
fn get_str(&self, field: &str) -> Option<&str> {
self.0.get(field).and_then(Value::as_str)
@ -356,9 +298,9 @@ impl Meta {
/// Set the W3C `traceparent` value (SEP-414).
///
/// ```
/// use rmcp::model::Meta;
/// use rmcp::model::MetaObject;
///
/// let mut meta = Meta::new();
/// let mut meta = MetaObject::new();
/// meta.set_traceparent("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01");
/// assert_eq!(
/// meta.get_traceparent(),
@ -389,10 +331,9 @@ impl Meta {
self.set_str(Self::BAGGAGE_FIELD, value);
}
pub fn extend(&mut self, other: Meta) {
for (k, v) in other.0.into_iter() {
self.0.insert(k, v);
}
/// Insert every entry of `other`, overwriting existing keys on conflict.
pub fn extend(&mut self, other: MetaObject) {
self.0.extend(other.0);
}
fn decode_value<T>(&self, key: &str) -> Option<T>
@ -412,7 +353,7 @@ impl Meta {
}
}
impl Deref for Meta {
impl Deref for MetaObject {
type Target = JsonObject;
fn deref(&self) -> &Self::Target {
@ -420,12 +361,353 @@ impl Deref for Meta {
}
}
impl DerefMut for Meta {
impl DerefMut for MetaObject {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<JsonObject> for MetaObject {
fn from(object: JsonObject) -> Self {
Self(object)
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for MetaObject {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("MetaObject")
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.",
"type": "object",
"additionalProperties": true,
})
}
}
/// The `_meta` map carried by requests (spec `RequestMetaObject`).
///
/// In addition to arbitrary extension keys, requests reserve:
/// - `progressToken` for progress tracking
/// - `io.modelcontextprotocol/protocolVersion` (SEP-2575)
/// - `io.modelcontextprotocol/clientInfo` (SEP-2575)
/// - `io.modelcontextprotocol/clientCapabilities` (SEP-2575)
/// - `io.modelcontextprotocol/logLevel` (SEP-2575)
///
/// The 2026-07-28 draft schema marks the protocol-version, client-info, and
/// client-capabilities keys as required; earlier protocol versions do not know
/// them. All keys therefore stay optional at runtime and in the generated
/// (version-shared) JSON schema — use
/// [`RequestMetaObject::missing_required_keys`] to validate a request against
/// the negotiated protocol version.
///
/// This type dereferences to [`MetaObject`] (and transitively to the underlying
/// map), so general helpers such as the SEP-414 trace-context accessors remain
/// available.
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(transparent)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RequestMetaObject(pub MetaObject);
impl RequestMetaObject {
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
const META_KEY_PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
const META_KEY_CLIENT_INFO: &str = "io.modelcontextprotocol/clientInfo";
const META_KEY_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
const META_KEY_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
/// Request `_meta` keys the 2026-07-28 draft schema marks as required.
pub const DRAFT_REQUIRED_KEYS: [&str; 3] = [
Self::META_KEY_PROTOCOL_VERSION,
Self::META_KEY_CLIENT_INFO,
Self::META_KEY_CLIENT_CAPABILITIES,
];
/// Create an empty request metadata map.
pub fn new() -> Self {
Self::default()
}
/// Create a new request meta with a progress token set
pub fn with_progress_token(token: ProgressToken) -> Self {
let mut meta = Self::new();
meta.set_progress_token(token);
meta
}
pub(crate) fn static_empty() -> &'static Self {
static EMPTY: std::sync::OnceLock<RequestMetaObject> = std::sync::OnceLock::new();
EMPTY.get_or_init(Default::default)
}
/// Get the progress token carried in `_meta`, if present and valid.
pub fn get_progress_token(&self) -> Option<ProgressToken> {
self.0.decode_value(Self::PROGRESS_TOKEN_FIELD)
}
/// Set the progress token carried in `_meta`.
pub fn set_progress_token(&mut self, token: ProgressToken) {
self.0.insert_serialized(Self::PROGRESS_TOKEN_FIELD, token);
}
/// Get the MCP protocol version carried in `_meta`, if present and valid.
pub fn protocol_version(&self) -> Option<ProtocolVersion> {
self.0.decode_value(Self::META_KEY_PROTOCOL_VERSION)
}
/// Set the MCP protocol version carried in `_meta`.
pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
self.0.0.insert(
Self::META_KEY_PROTOCOL_VERSION.to_string(),
Value::String(protocol_version.to_string()),
);
}
/// Get the client implementation identity carried in `_meta`, if present and valid.
pub fn client_info(&self) -> Option<Implementation> {
self.0.decode_value(Self::META_KEY_CLIENT_INFO)
}
/// Set the client implementation identity carried in `_meta`.
pub fn set_client_info(&mut self, client_info: Implementation) {
self.0
.insert_serialized(Self::META_KEY_CLIENT_INFO, client_info);
}
/// Get the client capabilities carried in `_meta`, if present and valid.
pub fn client_capabilities(&self) -> Option<ClientCapabilities> {
self.0.decode_value(Self::META_KEY_CLIENT_CAPABILITIES)
}
/// Set the client capabilities carried in `_meta`.
pub fn set_client_capabilities(&mut self, client_capabilities: ClientCapabilities) {
self.0
.insert_serialized(Self::META_KEY_CLIENT_CAPABILITIES, client_capabilities);
}
/// Get the requested per-request log level carried in `_meta`, if present and valid.
pub fn log_level(&self) -> Option<LoggingLevel> {
self.0.decode_value(Self::META_KEY_LOG_LEVEL)
}
/// Set the requested per-request log level carried in `_meta`.
pub fn set_log_level(&mut self, log_level: LoggingLevel) {
self.0
.insert_serialized(Self::META_KEY_LOG_LEVEL, log_level);
}
/// Return the [`Self::DRAFT_REQUIRED_KEYS`] whose values are absent or
/// invalid in this map, if `protocol_version` requires them.
///
/// A key counts as missing when it is not present *or* when its value does
/// not decode into the expected type (e.g. a numeric `protocolVersion` or
/// a string `clientInfo`), matching what the typed accessors return.
///
/// Protocol versions before 2026-07-28 have no required request metadata,
/// so this always returns an empty list for them.
///
/// # Examples
///
/// ```
/// use rmcp::model::{ProtocolVersion, RequestMetaObject};
///
/// let meta = RequestMetaObject::new();
/// // Older protocols have no required request metadata.
/// assert!(
/// meta.missing_required_keys(&ProtocolVersion::V_2025_11_25)
/// .is_empty()
/// );
/// // The 2026-07-28 draft requires the SEP-2575 keys.
/// assert_eq!(
/// meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
/// RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec(),
/// );
/// ```
pub fn missing_required_keys(&self, protocol_version: &ProtocolVersion) -> Vec<&'static str> {
if protocol_version.as_str() < ProtocolVersion::V_2026_07_28.as_str() {
return Vec::new();
}
let mut missing = Vec::new();
if self.protocol_version().is_none() {
missing.push(Self::META_KEY_PROTOCOL_VERSION);
}
if self.client_info().is_none() {
missing.push(Self::META_KEY_CLIENT_INFO);
}
if self.client_capabilities().is_none() {
missing.push(Self::META_KEY_CLIENT_CAPABILITIES);
}
missing
}
/// Insert every entry of `other`, overwriting existing keys on conflict.
pub fn extend(&mut self, other: RequestMetaObject) {
self.0.extend(other.0);
}
}
impl Deref for RequestMetaObject {
type Target = MetaObject;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for RequestMetaObject {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<MetaObject> for RequestMetaObject {
fn from(meta: MetaObject) -> Self {
Self(meta)
}
}
impl From<JsonObject> for RequestMetaObject {
fn from(object: JsonObject) -> Self {
Self(MetaObject(object))
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for RequestMetaObject {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("RequestMetaObject")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let progress_token = generator.subschema_for::<ProgressToken>();
let client_info = generator.subschema_for::<Implementation>();
let client_capabilities = generator.subschema_for::<ClientCapabilities>();
let log_level = generator.subschema_for::<LoggingLevel>();
// rmcp generates one schema shared by every supported protocol
// version, so the keys the 2026-07-28 draft marks as required are left
// optional here: a 2025-11-25 request whose `_meta` only carries
// `progressToken` is valid. Draft-strict validation is available at
// runtime via [`RequestMetaObject::missing_required_keys`].
schemars::json_schema!({
"description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
"type": "object",
"properties": {
"progressToken": progress_token,
"io.modelcontextprotocol/protocolVersion": {
"type": "string",
},
"io.modelcontextprotocol/clientInfo": client_info,
"io.modelcontextprotocol/clientCapabilities": client_capabilities,
"io.modelcontextprotocol/logLevel": log_level,
},
"additionalProperties": true,
})
}
}
/// The `_meta` map carried by notifications (spec `NotificationMetaObject`).
///
/// In addition to arbitrary extension keys, notifications reserve
/// `io.modelcontextprotocol/subscriptionId` to correlate a notification with a
/// prior subscription request.
///
/// This type dereferences to [`MetaObject`] (and transitively to the underlying
/// map), so general helpers such as the SEP-414 trace-context accessors remain
/// available.
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(transparent)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct NotificationMetaObject(pub MetaObject);
impl NotificationMetaObject {
const META_KEY_SUBSCRIPTION_ID: &str = "io.modelcontextprotocol/subscriptionId";
/// Create an empty notification metadata map.
pub fn new() -> Self {
Self::default()
}
pub(crate) fn static_empty() -> &'static Self {
static EMPTY: std::sync::OnceLock<NotificationMetaObject> = std::sync::OnceLock::new();
EMPTY.get_or_init(Default::default)
}
/// Get the subscription id carried in `_meta`, if present and valid.
///
/// # Examples
///
/// ```
/// use rmcp::model::{NotificationMetaObject, RequestId};
///
/// let mut meta = NotificationMetaObject::new();
/// assert_eq!(meta.subscription_id(), None);
/// meta.set_subscription_id(RequestId::Number(7));
/// assert_eq!(meta.subscription_id(), Some(RequestId::Number(7)));
/// ```
pub fn subscription_id(&self) -> Option<RequestId> {
self.0.decode_value(Self::META_KEY_SUBSCRIPTION_ID)
}
/// Set the subscription id carried in `_meta`.
pub fn set_subscription_id(&mut self, subscription_id: RequestId) {
self.0
.insert_serialized(Self::META_KEY_SUBSCRIPTION_ID, subscription_id);
}
/// Insert every entry of `other`, overwriting existing keys on conflict.
pub fn extend(&mut self, other: NotificationMetaObject) {
self.0.extend(other.0);
}
}
impl Deref for NotificationMetaObject {
type Target = MetaObject;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for NotificationMetaObject {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<MetaObject> for NotificationMetaObject {
fn from(meta: MetaObject) -> Self {
Self(meta)
}
}
impl From<JsonObject> for NotificationMetaObject {
fn from(object: JsonObject) -> Self {
Self(MetaObject(object))
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for NotificationMetaObject {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("NotificationMetaObject")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let subscription_id = generator.subschema_for::<RequestId>();
schemars::json_schema!({
"description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/subscriptionId": subscription_id,
},
"additionalProperties": true,
})
}
}
impl<Req, Resp, Noti> JsonRpcMessage<Req, Resp, Noti>
where
Req: GetExtensions,
@ -450,17 +732,18 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::model::NumberOrString;
#[derive(Default)]
struct Params {
meta: Option<Meta>,
meta: Option<RequestMetaObject>,
}
impl RequestParamsMeta for Params {
fn meta(&self) -> Option<&Meta> {
fn meta(&self) -> Option<&RequestMetaObject> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
&mut self.meta
}
}
@ -469,7 +752,7 @@ mod tests {
#[test]
fn trace_context_round_trip() {
let mut meta = Meta::new();
let mut meta = MetaObject::new();
meta.set_traceparent(TRACEPARENT);
meta.set_tracestate("vendor1=value1,vendor2=value2");
meta.set_baggage("userId=alice,region=us-east-1");
@ -480,7 +763,7 @@ mod tests {
#[test]
fn absent_field_is_none() {
let meta = Meta::new();
let meta = MetaObject::new();
assert_eq!(meta.get_traceparent(), None);
assert_eq!(meta.get_tracestate(), None);
assert_eq!(meta.get_baggage(), None);
@ -488,9 +771,9 @@ mod tests {
#[test]
fn non_string_value_is_none() {
let mut meta = Meta::new();
let mut meta = MetaObject::new();
meta.0
.insert(Meta::TRACEPARENT_FIELD.to_string(), Value::from(42));
.insert(MetaObject::TRACEPARENT_FIELD.to_string(), Value::from(42));
assert_eq!(meta.get_traceparent(), None);
}
@ -501,4 +784,91 @@ mod tests {
params.set_traceparent(TRACEPARENT);
assert_eq!(params.traceparent(), Some(TRACEPARENT));
}
#[test]
fn request_meta_derefs_to_general_helpers() {
let mut meta = RequestMetaObject::new();
meta.set_traceparent(TRACEPARENT);
meta.set_progress_token(ProgressToken(NumberOrString::Number(7)));
assert_eq!(meta.get_traceparent(), Some(TRACEPARENT));
assert_eq!(
meta.get_progress_token(),
Some(ProgressToken(NumberOrString::Number(7)))
);
}
mod subscription_id {
use super::*;
#[test]
fn returns_none_when_absent() {
let meta = NotificationMetaObject::new();
assert_eq!(meta.subscription_id(), None);
}
#[test]
fn round_trips_number_id() {
let mut meta = NotificationMetaObject::new();
meta.set_subscription_id(RequestId::Number(42));
assert_eq!(meta.subscription_id(), Some(RequestId::Number(42)));
}
#[test]
fn round_trips_string_id() {
let mut meta = NotificationMetaObject::new();
meta.set_subscription_id(RequestId::String("sub-1".into()));
assert_eq!(
meta.subscription_id(),
Some(RequestId::String("sub-1".into()))
);
}
}
mod missing_required_keys {
use super::*;
#[test]
fn is_empty_for_pre_draft_protocols() {
let meta = RequestMetaObject::new();
assert!(
meta.missing_required_keys(&ProtocolVersion::V_2025_11_25)
.is_empty()
);
}
#[test]
fn lists_all_draft_keys_for_empty_meta() {
let meta = RequestMetaObject::new();
assert_eq!(
meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec()
);
}
#[test]
fn treats_malformed_values_as_missing() {
let meta: RequestMetaObject = serde_json::from_value(serde_json::json!({
"io.modelcontextprotocol/protocolVersion": 123,
"io.modelcontextprotocol/clientInfo": "not an implementation",
"io.modelcontextprotocol/clientCapabilities": null,
}))
.unwrap();
assert_eq!(
meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec()
);
}
#[test]
fn is_empty_when_draft_keys_are_present() {
let mut meta = RequestMetaObject::new();
meta.set_protocol_version(ProtocolVersion::V_2026_07_28);
meta.set_client_info(Implementation::from_build_env());
meta.set_client_capabilities(ClientCapabilities::default());
assert!(
meta.missing_required_keys(&ProtocolVersion::V_2026_07_28)
.is_empty()
);
}
}
}

View file

@ -42,8 +42,8 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{
CallToolResult, CreateMessageRequest, ElicitRequest, GetPromptResult, ListRootsRequest, Meta,
ReadResourceResult, ResultType, ServerResult,
CallToolResult, CreateMessageRequest, ElicitRequest, GetPromptResult, ListRootsRequest,
MetaObject, ReadResourceResult, ResultType, ServerResult,
};
/// Default maximum number of MRTR rounds a high-level client call will drive.
@ -220,7 +220,7 @@ pub struct InputRequiredResult {
/// Optional protocol-level metadata.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
/// Custom deserializer that requires `resultType: "input_required"` to prevent
@ -238,7 +238,7 @@ impl<'de> Deserialize<'de> for InputRequiredResult {
input_requests: Option<InputRequests>,
request_state: Option<String>,
#[serde(rename = "_meta")]
meta: Option<Meta>,
meta: Option<MetaObject>,
}
let helper = Helper::deserialize(deserializer)?;
@ -283,7 +283,7 @@ impl InputRequiredResult {
}
/// Sets optional metadata.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use super::{
Annotations, ContentBlock, Icon, Meta, Role,
Annotations, ContentBlock, Icon, MetaObject, Role,
content::{AudioContent, EmbeddedResource, ImageContent, TextContent},
resource::ResourceContents,
};
@ -22,7 +22,7 @@ pub struct Prompt {
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl Prompt {
@ -70,7 +70,7 @@ impl Prompt {
self
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -144,7 +144,7 @@ impl PromptMessage {
role: Role,
data: &[u8],
mime_type: &str,
meta: Option<Meta>,
meta: Option<MetaObject>,
annotations: Option<Annotations>,
) -> Self {
use base64::{Engine, prelude::BASE64_STANDARD};
@ -166,7 +166,7 @@ impl PromptMessage {
role: Role,
data: &[u8],
mime_type: &str,
meta: Option<Meta>,
meta: Option<MetaObject>,
annotations: Option<Annotations>,
) -> Self {
use base64::{Engine, prelude::BASE64_STANDARD};
@ -188,8 +188,8 @@ impl PromptMessage {
uri: String,
mime_type: Option<String>,
text: Option<String>,
resource_meta: Option<Meta>,
resource_content_meta: Option<Meta>,
resource_meta: Option<MetaObject>,
resource_content_meta: Option<MetaObject>,
annotations: Option<Annotations>,
) -> Self {
let resource_contents = match text {
@ -216,7 +216,11 @@ impl PromptMessage {
}
}
pub fn new_text_with_meta<S: Into<String>>(role: Role, text: S, meta: Option<Meta>) -> Self {
pub fn new_text_with_meta<S: Into<String>>(
role: Role,
text: S,
meta: Option<MetaObject>,
) -> Self {
Self {
role,
content: ContentBlock::Text(TextContent {

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use super::{Annotations, Icon, Meta};
use super::{Annotations, Icon, MetaObject};
/// A known resource that the server is capable of reading (spec `Resource`).
///
@ -31,7 +31,7 @@ pub struct Resource {
pub icons: Option<Vec<Icon>>,
/// Optional protocol-level metadata for this resource.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -77,7 +77,7 @@ impl Resource {
self
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -112,7 +112,7 @@ pub struct ResourceTemplate {
pub icons: Option<Vec<Icon>>,
/// Optional protocol-level metadata for this resource template.
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
/// Optional annotations describing how the client should use this template.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@ -152,7 +152,7 @@ impl ResourceTemplate {
self
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -176,7 +176,7 @@ pub enum ResourceContents {
mime_type: Option<String>,
text: String,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<MetaObject>,
},
#[serde(rename_all = "camelCase")]
BlobResourceContents {
@ -185,7 +185,7 @@ pub enum ResourceContents {
mime_type: Option<String>,
blob: String,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
meta: Option<MetaObject>,
},
}
@ -216,7 +216,7 @@ impl ResourceContents {
self
}
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
match &mut self {
Self::TextResourceContents { meta: m, .. } => *m = Some(meta),
Self::BlobResourceContents { meta: m, .. } => *m = Some(meta),
@ -303,7 +303,7 @@ mod tests {
#[test]
fn test_resource_template_with_meta() {
let resource_template =
ResourceTemplate::new("file:///{path}", "template").with_meta(Meta::default());
ResourceTemplate::new("file:///{path}", "template").with_meta(MetaObject::default());
let json = serde_json::to_value(&resource_template).unwrap();
assert!(json.get("_meta").is_some());
}

View file

@ -3,12 +3,19 @@ use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use super::{
CustomNotification, CustomRequest, Extensions, Meta, Notification, NotificationNoParam,
Request, RequestNoParam, RequestOptionalParam,
CustomNotification, CustomRequest, Extensions, JsonObject, MetaObject, Notification,
NotificationMetaObject, NotificationNoParam, Request, RequestMetaObject, RequestNoParam,
RequestOptionalParam,
};
/// Wire-side view of `params`: the `_meta` map plus the remaining fields.
///
/// All metadata types are transparent wrappers over [`JsonObject`], so the
/// serde plumbing works on the raw map; call sites wrap/unwrap the typed
/// metadata ([`RequestMetaObject`] / [`NotificationMetaObject`]).
#[derive(Deserialize)]
struct WithMeta<'a, P> {
_meta: Option<Cow<'a, Meta>>,
_meta: Option<Cow<'a, JsonObject>>,
#[serde(flatten)]
_rest: P,
}
@ -25,7 +32,7 @@ impl<P: Serialize> Serialize for WithMeta<'_, P> {
serde_json::to_value(&self._rest).map_err(serde::ser::Error::custom)?;
// Extract _meta from the serialized params (if it's an object containing one)
let params_meta: Option<Meta> = rest_value
let params_meta: Option<JsonObject> = rest_value
.as_object_mut()
.and_then(|obj| obj.remove("_meta"))
.and_then(|v| serde_json::from_value(v).ok());
@ -78,6 +85,53 @@ struct ProxyNoParam<M> {
method: M,
}
/// Combine the message-specific `_meta` map with a legacy [`MetaObject`]
/// extension (inserted through the deprecated `Meta` name), so pre-3.x code
/// does not silently lose metadata on the wire. On key conflicts the
/// message-specific map wins.
fn merge_legacy_meta<'a>(
typed: Option<&'a JsonObject>,
extensions: &'a Extensions,
) -> Option<Cow<'a, JsonObject>> {
let legacy = extensions.get::<MetaObject>().map(|meta| &meta.0);
match (typed, legacy) {
(Some(typed), None) => Some(Cow::Borrowed(typed)),
(None, Some(legacy)) => Some(Cow::Borrowed(legacy)),
(Some(typed), Some(legacy)) => {
let mut merged = legacy.clone();
merged.extend(typed.clone());
Some(Cow::Owned(merged))
}
(None, None) => None,
}
}
/// Borrow the request `_meta` map from extensions, if any.
fn request_meta(extensions: &Extensions) -> Option<Cow<'_, JsonObject>> {
let typed = extensions.get::<RequestMetaObject>().map(|meta| &meta.0.0);
merge_legacy_meta(typed, extensions)
}
/// Borrow the notification `_meta` map from extensions, if any.
fn notification_meta(extensions: &Extensions) -> Option<Cow<'_, JsonObject>> {
let typed = extensions
.get::<NotificationMetaObject>()
.map(|meta| &meta.0.0);
merge_legacy_meta(typed, extensions)
}
/// Build extensions holding a typed metadata map deserialized from `params._meta`.
fn extensions_with_meta<T>(meta: Option<Cow<'_, JsonObject>>) -> Extensions
where
T: From<JsonObject> + Clone + Send + Sync + 'static,
{
let mut extensions = Extensions::new();
if let Some(meta) = meta {
extensions.insert(T::from(meta.into_owned()));
}
extensions
}
impl<M, R> Serialize for Request<M, R>
where
M: Serialize,
@ -87,14 +141,12 @@ where
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
_meta: request_meta(&self.extensions),
},
},
serializer,
@ -112,13 +164,8 @@ where
D: serde::Deserializer<'de>,
{
let body = Proxy::deserialize(deserializer)?;
let _meta = body.params._meta.map(|m| m.into_owned());
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
}
Ok(Request {
extensions,
extensions: extensions_with_meta::<RequestMetaObject>(body.params._meta),
method: body.method,
params: body.params._rest,
})
@ -134,14 +181,12 @@ where
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
_meta: request_meta(&self.extensions),
},
},
serializer,
@ -163,14 +208,10 @@ where
let mut _meta = None;
if let Some(body_params) = body.params {
params = body_params._rest;
_meta = body_params._meta.map(|m| m.into_owned());
}
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
_meta = body_params._meta;
}
Ok(RequestOptionalParam {
extensions,
extensions: extensions_with_meta::<RequestMetaObject>(_meta),
method: body.method,
params,
})
@ -185,14 +226,26 @@ where
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
)
// Emit `params` only when metadata is present, so the wire shape of
// meta-less requests stays `{"method": ...}`.
match request_meta(&self.extensions) {
Some(_meta) => Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_meta: Some(_meta),
_rest: (),
},
},
serializer,
),
None => ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
),
}
}
}
@ -204,10 +257,10 @@ where
where
D: serde::Deserializer<'de>,
{
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
let extensions = Extensions::new();
let body = ProxyOptionalParam::<'_, _, Option<JsonObject>>::deserialize(deserializer)?;
let _meta = body.params.and_then(|params| params._meta);
Ok(RequestNoParam {
extensions,
extensions: extensions_with_meta::<RequestMetaObject>(_meta),
method: body.method,
})
}
@ -222,14 +275,12 @@ where
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_rest: &self.params,
_meta,
_meta: notification_meta(&self.extensions),
},
},
serializer,
@ -248,10 +299,7 @@ where
{
let body = ProxyOptionalParam::<'_, _, R>::deserialize(deserializer)?;
let (_meta, params) = match body.params {
Some(with_meta) => {
let meta = with_meta._meta.map(|m| m.into_owned());
(meta, with_meta._rest)
}
Some(with_meta) => (with_meta._meta, with_meta._rest),
None => {
// JSON-RPC 2.0: params is optional. Treat absent params as {}.
let empty = serde_json::Value::Object(serde_json::Map::new());
@ -259,12 +307,8 @@ where
(None, r)
}
};
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
}
Ok(Notification {
extensions,
extensions: extensions_with_meta::<NotificationMetaObject>(_meta),
method: body.method,
params,
})
@ -279,14 +323,26 @@ where
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
)
// Emit `params` only when metadata is present, so the wire shape of
// meta-less notifications stays `{"method": ...}`.
match notification_meta(&self.extensions) {
Some(_meta) => Proxy::serialize(
&Proxy {
method: &self.method,
params: WithMeta {
_meta: Some(_meta),
_rest: (),
},
},
serializer,
),
None => ProxyNoParam::serialize(
&ProxyNoParam {
method: &self.method,
},
serializer,
),
}
}
}
@ -298,10 +354,10 @@ where
where
D: serde::Deserializer<'de>,
{
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
let extensions = Extensions::new();
let body = ProxyOptionalParam::<'_, _, Option<JsonObject>>::deserialize(deserializer)?;
let _meta = body.params.and_then(|params| params._meta);
Ok(NotificationNoParam {
extensions,
extensions: extensions_with_meta::<NotificationMetaObject>(_meta),
method: body.method,
})
}
@ -312,8 +368,7 @@ impl Serialize for CustomRequest {
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
let _meta = request_meta(&self.extensions);
let params = self.params.as_ref();
let params = if _meta.is_some() || params.is_some() {
@ -346,14 +401,10 @@ impl<'de> Deserialize<'de> for CustomRequest {
let mut _meta = None;
if let Some(body_params) = body.params {
params = body_params._rest;
_meta = body_params._meta.map(|m| m.into_owned());
}
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
_meta = body_params._meta;
}
Ok(CustomRequest {
extensions,
extensions: extensions_with_meta::<RequestMetaObject>(_meta),
method: body.method,
params,
})
@ -365,8 +416,7 @@ impl Serialize for CustomNotification {
where
S: serde::Serializer,
{
let extensions = &self.extensions;
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
let _meta = notification_meta(&self.extensions);
let params = self.params.as_ref();
let params = if _meta.is_some() || params.is_some() {
@ -399,14 +449,10 @@ impl<'de> Deserialize<'de> for CustomNotification {
let mut _meta = None;
if let Some(body_params) = body.params {
params = body_params._rest;
_meta = body_params._meta.map(|m| m.into_owned());
}
let mut extensions = Extensions::new();
if let Some(meta) = _meta {
extensions.insert(meta);
_meta = body_params._meta;
}
Ok(CustomNotification {
extensions,
extensions: extensions_with_meta::<NotificationMetaObject>(_meta),
method: body.method,
params,
})
@ -418,7 +464,8 @@ mod test {
use serde_json::json;
use crate::model::{
CallToolRequest, CallToolRequestParams, CustomRequest, Extensions, ListToolsRequest, Meta,
CallToolRequest, CallToolRequestParams, CustomRequest, Extensions, InitializedNotification,
ListToolsRequest, NotificationMetaObject, PingRequest, RequestMetaObject,
};
#[test]
@ -436,12 +483,12 @@ mod test {
// When both extensions and params contain _meta, the output should have
// a single merged _meta key (not two separate ones).
let mut extensions = Extensions::new();
let mut ext_meta = Meta::new();
ext_meta.0.insert("traceId".to_string(), json!("abc"));
let mut ext_meta = RequestMetaObject::new();
ext_meta.insert("traceId".to_string(), json!("abc"));
extensions.insert(ext_meta);
let mut params_meta = Meta::new();
params_meta.0.insert("progressToken".to_string(), json!(1));
let mut params_meta = RequestMetaObject::new();
params_meta.insert("progressToken".to_string(), json!(1));
let req = CallToolRequest {
extensions,
@ -479,8 +526,8 @@ mod test {
#[test]
fn test_meta_only_from_extensions() {
let mut extensions = Extensions::new();
let mut ext_meta = Meta::new();
ext_meta.0.insert("traceId".to_string(), json!("ext-only"));
let mut ext_meta = RequestMetaObject::new();
ext_meta.insert("traceId".to_string(), json!("ext-only"));
extensions.insert(ext_meta);
let req = CallToolRequest {
@ -503,8 +550,8 @@ mod test {
#[test]
fn test_meta_only_from_params() {
let mut params_meta = Meta::new();
params_meta.0.insert("progressToken".to_string(), json!(42));
let mut params_meta = RequestMetaObject::new();
params_meta.insert("progressToken".to_string(), json!(42));
let req = CallToolRequest {
extensions: Extensions::new(),
@ -550,19 +597,13 @@ mod test {
fn test_extensions_meta_takes_priority_on_conflict() {
// When both sources have the same key, extensions should win.
let mut extensions = Extensions::new();
let mut ext_meta = Meta::new();
ext_meta
.0
.insert("shared_key".to_string(), json!("from_extensions"));
let mut ext_meta = RequestMetaObject::new();
ext_meta.insert("shared_key".to_string(), json!("from_extensions"));
extensions.insert(ext_meta);
let mut params_meta = Meta::new();
params_meta
.0
.insert("shared_key".to_string(), json!("from_params"));
params_meta
.0
.insert("params_only".to_string(), json!("kept"));
let mut params_meta = RequestMetaObject::new();
params_meta.insert("shared_key".to_string(), json!("from_params"));
params_meta.insert("params_only".to_string(), json!("kept"));
let req = CallToolRequest {
extensions,
@ -586,10 +627,8 @@ mod test {
#[test]
fn test_round_trip_preserves_meta() {
let mut extensions = Extensions::new();
let mut ext_meta = Meta::new();
ext_meta
.0
.insert("traceId".to_string(), json!("round-trip"));
let mut ext_meta = RequestMetaObject::new();
ext_meta.insert("traceId".to_string(), json!("round-trip"));
extensions.insert(ext_meta);
let req = CallToolRequest {
@ -609,8 +648,8 @@ mod test {
let deserialized: CallToolRequest = serde_json::from_str(&serialized).unwrap();
// Extensions should have the meta after round-trip
let meta = deserialized.extensions.get::<Meta>().unwrap();
assert_eq!(meta.0.get("traceId").unwrap(), "round-trip");
let meta = deserialized.extensions.get::<RequestMetaObject>().unwrap();
assert_eq!(meta.get("traceId").unwrap(), "round-trip");
// Params should be preserved
assert_eq!(deserialized.params.name, "my_tool");
@ -630,10 +669,8 @@ mod test {
fn test_custom_request_no_duplicate_meta() {
// CustomRequest uses Option<Value> as params — verify no duplicate _meta.
let mut extensions = Extensions::new();
let mut ext_meta = Meta::new();
ext_meta
.0
.insert("traceId".to_string(), json!("custom-ext"));
let mut ext_meta = RequestMetaObject::new();
ext_meta.insert("traceId".to_string(), json!("custom-ext"));
extensions.insert(ext_meta);
let params = Some(json!({
@ -660,4 +697,162 @@ mod test {
assert_eq!(meta.get("traceId").unwrap(), "custom-ext");
assert_eq!(meta.get("progressToken").unwrap(), 99);
}
#[test]
fn test_request_no_param_meta_round_trip() {
// Ping-shaped requests must carry `params._meta` on the wire.
let mut extensions = Extensions::new();
let mut meta = RequestMetaObject::new();
meta.insert("traceId".to_string(), json!("ping-trace"));
extensions.insert(meta);
let req = PingRequest {
method: Default::default(),
extensions,
};
let value = serde_json::to_value(&req).unwrap();
assert_eq!(value["params"]["_meta"]["traceId"], json!("ping-trace"));
let deserialized: PingRequest = serde_json::from_value(value).unwrap();
let meta = deserialized
.extensions
.get::<RequestMetaObject>()
.expect("meta should survive the round-trip");
assert_eq!(meta.get("traceId").unwrap(), &json!("ping-trace"));
}
#[test]
fn test_request_no_param_without_meta_has_no_params_key() {
let req = PingRequest {
method: Default::default(),
extensions: Extensions::new(),
};
let value = serde_json::to_value(&req).unwrap();
assert!(
value.get("params").is_none(),
"meta-less no-param requests must keep the historical wire shape: {value}"
);
}
#[test]
fn test_notification_no_param_meta_round_trip() {
// Initialized-shaped notifications must carry `params._meta` on the wire.
let mut extensions = Extensions::new();
let mut meta = NotificationMetaObject::new();
meta.insert("traceId".to_string(), json!("init-trace"));
extensions.insert(meta);
let notification = InitializedNotification {
method: Default::default(),
extensions,
};
let value = serde_json::to_value(&notification).unwrap();
assert_eq!(value["params"]["_meta"]["traceId"], json!("init-trace"));
let deserialized: InitializedNotification = serde_json::from_value(value).unwrap();
let meta = deserialized
.extensions
.get::<NotificationMetaObject>()
.expect("meta should survive the round-trip");
assert_eq!(meta.get("traceId").unwrap(), &json!("init-trace"));
}
#[test]
fn test_notification_no_param_without_meta_has_no_params_key() {
let notification = InitializedNotification {
method: Default::default(),
extensions: Extensions::new(),
};
let value = serde_json::to_value(&notification).unwrap();
assert!(
value.get("params").is_none(),
"meta-less no-param notifications must keep the historical wire shape: {value}"
);
}
#[test]
fn test_no_param_ignores_unknown_params_fields() {
// Old/foreign peers may send params without _meta; both shapes must parse.
let _req: PingRequest =
serde_json::from_value(json!({"method": "ping", "params": {}})).unwrap();
let _req: PingRequest =
serde_json::from_value(json!({"method": "ping", "params": {"unknown": 1}})).unwrap();
let _req: PingRequest = serde_json::from_value(json!({"method": "ping"})).unwrap();
}
#[test]
fn test_legacy_meta_extension_still_serializes() {
// Pre-3.x code inserts `MetaObject` into extensions through the
// deprecated `Meta` name; its metadata must not be silently dropped.
let mut extensions = Extensions::new();
let mut legacy = crate::model::MetaObject::new();
legacy.insert("traceId".to_string(), json!("legacy"));
extensions.insert(legacy);
let req = CallToolRequest {
extensions,
method: Default::default(),
params: CallToolRequestParams {
meta: None,
name: "my_tool".into(),
arguments: None,
task: None,
input_responses: None,
request_state: None,
},
};
let value = serde_json::to_value(&req).unwrap();
assert_eq!(value["params"]["_meta"]["traceId"], json!("legacy"));
}
#[test]
fn test_typed_meta_wins_over_legacy_extension_on_conflict() {
let mut extensions = Extensions::new();
let mut legacy = crate::model::MetaObject::new();
legacy.insert("shared".to_string(), json!("legacy"));
legacy.insert("legacy_only".to_string(), json!("kept"));
extensions.insert(legacy);
let mut typed = RequestMetaObject::new();
typed.insert("shared".to_string(), json!("typed"));
extensions.insert(typed);
let req = CallToolRequest {
extensions,
method: Default::default(),
params: CallToolRequestParams {
meta: None,
name: "my_tool".into(),
arguments: None,
task: None,
input_responses: None,
request_state: None,
},
};
let value = serde_json::to_value(&req).unwrap();
let meta = value["params"]["_meta"].as_object().unwrap();
assert_eq!(meta.get("shared").unwrap(), "typed");
assert_eq!(meta.get("legacy_only").unwrap(), "kept");
}
#[test]
fn test_arbitrary_meta_keys_round_trip_unchanged() {
let input = json!({
"method": "tools/call",
"params": {
"_meta": {
"progressToken": 5,
"vendor.example/custom": {"nested": ["a", 1, null]},
"another-key": true
},
"name": "my_tool"
}
});
let req: CallToolRequest = serde_json::from_value(input.clone()).unwrap();
let output = serde_json::to_value(&req).unwrap();
assert_eq!(input, output);
}
}

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::Meta;
use super::MetaObject;
/// Metadata for augmenting a request with task execution (spec `TaskMetadata`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
@ -139,7 +139,7 @@ impl Task {
pub struct CreateTaskResult {
pub task: Task,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
impl CreateTaskResult {
@ -149,7 +149,7 @@ impl CreateTaskResult {
}
/// Sets the protocol-level metadata for this result.
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}
@ -165,7 +165,7 @@ impl CreateTaskResult {
#[non_exhaustive]
pub struct GetTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
#[serde(flatten)]
pub task: Task,
}
@ -222,7 +222,7 @@ impl<'de> serde::Deserialize<'de> for GetTaskPayloadResult {
#[non_exhaustive]
pub struct CancelTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
#[serde(flatten)]
pub task: Task,
}

View file

@ -7,7 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{Icon, JsonObject, Meta};
use super::{Icon, JsonObject, MetaObject};
/// A tool that can be used by a model.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -39,7 +39,7 @@ pub struct Tool {
pub icons: Option<Vec<Icon>>,
/// Optional additional metadata for this tool
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub meta: Option<MetaObject>,
}
/// Per-tool task support mode as defined in the MCP specification.
@ -286,7 +286,7 @@ impl Tool {
}
/// Set the metadata
pub fn with_meta(mut self, meta: Meta) -> Self {
pub fn with_meta(mut self, meta: MetaObject) -> Self {
self.meta = Some(meta);
self
}

View file

@ -52,8 +52,8 @@ use crate::{
error::ErrorData as McpError,
model::{
CancelledNotification, CancelledNotificationParam, Extensions, GetExtensions, GetMeta,
JsonRpcError, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, Meta,
NumberOrString, ProgressToken, RequestId,
JsonRpcError, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse,
NotificationMetaObject, NumberOrString, ProgressToken, RequestId, RequestMetaObject,
},
transport::{DynamicTransportError, IntoTransport, Transport},
};
@ -109,17 +109,17 @@ impl<T> TransferObject for T where
#[allow(private_bounds, reason = "there's no the third implementation")]
pub trait ServiceRole: std::fmt::Debug + Send + Sync + 'static + Copy + Clone {
type Req: TransferObject + GetMeta + GetExtensions;
type Req: TransferObject + GetMeta<Metadata = RequestMetaObject> + GetExtensions;
type Resp: TransferObject;
type Not: TryInto<CancelledNotification, Error = Self::Not>
+ From<CancelledNotification>
+ TransferObject;
type PeerReq: TransferObject + GetMeta + GetExtensions;
type PeerReq: TransferObject + GetMeta<Metadata = RequestMetaObject> + GetExtensions;
type PeerResp: TransferObject;
type PeerNot: TryInto<CancelledNotification, Error = Self::PeerNot>
+ From<CancelledNotification>
+ TransferObject
+ GetMeta
+ GetMeta<Metadata = NotificationMetaObject>
+ GetExtensions;
type InitializeError;
const IS_CLIENT: bool;
@ -544,7 +544,7 @@ type ProxyOutbound<R> = mpsc::Receiver<PeerSinkMessage<R>>;
#[non_exhaustive]
pub struct PeerRequestOptions {
pub timeout: Option<Duration>,
pub meta: Option<Meta>,
pub meta: Option<RequestMetaObject>,
/// Reset the request timeout when a matching progress notification is received.
pub reset_timeout_on_progress: bool,
/// Maximum total time to wait for the request, regardless of progress notifications.
@ -862,7 +862,7 @@ pub struct RequestContext<R: ServiceRole> {
/// this token will be cancelled when the [`CancelledNotification`] is received.
pub ct: CancellationToken,
pub id: RequestId,
pub meta: Meta,
pub meta: RequestMetaObject,
pub extensions: Extensions,
/// An interface to fetch the remote client or server
pub peer: Peer<R>,
@ -874,7 +874,7 @@ impl<R: ServiceRole> RequestContext<R> {
Self {
ct: CancellationToken::new(),
id,
meta: Meta::default(),
meta: RequestMetaObject::default(),
extensions: Extensions::default(),
peer,
}
@ -895,7 +895,7 @@ impl RequestContext<RoleServer> {
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NotificationContext<R: ServiceRole> {
pub meta: Meta,
pub meta: NotificationMetaObject,
pub extensions: Extensions,
/// An interface to fetch the remote client or server
pub peer: Peer<R>,
@ -1171,7 +1171,7 @@ where
let context_ct = request_ct.child_token();
local_ct_pool.insert(id.clone(), request_ct);
let mut extensions = Extensions::new();
let mut meta = Meta::new();
let mut meta = RequestMetaObject::new();
// avoid clone
// swap meta firstly, otherwise progress token will be lost
std::mem::swap(&mut meta, request.get_meta_mut());
@ -1226,7 +1226,7 @@ where
{
let service = shared_service.clone();
let mut extensions = Extensions::new();
let mut meta = Meta::new();
let mut meta = NotificationMetaObject::new();
// avoid clone
std::mem::swap(&mut extensions, notification.extensions_mut());
std::mem::swap(&mut meta, notification.get_meta_mut());

View file

@ -115,11 +115,11 @@ where
let mut context = NotificationContext {
peer: peer.clone(),
meta: Meta::default(),
meta: NotificationMetaObject::default(),
extensions: Extensions::default(),
};
if let Some(meta) = logging.extensions.get_mut::<Meta>() {
if let Some(meta) = logging.extensions.get_mut::<NotificationMetaObject>() {
std::mem::swap(&mut context.meta, meta);
}
std::mem::swap(&mut context.extensions, &mut logging.extensions);
@ -890,7 +890,7 @@ where
fn input_request_context<T>(&self, key: &str, request: &mut T) -> RequestContext<RoleClient>
where
T: GetMeta + GetExtensions,
T: GetMeta<Metadata = crate::model::RequestMetaObject> + GetExtensions,
{
let mut meta = Default::default();
let mut extensions = Default::default();

View file

@ -125,7 +125,7 @@ async fn test_elicitation_result_serialization() {
assert_eq!(deserialized.meta, None);
// Test protocol-level metadata round-trips as _meta.
let meta_result = ElicitResult::new(ElicitationAction::Accept).with_meta(Meta(object!({
let meta_result = ElicitResult::new(ElicitationAction::Accept).with_meta(MetaObject(object!({
"traceId": "elicitation-123"
})));
@ -139,7 +139,7 @@ async fn test_elicitation_result_serialization() {
let deserialized: ElicitResult = serde_json::from_value(expected).unwrap();
assert_eq!(
deserialized.meta,
Some(Meta(object!({ "traceId": "elicitation-123" })))
Some(MetaObject(object!({ "traceId": "elicitation-123" })))
);
}

View file

@ -1,12 +1,12 @@
use rmcp::model::{ContentBlock, EmbeddedResource, Meta, ResourceContents};
use rmcp::model::{ContentBlock, EmbeddedResource, MetaObject, ResourceContents};
use serde_json::json;
#[test]
fn serialize_embedded_text_resource_with_meta() {
let mut resource_content_meta = Meta::new();
let mut resource_content_meta = MetaObject::new();
resource_content_meta.insert("inner".to_string(), json!(2));
let mut resource_meta = Meta::new();
let mut resource_meta = MetaObject::new();
resource_meta.insert("top".to_string(), json!(1));
let content = ContentBlock::Resource(
@ -90,10 +90,10 @@ fn deserialize_embedded_text_resource_with_meta() {
#[test]
fn serialize_embedded_blob_resource_with_meta() {
let mut resource_content_meta = Meta::new();
let mut resource_content_meta = MetaObject::new();
resource_content_meta.insert("blob_inner".to_string(), json!(true));
let mut resource_meta = Meta::new();
let mut resource_meta = MetaObject::new();
resource_meta.insert("blob_top".to_string(), json!("t"));
let content = ContentBlock::Resource(

View file

@ -61,6 +61,60 @@ mod tests {
);
}
/// The three metadata definitions must expose the MCP 2026-07-28 draft
/// vocabulary: `MetaObject` is an open map, `RequestMetaObject` reserves
/// `progressToken` plus the SEP-2575 keys, and `NotificationMetaObject`
/// reserves `io.modelcontextprotocol/subscriptionId`. The keys the draft
/// marks as required stay optional because rmcp generates one schema
/// shared by every supported protocol version; draft-strict validation is
/// a runtime concern (`RequestMetaObject::missing_required_keys`).
#[test]
fn test_metadata_definitions_match_draft_schema() {
let settings = SchemaSettings::draft07();
let schema = settings
.into_generator()
.into_root_schema_for::<ClientJsonRpcMessage>();
let schema = serde_json::to_value(&schema).expect("Failed to serialize schema");
let definitions = &schema["definitions"];
assert_eq!(
definitions["MetaObject"],
serde_json::json!({
"description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.",
"type": "object",
"additionalProperties": true,
})
);
assert_eq!(
definitions["RequestMetaObject"],
serde_json::json!({
"description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
"type": "object",
"properties": {
"progressToken": { "$ref": "#/definitions/ProgressToken" },
"io.modelcontextprotocol/protocolVersion": { "type": "string" },
"io.modelcontextprotocol/clientInfo": { "$ref": "#/definitions/Implementation" },
"io.modelcontextprotocol/clientCapabilities": { "$ref": "#/definitions/ClientCapabilities" },
"io.modelcontextprotocol/logLevel": { "$ref": "#/definitions/LoggingLevel" },
},
"additionalProperties": true,
})
);
assert_eq!(
definitions["NotificationMetaObject"],
serde_json::json!({
"description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/subscriptionId": { "$ref": "#/definitions/NumberOrString" },
},
"additionalProperties": true,
})
);
}
#[test]
fn test_server_json_rpc_message_schema() {
let settings = SchemaSettings::draft07();

View file

@ -86,11 +86,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -128,11 +131,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"arguments": {
"description": "Arguments to pass to the tool (must match the tool's input schema)",
@ -187,11 +193,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -210,11 +219,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"reason": {
"type": [
@ -334,11 +346,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"argument": {
"$ref": "#/definitions/ArgumentInfo"
@ -474,11 +489,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"description": "The actual content of the message (text, image, audio, tool use, or tool result)",
@ -550,11 +568,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
@ -638,11 +659,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -727,11 +751,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"arguments": {
"type": [
@ -773,11 +800,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -797,11 +827,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -873,11 +906,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -952,11 +988,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"capabilities": {
"description": "The capabilities this client supports (sampling, roots, etc.)",
@ -1169,11 +1208,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"roots": {
"type": "array",
@ -1212,6 +1254,11 @@
"emergency"
]
},
"MetaObject": {
"description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.",
"type": "object",
"additionalProperties": true
},
"Notification": {
"type": "object",
"properties": {
@ -1257,6 +1304,16 @@
"params"
]
},
"NotificationMetaObject": {
"description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/subscriptionId": {
"$ref": "#/definitions/NumberOrString"
}
},
"additionalProperties": true
},
"NotificationNoParam": {
"type": "object",
"properties": {
@ -1294,11 +1351,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"cursor": {
"type": [
@ -1322,11 +1382,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"message": {
"description": "An optional message describing the current progress.",
@ -1397,11 +1460,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"inputResponses": {
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
@ -1641,6 +1707,28 @@
"params"
]
},
"RequestMetaObject": {
"description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/clientCapabilities": {
"$ref": "#/definitions/ClientCapabilities"
},
"io.modelcontextprotocol/clientInfo": {
"$ref": "#/definitions/Implementation"
},
"io.modelcontextprotocol/logLevel": {
"$ref": "#/definitions/LoggingLevel"
},
"io.modelcontextprotocol/protocolVersion": {
"type": "string"
},
"progressToken": {
"$ref": "#/definitions/ProgressToken"
}
},
"additionalProperties": true
},
"RequestNoParam": {
"type": "object",
"properties": {
@ -1763,11 +1851,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this resource.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this resource.",
@ -1841,11 +1932,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"mimeType": {
"type": [
@ -1869,11 +1963,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"blob": {
"type": "string"
@ -1925,11 +2022,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"name": {
"type": [
@ -2116,11 +2216,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"level": {
"description": "The desired logging level",
@ -2147,11 +2250,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"uri": {
"description": "The URI of the resource to subscribe to",
@ -2252,11 +2358,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
@ -2347,11 +2456,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -2378,11 +2490,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"type": "array",
@ -2412,11 +2527,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"id": {
"type": "string"
@ -2459,11 +2577,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"uri": {
"description": "The URI of the resource to unsubscribe from",

View file

@ -86,11 +86,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -128,11 +131,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"arguments": {
"description": "Arguments to pass to the tool (must match the tool's input schema)",
@ -187,11 +193,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -210,11 +219,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"reason": {
"type": [
@ -334,11 +346,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"argument": {
"$ref": "#/definitions/ArgumentInfo"
@ -474,11 +489,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"description": "The actual content of the message (text, image, audio, tool use, or tool result)",
@ -550,11 +568,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
@ -638,11 +659,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -727,11 +751,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"arguments": {
"type": [
@ -773,11 +800,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -797,11 +827,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"taskId": {
"type": "string"
@ -873,11 +906,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -952,11 +988,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"capabilities": {
"description": "The capabilities this client supports (sampling, roots, etc.)",
@ -1169,11 +1208,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"roots": {
"type": "array",
@ -1212,6 +1254,11 @@
"emergency"
]
},
"MetaObject": {
"description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.",
"type": "object",
"additionalProperties": true
},
"Notification": {
"type": "object",
"properties": {
@ -1257,6 +1304,16 @@
"params"
]
},
"NotificationMetaObject": {
"description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/subscriptionId": {
"$ref": "#/definitions/NumberOrString"
}
},
"additionalProperties": true
},
"NotificationNoParam": {
"type": "object",
"properties": {
@ -1294,11 +1351,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"cursor": {
"type": [
@ -1322,11 +1382,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"message": {
"description": "An optional message describing the current progress.",
@ -1397,11 +1460,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"inputResponses": {
"description": "Client responses to server-initiated input requests from a previous\n[`InputRequiredResult`].",
@ -1641,6 +1707,28 @@
"params"
]
},
"RequestMetaObject": {
"description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/clientCapabilities": {
"$ref": "#/definitions/ClientCapabilities"
},
"io.modelcontextprotocol/clientInfo": {
"$ref": "#/definitions/Implementation"
},
"io.modelcontextprotocol/logLevel": {
"$ref": "#/definitions/LoggingLevel"
},
"io.modelcontextprotocol/protocolVersion": {
"type": "string"
},
"progressToken": {
"$ref": "#/definitions/ProgressToken"
}
},
"additionalProperties": true
},
"RequestNoParam": {
"type": "object",
"properties": {
@ -1763,11 +1851,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this resource.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this resource.",
@ -1841,11 +1932,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"mimeType": {
"type": [
@ -1869,11 +1963,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"blob": {
"type": "string"
@ -1925,11 +2022,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"name": {
"type": [
@ -2116,11 +2216,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"level": {
"description": "The desired logging level",
@ -2147,11 +2250,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"uri": {
"description": "The URI of the resource to subscribe to",
@ -2252,11 +2358,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
@ -2347,11 +2456,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -2378,11 +2490,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"type": "array",
@ -2412,11 +2527,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"id": {
"type": "string"
@ -2459,11 +2577,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"uri": {
"description": "The URI of the resource to unsubscribe from",

View file

@ -76,11 +76,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -171,11 +174,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"description": "The content returned by the tool (text, images, etc.)",
@ -211,11 +217,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
@ -279,11 +288,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"reason": {
"type": [
@ -303,15 +315,89 @@
}
}
},
"CompleteResult": {
"ClientCapabilities": {
"title": "Builder",
"description": "```rust\n# use rmcp::model::ClientCapabilities;\nlet cap = ClientCapabilities::builder()\n .enable_experimental()\n .build();\n```",
"type": "object",
"properties": {
"_meta": {
"elicitation": {
"description": "Capability to handle elicitation requests from servers for interactive user input",
"anyOf": [
{
"$ref": "#/definitions/ElicitationCapability"
},
{
"type": "null"
}
]
},
"experimental": {
"type": [
"object",
"null"
],
"additionalProperties": true
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"extensions": {
"description": "Optional MCP extensions that the client supports (SEP-1724).\nKeys are extension identifiers (e.g., `\"io.modelcontextprotocol/ui\"`),\nvalues are per-extension settings objects. An empty object indicates\nsupport with no settings.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"roots": {
"description": "Capability for filesystem roots (deprecated by SEP-2577).",
"anyOf": [
{
"$ref": "#/definitions/RootsCapabilities"
},
{
"type": "null"
}
]
},
"sampling": {
"description": "Capability for LLM sampling requests (SEP-1577, deprecated by SEP-2577).",
"anyOf": [
{
"$ref": "#/definitions/SamplingCapability"
},
{
"type": "null"
}
]
},
"tasks": {
"anyOf": [
{
"$ref": "#/definitions/TasksCapability"
},
{
"type": "null"
}
]
}
}
},
"CompleteResult": {
"type": "object",
"properties": {
"_meta": {
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"completion": {
"$ref": "#/definitions/CompletionInfo"
@ -495,11 +581,14 @@
"properties": {
"_meta": {
"description": "Protocol-level metadata for this request (SEP-1319)",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"includeContext": {
"description": "How much context to include from MCP servers",
@ -608,11 +697,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"task": {
"$ref": "#/definitions/Task"
@ -658,11 +750,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"message": {
"type": "string"
@ -685,11 +780,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"elicitationId": {
"type": "string"
@ -716,11 +814,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/RequestMetaObject"
},
{
"type": "null"
}
]
},
"message": {
"type": "string"
@ -742,11 +843,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this result.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"action": {
"description": "The user's decision on how to handle the elicitation request",
@ -784,6 +888,34 @@
}
]
},
"ElicitationCapability": {
"description": "Elicitation allows servers to request interactive input from users during tool execution.\nThis capability indicates that a client can handle elicitation requests and present\nappropriate UI to users for collecting the requested information.",
"type": "object",
"properties": {
"form": {
"description": "Whether client supports form-based elicitation.",
"anyOf": [
{
"$ref": "#/definitions/FormElicitationCapability"
},
{
"type": "null"
}
]
},
"url": {
"description": "Whether client supports URL-based elicitation.",
"anyOf": [
{
"$ref": "#/definitions/UrlElicitationCapability"
},
{
"type": "null"
}
]
}
}
},
"ElicitationCreateRequestMethod": {
"type": "string",
"format": "const",
@ -856,11 +988,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -935,15 +1070,31 @@
"message"
]
},
"FormElicitationCapability": {
"description": "Capability for handling elicitation requests from servers.\nElicitation allows servers to request interactive input from users during tool execution.\nThis capability indicates that a client can handle elicitation requests and present\nappropriate UI to users for collecting the requested information.\n\nCapability for form mode elicitation.",
"type": "object",
"properties": {
"schemaValidation": {
"description": "Whether the client supports JSON Schema validation for elicitation responses.\nWhen true, the client will validate user input against the requested_schema\nbefore sending the response back to the server.",
"type": [
"boolean",
"null"
]
}
}
},
"GetPromptResult": {
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"description": {
"type": [
@ -979,11 +1130,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
@ -1100,11 +1254,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -1178,11 +1335,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"capabilities": {
"description": "The capabilities this server provides (tools, resources, prompts, etc.)",
@ -1257,11 +1417,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"inputRequests": {
"description": "Server-initiated requests that the client must fulfill before retrying.",
@ -1524,11 +1687,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"cacheScope": {
"description": "Scope describing who may cache this result (SEP-2549).\nRequired by spec version 2026-07-28, but optional here to maintain compatibility\nwith older spec versions.",
@ -1580,11 +1746,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"cacheScope": {
"description": "Scope describing who may cache this result (SEP-2549).\nRequired by spec version 2026-07-28, but optional here to maintain compatibility\nwith older spec versions.",
@ -1636,11 +1805,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"cacheScope": {
"description": "Scope describing who may cache this result (SEP-2549).\nRequired by spec version 2026-07-28, but optional here to maintain compatibility\nwith older spec versions.",
@ -1697,11 +1869,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"nextCursor": {
"type": [
@ -1724,11 +1899,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"cacheScope": {
"description": "Scope describing who may cache this result (SEP-2549).\nRequired by spec version 2026-07-28, but optional here to maintain compatibility\nwith older spec versions.",
@ -1801,11 +1979,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"data": {
"description": "The actual log data"
@ -1832,6 +2013,11 @@
"data"
]
},
"MetaObject": {
"description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.",
"type": "object",
"additionalProperties": true
},
"ModelHint": {
"description": "A hint suggesting a preferred model name or family.\n\nModel hints are advisory suggestions that help clients choose appropriate\nmodels. They can be specific model names or general families like \"claude\" or \"gpt\".",
"type": "object",
@ -1973,6 +2159,16 @@
"params"
]
},
"NotificationMetaObject": {
"description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/subscriptionId": {
"$ref": "#/definitions/NumberOrString"
}
},
"additionalProperties": true
},
"NotificationNoParam": {
"type": "object",
"properties": {
@ -2140,11 +2336,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"message": {
"description": "An optional message describing the current progress.",
@ -2188,11 +2387,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"arguments": {
"type": [
@ -2303,11 +2505,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"cacheScope": {
"description": "Scope describing who may cache this result (SEP-2549).\nRequired by spec version 2026-07-28, but optional here to maintain compatibility\nwith older spec versions.",
@ -2382,6 +2587,28 @@
"params"
]
},
"RequestMetaObject": {
"description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
"type": "object",
"properties": {
"io.modelcontextprotocol/clientCapabilities": {
"$ref": "#/definitions/ClientCapabilities"
},
"io.modelcontextprotocol/clientInfo": {
"$ref": "#/definitions/Implementation"
},
"io.modelcontextprotocol/logLevel": {
"$ref": "#/definitions/LoggingLevel"
},
"io.modelcontextprotocol/protocolVersion": {
"type": "string"
},
"progressToken": {
"$ref": "#/definitions/ProgressToken"
}
},
"additionalProperties": true
},
"RequestNoParam": {
"type": "object",
"properties": {
@ -2410,11 +2637,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this resource.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this resource.",
@ -2488,11 +2718,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"mimeType": {
"type": [
@ -2516,11 +2749,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"blob": {
"type": "string"
@ -2553,11 +2789,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this resource template.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this template.",
@ -2625,11 +2864,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"uri": {
"description": "The URI of the resource that was updated",
@ -2676,6 +2918,40 @@
}
]
},
"RootsCapabilities": {
"description": "Roots capability. Deprecated by SEP-2577; remains functional and will be\nremoved in a future release.\nSee <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>.",
"type": "object",
"properties": {
"listChanged": {
"type": [
"boolean",
"null"
]
}
}
},
"SamplingCapability": {
"description": "Sampling capability with optional sub-capabilities (SEP-1577).\n\nDeprecated by SEP-2577; remains functional and will be removed in a future\nrelease.\nSee <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>.",
"type": "object",
"properties": {
"context": {
"description": "Support for `includeContext` (soft-deprecated)",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"tools": {
"description": "Support for `tools` and `toolChoice` parameters",
"type": [
"object",
"null"
],
"additionalProperties": true
}
}
},
"SamplingContent": {
"description": "Single or array content wrapper (SEP-1577).",
"anyOf": [
@ -2695,11 +2971,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"description": "The actual content of the message (text, image, audio, tool use, or tool result)",
@ -3226,11 +3505,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/NotificationMetaObject"
},
{
"type": "null"
}
]
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
@ -3341,11 +3623,14 @@
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block.",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional annotations describing how the client should use this content.",
@ -3478,11 +3763,14 @@
"properties": {
"_meta": {
"description": "Optional additional metadata for this tool",
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"annotations": {
"description": "Optional additional tool information.",
@ -3658,11 +3946,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"content": {
"type": "array",
@ -3692,11 +3983,14 @@
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
"anyOf": [
{
"$ref": "#/definitions/MetaObject"
},
{
"type": "null"
}
]
},
"id": {
"type": "string"
@ -3848,6 +4142,10 @@
"type",
"enum"
]
},
"UrlElicitationCapability": {
"description": "Capability for URL mode elicitation.",
"type": "object"
}
}
}

Some files were not shown because too many files have changed in this diff Show more