use rmcp::ServiceExt;
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::handler::server::wrapper::Parameters;
use rmcp::model::{CallToolResult, ContentBlock, ServerCapabilities, ServerInfo};
use rmcp::transport::stdio;
use rmcp::{ErrorData, ServerHandler, tool, tool_handler, tool_router};
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Debug, Deserialize, JsonSchema)]
struct AppendArgs {
line: String,
}
#[derive(Clone)]
struct Fixture {
tool_router: ToolRouter<Self>,
}
#[tool_router]
impl Fixture {
fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
#[tool(
description = "Read the note. Observes state only.",
annotations(read_only_hint = true)
)]
async fn read_note(&self) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![ContentBlock::text(
"the note says hello",
)]))
}
#[tool(
description = "Append a line to the note. Idempotent for a given line.",
annotations(idempotent_hint = true)
)]
async fn append_note(
&self,
Parameters(AppendArgs { line }): Parameters<AppendArgs>,
) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![ContentBlock::text(format!(
"appended: {line}"
))]))
}
#[tool(description = "Do something with unstated effects.")]
async fn mutate(&self) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![ContentBlock::text("mutated")]))
}
#[tool(description = "Always fails with a tool-reported error.")]
async fn explode(&self) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::error(vec![ContentBlock::text(
"boom: the explode tool always fails",
)]))
}
}
#[tool_handler(router = self.tool_router)]
impl ServerHandler for Fixture {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions("Salvor MCP integration-test fixture server.")
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let service = Fixture::new().serve(stdio()).await?;
service.waiting().await?;
Ok(())
}