fix!: remove server_info from DiscoverResult (#1065)
Fixes https://github.com/modelcontextprotocol/rust-sdk/issues/1064 Replaces https://github.com/modelcontextprotocol/rust-sdk/pull/1044 This removes the `server_info` field which is not part of the spec: https://modelcontextprotocol.io/specification/draft/schema#discoverresult.
This commit is contained in:
parent
e54c2220af
commit
9a41811a66
19 changed files with 373 additions and 155 deletions
2
.github/workflows/conformance.yml
vendored
2
.github/workflows/conformance.yml
vendored
|
|
@ -12,7 +12,7 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CONFORMANCE_VERSION: "0.2.0-alpha.9"
|
CONFORMANCE_VERSION: "0.2.0-alpha.10"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
server:
|
server:
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,6 @@ server: []
|
||||||
client:
|
client:
|
||||||
# Informational OAuth extension scenarios.
|
# Informational OAuth extension scenarios.
|
||||||
- auth/enterprise-managed-authorization
|
- auth/enterprise-managed-authorization
|
||||||
|
- auth/dpop
|
||||||
|
- auth/dpop-nonce
|
||||||
|
- auth/wif-jwt-bearer
|
||||||
|
|
|
||||||
|
|
@ -1085,6 +1085,66 @@ impl InitializeResult {
|
||||||
pub type ServerInfo = InitializeResult;
|
pub type ServerInfo = InitializeResult;
|
||||||
pub type ClientInfo = InitializeRequestParams;
|
pub type ClientInfo = InitializeRequestParams;
|
||||||
|
|
||||||
|
/// Information negotiated about a server peer.
|
||||||
|
///
|
||||||
|
/// Unlike [`InitializeResult`], the server implementation identity is optional
|
||||||
|
/// because discovery responses are not required to provide it.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct ServerPeerInfo {
|
||||||
|
/// The negotiated MCP protocol version.
|
||||||
|
pub protocol_version: ProtocolVersion,
|
||||||
|
/// The capabilities this server provides.
|
||||||
|
pub capabilities: ServerCapabilities,
|
||||||
|
/// Information about the server implementation, when provided.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server_info: Option<Implementation>,
|
||||||
|
/// Optional human-readable instructions about using this server.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub instructions: Option<String>,
|
||||||
|
/// Protocol-level response metadata.
|
||||||
|
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub meta: Option<MetaObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerPeerInfo {
|
||||||
|
/// Create peer information without a server implementation identity.
|
||||||
|
pub fn new(protocol_version: ProtocolVersion, capabilities: ServerCapabilities) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_version,
|
||||||
|
capabilities,
|
||||||
|
server_info: None,
|
||||||
|
instructions: None,
|
||||||
|
meta: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the server implementation identity.
|
||||||
|
pub fn with_server_info(mut self, server_info: Implementation) -> Self {
|
||||||
|
self.server_info = Some(server_info);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set instructions supplied by the server.
|
||||||
|
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
|
||||||
|
self.instructions = Some(instructions.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InitializeResult> for ServerPeerInfo {
|
||||||
|
fn from(result: InitializeResult) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_version: result.protocol_version,
|
||||||
|
capabilities: result.capabilities,
|
||||||
|
server_info: Some(result.server_info),
|
||||||
|
instructions: result.instructions,
|
||||||
|
meta: result.meta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const_string!(DiscoverRequestMethod = "server/discover");
|
const_string!(DiscoverRequestMethod = "server/discover");
|
||||||
|
|
||||||
/// Parameters for [`DiscoverRequest`].
|
/// Parameters for [`DiscoverRequest`].
|
||||||
|
|
@ -1116,9 +1176,9 @@ impl schemars::JsonSchema for DiscoverRequestParams {
|
||||||
pub type DiscoverRequest = Request<DiscoverRequestMethod, DiscoverRequestParams>;
|
pub type DiscoverRequest = Request<DiscoverRequestMethod, DiscoverRequestParams>;
|
||||||
|
|
||||||
/// The server's response to a [`DiscoverRequest`].
|
/// The server's response to a [`DiscoverRequest`].
|
||||||
#[derive(Debug, Serialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct DiscoverResult {
|
pub struct DiscoverResult {
|
||||||
/// Identifies how the result should be parsed.
|
/// Identifies how the result should be parsed.
|
||||||
|
|
@ -1127,8 +1187,6 @@ pub struct DiscoverResult {
|
||||||
pub supported_versions: Vec<ProtocolVersion>,
|
pub supported_versions: Vec<ProtocolVersion>,
|
||||||
/// Capabilities provided by this server.
|
/// Capabilities provided by this server.
|
||||||
pub capabilities: ServerCapabilities,
|
pub capabilities: ServerCapabilities,
|
||||||
/// Information about the server implementation.
|
|
||||||
pub server_info: Implementation,
|
|
||||||
/// Optional guidance for using the server.
|
/// Optional guidance for using the server.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub instructions: Option<String>,
|
pub instructions: Option<String>,
|
||||||
|
|
@ -1141,65 +1199,15 @@ pub struct DiscoverResult {
|
||||||
pub meta: Option<MetaObject>,
|
pub meta: Option<MetaObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for DiscoverResult {
|
|
||||||
fn deserialize<__D>(deserializer: __D) -> Result<Self, __D::Error>
|
|
||||||
where
|
|
||||||
__D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Helper {
|
|
||||||
result_type: ResultType,
|
|
||||||
supported_versions: Vec<ProtocolVersion>,
|
|
||||||
capabilities: ServerCapabilities,
|
|
||||||
server_info: Option<Implementation>,
|
|
||||||
instructions: Option<String>,
|
|
||||||
ttl_ms: u64,
|
|
||||||
cache_scope: CacheScope,
|
|
||||||
#[serde(rename = "_meta")]
|
|
||||||
meta: Option<MetaObject>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let helper = Helper::deserialize(deserializer)?;
|
|
||||||
let server_info = match helper.server_info {
|
|
||||||
Some(server_info) => server_info,
|
|
||||||
None => {
|
|
||||||
let metadata_server_info = helper
|
|
||||||
.meta
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|metadata| metadata.0.get("io.modelcontextprotocol/serverInfo"))
|
|
||||||
.ok_or_else(|| serde::de::Error::missing_field("serverInfo"))?;
|
|
||||||
|
|
||||||
serde_json::from_value(metadata_server_info.clone())
|
|
||||||
.map_err(serde::de::Error::custom)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
result_type: helper.result_type,
|
|
||||||
supported_versions: helper.supported_versions,
|
|
||||||
capabilities: helper.capabilities,
|
|
||||||
server_info,
|
|
||||||
instructions: helper.instructions,
|
|
||||||
ttl_ms: helper.ttl_ms,
|
|
||||||
cache_scope: helper.cache_scope,
|
|
||||||
meta: helper.meta,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiscoverResult {
|
impl DiscoverResult {
|
||||||
|
const SERVER_INFO_META_KEY: &str = "io.modelcontextprotocol/serverInfo";
|
||||||
|
|
||||||
/// Create a non-cacheable private discovery result.
|
/// Create a non-cacheable private discovery result.
|
||||||
pub fn new(
|
pub fn new(supported_versions: Vec<ProtocolVersion>, capabilities: ServerCapabilities) -> Self {
|
||||||
supported_versions: Vec<ProtocolVersion>,
|
|
||||||
capabilities: ServerCapabilities,
|
|
||||||
server_info: Implementation,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
result_type: ResultType::COMPLETE,
|
result_type: ResultType::COMPLETE,
|
||||||
supported_versions,
|
supported_versions,
|
||||||
capabilities,
|
capabilities,
|
||||||
server_info,
|
|
||||||
instructions: None,
|
instructions: None,
|
||||||
ttl_ms: 0,
|
ttl_ms: 0,
|
||||||
cache_scope: CacheScope::Private,
|
cache_scope: CacheScope::Private,
|
||||||
|
|
@ -1207,6 +1215,31 @@ impl DiscoverResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the server implementation information stored in result metadata.
|
||||||
|
pub fn server_info(&self) -> Option<Implementation> {
|
||||||
|
self.meta
|
||||||
|
.as_ref()?
|
||||||
|
.0
|
||||||
|
.get(Self::SERVER_INFO_META_KEY)
|
||||||
|
.and_then(|value| serde_json::from_value(value.clone()).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store server implementation information in result metadata.
|
||||||
|
pub fn set_server_info(&mut self, server_info: Implementation) {
|
||||||
|
let server_info =
|
||||||
|
serde_json::to_value(server_info).expect("Implementation serialization cannot fail");
|
||||||
|
self.meta
|
||||||
|
.get_or_insert_default()
|
||||||
|
.0
|
||||||
|
.insert(Self::SERVER_INFO_META_KEY.to_owned(), server_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store server implementation information in result metadata.
|
||||||
|
pub fn with_server_info(mut self, server_info: Implementation) -> Self {
|
||||||
|
self.set_server_info(server_info);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a discovery result from the server's initialization information.
|
/// Create a discovery result from the server's initialization information.
|
||||||
pub fn from_server_info(
|
pub fn from_server_info(
|
||||||
supported_versions: Vec<ProtocolVersion>,
|
supported_versions: Vec<ProtocolVersion>,
|
||||||
|
|
@ -1219,9 +1252,16 @@ impl DiscoverResult {
|
||||||
meta,
|
meta,
|
||||||
..
|
..
|
||||||
} = server_info;
|
} = server_info;
|
||||||
let mut result = Self::new(supported_versions, capabilities, server_info);
|
let mut result = Self {
|
||||||
result.instructions = instructions;
|
result_type: ResultType::COMPLETE,
|
||||||
result.meta = meta;
|
supported_versions,
|
||||||
|
capabilities,
|
||||||
|
instructions,
|
||||||
|
ttl_ms: 0,
|
||||||
|
cache_scope: CacheScope::Private,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
result.set_server_info(server_info);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1238,6 +1278,20 @@ impl DiscoverResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ServerPeerInfo {
|
||||||
|
/// Create peer information from a discovery result and the selected version.
|
||||||
|
pub fn from_discover_result(protocol_version: ProtocolVersion, result: DiscoverResult) -> Self {
|
||||||
|
let server_info = result.server_info();
|
||||||
|
Self {
|
||||||
|
protocol_version,
|
||||||
|
capabilities: result.capabilities,
|
||||||
|
server_info,
|
||||||
|
instructions: result.instructions,
|
||||||
|
meta: result.meta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::derivable_impls)]
|
#[allow(clippy::derivable_impls)]
|
||||||
impl Default for ServerInfo {
|
impl Default for ServerInfo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
|
|
||||||
|
|
@ -374,9 +374,10 @@ impl schemars::JsonSchema for MetaObject {
|
||||||
/// - `io.modelcontextprotocol/clientCapabilities` (SEP-2575)
|
/// - `io.modelcontextprotocol/clientCapabilities` (SEP-2575)
|
||||||
/// - `io.modelcontextprotocol/logLevel` (SEP-2575)
|
/// - `io.modelcontextprotocol/logLevel` (SEP-2575)
|
||||||
///
|
///
|
||||||
/// The 2026-07-28 schema defines required per-request metadata; earlier
|
/// The 2026-07-28 draft schema requires the protocol-version and
|
||||||
/// protocol versions do not know these keys. All keys therefore stay optional
|
/// client-capabilities keys; client-info is optional. Earlier protocol versions
|
||||||
/// at runtime and in the generated (version-shared) JSON schema — use
|
/// 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
|
/// [`RequestMetaObject::missing_required_keys`] to validate a request against
|
||||||
/// the negotiated protocol version.
|
/// the negotiated protocol version.
|
||||||
///
|
///
|
||||||
|
|
@ -395,10 +396,9 @@ impl RequestMetaObject {
|
||||||
const META_KEY_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
|
const META_KEY_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
|
||||||
const META_KEY_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
|
const META_KEY_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
|
||||||
|
|
||||||
/// Request `_meta` keys validated for the 2026-07-28 protocol.
|
/// Request `_meta` keys the 2026-07-28 draft schema marks as required.
|
||||||
pub const DRAFT_REQUIRED_KEYS: [&str; 3] = [
|
pub const DRAFT_REQUIRED_KEYS: [&str; 2] = [
|
||||||
Self::META_KEY_PROTOCOL_VERSION,
|
Self::META_KEY_PROTOCOL_VERSION,
|
||||||
Self::META_KEY_CLIENT_INFO,
|
|
||||||
Self::META_KEY_CLIENT_CAPABILITIES,
|
Self::META_KEY_CLIENT_CAPABILITIES,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -523,9 +523,6 @@ impl RequestMetaObject {
|
||||||
if self.protocol_version().is_none() {
|
if self.protocol_version().is_none() {
|
||||||
missing.push(Self::META_KEY_PROTOCOL_VERSION);
|
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() {
|
if self.client_capabilities().is_none() {
|
||||||
missing.push(Self::META_KEY_CLIENT_CAPABILITIES);
|
missing.push(Self::META_KEY_CLIENT_CAPABILITIES);
|
||||||
}
|
}
|
||||||
|
|
@ -838,7 +835,6 @@ mod tests {
|
||||||
fn treats_malformed_values_as_missing() {
|
fn treats_malformed_values_as_missing() {
|
||||||
let meta: RequestMetaObject = serde_json::from_value(serde_json::json!({
|
let meta: RequestMetaObject = serde_json::from_value(serde_json::json!({
|
||||||
"io.modelcontextprotocol/protocolVersion": 123,
|
"io.modelcontextprotocol/protocolVersion": 123,
|
||||||
"io.modelcontextprotocol/clientInfo": "not an implementation",
|
|
||||||
"io.modelcontextprotocol/clientCapabilities": null,
|
"io.modelcontextprotocol/clientCapabilities": null,
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::{
|
||||||
NumberOrString, PaginatedRequestParams, ProgressNotification, ProgressNotificationParam,
|
NumberOrString, PaginatedRequestParams, ProgressNotification, ProgressNotificationParam,
|
||||||
ProtocolVersion, ReadResourceRequest, ReadResourceRequestParams, ReadResourceResponse,
|
ProtocolVersion, ReadResourceRequest, ReadResourceRequestParams, ReadResourceResponse,
|
||||||
ReadResourceResult, Reference, RequestId, RequestMetaObject, RootsListChangedNotification,
|
ReadResourceResult, Reference, RequestId, RequestMetaObject, RootsListChangedNotification,
|
||||||
ServerInfo, ServerJsonRpcMessage, ServerNotification, ServerRequest, ServerResult,
|
ServerJsonRpcMessage, ServerNotification, ServerPeerInfo, ServerRequest, ServerResult,
|
||||||
SetLevelRequest, SetLevelRequestParams, SubscribeRequest, SubscribeRequestParams,
|
SetLevelRequest, SetLevelRequestParams, SubscribeRequest, SubscribeRequestParams,
|
||||||
SubscriptionFilter, SubscriptionsListenRequest, SubscriptionsListenRequestParams,
|
SubscriptionFilter, SubscriptionsListenRequest, SubscriptionsListenRequestParams,
|
||||||
SubscriptionsListenResult, UnsubscribeRequest, UnsubscribeRequestParams, UpdateTaskParams,
|
SubscriptionsListenResult, UnsubscribeRequest, UnsubscribeRequestParams, UpdateTaskParams,
|
||||||
|
|
@ -213,7 +213,7 @@ impl ServiceRole for RoleClient {
|
||||||
type PeerResp = ServerResult;
|
type PeerResp = ServerResult;
|
||||||
type PeerNot = ServerNotification;
|
type PeerNot = ServerNotification;
|
||||||
type Info = ClientInfo;
|
type Info = ClientInfo;
|
||||||
type PeerInfo = ServerInfo;
|
type PeerInfo = ServerPeerInfo;
|
||||||
type InitializeError = ClientInitializeError;
|
type InitializeError = ClientInitializeError;
|
||||||
const IS_CLIENT: bool = true;
|
const IS_CLIENT: bool = true;
|
||||||
|
|
||||||
|
|
@ -776,7 +776,7 @@ where
|
||||||
let ServerResult::InitializeResult(initialize_result) = response else {
|
let ServerResult::InitializeResult(initialize_result) = response else {
|
||||||
return Err(ClientInitializeError::ExpectedInitResult(Some(response)));
|
return Err(ClientInitializeError::ExpectedInitResult(Some(response)));
|
||||||
};
|
};
|
||||||
peer.set_peer_info(initialize_result);
|
peer.set_peer_info(initialize_result.into());
|
||||||
|
|
||||||
// send notification
|
// send notification
|
||||||
let notification = ClientJsonRpcMessage::notification(
|
let notification = ClientJsonRpcMessage::notification(
|
||||||
|
|
@ -846,13 +846,10 @@ where
|
||||||
server_supported: result.supported_versions,
|
server_supported: result.supported_versions,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
peer.set_peer_info(ServerInfo {
|
peer.set_peer_info(ServerPeerInfo::from_discover_result(
|
||||||
protocol_version: selected.clone(),
|
selected.clone(),
|
||||||
capabilities: result.capabilities,
|
result,
|
||||||
server_info: result.server_info,
|
));
|
||||||
instructions: result.instructions,
|
|
||||||
meta: result.meta,
|
|
||||||
});
|
|
||||||
peer.set_client_request_metadata(ClientRequestMetadata {
|
peer.set_client_request_metadata(ClientRequestMetadata {
|
||||||
protocol_version: selected,
|
protocol_version: selected,
|
||||||
client_info: client_info.client_info.clone(),
|
client_info: client_info.client_info.clone(),
|
||||||
|
|
@ -2197,13 +2194,10 @@ mod tests {
|
||||||
let peer = disconnected_peer();
|
let peer = disconnected_peer();
|
||||||
let meta = RequestMetaObject::default();
|
let meta = RequestMetaObject::default();
|
||||||
let key = discover_cache_key();
|
let key = discover_cache_key();
|
||||||
let expected = DiscoverResult::new(
|
let expected = DiscoverResult::new(vec![ProtocolVersion::default()], Default::default())
|
||||||
vec![ProtocolVersion::default()],
|
.with_server_info(crate::model::Implementation::from_build_env())
|
||||||
Default::default(),
|
.with_ttl_ms(5_000)
|
||||||
crate::model::Implementation::from_build_env(),
|
.with_cache_scope(CacheScope::Public);
|
||||||
)
|
|
||||||
.with_ttl_ms(5_000)
|
|
||||||
.with_cache_scope(CacheScope::Public);
|
|
||||||
peer.cache_response(
|
peer.cache_response(
|
||||||
key,
|
key,
|
||||||
ServerResult::DiscoverResult(expected.clone()),
|
ServerResult::DiscoverResult(expected.clone()),
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,14 @@ async fn client_initialization_accepts_stringified_numeric_response_id() {
|
||||||
.serve(client_transport)
|
.serve(client_transport)
|
||||||
.await
|
.await
|
||||||
.expect("client should accept stringified initialize response ID");
|
.expect("client should accept stringified initialize response ID");
|
||||||
|
assert!(
|
||||||
|
client
|
||||||
|
.peer_info()
|
||||||
|
.expect("peer info should be retained")
|
||||||
|
.server_info
|
||||||
|
.is_some(),
|
||||||
|
"initialize always provides a server implementation identity"
|
||||||
|
);
|
||||||
client.cancel().await.expect("cancel client");
|
client.cancel().await.expect("cancel client");
|
||||||
server_task.await.expect("server task");
|
server_task.await.expect("server task");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,13 @@ async fn discover_startup_accepts_stringified_numeric_response_id() {
|
||||||
};
|
};
|
||||||
server
|
server
|
||||||
.send(ServerJsonRpcMessage::response(
|
.send(ServerJsonRpcMessage::response(
|
||||||
ServerResult::DiscoverResult(DiscoverResult::new(
|
ServerResult::DiscoverResult(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
DiscoverResult::new(
|
||||||
ServerCapabilities::default(),
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
Implementation::new("discover-server", "1.0.0"),
|
ServerCapabilities::default(),
|
||||||
)),
|
)
|
||||||
|
.with_server_info(Implementation::new("discover-server", "1.0.0")),
|
||||||
|
),
|
||||||
RequestId::String(response_id.to_string().into()),
|
RequestId::String(response_id.to_string().into()),
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|
@ -60,6 +62,110 @@ async fn discover_startup_accepts_stringified_numeric_response_id() {
|
||||||
server_task.await.expect("server task");
|
server_task.await.expect("server task");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[allow(deprecated)]
|
||||||
|
async fn discover_startup_accepts_missing_optional_server_info() {
|
||||||
|
let (server_transport, client_transport) = tokio::io::duplex(4096);
|
||||||
|
let mut server = IntoTransport::<rmcp::RoleServer, _, _>::into_transport(server_transport);
|
||||||
|
let (rejection_observed_tx, rejection_observed_rx) = tokio::sync::oneshot::channel();
|
||||||
|
let server_task = tokio::spawn(async move {
|
||||||
|
let ClientJsonRpcMessage::Request(discover_request) =
|
||||||
|
server.receive().await.expect("expected discover request")
|
||||||
|
else {
|
||||||
|
panic!("expected discover request");
|
||||||
|
};
|
||||||
|
let mut result = DiscoverResult::new(
|
||||||
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
|
ServerCapabilities::builder().enable_tools().build(),
|
||||||
|
);
|
||||||
|
result.instructions = Some("discovery instructions".into());
|
||||||
|
result.meta = Some(rmcp::model::MetaObject::new());
|
||||||
|
result
|
||||||
|
.meta
|
||||||
|
.as_mut()
|
||||||
|
.expect("metadata")
|
||||||
|
.0
|
||||||
|
.insert("example.test/key".into(), serde_json::json!(7));
|
||||||
|
server
|
||||||
|
.send(ServerJsonRpcMessage::response(
|
||||||
|
ServerResult::DiscoverResult(result),
|
||||||
|
discover_request.id,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.expect("send discover response");
|
||||||
|
|
||||||
|
server
|
||||||
|
.send(ServerJsonRpcMessage::request(
|
||||||
|
rmcp::model::ServerRequest::CreateMessageRequest(
|
||||||
|
rmcp::model::CreateMessageRequest::new(
|
||||||
|
rmcp::model::CreateMessageRequestParams::new(
|
||||||
|
vec![rmcp::model::SamplingMessage::user_text("unsolicited")],
|
||||||
|
16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RequestId::Number(99),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.expect("send unsolicited server request");
|
||||||
|
let Some(ClientJsonRpcMessage::Error(error)) = server.receive().await else {
|
||||||
|
panic!("expected unsolicited server request to be rejected");
|
||||||
|
};
|
||||||
|
assert_eq!(error.error.code, ErrorCode::INVALID_PARAMS);
|
||||||
|
rejection_observed_tx
|
||||||
|
.send(())
|
||||||
|
.expect("signal observed rejection");
|
||||||
|
|
||||||
|
let ClientJsonRpcMessage::Request(request) =
|
||||||
|
server.receive().await.expect("expected normal request")
|
||||||
|
else {
|
||||||
|
panic!("expected normal request");
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
request.request.get_meta().protocol_version(),
|
||||||
|
Some(ProtocolVersion::V_2026_07_28)
|
||||||
|
);
|
||||||
|
server
|
||||||
|
.send(ServerJsonRpcMessage::response(
|
||||||
|
ServerResult::ListToolsResult(Default::default()),
|
||||||
|
request.id,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.expect("send list tools response");
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = DiscoverClient
|
||||||
|
.serve_with_lifecycle(
|
||||||
|
client_transport,
|
||||||
|
ClientLifecycleMode::Discover {
|
||||||
|
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("missing optional server info should not fail discovery");
|
||||||
|
let peer_info = client.peer_info().expect("peer info should be retained");
|
||||||
|
assert_eq!(peer_info.protocol_version, ProtocolVersion::V_2026_07_28);
|
||||||
|
assert!(peer_info.capabilities.tools.is_some());
|
||||||
|
assert_eq!(peer_info.server_info, None);
|
||||||
|
assert_eq!(
|
||||||
|
peer_info.instructions.as_deref(),
|
||||||
|
Some("discovery instructions")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
peer_info
|
||||||
|
.meta
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|meta| meta.0.get("example.test/key")),
|
||||||
|
Some(&serde_json::json!(7))
|
||||||
|
);
|
||||||
|
rejection_observed_rx
|
||||||
|
.await
|
||||||
|
.expect("server should observe association rejection");
|
||||||
|
client.list_tools(None).await.expect("list tools");
|
||||||
|
client.cancel().await.expect("cancel client");
|
||||||
|
server_task.await.expect("server task");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn high_level_server_accepts_discover_startup_without_initialize() {
|
async fn high_level_server_accepts_discover_startup_without_initialize() {
|
||||||
let (server_transport, client_transport) = tokio::io::duplex(4096);
|
let (server_transport, client_transport) = tokio::io::duplex(4096);
|
||||||
|
|
@ -103,11 +209,13 @@ async fn discover_startup_omits_initialize() {
|
||||||
|
|
||||||
server
|
server
|
||||||
.send(ServerJsonRpcMessage::response(
|
.send(ServerJsonRpcMessage::response(
|
||||||
ServerResult::DiscoverResult(DiscoverResult::new(
|
ServerResult::DiscoverResult(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
DiscoverResult::new(
|
||||||
ServerCapabilities::default(),
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
Implementation::new("discover-server", "1.0.0"),
|
ServerCapabilities::default(),
|
||||||
)),
|
)
|
||||||
|
.with_server_info(Implementation::new("discover-server", "1.0.0")),
|
||||||
|
),
|
||||||
request.id,
|
request.id,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|
@ -267,11 +375,13 @@ async fn discover_startup_retries_a_mutually_supported_version() {
|
||||||
);
|
);
|
||||||
server
|
server
|
||||||
.send(ServerJsonRpcMessage::response(
|
.send(ServerJsonRpcMessage::response(
|
||||||
ServerResult::DiscoverResult(DiscoverResult::new(
|
ServerResult::DiscoverResult(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
DiscoverResult::new(
|
||||||
ServerCapabilities::default(),
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
Implementation::new("discover-server", "1.0.0"),
|
ServerCapabilities::default(),
|
||||||
)),
|
)
|
||||||
|
.with_server_info(Implementation::new("discover-server", "1.0.0")),
|
||||||
|
),
|
||||||
second.id,
|
second.id,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|
@ -326,11 +436,13 @@ async fn discover_startup_retries_current_version_once_when_server_reports_it_su
|
||||||
);
|
);
|
||||||
server
|
server
|
||||||
.send(ServerJsonRpcMessage::response(
|
.send(ServerJsonRpcMessage::response(
|
||||||
ServerResult::DiscoverResult(DiscoverResult::new(
|
ServerResult::DiscoverResult(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
DiscoverResult::new(
|
||||||
ServerCapabilities::default(),
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
Implementation::new("discover-server", "1.0.0"),
|
ServerCapabilities::default(),
|
||||||
)),
|
)
|
||||||
|
.with_server_info(Implementation::new("discover-server", "1.0.0")),
|
||||||
|
),
|
||||||
second.id,
|
second.id,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,13 @@ mod tests {
|
||||||
let schema = settings
|
let schema = settings
|
||||||
.into_generator()
|
.into_generator()
|
||||||
.into_root_schema_for::<ServerJsonRpcMessage>();
|
.into_root_schema_for::<ServerJsonRpcMessage>();
|
||||||
|
let schema_value =
|
||||||
|
serde_json::to_value(&schema).expect("Failed to serialize server schema");
|
||||||
|
let discover_result = &schema_value["definitions"]["DiscoverResult"];
|
||||||
|
assert!(
|
||||||
|
discover_result["properties"].get("serverInfo").is_none(),
|
||||||
|
"DiscoverResult serverInfo belongs in namespaced _meta"
|
||||||
|
);
|
||||||
let schema_str = serde_json::to_string_pretty(&schema).expect("Failed to serialize schema");
|
let schema_str = serde_json::to_string_pretty(&schema).expect("Failed to serialize schema");
|
||||||
|
|
||||||
compare_schemas(
|
compare_schemas(
|
||||||
|
|
|
||||||
|
|
@ -2255,7 +2255,6 @@
|
||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
"required": [
|
"required": [
|
||||||
"io.modelcontextprotocol/protocolVersion",
|
"io.modelcontextprotocol/protocolVersion",
|
||||||
"io.modelcontextprotocol/clientInfo",
|
|
||||||
"io.modelcontextprotocol/clientCapabilities"
|
"io.modelcontextprotocol/clientCapabilities"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2255,7 +2255,6 @@
|
||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
"required": [
|
"required": [
|
||||||
"io.modelcontextprotocol/protocolVersion",
|
"io.modelcontextprotocol/protocolVersion",
|
||||||
"io.modelcontextprotocol/clientInfo",
|
|
||||||
"io.modelcontextprotocol/clientCapabilities"
|
"io.modelcontextprotocol/clientCapabilities"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -759,14 +759,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"serverInfo": {
|
|
||||||
"description": "Information about the server implementation.",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/Implementation"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"supportedVersions": {
|
"supportedVersions": {
|
||||||
"description": "Protocol versions implemented by this server.",
|
"description": "Protocol versions implemented by this server.",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
|
@ -785,7 +777,6 @@
|
||||||
"resultType",
|
"resultType",
|
||||||
"supportedVersions",
|
"supportedVersions",
|
||||||
"capabilities",
|
"capabilities",
|
||||||
"serverInfo",
|
|
||||||
"ttlMs",
|
"ttlMs",
|
||||||
"cacheScope"
|
"cacheScope"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -759,14 +759,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"serverInfo": {
|
|
||||||
"description": "Information about the server implementation.",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/Implementation"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"supportedVersions": {
|
"supportedVersions": {
|
||||||
"description": "Protocol versions implemented by this server.",
|
"description": "Protocol versions implemented by this server.",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
|
@ -785,7 +777,6 @@
|
||||||
"resultType",
|
"resultType",
|
||||||
"supportedVersions",
|
"supportedVersions",
|
||||||
"capabilities",
|
"capabilities",
|
||||||
"serverInfo",
|
|
||||||
"ttlMs",
|
"ttlMs",
|
||||||
"cacheScope"
|
"cacheScope"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ where
|
||||||
let client = serve_directly::<RoleClient, _, _, _, _>(
|
let client = serve_directly::<RoleClient, _, _, _, _>(
|
||||||
MrtrClient,
|
MrtrClient,
|
||||||
client_transport,
|
client_transport,
|
||||||
Some(client_peer_info),
|
Some(client_peer_info.into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = body(client).await;
|
let result = body(client).await;
|
||||||
|
|
@ -580,7 +580,7 @@ async fn request_state_codec_seals_and_verifies_through_the_loop() -> anyhow::Re
|
||||||
let client = serve_directly::<RoleClient, _, _, _, _>(
|
let client = serve_directly::<RoleClient, _, _, _, _>(
|
||||||
MrtrClient,
|
MrtrClient,
|
||||||
client_transport,
|
client_transport,
|
||||||
Some(server_info(ProtocolVersion::V_2026_07_28)),
|
Some(server_info(ProtocolVersion::V_2026_07_28).into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = client
|
let result = client
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,9 @@ fn discover_result_accepts_server_info_in_namespaced_metadata() {
|
||||||
panic!("expected discovery response, not a tool-call result");
|
panic!("expected discovery response, not a tool-call result");
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(result.server_info.name, "conformance-mock-server");
|
let server_info = result.server_info().expect("server info should be present");
|
||||||
assert_eq!(result.server_info.version, "1.0.0");
|
assert_eq!(server_info.name, "conformance-mock-server");
|
||||||
|
assert_eq!(server_info.version, "1.0.0");
|
||||||
|
|
||||||
let metadata = result.meta.expect("discovery metadata should be preserved");
|
let metadata = result.meta.expect("discovery metadata should be preserved");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -121,25 +122,42 @@ fn discover_result_accepts_server_info_in_namespaced_metadata() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discover_result_serializes_top_level_server_info() {
|
fn discover_result_serializes_server_info_in_namespaced_metadata() {
|
||||||
let result = DiscoverResult::new(
|
let mut result = DiscoverResult::new(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
rmcp::model::ServerCapabilities::default(),
|
rmcp::model::ServerCapabilities::default(),
|
||||||
rmcp::model::Implementation::new("test-server", "1.0.0"),
|
|
||||||
);
|
);
|
||||||
|
result.meta = Some(rmcp::model::MetaObject(
|
||||||
|
json!({
|
||||||
|
"io.modelcontextprotocol/serverInfo": {
|
||||||
|
"name": "stale-server",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
"unrelated": { "preserved": true }
|
||||||
|
})
|
||||||
|
.as_object()
|
||||||
|
.expect("metadata is an object")
|
||||||
|
.clone(),
|
||||||
|
));
|
||||||
|
result.set_server_info(rmcp::model::Implementation::new("test-server", "1.0.0"));
|
||||||
|
|
||||||
let serialized = serde_json::to_value(result).expect("serialize discovery result");
|
let serialized = serde_json::to_value(result).expect("serialize discovery result");
|
||||||
|
assert!(serialized.get("serverInfo").is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialized["serverInfo"],
|
serialized["_meta"]["io.modelcontextprotocol/serverInfo"],
|
||||||
json!({
|
json!({
|
||||||
"name": "test-server",
|
"name": "test-server",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serialized["_meta"]["unrelated"],
|
||||||
|
json!({ "preserved": true })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discover_result_prefers_top_level_server_info_over_namespaced_metadata() {
|
fn discover_result_ignores_legacy_top_level_server_info() {
|
||||||
let result: DiscoverResult = serde_json::from_value(json!({
|
let result: DiscoverResult = serde_json::from_value(json!({
|
||||||
"resultType": "complete",
|
"resultType": "complete",
|
||||||
"supportedVersions": ["2026-07-28"],
|
"supportedVersions": ["2026-07-28"],
|
||||||
|
|
@ -160,8 +178,11 @@ fn discover_result_prefers_top_level_server_info_over_namespaced_metadata() {
|
||||||
}))
|
}))
|
||||||
.expect("top-level server info should remain supported");
|
.expect("top-level server info should remain supported");
|
||||||
|
|
||||||
assert_eq!(result.server_info.name, "top-level-server");
|
let server_info = result
|
||||||
assert_eq!(result.server_info.version, "2.0.0");
|
.server_info()
|
||||||
|
.expect("namespaced server info is present");
|
||||||
|
assert_eq!(server_info.name, "metadata-server");
|
||||||
|
assert_eq!(server_info.version, "1.0.0");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result
|
result
|
||||||
.meta
|
.meta
|
||||||
|
|
@ -172,7 +193,7 @@ fn discover_result_prefers_top_level_server_info_over_namespaced_metadata() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discover_result_requires_valid_top_level_or_namespaced_server_info() {
|
fn discover_result_allows_missing_or_malformed_optional_server_info() {
|
||||||
let result = json!({
|
let result = json!({
|
||||||
"resultType": "complete",
|
"resultType": "complete",
|
||||||
"supportedVersions": ["2026-07-28"],
|
"supportedVersions": ["2026-07-28"],
|
||||||
|
|
@ -182,7 +203,9 @@ fn discover_result_requires_valid_top_level_or_namespaced_server_info() {
|
||||||
"_meta": { "unrelated": true }
|
"_meta": { "unrelated": true }
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(serde_json::from_value::<DiscoverResult>(result).is_err());
|
let result =
|
||||||
|
serde_json::from_value::<DiscoverResult>(result).expect("server info metadata is optional");
|
||||||
|
assert_eq!(result.server_info(), None);
|
||||||
|
|
||||||
let malformed_server_info = json!({
|
let malformed_server_info = json!({
|
||||||
"resultType": "complete",
|
"resultType": "complete",
|
||||||
|
|
@ -193,7 +216,9 @@ fn discover_result_requires_valid_top_level_or_namespaced_server_info() {
|
||||||
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "missing-version" } }
|
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "missing-version" } }
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(serde_json::from_value::<DiscoverResult>(malformed_server_info).is_err());
|
let result = serde_json::from_value::<DiscoverResult>(malformed_server_info)
|
||||||
|
.expect("opaque metadata should not prevent deserialization");
|
||||||
|
assert_eq!(result.server_info(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ async fn client_discover_helper_returns_typed_result() {
|
||||||
.expect("discover should succeed");
|
.expect("discover should succeed");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.server_info,
|
result.server_info(),
|
||||||
Implementation::new("discovery-server", "1.0.0")
|
Some(Implementation::new("discovery-server", "1.0.0"))
|
||||||
);
|
);
|
||||||
client.cancel().await.expect("client should cancel");
|
client.cancel().await.expect("client should cancel");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,11 @@ async fn discover_returns_server_metadata_without_session() {
|
||||||
"resultType": "complete",
|
"resultType": "complete",
|
||||||
"supportedVersions": ["2025-11-25"],
|
"supportedVersions": ["2025-11-25"],
|
||||||
"capabilities": { "tools": {} },
|
"capabilities": { "tools": {} },
|
||||||
"serverInfo": {
|
"_meta": {
|
||||||
"name": "discovery-server",
|
"io.modelcontextprotocol/serverInfo": {
|
||||||
"version": "1.0.0"
|
"name": "discovery-server",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"instructions": "Use the tools carefully",
|
"instructions": "Use the tools carefully",
|
||||||
"ttlMs": 0,
|
"ttlMs": 0,
|
||||||
|
|
@ -327,6 +329,38 @@ async fn discover_rejects_missing_client_capabilities() {
|
||||||
cancellation_token.cancel();
|
cancellation_token.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn discover_accepts_missing_optional_client_info() {
|
||||||
|
let (client, url, cancellation_token) = spawn_server(true).await;
|
||||||
|
let body = json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"method": "server/discover",
|
||||||
|
"params": {
|
||||||
|
"_meta": {
|
||||||
|
"io.modelcontextprotocol/protocolVersion": "2025-11-25",
|
||||||
|
"io.modelcontextprotocol/clientCapabilities": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "application/json, text/event-stream")
|
||||||
|
.header("MCP-Protocol-Version", "2025-11-25")
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("request should send");
|
||||||
|
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
let body: serde_json::Value = response.json().await.expect("response should be JSON");
|
||||||
|
assert!(body.get("result").is_some());
|
||||||
|
|
||||||
|
cancellation_token.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn discover_error_uses_http_400_when_sse_is_configured() {
|
async fn discover_error_uses_http_400_when_sse_is_configured() {
|
||||||
let (client, url, cancellation_token) = spawn_server(false).await;
|
let (client, url, cancellation_token) = spawn_server(false).await;
|
||||||
|
|
|
||||||
|
|
@ -262,8 +262,8 @@ impl rmcp::service::Service<RoleServer> for MalformedAcknowledgmentServer {
|
||||||
context: RequestContext<RoleServer>,
|
context: RequestContext<RoleServer>,
|
||||||
) -> Result<ServerResult, rmcp::ErrorData> {
|
) -> Result<ServerResult, rmcp::ErrorData> {
|
||||||
match request {
|
match request {
|
||||||
ClientRequest::DiscoverRequest(_) => {
|
ClientRequest::DiscoverRequest(_) => Ok(ServerResult::DiscoverResult(
|
||||||
Ok(ServerResult::DiscoverResult(DiscoverResult::new(
|
DiscoverResult::new(
|
||||||
vec![ProtocolVersion::V_2026_07_28],
|
vec![ProtocolVersion::V_2026_07_28],
|
||||||
ServerCapabilities::builder()
|
ServerCapabilities::builder()
|
||||||
.enable_tools()
|
.enable_tools()
|
||||||
|
|
@ -271,9 +271,9 @@ impl rmcp::service::Service<RoleServer> for MalformedAcknowledgmentServer {
|
||||||
.enable_prompts()
|
.enable_prompts()
|
||||||
.enable_prompts_list_changed()
|
.enable_prompts_list_changed()
|
||||||
.build(),
|
.build(),
|
||||||
Implementation::new("malformed-ack-server", "1.0.0"),
|
)
|
||||||
)))
|
.with_server_info(Implementation::new("malformed-ack-server", "1.0.0")),
|
||||||
}
|
)),
|
||||||
ClientRequest::SubscriptionsListenRequest(_) => {
|
ClientRequest::SubscriptionsListenRequest(_) => {
|
||||||
let mut acknowledgment = SubscriptionsAcknowledgedNotification::new(
|
let mut acknowledgment = SubscriptionsAcknowledgedNotification::new(
|
||||||
SubscriptionsAcknowledgedNotificationParams::new(
|
SubscriptionsAcknowledgedNotificationParams::new(
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,6 @@ fn subscription_schemas_mark_only_draft_required_fields_as_required() {
|
||||||
request_schema["properties"]["_meta"]["required"],
|
request_schema["properties"]["_meta"]["required"],
|
||||||
json!([
|
json!([
|
||||||
"io.modelcontextprotocol/protocolVersion",
|
"io.modelcontextprotocol/protocolVersion",
|
||||||
"io.modelcontextprotocol/clientInfo",
|
|
||||||
"io.modelcontextprotocol/clientCapabilities"
|
"io.modelcontextprotocol/clientCapabilities"
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,10 @@ async fn test_stdio_transport(records: u32) -> Result<()> {
|
||||||
// Initialize
|
// Initialize
|
||||||
let server_info = service.peer_info();
|
let server_info = service.peer_info();
|
||||||
if let Some(info) = server_info {
|
if let Some(info) = server_info {
|
||||||
tracing::info!("Connected to server: {:?}", info.server_info.name);
|
tracing::info!(
|
||||||
|
"Connected to server: {:?}",
|
||||||
|
info.server_info.as_ref().map(|server| &server.name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// List tools
|
// List tools
|
||||||
|
|
@ -214,7 +217,10 @@ async fn test_http_transport(http_url: &str, records: u32) -> Result<()> {
|
||||||
// Initialize
|
// Initialize
|
||||||
let server_info = client.peer_info();
|
let server_info = client.peer_info();
|
||||||
if let Some(info) = server_info {
|
if let Some(info) = server_info {
|
||||||
tracing::info!("Connected to server: {:?}", info.server_info.name);
|
tracing::info!(
|
||||||
|
"Connected to server: {:?}",
|
||||||
|
info.server_info.as_ref().map(|server| &server.name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// List tools
|
// List tools
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue