#![allow(
clippy::expect_used,
clippy::panic,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
dead_code
)]
use std::future::Future;
use std::pin::Pin;
use loopctl::mcp::McpServerAdapter;
use loopctl::tool::{Tool, ToolContext, ToolError, ToolOutput, ToolRegistry, ToolSchema};
use serde_json::json;
struct EchoTool;
impl Tool for EchoTool {
fn name(&self) -> &'static str {
"echo"
}
fn description(&self) -> &'static str {
"Echo back the message field"
}
fn schema(&self) -> ToolSchema {
ToolSchema {
tool: "echo".into(),
description: "Echo back the message field".into(),
input_schema: json!({
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}),
}
}
fn call(
&self,
input: serde_json::Value,
_ctx: &ToolContext,
) -> Pin<Box<dyn Future<Output = Result<ToolOutput, ToolError>> + Send + '_>> {
Box::pin(async move {
let Some(message) = input.get("message").and_then(serde_json::Value::as_str) else {
return Err(ToolError::InvalidInput(
"expected a string field `message`".into(),
));
};
Ok(ToolOutput::text(message.to_string()))
})
}
}
struct FailTool;
impl Tool for FailTool {
fn name(&self) -> &'static str {
"fail"
}
fn description(&self) -> &'static str {
"Always fails with a hard tool error"
}
fn schema(&self) -> ToolSchema {
ToolSchema {
tool: "fail".into(),
description: "Always fails with a hard tool error".into(),
input_schema: json!({"type": "object"}),
}
}
fn call(
&self,
_input: serde_json::Value,
_ctx: &ToolContext,
) -> Pin<Box<dyn Future<Output = Result<ToolOutput, ToolError>> + Send + '_>> {
Box::pin(async { Err(ToolError::Execution("this tool always fails".into())) })
}
}
#[tokio::main]
async fn main() {
let mut registry = ToolRegistry::new();
registry.register(EchoTool);
registry.register(FailTool);
let adapter = McpServerAdapter::new(
registry,
ToolContext::default(),
"loopctl-example".into(),
env!("CARGO_PKG_VERSION").into(),
);
let service = adapter.serve_stdio().await.expect("stdio serve");
let cancel = service.cancellation_token();
tokio::select! {
quit = service.waiting() => {
quit.expect("server loop");
}
_ = tokio::signal::ctrl_c() => {
eprintln!("shutting down");
cancel.cancel();
}
}
}