use turbomcp::__macro_support::turbomcp_core::handler::McpHandler;
use turbomcp::prelude::*;
use turbomcp_server::CompositeHandler;
#[derive(Clone)]
struct WeatherService;
#[turbomcp::server(name = "weather", version = "1.0.0")]
impl WeatherService {
#[tool(description = "Get current weather")]
async fn get_current(&self, city: String) -> McpResult<String> {
Ok(format!("Weather in {}: Sunny, 72°F", city))
}
#[tool(description = "Get 5-day forecast")]
async fn get_forecast(&self, city: String, days: Option<u32>) -> McpResult<String> {
let days = days.unwrap_or(5);
Ok(format!(
"{}-day forecast for {}: Sunny -> Cloudy -> Rain",
days, city
))
}
#[resource("alerts://active", description = "Active weather alerts")]
async fn get_alerts(&self, _uri: String, _ctx: &RequestContext) -> McpResult<String> {
Ok(r#"{"alerts": ["Heat advisory until 8PM"]}"#.into())
}
}
#[derive(Clone)]
struct NewsService;
#[turbomcp::server(name = "news", version = "1.0.0")]
impl NewsService {
#[tool(description = "Get top news headlines")]
async fn get_headlines(&self, category: Option<String>) -> McpResult<String> {
let cat = category.unwrap_or_else(|| "general".into());
Ok(format!(
"Top {} headlines: AI advances, Tech stocks rise",
cat
))
}
#[tool(description = "Search news articles")]
async fn search(&self, query: String) -> McpResult<String> {
Ok(format!("Found 42 articles matching '{}'", query))
}
#[resource("feed://latest", description = "Latest news feed")]
async fn get_feed(&self, _uri: String, _ctx: &RequestContext) -> McpResult<String> {
Ok(r#"{"articles": [{"title": "Breaking: AI advances"}]}"#.into())
}
}
#[derive(Clone)]
struct CalculatorService;
#[turbomcp::server(name = "calc", version = "1.0.0")]
impl CalculatorService {
#[tool(description = "Add two numbers")]
async fn add(&self, a: f64, b: f64) -> McpResult<f64> {
Ok(a + b)
}
#[tool(description = "Multiply two numbers")]
async fn multiply(&self, a: f64, b: f64) -> McpResult<f64> {
Ok(a * b)
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Server Composition Demo ===\n");
let weather = WeatherService;
let news = NewsService;
let calc = CalculatorService;
println!("Individual services:\n");
println!("Weather Service tools:");
for tool in weather.list_tools() {
println!(" - {}", tool.name);
}
println!("\nNews Service tools:");
for tool in news.list_tools() {
println!(" - {}", tool.name);
}
println!("\nCalculator Service tools:");
for tool in calc.list_tools() {
println!(" - {}", tool.name);
}
println!("\n=== Composed Server ===\n");
let composite = CompositeHandler::new("unified-api", "1.0.0")
.with_description("Unified API combining weather, news, and calculator")
.mount(weather, "weather")
.mount(news, "news")
.mount(calc, "calc");
let info = composite.server_info();
println!("Server: {} v{}", info.name, info.version);
println!("Mounted handlers: {}", composite.handler_count());
println!("Prefixes: {:?}", composite.prefixes());
println!("\nAll tools (namespaced):");
println!("-----------------------");
for tool in composite.list_tools() {
println!(" {} - {:?}", tool.name, tool.description);
}
println!("\nAll resources (namespaced):");
println!("---------------------------");
for resource in composite.list_resources() {
println!(" {} ({})", resource.name, resource.uri);
}
println!("\n=== Tool Calls ===\n");
tokio::runtime::Runtime::new()?.block_on(async {
let ctx = RequestContext::default();
let result = composite
.call_tool(
"weather_get_current",
serde_json::json!({"city": "Seattle"}),
&ctx,
)
.await?;
println!("weather_get_current: {:?}", result.first_text());
let result = composite
.call_tool("news_get_headlines", serde_json::json!({}), &ctx)
.await?;
println!("news_get_headlines: {:?}", result.first_text());
let result = composite
.call_tool("calc_add", serde_json::json!({"a": 5, "b": 3}), &ctx)
.await?;
println!("calc_add: {:?}", result.first_text());
Ok::<_, McpError>(())
})?;
println!("\n=== Error Handling ===\n");
let result = CompositeHandler::new("test", "1.0.0")
.mount(WeatherService, "api")
.try_mount(NewsService, "api");
match result {
Ok(_) => println!("Unexpected success"),
Err(e) => println!("Correctly caught duplicate prefix: {}", e),
}
println!("\nNote: Use try_mount() for fallible mounting or mount() to panic on duplicates.");
Ok(())
}