#![cfg(feature = "client")]
use mcplease::{
client::{ClientProtocol, Negotiation, NegotiationError, Step},
types::{
ClientCapabilities, Implementation, JsonRpcError, LATEST_PROTOCOL_VERSION, RequestContext,
SUPPORTED_PROTOCOL_VERSIONS,
},
};
use serde_json::json;
fn client() -> ClientProtocol {
ClientProtocol::new(Implementation::new("test-client", "9.9.9"))
}
#[test]
fn every_request_carries_the_meta_a_server_rebuilds_its_context_from() {
let mut protocol = client().with_capabilities(ClientCapabilities {
elicitation: Some(json!({})),
..ClientCapabilities::default()
});
let request = protocol.tools_call("search", &json!({ "query": "rust" }));
let context = RequestContext::from_params(request.params.as_ref());
assert_eq!(
context.protocol_version.as_deref(),
Some(LATEST_PROTOCOL_VERSION)
);
let client_info = context.client_info.as_ref().expect("clientInfo");
assert_eq!(client_info.name, "test-client");
assert_eq!(client_info.version, "9.9.9");
assert!(context.supports_elicitation());
let params = request.params.unwrap();
assert_eq!(params["name"], "search");
assert_eq!(params["arguments"]["query"], "rust");
}
#[test]
fn a_paramless_request_still_carries_meta() {
let mut protocol = client();
let request = protocol.request("ping", None);
let context = RequestContext::from_params(request.params.as_ref());
assert_eq!(context.client_info.unwrap().name, "test-client");
}
#[test]
fn ids_are_unique_per_client() {
let mut protocol = client();
let ids: Vec<_> = (0..3).map(|_| protocol.tools_list(None).id).collect();
assert_eq!(ids[0], 1.into());
assert_eq!(ids[1], 2.into());
assert_eq!(ids[2], 3.into());
}
#[test]
fn notifications_carry_no_meta() {
let protocol = client();
let notification = protocol.notification("notifications/initialized", None);
assert!(notification.params.is_none());
}
fn discovered(offered: &[&str]) -> Result<Step, NegotiationError> {
let mut protocol = client();
let (mut negotiation, _) = Negotiation::start(&mut protocol);
negotiation.on_result(
&mut protocol,
Ok(json!({ "supportedVersions": offered, "capabilities": {} })),
)
}
#[test]
fn negotiation_takes_the_newest_revision_both_sides_speak() {
let Ok(Step::Done(negotiated)) = discovered(&["2024-11-05", "2077-01-01", "2025-06-18"]) else {
panic!("expected a settled negotiation");
};
assert_eq!(negotiated.protocol_version, "2025-06-18");
assert!(!negotiated.stateful);
}
#[test]
fn a_server_with_no_revision_in_common_fails_the_negotiation() {
let Err(error) = discovered(&["2077-01-01"]) else {
panic!("expected the negotiation to fail");
};
let message = error.to_string();
assert!(message.contains("2077-01-01"), "{message}");
assert!(
message.contains(SUPPORTED_PROTOCOL_VERSIONS[0]),
"{message}"
);
}
#[test]
fn a_server_that_refuses_both_entry_points_reports_both() {
let mut protocol = client();
let (mut negotiation, _) = Negotiation::start(&mut protocol);
let declined = Err(JsonRpcError::method_not_found("server/discover"));
let Ok(Step::Send(_)) = negotiation.on_result(&mut protocol, declined) else {
panic!("expected the fallback to initialize");
};
let refused = Err(JsonRpcError::internal("no thanks"));
let Err(error) = negotiation.on_result(&mut protocol, refused) else {
panic!("expected the negotiation to fail");
};
let message = error.to_string();
assert!(message.contains("server/discover"), "{message}");
assert!(message.contains("no thanks"), "{message}");
}
#[test]
fn an_unparseable_result_names_the_method_it_came_from() {
let mut protocol = client();
let (mut negotiation, _) = Negotiation::start(&mut protocol);
let Err(error) = negotiation.on_result(&mut protocol, Ok(json!({ "nonsense": true }))) else {
panic!("expected the negotiation to fail");
};
assert!(error.to_string().contains("server/discover"));
}