Create an MCP server with tools for AI agents. Reference: extensions/mcp/systemprompt/ for working example.

Help: { "command": "core playbooks show build_create-mcp-server" }


Structure

extensions/mcp/{name}/
├── Cargo.toml
├── module.yml
└── src/
    ├── main.rs
    ├── lib.rs
    ├── server/
    │   ├── mod.rs
    │   ├── constructor.rs
    │   └── handlers/
    ├── tools/
    │   ├── mod.rs
    │   └── {tool_name}/
    ├── prompts/
    └── resources/

Cargo.toml

[package]
name = "systemprompt-mcp-my-server"
version = "1.0.0"
edition = "2021"

[[bin]]
name = "systemprompt-mcp-my-server"
path = "src/main.rs"

[dependencies]
systemprompt-core-mcp = { git = "https://github.com/systempromptio/systemprompt-core" }
systemprompt-core-database = { git = "https://github.com/systempromptio/systemprompt-core" }
systemprompt-models = { git = "https://github.com/systempromptio/systemprompt-core" }
systemprompt-identifiers = { git = "https://github.com/systempromptio/systemprompt-core" }
rmcp = "0.8"
tokio = { version = "1.47", features = ["full"] }
axum = "0.8"
tracing = "0.1"
anyhow = "1.0"

module.yml

name: my-server
display_name: "My MCP Server"
version: "1.0.0"
description: "Provides tools for X, Y, Z"

server:
  port: 5003
  host: "0.0.0.0"

tools:
  - name: my_tool
    description: "Does something useful"

Main Entry Point

File: src/main.rs. See extensions/mcp/systemprompt/src/main.rs:1-30 for reference.

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    systemprompt_core_logging::init();

    let config = load_config()?;
    let server = MyServer::new(config);

    let router = systemprompt_core_mcp::create_router(server);

    let addr = format!("0.0.0.0:{}", config.port);
    let listener = tokio::net::TcpListener::bind(&addr).await?;
    tracing::info!(addr = %addr, "MCP server listening");

    axum::serve(listener, router).await?;
    Ok(())
}

Tool Implementation

File: src/tools/mod.rs. See extensions/mcp/systemprompt/src/tools/ for reference.

pub fn register_tools() -> Vec<Tool> {
    vec![
        create_tool("my_tool", "My Tool", "Description", input_schema(), output_schema()),
    ]
}

pub async fn handle_tool_call(
    name: &str,
    request: CallToolRequestParam,
    db_pool: &DbPool,
) -> Result<CallToolResult, McpError> {
    match name {
        "my_tool" => handle_my_tool(db_pool, request).await,
        _ => Err(McpError::method_not_found())
    }
}

Error Handling

use systemprompt_traits::ExtensionError;

#[derive(Error, Debug)]
pub enum ToolError {
    #[error("Missing parameter: {name}")]
    MissingParameter { name: String },

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

impl ExtensionError for ToolError {
    fn code(&self) -> &'static str {
        match self {
            Self::MissingParameter { .. } => "MISSING_PARAMETER",
            Self::Io(_) => "IO_ERROR",
            Self::Database(_) => "DATABASE_ERROR",
        }
    }

    fn status(&self) -> StatusCode {
        match self {
            Self::MissingParameter { .. } => StatusCode::BAD_REQUEST,
            Self::Io(_) | Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

Checklist

  • Package name follows systemprompt-mcp-{name} pattern
  • Located in extensions/mcp/
  • module.yml with server metadata and tools
  • Binary target defined in Cargo.toml
  • Initializes logging
  • Loads configuration
  • Registers tools
  • Binds to configured port

Tool Quality

  • Each tool has unique name
  • Clear description of purpose
  • Input schema defines all parameters
  • Output schema documents response format
  • Proper error handling with descriptive messages
  • Structured logging with context

Code Quality

Metric Limit
File length 300 lines
Function length 75 lines
No unwrap() Use ? or ok_or_else()
No inline comments Code documents itself

Troubleshooting

Issue Solution
Port in use Change port in module.yml
Tool not found Check tool name in registration
Config error Verify module.yml syntax

Quick Reference

Task Command
Build cargo build -p systemprompt-mcp-{name}
Run cargo run -p systemprompt-mcp-{name}
Lint cargo clippy -p systemprompt-mcp-{name} -- -D warnings
Format cargo fmt -p systemprompt-mcp-{name} -- --check

-> See Rust Standards for code style -> See MCP Extension for domain reference