use async_trait::async_trait;
use serde_json::Value;
use std::env;
use tenx_mcp::{MCPServer, Result, ToolHandler};
use tokio::net::TcpListener;
use tokio::signal;
use tracing::{error, info};
struct EchoToolHandler;
#[async_trait]
impl ToolHandler for EchoToolHandler {
fn metadata(&self) -> tenx_mcp::schema::Tool {
tenx_mcp::schema::Tool {
name: "echo".to_string(),
description: Some("Echoes back the provided message".to_string()),
input_schema: tenx_mcp::schema::ToolInputSchema {
schema_type: "object".to_string(),
properties: Some({
let mut props = std::collections::HashMap::new();
props.insert(
"message".to_string(),
serde_json::json!({
"type": "string",
"description": "The message to echo back"
}),
);
props
}),
required: Some(vec!["message".to_string()]),
},
annotations: None,
}
}
async fn execute(&self, arguments: Option<Value>) -> Result<Vec<tenx_mcp::schema::Content>> {
let message = arguments
.as_ref()
.and_then(|v| v.get("message"))
.and_then(|v| v.as_str())
.unwrap_or("No message provided");
Ok(vec![tenx_mcp::schema::Content::Text(
tenx_mcp::schema::TextContent {
text: message.to_string(),
annotations: None,
},
)])
}
}
struct AddToolHandler;
#[async_trait]
impl ToolHandler for AddToolHandler {
fn metadata(&self) -> tenx_mcp::schema::Tool {
tenx_mcp::schema::Tool {
name: "add".to_string(),
description: Some("Adds two numbers together".to_string()),
input_schema: tenx_mcp::schema::ToolInputSchema {
schema_type: "object".to_string(),
properties: Some({
let mut props = std::collections::HashMap::new();
props.insert(
"a".to_string(),
serde_json::json!({
"type": "number",
"description": "First number"
}),
);
props.insert(
"b".to_string(),
serde_json::json!({
"type": "number",
"description": "Second number"
}),
);
props
}),
required: Some(vec!["a".to_string(), "b".to_string()]),
},
annotations: None,
}
}
async fn execute(&self, arguments: Option<Value>) -> Result<Vec<tenx_mcp::schema::Content>> {
let a = arguments
.as_ref()
.and_then(|v| v.get("a"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let b = arguments
.as_ref()
.and_then(|v| v.get("b"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let result = a + b;
Ok(vec![tenx_mcp::schema::Content::Text(
tenx_mcp::schema::TextContent {
text: format!("{a} + {b} = {result}"),
annotations: None,
},
)])
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let args: Vec<String> = env::args().collect();
let (host, port) = if args.len() == 3 {
(
args[1].clone(),
args[2].parse::<u16>().expect("Invalid port number"),
)
} else if args.len() == 1 {
("127.0.0.1".to_string(), 3000)
} else {
eprintln!("Usage: {} [host] [port]", args[0]);
eprintln!("Example: {} 127.0.0.1 3000", args[0]);
eprintln!("If no arguments provided, defaults to 127.0.0.1:3000");
std::process::exit(1);
};
let addr = format!("{host}:{port}");
let listener = TcpListener::bind(&addr).await?;
info!("MCP server listening on {}", addr);
loop {
tokio::select! {
result = listener.accept() => {
match result {
Ok((stream, peer_addr)) => {
info!("New connection from {}", peer_addr);
let mut server = MCPServer::new(
"tenx-mcp-example-server".to_string(),
"0.1.0".to_string(),
);
server.register_tool(Box::new(EchoToolHandler)).await;
server.register_tool(Box::new(AddToolHandler)).await;
let transport = Box::new(tenx_mcp::transport::TcpServerTransport::new(stream));
tokio::spawn(async move {
info!("Handling connection from {}", peer_addr);
match server.serve(transport).await {
Ok(()) => info!("Connection from {} closed", peer_addr),
Err(e) => error!("Error handling connection from {}: {}", peer_addr, e),
}
});
}
Err(e) => {
error!("Failed to accept connection: {}", e);
}
}
}
_ = signal::ctrl_c() => {
info!("\nShutting down server...");
break;
}
}
}
info!("Server stopped");
Ok(())
}