template_rust_mcp_server

sebetc4/template_rust_mcp_server

3.2

If you are the rightful owner of template_rust_mcp_server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

A scalable Rust MCP server template built with rmcp.

Tools
3
Resources
0
Prompts
0

MCP Server Template

A scalable Rust MCP (Model Context Protocol) server template built with rmcp.

Features

  • Domain-Driven Architecture: Organized by functional domains (tools, resources, prompts)
  • Service Layer Pattern: Each domain has its own service for business logic
  • Type-Safe Error Handling: Custom error types with thiserror
  • Configurable: Centralized configuration system
  • Async Runtime: Built on Tokio for high-performance async I/O
  • Comprehensive Logging: Tracing integration for structured logging

Project Structure

src/
├── lib.rs                    # Library crate root
├── main.rs                   # Application entry point
├── core/                     # Core server components
│   ├── mod.rs               # Module exports
│   ├── config.rs            # Server configuration
│   ├── error.rs             # Unified error types
│   ├── server.rs            # MCP ServerHandler implementation
│   └── transport/           # Transport layer
│       ├── mod.rs           # Transport module exports
│       ├── config.rs        # Transport configuration
│       ├── error.rs         # Transport errors
│       ├── service.rs       # Transport service orchestration
│       ├── stdio.rs         # STDIO transport (standard MCP)
│       ├── tcp.rs           # TCP transport (JSON-RPC)
│       └── http.rs          # HTTP transport (JSON-RPC over HTTP)
└── domains/                  # Domain-specific modules
    ├── mod.rs               # Domains module exports
    ├── tools/               # Tools domain
    │   ├── mod.rs           # Module exports
    │   ├── error.rs         # Tool-specific errors
    │   ├── service.rs       # Tool management service
    │   ├── router.rs        # Dynamic ToolRouter builder (STDIO/TCP)
    │   ├── registry.rs      # Tool dispatch registry (HTTP)
    │   ├── handlers.rs      # Legacy handlers
    │   └── definitions/     # Tool implementations (one file per tool)
    │       ├── mod.rs       # Tool exports
    │       ├── echo.rs      # Echo tool
    │       ├── add.rs       # Add tool
    │       └── system_info.rs # System info tool
    ├── resources/           # Resources domain
    │   ├── mod.rs
    │   ├── error.rs         # Resource-specific errors
    │   ├── service.rs       # Resource management service
    │   └── handlers.rs      # Resource providers
    └── prompts/             # Prompts domain
        ├── mod.rs
        ├── error.rs         # Prompt-specific errors
        ├── service.rs       # Prompt management service
        └── templates.rs     # Prompt template engine

Available Tools

  • echo: Echo back a provided message
  • add: Calculate the sum of two numbers
  • system_info: Get server information

Available Resources

  • mcp://server/info: Server information (JSON)
  • mcp://server/config/example: Example configuration
  • mcp://server/docs/readme: Server documentation (Markdown)

Available Prompts

  • greeting: A customizable greeting prompt
  • code_review: A code review prompt template
  • explain: Ask for an explanation of a concept
  • summarize: Summarize text or content

Getting Started

Prerequisites

  • Rust 2024 edition or later
  • Cargo package manager

Building

# Development build
cargo build

# Release build (optimized)
cargo build --release

Running

# Run the server (STDIO mode - default)
cargo run

# Run with debug logging
RUST_LOG=debug cargo run

# Run in TCP mode on port 4000
MCP_TRANSPORT=tcp MCP_TCP_PORT=4000 cargo run

# Run in TCP mode on custom host/port
MCP_TRANSPORT=tcp MCP_TCP_PORT=8080 MCP_TCP_HOST=0.0.0.0 cargo run

# Run in HTTP mode on port 9090
MCP_TRANSPORT=http MCP_HTTP_PORT=9090 cargo run

# Run HTTP mode with custom configuration
MCP_TRANSPORT=http MCP_HTTP_PORT=8080 MCP_HTTP_HOST=0.0.0.0 MCP_HTTP_PATH=/api/mcp cargo run

