Merge pull request #207 from modelcontextprotocol/examples/calculator-rust
feat: add Rust binary calculator example
This commit is contained in:
commit
e54083549b
7 changed files with 222 additions and 5 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -109,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
|
||||
|
|
@ -15,10 +15,11 @@ 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 |
|
||||
| `calculator-rust` | Binary | Compiled Rust binary as MCP Bundle |
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
5
examples/calculator-rust/.mcpbignore
Normal file
5
examples/calculator-rust/.mcpbignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Cargo.toml
|
||||
Cargo.lock
|
||||
src/
|
||||
target/
|
||||
README.md
|
||||
18
examples/calculator-rust/Cargo.toml
Normal file
18
examples/calculator-rust/Cargo.toml
Normal file
|
|
@ -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
|
||||
73
examples/calculator-rust/README.md
Normal file
73
examples/calculator-rust/README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# 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
|
||||
├── .mcpbignore # Exclude source from bundle
|
||||
└── src/
|
||||
└── main.rs # Calculator MCP server (~80 LOC)
|
||||
```
|
||||
|
||||
After building, the `server/` directory is created with the compiled binary.
|
||||
|
||||
## Building
|
||||
|
||||
Requires [Rust](https://rustup.rs/) 1.85+.
|
||||
|
||||
```bash
|
||||
cd examples/calculator-rust
|
||||
cargo build --release
|
||||
mkdir -p server
|
||||
cp target/release/mcp-calculator server/
|
||||
```
|
||||
|
||||
## 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)
|
||||
36
examples/calculator-rust/manifest.json
Normal file
36
examples/calculator-rust/manifest.json
Normal file
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
80
examples/calculator-rust/src/main.rs
Normal file
80
examples/calculator-rust/src/main.rs
Normal file
|
|
@ -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<Self>,
|
||||
}
|
||||
|
||||
#[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<SumRequest>) -> String {
|
||||
(a + b).to_string()
|
||||
}
|
||||
|
||||
#[tool(description = "Calculate the difference of two numbers")]
|
||||
fn sub(&self, Parameters(SubRequest { a, b }): Parameters<SubRequest>) -> 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(())
|
||||
}
|
||||
Loading…
Reference in a new issue