use supercode_harness::mcp::{McpClient, McpServerHandle};
use supercode_harness::tools::ToolContext;
fn tmp() -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
let d = std::env::temp_dir().join(format!(
"sc-mcp-resources-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::SeqCst)
));
std::fs::create_dir_all(&d).unwrap();
d
}
fn have_python3() -> bool {
std::process::Command::new("python3")
.arg("--version")
.output()
.is_ok()
}
const RESOURCE_SERVER: &str = r#"
import sys, json
def send(o): sys.stdout.write(json.dumps(o)+"\n"); sys.stdout.flush()
subscribed = []
for line in sys.stdin:
line=line.strip()
if not line: continue
msg=json.loads(line)
m=msg.get("method"); i=msg.get("id")
if m=="initialize":
send({"jsonrpc":"2.0","id":i,"result":{"protocolVersion":"2025-06-18","serverInfo":{"name":"fake"}}})
elif m=="tools/list":
send({"jsonrpc":"2.0","id":i,"result":{"tools":[]}})
elif m=="resources/list":
send({"jsonrpc":"2.0","id":i,"result":{"resources":[
{"uri":"file:///a.txt","name":"a","description":"file a","mimeType":"text/plain"}
]}})
elif m=="resources/templates/list":
send({"jsonrpc":"2.0","id":i,"result":{"resourceTemplates":[
{"uriTemplate":"file:///{name}.txt","name":"any file","description":"template"}
]}})
elif m=="resources/read":
uri = msg.get("params",{}).get("uri")
send({"jsonrpc":"2.0","id":i,"result":{"contents":[{"uri":uri,"text":"CONTENT OF "+uri}]}})
elif m=="resources/subscribe":
uri = msg.get("params",{}).get("uri")
subscribed.append(uri)
send({"jsonrpc":"2.0","id":i,"result":{}})
else:
if i is not None:
send({"jsonrpc":"2.0","id":i,"error":{"code":-32601,"message":"unknown"}})
"#;
async fn connect_fake() -> Option<McpServerHandle> {
if !have_python3() {
eprintln!("skipping: no python3");
return None;
}
let dir = tmp();
let script = dir.join("server.py");
std::fs::write(&script, RESOURCE_SERVER).unwrap();
let client = McpClient::connect("python3", &[script.to_str().unwrap()], &Default::default())
.await
.unwrap();
Some(McpServerHandle::new("fake", client))
}
fn ctx() -> ToolContext {
ToolContext::new(std::env::temp_dir())
}
#[tokio::test]
async fn resource_tools_are_namespaced_per_server() {
let Some(handle) = connect_fake().await else {
return;
};
let tools = handle.resource_tools();
let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
assert_eq!(
names,
vec![
"mcp__fake__resources_list",
"mcp__fake__resources_read",
"mcp__fake__resources_subscribe",
]
);
}
#[tokio::test]
async fn resources_list_tool_reports_resources_and_templates() {
let Some(handle) = connect_fake().await else {
return;
};
let tools = handle.resource_tools();
let list_tool = &tools[0];
let out = list_tool
.execute(serde_json::json!({}), &ctx())
.await
.unwrap();
assert!(out.contains("file:///a.txt"));
assert!(out.contains("template"));
}
#[tokio::test]
async fn resources_read_tool_returns_the_resource_content() {
let Some(handle) = connect_fake().await else {
return;
};
let tools = handle.resource_tools();
let read_tool = &tools[1];
let out = read_tool
.execute(serde_json::json!({"uri": "file:///a.txt"}), &ctx())
.await
.unwrap();
assert_eq!(out, "CONTENT OF file:///a.txt");
}
#[tokio::test]
async fn resources_subscribe_tool_confirms_and_client_logs_the_update_notification() {
let Some(handle) = connect_fake().await else {
return;
};
let tools = handle.resource_tools();
let subscribe_tool = &tools[2];
let out = subscribe_tool
.execute(serde_json::json!({"uri": "file:///a.txt"}), &ctx())
.await
.unwrap();
assert!(out.contains("file:///a.txt"));
}
const OVERSIZED_RESOURCE_SERVER: &str = r#"
import sys, json
def send(o): sys.stdout.write(json.dumps(o)+"\n"); sys.stdout.flush()
for line in sys.stdin:
line=line.strip()
if not line: continue
msg=json.loads(line)
m=msg.get("method"); i=msg.get("id")
if m=="initialize":
send({"jsonrpc":"2.0","id":i,"result":{"protocolVersion":"2025-06-18","serverInfo":{"name":"fake"}}})
elif m=="resources/read":
uri = msg.get("params",{}).get("uri")
# 17 MiB of text -- over the 16 MiB MCP_MAX_RESOURCE_BYTES cap
# (crates/harness/src/mcp.rs) this client enforces.
big = "x" * (17 * 1024 * 1024)
send({"jsonrpc":"2.0","id":i,"result":{"contents":[{"uri":uri,"text":big}]}})
else:
if i is not None:
send({"jsonrpc":"2.0","id":i,"error":{"code":-32601,"message":"unknown"}})
"#;
async fn connect_oversized_fake() -> Option<McpClient> {
if !have_python3() {
eprintln!("skipping: no python3");
return None;
}
let dir = tmp();
let script = dir.join("server.py");
std::fs::write(&script, OVERSIZED_RESOURCE_SERVER).unwrap();
let client = McpClient::connect("python3", &[script.to_str().unwrap()], &Default::default())
.await
.unwrap();
Some(client)
}
#[tokio::test]
async fn read_resource_over_the_cap_errors_named_not_unbounded() {
let Some(mut client) = connect_oversized_fake().await else {
return;
};
let result = tokio::time::timeout(
std::time::Duration::from_secs(20),
client.read_resource("file:///huge.txt"),
)
.await
.expect("must not hang");
let err = result.expect_err("an oversized resource must error, not silently succeed");
assert!(
err.to_string().contains("exceeded max"),
"error should name the cap: {err}"
);
}
#[tokio::test]
async fn resources_read_tool_rejects_missing_uri_argument() {
let Some(handle) = connect_fake().await else {
return;
};
let tools = handle.resource_tools();
let read_tool = &tools[1];
let err = read_tool
.execute(serde_json::json!({}), &ctx())
.await
.unwrap_err();
assert!(err.to_string().contains("uri") || err.to_string().contains("invalid"));
}