mod common;
use common::run_cli_loop;
use rmcp::{
ServiceExt,
model::CallToolRequestParams,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use std::io::{Write, stdout};
use tiny_loop::{
Agent,
llm::OpenAIProvider,
types::{Parameters, ToolDefinition, ToolFunction},
};
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let service = ()
.serve(TokioChildProcess::new(Command::new("npx").configure(
|cmd| {
cmd.args(&["-y", "@modelcontextprotocol/server-filesystem", "."]);
},
))?)
.await?;
println!("Connected to MCP server");
let tools = service.list_tools(Default::default()).await?.tools;
let names = tools.iter().map(|t| t.name.to_string()).collect::<Vec<_>>();
println!("Available tools: {names:#?}");
let mcp_tool_defs = tools
.iter()
.map(|t| ToolDefinition {
tool_type: "function".into(),
function: ToolFunction {
name: t.name.to_string(),
description: t.description.as_deref().unwrap_or_default().to_string(),
parameters: Parameters::from_object(t.input_schema.as_ref().clone()),
},
})
.collect();
let mcp_tool_executor = {
let peer = service.clone();
move |name: String, args: String| {
let peer = peer.clone();
async move {
peer.call_tool(CallToolRequestParams {
meta: None,
name: name.into(),
arguments: serde_json::from_str(&args).unwrap(),
task: None,
})
.await
.unwrap()
.content[0]
.as_text()
.unwrap()
.text
.clone()
}
}
};
let api_key = std::env::var("LLM_API_KEY").expect("LLM_API_KEY not set");
let llm = OpenAIProvider::new()
.api_key(api_key)
.base_url("https://openrouter.ai/api/v1")
.model("google/gemini-3-flash-preview")
.stream_callback(|chunk| {
print!("{}", chunk);
stdout().flush().unwrap();
});
let agent = Agent::new(llm)
.system("You are a helpful assistant")
.external(mcp_tool_defs, mcp_tool_executor);
run_cli_loop(agent).await;
service.cancel().await?;
Ok(())
}