Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active July 20, 2025 17:24
Show Gist options
  • Save matthewjberger/3736d46ccc6048a794c2e7aadfac96e0 to your computer and use it in GitHub Desktop.
Save matthewjberger/3736d46ccc6048a794c2e7aadfac96e0 to your computer and use it in GitHub Desktop.
Rust MCP minimal example
{
"mcpServers": {
"weather": {
"command": "cargo",
"args": [
"run",
"--manifest-path",
"C:\\Users\\you\\code\\my-rust-mcp-server\\Cargo.toml"
]
}
}
}
// [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