Skip to content

Commit af4730c

Browse files
fix: preserve discovery server-info compatibility
1 parent a08dd9a commit af4730c

8 files changed

Lines changed: 64 additions & 224 deletions

File tree

crates/rmcp/src/model.rs

Lines changed: 22 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,42 +1089,6 @@ impl InitializeResult {
10891089
pub type ServerInfo = InitializeResult;
10901090
pub type ClientInfo = InitializeRequestParams;
10911091

1092-
/// Information learned about a server by a client.
1093-
///
1094-
/// Legacy initialization requires [`server_info`](Self::server_info), while
1095-
/// the modern discovery lifecycle carries it as optional, self-reported result
1096-
/// metadata. The remaining fields are available in both lifecycle modes.
1097-
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1098-
#[serde(rename_all = "camelCase")]
1099-
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1100-
#[non_exhaustive]
1101-
pub struct ServerPeerInfo {
1102-
/// The negotiated protocol version.
1103-
pub protocol_version: ProtocolVersion,
1104-
/// The capabilities advertised by the server.
1105-
pub capabilities: ServerCapabilities,
1106-
/// Optional, self-reported server implementation identity.
1107-
pub server_info: Option<Implementation>,
1108-
/// Optional human-readable instructions about using the server.
1109-
#[serde(skip_serializing_if = "Option::is_none")]
1110-
pub instructions: Option<String>,
1111-
/// Protocol-level response metadata.
1112-
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1113-
pub meta: Option<MetaObject>,
1114-
}
1115-
1116-
impl From<ServerInfo> for ServerPeerInfo {
1117-
fn from(info: ServerInfo) -> Self {
1118-
Self {
1119-
protocol_version: info.protocol_version,
1120-
capabilities: info.capabilities,
1121-
server_info: Some(info.server_info),
1122-
instructions: info.instructions,
1123-
meta: info.meta,
1124-
}
1125-
}
1126-
}
1127-
11281092
const_string!(DiscoverRequestMethod = "server/discover");
11291093

11301094
/// Parameters for [`DiscoverRequest`].
@@ -1167,6 +1131,8 @@ pub struct DiscoverResult {
11671131
pub supported_versions: Vec<ProtocolVersion>,
11681132
/// Capabilities provided by this server.
11691133
pub capabilities: ServerCapabilities,
1134+
/// Information about the server implementation.
1135+
pub server_info: Implementation,
11701136
/// Optional guidance for using the server.
11711137
#[serde(skip_serializing_if = "Option::is_none")]
11721138
pub instructions: Option<String>,
@@ -1180,44 +1146,44 @@ pub struct DiscoverResult {
11801146
}
11811147

11821148
impl<'de> Deserialize<'de> for DiscoverResult {
1183-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1149+
fn deserialize<__D>(deserializer: __D) -> Result<Self, __D::Error>
11841150
where
1185-
D: serde::Deserializer<'de>,
1151+
__D: serde::Deserializer<'de>,
11861152
{
11871153
#[derive(Deserialize)]
11881154
#[serde(rename_all = "camelCase")]
11891155
struct Helper {
11901156
result_type: ResultType,
11911157
supported_versions: Vec<ProtocolVersion>,
11921158
capabilities: ServerCapabilities,
1193-
server_info: Option<serde_json::Value>,
1159+
server_info: Option<Implementation>,
11941160
instructions: Option<String>,
11951161
ttl_ms: u64,
11961162
cache_scope: CacheScope,
11971163
#[serde(rename = "_meta")]
11981164
meta: Option<MetaObject>,
11991165
}
12001166

1201-
let mut helper = Helper::deserialize(deserializer)?;
1202-
let has_canonical_server_info = helper
1203-
.meta
1204-
.as_ref()
1205-
.is_some_and(|metadata| metadata.0.contains_key(MetaObject::META_KEY_SERVER_INFO));
1206-
if !has_canonical_server_info
1207-
&& let Some(server_info) = helper
1208-
.server_info
1209-
.and_then(|value| serde_json::from_value::<Implementation>(value).ok())
1210-
{
1211-
helper
1212-
.meta
1213-
.get_or_insert_with(MetaObject::new)
1214-
.set_server_info(server_info);
1215-
}
1167+
let helper = Helper::deserialize(deserializer)?;
1168+
let server_info = match helper.server_info {
1169+
Some(server_info) => server_info,
1170+
None => {
1171+
let metadata_server_info = helper
1172+
.meta
1173+
.as_ref()
1174+
.and_then(|metadata| metadata.0.get("io.modelcontextprotocol/serverInfo"))
1175+
.ok_or_else(|| serde::de::Error::missing_field("serverInfo"))?;
1176+
1177+
serde_json::from_value(metadata_server_info.clone())
1178+
.map_err(serde::de::Error::custom)?
1179+
}
1180+
};
12161181

12171182
Ok(Self {
12181183
result_type: helper.result_type,
12191184
supported_versions: helper.supported_versions,
12201185
capabilities: helper.capabilities,
1186+
server_info,
12211187
instructions: helper.instructions,
12221188
ttl_ms: helper.ttl_ms,
12231189
cache_scope: helper.cache_scope,
@@ -1232,24 +1198,12 @@ impl DiscoverResult {
12321198
supported_versions: Vec<ProtocolVersion>,
12331199
capabilities: ServerCapabilities,
12341200
server_info: Implementation,
1235-
) -> Self {
1236-
Self::new_without_server_info(supported_versions, capabilities)
1237-
.with_server_info(server_info)
1238-
}
1239-
1240-
/// Create a non-cacheable private discovery result without a server identity.
1241-
///
1242-
/// Server identity is optional display-only metadata. Servers should normally
1243-
/// use [`DiscoverResult::new`], but this constructor supports peers that do
1244-
/// not advertise an implementation name and version.
1245-
pub fn new_without_server_info(
1246-
supported_versions: Vec<ProtocolVersion>,
1247-
capabilities: ServerCapabilities,
12481201
) -> Self {
12491202
Self {
12501203
result_type: ResultType::COMPLETE,
12511204
supported_versions,
12521205
capabilities,
1206+
server_info,
12531207
instructions: None,
12541208
ttl_ms: 0,
12551209
cache_scope: CacheScope::Private,
@@ -1271,30 +1225,10 @@ impl DiscoverResult {
12711225
} = server_info;
12721226
let mut result = Self::new(supported_versions, capabilities, server_info);
12731227
result.instructions = instructions;
1274-
if let Some(meta) = meta {
1275-
result.meta.get_or_insert_with(MetaObject::new).extend(meta);
1276-
}
1228+
result.meta = meta;
12771229
result
12781230
}
12791231

1280-
/// Return the optional self-reported server identity from result metadata.
1281-
pub fn server_info(&self) -> Option<Implementation> {
1282-
self.meta.as_ref().and_then(MetaObject::server_info)
1283-
}
1284-
1285-
/// Set the self-reported server identity in canonical result metadata.
1286-
pub fn set_server_info(&mut self, server_info: Implementation) {
1287-
self.meta
1288-
.get_or_insert_with(MetaObject::new)
1289-
.set_server_info(server_info);
1290-
}
1291-
1292-
/// Set the self-reported server identity in canonical result metadata.
1293-
pub fn with_server_info(mut self, server_info: Implementation) -> Self {
1294-
self.set_server_info(server_info);
1295-
self
1296-
}
1297-
12981232
/// Set the cache lifetime hint in milliseconds.
12991233
pub fn with_ttl_ms(mut self, ttl_ms: u64) -> Self {
13001234
self.ttl_ms = ttl_ms;

crates/rmcp/src/model/meta.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,6 @@ pub struct MetaObject(pub JsonObject);
253253
pub use self::MetaObject as Meta;
254254

255255
impl MetaObject {
256-
/// Reserved result metadata key for the server implementation identity.
257-
pub const META_KEY_SERVER_INFO: &'static str = "io.modelcontextprotocol/serverInfo";
258256
/// Reserved `_meta` key for the W3C Trace Context `traceparent` value (SEP-414).
259257
const TRACEPARENT_FIELD: &str = "traceparent";
260258
/// Reserved `_meta` key for the W3C Trace Context `tracestate` value (SEP-414).
@@ -324,19 +322,6 @@ impl MetaObject {
324322
self.0.extend(other.0);
325323
}
326324

327-
/// Get the self-reported server implementation identity, if present and valid.
328-
///
329-
/// This value is intended for display, logging, and debugging. Callers must
330-
/// not use it for behavioral or security decisions.
331-
pub fn server_info(&self) -> Option<Implementation> {
332-
self.decode_value(Self::META_KEY_SERVER_INFO)
333-
}
334-
335-
/// Set the self-reported server implementation identity.
336-
pub fn set_server_info(&mut self, server_info: Implementation) {
337-
self.insert_serialized(Self::META_KEY_SERVER_INFO, server_info);
338-
}
339-
340325
fn decode_value<T>(&self, key: &str) -> Option<T>
341326
where
342327
T: for<'de> Deserialize<'de>,

crates/rmcp/src/service/client.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
NumberOrString, PaginatedRequestParams, ProgressNotification, ProgressNotificationParam,
2626
ProtocolVersion, ReadResourceRequest, ReadResourceRequestParams, ReadResourceResponse,
2727
ReadResourceResult, Reference, RequestId, RequestMetaObject, RootsListChangedNotification,
28-
ServerJsonRpcMessage, ServerNotification, ServerPeerInfo, ServerRequest, ServerResult,
28+
ServerInfo, ServerJsonRpcMessage, ServerNotification, ServerRequest, ServerResult,
2929
SetLevelRequest, SetLevelRequestParams, SubscribeRequest, SubscribeRequestParams,
3030
SubscriptionFilter, SubscriptionsListenRequest, SubscriptionsListenRequestParams,
3131
SubscriptionsListenResult, UnsubscribeRequest, UnsubscribeRequestParams, UpdateTaskParams,
@@ -188,7 +188,7 @@ impl ServiceRole for RoleClient {
188188
type PeerResp = ServerResult;
189189
type PeerNot = ServerNotification;
190190
type Info = ClientInfo;
191-
type PeerInfo = ServerPeerInfo;
191+
type PeerInfo = ServerInfo;
192192
type InitializeError = ClientInitializeError;
193193
const IS_CLIENT: bool = true;
194194

@@ -751,7 +751,7 @@ where
751751
let ServerResult::InitializeResult(initialize_result) = response else {
752752
return Err(ClientInitializeError::ExpectedInitResult(Some(response)));
753753
};
754-
peer.set_peer_info(initialize_result.into());
754+
peer.set_peer_info(initialize_result);
755755

756756
// send notification
757757
let notification = ClientJsonRpcMessage::notification(
@@ -821,11 +821,10 @@ where
821821
server_supported: result.supported_versions,
822822
});
823823
};
824-
let server_info = result.server_info();
825-
peer.set_peer_info(ServerPeerInfo {
824+
peer.set_peer_info(ServerInfo {
826825
protocol_version: selected.clone(),
827826
capabilities: result.capabilities,
828-
server_info,
827+
server_info: result.server_info,
829828
instructions: result.instructions,
830829
meta: result.meta,
831830
});

crates/rmcp/tests/test_client_lifecycle_modes.rs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -60,51 +60,6 @@ async fn discover_startup_accepts_stringified_numeric_response_id() {
6060
server_task.await.expect("server task");
6161
}
6262

63-
#[tokio::test]
64-
async fn discover_startup_accepts_anonymous_server() {
65-
let (server_transport, client_transport) = tokio::io::duplex(4096);
66-
let mut server = IntoTransport::<rmcp::RoleServer, _, _>::into_transport(server_transport);
67-
let server_task = tokio::spawn(async move {
68-
let ClientJsonRpcMessage::Request(request) =
69-
server.receive().await.expect("expected discover request")
70-
else {
71-
panic!("expected discover request");
72-
};
73-
let result: DiscoverResult = serde_json::from_value(serde_json::json!({
74-
"resultType": "complete",
75-
"supportedVersions": ["2026-07-28"],
76-
"capabilities": {},
77-
"ttlMs": 0,
78-
"cacheScope": "private"
79-
}))
80-
.expect("anonymous discovery result");
81-
server
82-
.send(ServerJsonRpcMessage::response(
83-
ServerResult::DiscoverResult(result),
84-
request.id,
85-
))
86-
.await
87-
.expect("send discover response");
88-
});
89-
90-
let client = DiscoverClient
91-
.serve_with_lifecycle(
92-
client_transport,
93-
ClientLifecycleMode::Discover {
94-
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
95-
},
96-
)
97-
.await
98-
.expect("anonymous server should remain discoverable");
99-
let peer = client
100-
.peer_info()
101-
.expect("discovery should store peer state");
102-
assert_eq!(peer.protocol_version, ProtocolVersion::V_2026_07_28);
103-
assert_eq!(peer.server_info, None);
104-
client.cancel().await.expect("cancel client");
105-
server_task.await.expect("server task");
106-
}
107-
10863
#[tokio::test]
10964
async fn high_level_server_accepts_discover_startup_without_initialize() {
11065
let (server_transport, client_transport) = tokio::io::duplex(4096);

crates/rmcp/tests/test_mrtr_behavior.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ where
316316
let client = serve_directly::<RoleClient, _, _, _, _>(
317317
MrtrClient,
318318
client_transport,
319-
Some(client_peer_info.into()),
319+
Some(client_peer_info),
320320
);
321321

322322
let result = body(client).await;
@@ -580,7 +580,7 @@ async fn request_state_codec_seals_and_verifies_through_the_loop() -> anyhow::Re
580580
let client = serve_directly::<RoleClient, _, _, _, _>(
581581
MrtrClient,
582582
client_transport,
583-
Some(server_info(ProtocolVersion::V_2026_07_28).into()),
583+
Some(server_info(ProtocolVersion::V_2026_07_28)),
584584
);
585585

586586
let result = client

0 commit comments

Comments
 (0)