From cc4aa751b6eab80dcda3f29e2028c1c07c609ada Mon Sep 17 00:00:00 2001 From: Bryan Thompson Date: Fri, 13 Mar 2026 16:36:47 -0500 Subject: [PATCH 1/2] feat: add Rust binary calculator example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First example of the binary server type — a compiled Rust MCP server packaged as an MCPB. Based on the official Rust SDK calculator example (rmcp 1.2.0, 2 tools: sum/sub, ~80 LOC). Includes Cargo.toml, Dockerfile for Docker-based builds, .mcpbignore to exclude source from the bundle, and README with build/pack/test instructions. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/README.md | 11 ++-- examples/calculator-rust/.gitignore | 3 + examples/calculator-rust/.mcpbignore | 7 +++ examples/calculator-rust/Cargo.toml | 18 ++++++ examples/calculator-rust/Dockerfile | 11 ++++ examples/calculator-rust/README.md | 87 ++++++++++++++++++++++++++ examples/calculator-rust/manifest.json | 36 +++++++++++ examples/calculator-rust/src/main.rs | 80 +++++++++++++++++++++++ 8 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 examples/calculator-rust/.gitignore create mode 100644 examples/calculator-rust/.mcpbignore create mode 100644 examples/calculator-rust/Cargo.toml create mode 100644 examples/calculator-rust/Dockerfile create mode 100644 examples/calculator-rust/README.md create mode 100644 examples/calculator-rust/manifest.json create mode 100644 examples/calculator-rust/src/main.rs diff --git a/examples/README.md b/examples/README.md index 24480ed..334e531 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,11 +14,12 @@ But, the MCP servers themselves are not robust secure production ready servers a ## Examples Included -| Example | Type | Demonstrates | -| --------------------- | ------- | ---------------------------------------- | -| `hello-world-node` | Node.js | Basic MCP server with simple time tool | -| `chrome-applescript` | Node.js | Browser automation via AppleScript | -| `file-manager-python` | Python | File system operations and path handling | +| Example | Type | Demonstrates | +| --------------------- | ------- | ------------------------------------------- | +| `hello-world-node` | Node.js | Basic MCP server with simple time tool | +| `chrome-applescript` | Node.js | Browser automation via AppleScript | +| `file-manager-python` | Python | File system operations and path handling | +| `calculator-rust` | Binary | Compiled Rust binary as MCP Bundle | ## Usage diff --git a/examples/calculator-rust/.gitignore b/examples/calculator-rust/.gitignore new file mode 100644 index 0000000..ae1d722 --- /dev/null +++ b/examples/calculator-rust/.gitignore @@ -0,0 +1,3 @@ +target/ +Cargo.lock +server/mcp-calculator diff --git a/examples/calculator-rust/.mcpbignore b/examples/calculator-rust/.mcpbignore new file mode 100644 index 0000000..3b44c2b --- /dev/null +++ b/examples/calculator-rust/.mcpbignore @@ -0,0 +1,7 @@ +Cargo.toml +Cargo.lock +src/ +target/ +Dockerfile +README.md +.gitignore diff --git a/examples/calculator-rust/Cargo.toml b/examples/calculator-rust/Cargo.toml new file mode 100644 index 0000000..dfbca7e --- /dev/null +++ b/examples/calculator-rust/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mcp-calculator" +version = "1.0.0" +edition = "2024" + +[dependencies] +rmcp = { version = "1.2.0", features = ["server", "macros", "transport-io", "schemars"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] } +schemars = "1.0" + +[profile.release] +strip = true +lto = true diff --git a/examples/calculator-rust/Dockerfile b/examples/calculator-rust/Dockerfile new file mode 100644 index 0000000..a8358f5 --- /dev/null +++ b/examples/calculator-rust/Dockerfile @@ -0,0 +1,11 @@ +FROM rust:1.94-bookworm AS builder + +WORKDIR /app +COPY Cargo.toml ./ +COPY src/ ./src/ + +RUN cargo build --release + +FROM debian:bookworm-slim +COPY --from=builder /app/target/release/mcp-calculator /mcp-calculator +ENTRYPOINT ["/mcp-calculator"] diff --git a/examples/calculator-rust/README.md b/examples/calculator-rust/README.md new file mode 100644 index 0000000..adaf4d1 --- /dev/null +++ b/examples/calculator-rust/README.md @@ -0,0 +1,87 @@ +# Calculator Rust Binary Example + +Demonstrates packaging a compiled Rust binary as an MCP Bundle using the `binary` server type. Based on the [official Rust MCP SDK calculator example](https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples/servers). + +## What is Binary Server Type? + +Unlike `node` or `uv` server types where the source code is bundled and executed by a runtime, the `binary` server type packages a pre-compiled native executable. This is useful for: + +- Rust, Go, C/C++ MCP servers +- Performance-sensitive workloads +- Servers with no runtime dependencies + +The tradeoff is that binaries are platform-specific — you need a separate build for each target OS/architecture. + +## Structure + +``` +calculator-rust/ +├── manifest.json # server.type = "binary" +├── Cargo.toml # Rust project +├── Dockerfile # Build via Docker +├── .mcpbignore # Exclude source from bundle +├── .gitignore # Exclude build artifacts +└── src/ + └── main.rs # Calculator MCP server (~80 LOC) +``` + +After building, the `server/` directory is created with the compiled binary. + +## Building + +### Native (requires Rust 1.85+) + +```bash +cd examples/calculator-rust +cargo build --release +mkdir -p server +cp target/release/mcp-calculator server/ +``` + +### Docker (no local Rust required) + +```bash +cd examples/calculator-rust +docker build -t mcp-calculator . +# Extract the binary (linux only) +docker create --name calc mcp-calculator +docker cp calc:/mcp-calculator server/mcp-calculator +docker rm calc +chmod +x server/mcp-calculator +``` + +## Packing + +```bash +mcpb pack examples/calculator-rust +``` + +The `.mcpbignore` file excludes source code and build artifacts — only `manifest.json` and `server/mcp-calculator` end up in the bundle. + +## Testing + +```bash +# MCP protocol handshake +echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' \ + | ./server/mcp-calculator + +# Tool call +(printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}\n' +printf '{"jsonrpc":"2.0","method":"notifications/initialized"}\n' +printf '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"sum","arguments":{"a":3,"b":4}}}\n' +sleep 1) | ./server/mcp-calculator +``` + +Expected: `sum(3, 4)` returns `{"text":"7"}`. + +## Tools + +- **sum** — Calculate the sum of two numbers +- **sub** — Calculate the difference of two numbers + +## Notes + +- Binary size: ~2.5 MB (stripped, LTO enabled) +- The `Cargo.toml` uses `edition = "2024"` (Rust 1.85+) +- Logs are written to stderr via `tracing`, so they don't interfere with MCP's stdio transport +- Uses the [rmcp](https://crates.io/crates/rmcp) crate (official Rust MCP SDK) diff --git a/examples/calculator-rust/manifest.json b/examples/calculator-rust/manifest.json new file mode 100644 index 0000000..fd31518 --- /dev/null +++ b/examples/calculator-rust/manifest.json @@ -0,0 +1,36 @@ +{ + "$schema": "../../dist/mcpb-manifest.schema.json", + "manifest_version": "0.3", + "name": "calculator-rust", + "display_name": "Calculator (Rust Binary)", + "version": "1.0.0", + "description": "A simple calculator MCP server compiled from Rust", + "long_description": "Demonstrates packaging a compiled Rust binary as an MCP Bundle using the binary server type. The server provides sum and sub tools for basic arithmetic. Based on the official Rust MCP SDK calculator example.", + "author": { + "name": "Model Context Protocol" + }, + "server": { + "type": "binary", + "entry_point": "server/mcp-calculator", + "mcp_config": { + "command": "${__dirname}/server/mcp-calculator", + "args": [], + "env": {} + } + }, + "tools": [ + { + "name": "sum", + "description": "Calculate the sum of two numbers" + }, + { + "name": "sub", + "description": "Calculate the difference of two numbers" + } + ], + "keywords": ["calculator", "rust", "binary", "example"], + "license": "Apache-2.0", + "compatibility": { + "platforms": ["darwin", "linux"] + } +} diff --git a/examples/calculator-rust/src/main.rs b/examples/calculator-rust/src/main.rs new file mode 100644 index 0000000..4a0f885 --- /dev/null +++ b/examples/calculator-rust/src/main.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use rmcp::{ + ServerHandler, ServiceExt, + handler::server::{router::tool::ToolRouter, wrapper::Parameters}, + model::{ServerCapabilities, ServerInfo}, + schemars, tool, tool_handler, tool_router, + transport::stdio, +}; +use tracing_subscriber::{self, EnvFilter}; + +// --- Calculator types --- + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct SumRequest { + #[schemars(description = "the left hand side number")] + pub a: i32, + pub b: i32, +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct SubRequest { + #[schemars(description = "the left hand side number")] + pub a: i32, + #[schemars(description = "the right hand side number")] + pub b: i32, +} + +// --- Calculator server --- + +#[derive(Debug, Clone)] +pub struct Calculator { + tool_router: ToolRouter, +} + +#[tool_router] +impl Calculator { + pub fn new() -> Self { + Self { + tool_router: Self::tool_router(), + } + } + + #[tool(description = "Calculate the sum of two numbers")] + fn sum(&self, Parameters(SumRequest { a, b }): Parameters) -> String { + (a + b).to_string() + } + + #[tool(description = "Calculate the difference of two numbers")] + fn sub(&self, Parameters(SubRequest { a, b }): Parameters) -> String { + (a - b).to_string() + } +} + +#[tool_handler] +impl ServerHandler for Calculator { + fn get_info(&self) -> ServerInfo { + ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + .with_instructions("A simple calculator".to_string()) + } +} + +// --- Main --- + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into())) + .with_writer(std::io::stderr) + .with_ansi(false) + .init(); + + tracing::info!("Starting Calculator MCP server"); + + let service = Calculator::new().serve(stdio()).await.inspect_err(|e| { + tracing::error!("serving error: {:?}", e); + })?; + + service.waiting().await?; + Ok(()) +} From a907aead99c57b32a939e5d359ffd797fad0c991 Mon Sep 17 00:00:00 2001 From: Bryan Thompson Date: Fri, 13 Mar 2026 16:41:30 -0500 Subject: [PATCH 2/2] chore: remove Dockerfile and .gitignore from calculator-rust example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align with other examples — no example ships a Dockerfile or its own .gitignore. Rust build artifacts are now covered by the repo-level .gitignore instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 6 ++++++ examples/calculator-rust/.gitignore | 3 --- examples/calculator-rust/.mcpbignore | 2 -- examples/calculator-rust/Dockerfile | 11 ----------- examples/calculator-rust/README.md | 16 +--------------- 5 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 examples/calculator-rust/.gitignore delete mode 100644 examples/calculator-rust/Dockerfile diff --git a/.gitignore b/.gitignore index ca98aa1..ec20709 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,8 @@ typings/ # Claude files .claude.json .claude/ +CLAUDE.md +PROJECT_STATUS.md # Vite .vite/ @@ -107,4 +109,8 @@ self-signed-key.pem invalid-json.json **/server/lib/** +# Rust example build artifacts +examples/calculator-rust/target/ +examples/calculator-rust/server/ + .yarn/install-state.gz \ No newline at end of file diff --git a/examples/calculator-rust/.gitignore b/examples/calculator-rust/.gitignore deleted file mode 100644 index ae1d722..0000000 --- a/examples/calculator-rust/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -target/ -Cargo.lock -server/mcp-calculator diff --git a/examples/calculator-rust/.mcpbignore b/examples/calculator-rust/.mcpbignore index 3b44c2b..29c73c3 100644 --- a/examples/calculator-rust/.mcpbignore +++ b/examples/calculator-rust/.mcpbignore @@ -2,6 +2,4 @@ Cargo.toml Cargo.lock src/ target/ -Dockerfile README.md -.gitignore diff --git a/examples/calculator-rust/Dockerfile b/examples/calculator-rust/Dockerfile deleted file mode 100644 index a8358f5..0000000 --- a/examples/calculator-rust/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM rust:1.94-bookworm AS builder - -WORKDIR /app -COPY Cargo.toml ./ -COPY src/ ./src/ - -RUN cargo build --release - -FROM debian:bookworm-slim -COPY --from=builder /app/target/release/mcp-calculator /mcp-calculator -ENTRYPOINT ["/mcp-calculator"] diff --git a/examples/calculator-rust/README.md b/examples/calculator-rust/README.md index adaf4d1..1482131 100644 --- a/examples/calculator-rust/README.md +++ b/examples/calculator-rust/README.md @@ -18,9 +18,7 @@ The tradeoff is that binaries are platform-specific — you need a separate buil calculator-rust/ ├── manifest.json # server.type = "binary" ├── Cargo.toml # Rust project -├── Dockerfile # Build via Docker ├── .mcpbignore # Exclude source from bundle -├── .gitignore # Exclude build artifacts └── src/ └── main.rs # Calculator MCP server (~80 LOC) ``` @@ -29,7 +27,7 @@ After building, the `server/` directory is created with the compiled binary. ## Building -### Native (requires Rust 1.85+) +Requires [Rust](https://rustup.rs/) 1.85+. ```bash cd examples/calculator-rust @@ -38,18 +36,6 @@ mkdir -p server cp target/release/mcp-calculator server/ ``` -### Docker (no local Rust required) - -```bash -cd examples/calculator-rust -docker build -t mcp-calculator . -# Extract the binary (linux only) -docker create --name calc mcp-calculator -docker cp calc:/mcp-calculator server/mcp-calculator -docker rm calc -chmod +x server/mcp-calculator -``` - ## Packing ```bash