Testing TCP Mode

The TCP transport accepts JSON-RPC messages (not HTTP). Each message must be a complete JSON object on a single line.

# Test with the provided Python client
python3 scripts/test_tcp_client.py 4000

# Or test with the bash script (requires netcat)
./scripts/test_tcp_client.sh 4000

# Or manually with netcat
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | nc localhost 4000

Testing HTTP Mode

The HTTP transport provides a REST-like interface with JSON-RPC over HTTP POST:

# Start server in HTTP mode
MCP_TRANSPORT=http MCP_HTTP_PORT=9090 cargo run

# Test with the provided Python client
python3 scripts/test_http_client.py

# Or test with curl
curl http://localhost:9090/health
curl http://localhost:9090/

# Initialize session
curl -X POST http://localhost:9090/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

# List tools
curl -X POST http://localhost:9090/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# Call a tool
curl -X POST http://localhost:9090/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add","arguments":{"a":5,"b":3}}}'

Testing

# Run all tests
cargo test

# Run tests with output
cargo test -- --nocapture

# Test TCP connectivity
python3 scripts/test_tcp_client.py 4000

# Test HTTP connectivity
python3 scripts/test_http_client.py

# Test error handling
python3 scripts/test_error_handling.py 4000

Error Handling

The server is designed to be resilient and handle errors gracefully:

  • Connection-level isolation: Each client connection runs in its own task. Errors in one connection don't affect others or crash the server
  • Proper error responses: Invalid requests return JSON-RPC error responses with appropriate error codes
  • Protocol error handling: Missing parameters, invalid tool names, wrong types, and malformed JSON are handled gracefully
  • Connection recovery: Connections can continue after encountering errors
  • Comprehensive logging: All errors are logged with appropriate severity levels (ERROR, WARN) for debugging

The error handling test suite validates all these scenarios automatically.

Configuration

The server can be configured through environment variables:

Transport Configuration

STDIO Mode (Default) - Standard for MCP:

# No configuration needed - STDIO is the default
cargo run

TCP Mode - JSON-RPC over raw TCP:

# Set transport to TCP
MCP_TRANSPORT=tcp

# Configure port (default: 3000)
MCP_TCP_PORT=8080

# Configure host (default: 127.0.0.1)
MCP_TCP_HOST=0.0.0.0

HTTP Mode - JSON-RPC over HTTP POST:

# Set transport to HTTP
MCP_TRANSPORT=http

# Configure port (default: 8080)
MCP_HTTP_PORT=9090

# Configure host (default: 127.0.0.1)
MCP_HTTP_HOST=0.0.0.0

# Configure RPC endpoint path (default: /mcp)
MCP_HTTP_PATH=/api/mcp

# Enable/disable CORS (default: true)
MCP_HTTP_CORS=true

HTTP Transport Endpoints

When running in HTTP mode, the server exposes:

EndpointMethodDescription
/GETServer information and API docs
/healthGETHealth check endpoint
/mcp (configurable)POSTJSON-RPC endpoint for MCP messages

Other Configuration

# Server identification
MCP_SERVER_NAME="My MCP Server"

# Log level (trace, debug, info, warn, error)
MCP_LOG_LEVEL=debug

# Resources base path
MCP_RESOURCES_BASE_PATH=/path/to/resources

Programmatic Configuration

You can also configure the server programmatically in src/core/config.rs:

let config = Config {
    server: ServerConfig {
        name: "My MCP Server".to_string(),
        version: "1.0.0".to_string(),
    },
    resources: ResourcesConfig {
        base_path: Some("/path/to/resources".to_string()),
    },
    prompts: PromptsConfig::default(),
    transport: TransportConfig::Stdio, // or TransportConfig::Tcp { port: 3000, host: "127.0.0.1".to_string() }
};

Adding New Features

For detailed step-by-step guides, see

Adding a New Tool

Tools are organized in src/domains/tools/definitions/ with one file per tool. The architecture is designed so that adding a new tool does NOT require modifying server.rs.

