Last active
July 20, 2025 17:24
-
-
Save matthewjberger/3736d46ccc6048a794c2e7aadfac96e0 to your computer and use it in GitHub Desktop.
Rust MCP minimal example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"mcpServers": { | |
"weather": { | |
"command": "cargo", | |
"args": [ | |
"run", | |
"--manifest-path", | |
"C:\\Users\\you\\code\\my-rust-mcp-server\\Cargo.toml" | |
] | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [dependencies] | |
// anyhow = "1.0.98" | |
// axum = { version = "0.8.4", features = ["macros"] } | |
// rmcp = { version = "0.2.0", features = ["auth", "server", "transport-io", "transport-sse-server", "transport-streamable-http-server"] } | |
// serde = { version = "1.0.219", features = ["derive"] } | |
// tokio = { version = "1.46.0", features = ["macros", "rt-multi-thread", "signal"] } | |
// tracing = "0.1.41" | |
// tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } | |
// Inspect the server with npx @modelcontextprotocol/inspector | |
// Add to claude code with claude mcp add --transport http nightshade http://127.0.0.1:8000/mcp | |
// and make sure this app is running when claude code starts up | |
use anyhow::Result; | |
use rmcp::{ | |
ServerHandler, | |
handler::server::{router::tool::ToolRouter, tool::Parameters}, | |
model::*, | |
schemars, tool, tool_handler, tool_router, | |
transport::streamable_http_server::{ | |
StreamableHttpService, session::local::LocalSessionManager, | |
}, | |
}; | |
use tokio::time::{Duration, sleep}; | |
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; | |
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] | |
pub struct Request { | |
#[schemars(description = "Input parameter")] | |
pub input: String, | |
} | |
#[derive(Debug, Clone)] | |
pub struct Server { | |
tool_router: ToolRouter<Self>, | |
} | |
#[tool_router] | |
impl Server { | |
pub fn new() -> Self { | |
Self { | |
tool_router: Self::tool_router(), | |
} | |
} | |
#[tool(description = "Sleeps for 2 seconds and returns 4")] | |
async fn my_tool(&self, Parameters(Request { input }): Parameters<Request>) -> String { | |
tracing::info!("Received request with input: {}", input); | |
sleep(Duration::from_secs(2)).await; | |
tracing::info!("Completed async work"); | |
format!("Processed '{}' and got result: 4", input) | |
} | |
} | |
#[tool_handler] | |
impl ServerHandler for Server { | |
fn get_info(&self) -> ServerInfo { | |
ServerInfo { | |
instructions: Some("A simple MCP server that returns 4".into()), | |
capabilities: ServerCapabilities::builder().enable_tools().build(), | |
..Default::default() | |
} | |
} | |
} | |
#[tokio::main] | |
async fn main() -> Result<()> { | |
tracing_subscriber::registry() | |
.with( | |
tracing_subscriber::EnvFilter::try_from_default_env() | |
.unwrap_or_else(|_| "debug".to_string().into()), | |
) | |
.with(tracing_subscriber::fmt::layer()) | |
.init(); | |
let service = StreamableHttpService::new( | |
|| Ok(Server::new()), | |
LocalSessionManager::default().into(), | |
Default::default(), | |
); | |
let router = axum::Router::new().nest_service("/mcp", service); | |
let tcp_listener = tokio::net::TcpListener::bind("127.0.0.1:8000").await?; | |
tracing::info!("MCP server listening on 127.0.0.1:8000"); | |
axum::serve(tcp_listener, router) | |
.with_graceful_shutdown(async { tokio::signal::ctrl_c().await.unwrap() }) | |
.await?; | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment