Skip to main content

Module testing

Module testing 

Source
Expand description

Test utilities for MCP servers.

This module provides TestClient, an ergonomic wrapper around McpRouter for writing concise MCP server tests without manual JSON-RPC construction.

§Quick Start

use tower_mcp::{CallToolResult, McpRouter, ToolBuilder, TestClient};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize, JsonSchema)]
struct EchoInput {
    message: String,
}

let echo = ToolBuilder::new("echo")
    .description("Echo a message")
    .handler(|input: EchoInput| async move {
        Ok(CallToolResult::text(input.message))
    })
    .build();

let router = McpRouter::new()
    .server_info("test-server", "1.0.0")
    .tool(echo);

let mut client = TestClient::from_router(router);
client.initialize().await;

let result = client.call_tool("echo", json!({"message": "hello"})).await;
assert_eq!(result.all_text(), "hello");

§Full Example

The following shows a complete test setup with tools, resources, and prompts:

use std::collections::HashMap;
use tower_mcp::{
    CallToolResult, GetPromptResult, McpRouter, ReadResourceResult,
    PromptBuilder, ResourceBuilder, TestClient, ToolBuilder,
};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize, JsonSchema)]
struct AddInput {
    a: i64,
    b: i64,
}

// -- Build the router ------------------------------------------------
let add = ToolBuilder::new("add")
    .description("Add two numbers")
    .handler(|input: AddInput| async move {
        Ok(CallToolResult::text(format!("{}", input.a + input.b)))
    })
    .build();

let readme = ResourceBuilder::new("file:///README.md")
    .name("README")
    .description("Project readme")
    .text("# My Project");

let greet = PromptBuilder::new("greet")
    .description("Greet someone")
    .required_arg("name", "Name to greet")
    .handler(|args: HashMap<String, String>| async move {
        let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
        Ok(GetPromptResult::user_message(
            format!("Please greet {} warmly.", name),
        ))
    })
    .build();

let router = McpRouter::new()
    .server_info("test-server", "1.0.0")
    .tool(add)
    .resource(readme)
    .prompt(greet);

// -- Create client and initialize ------------------------------------
let mut client = TestClient::from_router(router);
let init = client.initialize().await;
assert!(init.get("protocolVersion").is_some());

// -- Tools -----------------------------------------------------------
let tools = client.list_tools().await;
assert_eq!(tools.len(), 1);

let result = client.call_tool("add", json!({"a": 2, "b": 3})).await;
assert_eq!(result.all_text(), "5");
assert_eq!(result.first_text(), Some("5"));
assert!(!result.is_error);

// -- Resources -------------------------------------------------------
let resources = client.list_resources().await;
assert_eq!(resources.len(), 1);

let readme = client.read_resource("file:///README.md").await;
assert_eq!(readme.first_text(), Some("# My Project"));
assert_eq!(readme.first_uri(), Some("file:///README.md"));

// -- Prompts ---------------------------------------------------------
let prompts = client.list_prompts().await;
assert_eq!(prompts.len(), 1);

let mut args = HashMap::new();
args.insert("name".to_string(), "Alice".to_string());
let prompt = client.get_prompt("greet", args).await;
assert!(prompt.first_message_text().unwrap().contains("Alice"));

// -- Error handling --------------------------------------------------
// Expect a JSON-RPC error for a non-existent tool:
let error = client
    .call_tool_expect_error("nonexistent", json!({}))
    .await;
assert!(error.get("code").is_some());

// Expect a JSON-RPC error for an unknown method:
let error = client
    .send_request_expect_error("unknown/method", None)
    .await;
assert_eq!(error.get("code").and_then(|v| v.as_i64()), Some(-32601));

// -- Raw escape hatch ------------------------------------------------
// Use send_request for methods without typed helpers:
let pong = client.send_request("ping", None).await;
assert_eq!(pong, json!({}));

Structs§

TestClient
An ergonomic test client for MCP servers.