Each tool has:

  • Parameters: The input schema for the tool
  • execute(): The core logic (used by STDIO/TCP via rmcp)
  • http_handler(): HTTP transport handler (used by HTTP transport)

Step 1: Create a new file src/domains/tools/definitions/my_tool.rs:

//! My Tool definition.

use rmcp::model::{CallToolResult, Content};
use schemars::JsonSchema;
use serde::Deserialize;
use tracing::info;

// Parameters
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct MyToolParams {
    /// The input value.
    pub input: String,
}

// Tool definition
pub struct MyTool;

impl MyTool {
    pub const NAME: &'static str = "my_tool";
    pub const DESCRIPTION: &'static str = "Description of my tool";

    /// Execute (for STDIO/TCP)
    pub fn execute(params: &MyToolParams) -> CallToolResult {
        info!("MyTool called with input: {}", params.input);
        CallToolResult::success(vec![Content::text(format!("Result: {}", params.input))])
    }

    /// HTTP handler (for HTTP transport)
    pub fn http_handler(arguments: serde_json::Value) -> Result<serde_json::Value, String> {
        let input = arguments
            .get("input")
            .and_then(|v| v.as_str())
            .ok_or_else(|| "Missing 'input' parameter".to_string())?;
        
        Ok(serde_json::json!({
            "content": [{ "type": "text", "text": format!("Result: {}", input) }],
            "isError": false
        }))
    }
}

Step 2: Export in src/domains/tools/definitions/mod.rs:

mod my_tool;
pub use my_tool::{MyTool, MyToolParams};

Step 3: Register for STDIO/TCP in src/domains/tools/router.rs:

use super::definitions::{MyTool, MyToolParams, ...};

// Add a new route builder function
fn create_my_tool_route<S>() -> ToolRoute<S>
where
    S: Send + Sync + 'static,
{
    let tool = Tool {
        name: MyTool::NAME.into(),
        description: Some(MyTool::DESCRIPTION.into()),
        input_schema: cached_schema_for_type::<MyToolParams>(),
        ..Default::default()
    };
    
    ToolRoute::new_dyn(tool, |ctx: ToolCallContext<'_, S>| {
        let args = ctx.arguments.clone().unwrap_or_default();
        async move {
            let params: MyToolParams = serde_json::from_value(
                serde_json::Value::Object(args)
            ).map_err(|e| McpError::invalid_params(e.to_string(), None))?;
            Ok(MyTool::execute(&params))
        }.boxed()
    })
}

// Add to build_tool_router()
pub fn build_tool_router<S>() -> ToolRouter<S>
where
    S: Send + Sync + 'static + HasToolContext,
{
    ToolRouter::new()
        .with_route(create_echo_route())
        .with_route(create_add_route())
        .with_route(create_system_info_route())
        .with_route(create_my_tool_route())  // Add your tool here
}

Step 4: Register for HTTP in src/domains/tools/registry.rs:

// In tool_names()
vec![..., MyTool::NAME]

// In call_tool()
MyTool::NAME => MyTool::http_handler(arguments),

That's it! No need to modify server.rs - the tool router is built dynamically.

Adding a New Resource

In src/domains/resources/service.rs:

self.register_resource(ResourceEntry {
    resource: create_resource(
        "mcp://my/resource",
        "My Resource",
        Some("Resource description"),
        Some("application/json"),
    ),
    content: ResourceContent::Text("content".to_string()),
});

Adding a New Prompt

In src/domains/prompts/service.rs:

self.register_prompt(PromptTemplate {
    name: "my_prompt".to_string(),
    description: Some("My prompt description".to_string()),
    arguments: vec![
        PromptArgument {
            name: "input".to_string(),
            title: None,
            description: Some("The input".to_string()),
            required: Some(true),
        },
    ],
    template: "Process this: {{input}}".to_string(),
});

Integration

To use this server with an MCP client:

{
  "mcpServers": {
    "template": {
      "command": "path/to/mcp_server",
      "args": []
    }
  }
}

License

MIT

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.