use std::collections::HashSet;
use std::sync::Arc;
use anyhow;
use codetether_agent::mcp::{CallToolResult, McpServer, ToolContent};
use serde_json::json;
#[tokio::test]
async fn mcp_local_server_lists_tools_without_stdio_transport() {
let server = McpServer::new_local();
server.setup_tools_public().await;
let tools = server.get_all_tool_metadata().await;
let names: HashSet<&str> = tools.iter().map(|t| t.name.as_str()).collect();
for expected in [
"run_command",
"read_file",
"write_file",
"list_directory",
"search_files",
"grep_search",
] {
assert!(names.contains(expected), "missing tool: {expected}");
}
}
#[tokio::test]
async fn mcp_dynamic_tool_registration() {
let server = McpServer::new_local();
let tool_name = "dynamic_echo";
let tool_description = "Echoes the input message with a prefix";
let input_schema = json!({
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Message to echo"
},
"repeat": {
"type": "integer",
"description": "Number of times to repeat",
"default": 1
}
},
"required": ["message"]
});
server
.register_tool(
tool_name,
tool_description,
input_schema.clone(),
Arc::new(|args| {
let message = args
.get("message")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing message"))?;
let repeat = args.get("repeat").and_then(|v| v.as_i64()).unwrap_or(1) as usize;
let result = message.repeat(repeat);
Ok(CallToolResult {
content: vec![ToolContent::Text { text: result }],
is_error: false,
})
}),
)
.await;
let tools = server.get_all_tool_metadata().await;
let names: HashSet<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert!(
names.contains(tool_name),
"dynamic tool should appear in tools/list"
);
let tool_meta = tools
.iter()
.find(|t| t.name == tool_name)
.expect("tool should exist");
assert_eq!(tool_meta.name, tool_name, "tool name should match");
assert_eq!(
tool_meta.description.as_deref(),
Some(tool_description),
"tool description should match"
);
assert_eq!(
tool_meta.input_schema, input_schema,
"tool input_schema should match"
);
let result = server
.call_tool_direct(tool_name, json!({"message": "Hello, World!", "repeat": 2}))
.await
.expect("tool should execute");
let output = match &result.content[0] {
ToolContent::Text { text } => text.clone(),
_ => panic!("expected text content"),
};
assert_eq!(
output, "Hello, World!Hello, World!",
"tool should echo the message"
);
}