use rmcp::ServiceExt;
use rmcp::transport::TokioChildProcess;
async fn connect() -> rmcp::service::RunningService<rmcp::RoleClient, impl rmcp::service::Service<rmcp::RoleClient>> {
let mut cmd = tokio::process::Command::new(env!("CARGO_BIN_EXE_servo-fetch"));
cmd.arg("mcp");
let transport = TokioChildProcess::new(cmd).unwrap();
().serve(transport).await.expect("MCP handshake failed")
}
fn call_params(name: &str, args: &serde_json::Value) -> rmcp::model::CallToolRequestParams {
let mut params = rmcp::model::CallToolRequestParams::default();
params.name = String::from(name).into();
params.arguments = Some(args.as_object().unwrap().clone());
params
}
#[tokio::test]
#[ignore = "requires Servo + network"]
async fn parallel_fetches_do_not_cross_contaminate() {
const URLS: &[(&str, &str)] = &[
("https://example.com", "Example Domain"),
("https://example.org", "Example Domain"),
];
let client = connect().await;
let calls = URLS.iter().map(|(url, _)| {
client.call_tool(call_params(
"fetch",
&serde_json::json!({ "url": url, "max_length": 2000, "timeout": 60 }),
))
});
let results = futures_util::future::join_all(calls).await;
for ((url, marker), result) in URLS.iter().zip(results) {
let r = result.unwrap_or_else(|e| panic!("fetch {url} failed: {e}"));
let text = r
.content
.iter()
.filter_map(|c| c.as_text())
.map(|t| t.text.as_str())
.collect::<String>();
assert!(
text.contains(marker),
"response for {url} missing marker {marker:?}; got: {text}"
);
}
}
#[tokio::test]
#[ignore = "requires Servo + network"]
async fn concurrent_full_page_screenshots_are_distinct() {
const URLS: &[&str] = &[
"https://example.com",
"https://www.iana.org/help/example-domains",
"https://httpbin.org/html",
];
let client = connect().await;
let calls = URLS.iter().map(|url| {
client.call_tool(call_params(
"screenshot",
&serde_json::json!({ "url": url, "full_page": true, "timeout": 60 }),
))
});
let results = futures_util::future::join_all(calls).await;
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
for (url, result) in URLS.iter().zip(results) {
let r = result.unwrap_or_else(|e| panic!("screenshot {url} failed: {e}"));
let png_b64 = r
.content
.iter()
.find_map(|c| c.as_image().map(|i| i.data.clone()))
.unwrap_or_else(|| panic!("no image content for {url}"));
assert!(
seen.insert(png_b64),
"duplicate screenshot for {url}; framebuffer likely leaked across WebViews"
);
}
}