use std::sync::Arc;
use molo::tool::{SharedState, ToolRegistry};
use molo::{Agent, FakeProvider, FakeReply, McpClient, ReActAgent, ToolCall};
use rmcp::handler::server::ServerHandler;
use rmcp::model::{
CallToolRequestParams, CallToolResponse, CallToolResult, ContentBlock, ListToolsResult,
PaginatedRequestParams, ServerCapabilities, ServerInfo, Tool as RmcpTool,
};
use rmcp::service::{RequestContext, RoleServer};
use rmcp::transport::stdio;
use rmcp::{ErrorData, serve_server};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
if std::env::args().any(|arg| arg == "--as-server") {
return run_fake_server().await;
}
let program = std::env::current_exe()?.to_string_lossy().into_owned();
let mut client = McpClient::from_command("fake", program, ["--as-server"]);
let mut registry = ToolRegistry::new();
for tool in client.tools().await? {
registry.register(tool);
}
println!("tools connected: {:?}", registry.names());
let state = SharedState::new();
let text = registry
.call("fake__echo", r#"{"text":"hello, MCP!"}"#, &state)
.await?;
println!("direct call to fake__echo: {text}");
let err = registry
.call("fake__fail", "{}", &state)
.await
.expect_err("fail tool must fail");
println!("tool-level failure fed back: {err}");
let fake = Arc::new(FakeProvider::new([
FakeReply::ToolCalls {
content: "let me call the external tool first".into(),
calls: vec![ToolCall {
id: "c1".into(),
name: "fake__echo".into(),
arguments: r#"{"text":"an MCP call from the model's perspective"}"#.into(),
}],
},
FakeReply::Text("tool result received, task complete".into()),
]));
let mut agent = ReActAgent::new(
fake.clone(),
registry,
"You are an assistant that uses external tools; tool results are fed back as ToolResult messages",
);
let answer = agent.run("say hello with the fake__echo tool").await?;
println!("Agent answer: {answer}");
Ok(())
}
async fn run_fake_server() -> Result<(), Box<dyn std::error::Error>> {
let running = serve_server(FakeServer, stdio()).await?;
running.waiting().await?;
Ok(())
}
#[derive(Default)]
struct FakeServer;
impl ServerHandler for FakeServer {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
}
async fn list_tools(
&self,
_request: Option<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, ErrorData> {
Ok(ListToolsResult {
tools: vec![
RmcpTool::new(
"echo",
"Returns the text field as-is",
json!({
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
})
.as_object()
.expect("schema must be an object")
.clone(),
),
RmcpTool::new(
"fail",
"Always fails at the tool level",
json!({ "type": "object" })
.as_object()
.expect("schema must be an object")
.clone(),
),
],
..Default::default()
})
}
async fn call_tool(
&self,
request: CallToolRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<CallToolResponse, ErrorData> {
match request.name.as_ref() {
"echo" => {
let text = request
.arguments
.as_ref()
.and_then(|args| args.get("text"))
.and_then(|v| v.as_str())
.unwrap_or_default();
Ok(CallToolResult::success(vec![ContentBlock::text(text.to_string())]).into())
}
"fail" => Ok(CallToolResult::error(vec![ContentBlock::text(
"tool execution failed (demo)",
)])
.into()),
name => Err(ErrorData::invalid_params(
format!("unknown tool: {name}"),
None,
)),
}
}
}