Leveraging coding agents and the underlying LLM to build Model Context Protocol (MCP) AI applications in the Rust programming language with a local development environment.
This looks strangely Familiar!
What is old is new. This article revisits the original MCP article first published in February 2026. A lot has changed! and a lot hasn’t.
MCP Development with Rust and Gemini CLI
What has changed since February?
Two things, and they pull in opposite directions.
The official Rust SDK moved from rmcp 0.14.0 to rmcp 3.1.0 — a version number that looks alarming until you actually do the upgrade. The whole tool_router / tool / Parameters macro surface that the original tutorial was built on survived intact.
The coding agent changed. This time the MCP client is Claude Code rather than Gemini CLI. The point the original article made still holds: the MCP server does not know or care which client is on the other end of the pipe. The same compiled Rust binary that Gemini CLI talked to in February is the binary Claude Code talks to here, unchanged apart from the SDK bump.
Everything in this walkthrough was run on rustc 1.96.0 with Claude Code 2.1.220.
Why not just use Python?
Python has traditionally been the main coding language for ML and AI tools. One of the strengths of the MCP protocol is that the actual implementation details are independent of the development language. The reality is that not every project is coded in Python- and MCP allows you to use the latest AI approaches with other coding languages.
What is this Tutorial Trying to Do?
Traditionally, ML and AI tools have been deployed in interpreted languages like Python, and Java. One of the key goals of this tutorial is to validate that a compiled language like Rust can be used for AI software development beyond the traditional interpreted languages.
What is Rust?
Rust is a high performance, memory safe, compiled language:
Rust provides memory safe operations beyond C/C++ and also can provide exceptional performance gains as it is compiled directly to native binaries.
Initial Environment Setup
The environment is meant to be run from a Bash like shell. You can run this from a Linux VM, ChromeOS Linux VM, Firebase Studio environment, or any environment that provides a basic shell. You will also need a working Docker environment.
Rust Setup
Instructions to install Rust are available here:
For a Linux like environment the command looks like this:
curl — proto ‘=https’ — tlsv1.2 -sSf https://sh.rustup.rs | sh
Rust also depends on a working C compiler and OpenSSL setup. For a Debian 13 system— install the basic tools for development:
sudo apt install build-essential
sudo apt install libssl-dev
sudo apt install pkg-config
sudo apt-get install libudev-dev
sudo apt install make
sudo apt install git
Claude Code Setup
You will also need a working Claude Code installation to act as the MCP client. Instructions are available here:
Advanced setup - Claude Code Docs
Check the install with:
xbill@penguin:~$ claude --version
2.1.220 (Claude Code)
Getting Started with Rust and MCP
When MCP was first released, there were several competing Rust frameworks that provided support for the protocol. Eventually, one official supported SDK was consolidated to provide a standard package for building MCP applications with Rust. This SDK is more like a toolbox that provides many options- clients/servers, different transports, and even more advanced integration options.
The official MCP Rust SDK (rmcp) is available here:
GitHub - modelcontextprotocol/rust-sdk: The official Rust SDK for the Model Context Protocol
The SDK has moved on considerably since February. The dependency block for this project now looks like this:
[dependencies]
anyhow = "^1.0.104"
rmcp = { version = "3.1.0", features = ["server", "macros", "transport-io"] }
serde = { version = "^1.0.229", features = ["derive"] }
serde_json = "^1.0.151"
schemars = "^1.2.2"
sysinfo = "0.39.6"
tokio = { version = "^1.53.1", features = ["macros", "rt-multi-thread", "signal"] }
tracing = "^0.1.44"
tracing-subscriber = { version = "^0.3.23", features = ["env-filter", "json", "tracing-log"] }
The feature flags are unchanged — server, macros, and transport-io are still all you need for a stdio server.
Where do I start?
The strategy for validating Rust for MCP development is a incremental step by step approach.
First, the basic development environment is setup with the required system variables and a working Claude Code configuration.
A command line version of the System Information tool is built with Claude Code.
Then, a minimal Rust MCP Server is built with the stdio transport working directly with Claude Code in the local environment. This validates the connection from Claude Code to the local compiled Rust process via MCP. The MCP client (Claude Code) and the Rust MCP compiled binary Server both run in the same environment.
Setup the Basic Environment
At this point you should have a working Rust compiler and a working Claude Code installation. The next step is to clone the GitHub samples repository with support scripts:
cd ~
git clone https://github.com/xbill9/iap-https-rust
Then run init.sh from the cloned directory.
The script will attempt to determine your shell environment and set the correct variables:
cd iap-https-rust
source init.sh
If your session times out or you need to re-authenticate- you can run the set_env.sh script to reset your environment variables:
cd iap-https-rust
source set_env.sh
Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.
Minimal System Information Tool Build
The first step is to build the basic tool directly with Rust. This allows the tool to be debugged and tested locally before adding the MCP layer.
First build the tool locally:
xbill@penguin:~/iap-https-rust/stdio$ make
Building the Rust project...
Compiling sysutils-stdio-rust v0.3.0 (/home/xbill/iap-https-rust/stdio)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.69s
xbill@penguin:~/iap-https-rust/stdio$
then lint check the code:
xbill@penguin:~/iap-https-rust/stdio$ make lint
Linting code...
Checking sysutils-stdio-rust v0.3.0 (/home/xbill/iap-https-rust/stdio)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.71s
xbill@penguin:~/iap-https-rust/stdio$
and run local tests:
xbill@penguin:~/iap-https-rust/stdio$ make test
Running tests...
Compiling sysutils-stdio-rust v0.3.0 (/home/xbill/iap-https-rust/stdio)
Finished `test` profile [unoptimized + debuginfo] target(s) in 2.79s
Running unittests src/main.rs (target/debug/deps/sysutils_stdio_rust-bf841fe23f00c0f0)
running 3 tests
test tests::test_schema_generation ... ok
test tests::test_disk_usage ... ok
test tests::test_local_system_info ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
xbill@penguin:~/iap-https-rust/stdio$
The last step is to build the production version:
xbill@penguin:~/iap-https-rust/stdio$ make release
Building Release...
Compiling sysutils-stdio-rust v0.3.0 (/home/xbill/iap-https-rust/stdio)
Finished `release` profile [optimized] target(s) in 1m 47s
xbill@penguin:~/iap-https-rust/stdio$
Running the Tool Locally
Once the release version has been built- the resulting binary can be executed directly in the local environment.
The quick summary of local system info can be run right from the Makefile:
xbill@penguin:~/iap-https-rust/stdio$ make info
System Information Report
=========================
System Information
------------------
System Name: Debian GNU/Linux
Kernel Version: 6.6.119-09283-g9c138e0af8c3
OS Version: 13
Host Name: penguin
CPU Information
---------------
Number of Cores: 12
Memory Information
------------------
Total Memory: 4424 MB
Used Memory: 3989 MB
Total Swap: 0 MB
Used Swap: 0 MB
Network Interfaces
------------------
dummy0 : RX: 0 bytes, TX: 0 bytes (MAC: 0a:5b:cd:04:62:01)
lo : RX: 10469 bytes, TX: 10469 bytes (MAC: 00:00:00:00:00:00)
docker0 : RX: 0 bytes, TX: 0 bytes (MAC: 9e:3a:b3:cf:98:5c)
eth0 : RX: 1065471328 bytes, TX: 303012262 bytes (MAC: e6:68:83:c3:b8:3c)
and also local disk information:
xbill@penguin:~/iap-https-rust/stdio$ make disk
Disk Usage Report
=================
/ btrfs 36921 / 385024 MB used (9.6%)
/opt/google/cros-containers ext4 74 / 74 MB used (100.0%)
/mnt/chromeos/fonts virtiofs 2071 / 2287 MB used (90.6%)
/mnt/shared 9p 1 / 2820 MB used (0.1%)
System Information with MCP STDIO Transport
One of the key features that the Rust rmcp SDK provides is abstracting various transport methods.
The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.
The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.
First- switch the directory with the Rust stdio sample code:
xbill@penguin:~/iap-https-rust/stdio$ make release
Building Release...
Finished `release` profile [optimized] target(s) in 0.38s
xbill@penguin:~/iap-https-rust/stdio$
You can validate the final result of the build by checking the compiled Rust binary:
xbill@penguin:~/iap-https-rust/stdio/target/release$ file sysutils-stdio-rust
sysutils-stdio-rust: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=eb3a59b145e1a195cee7ab83a7e08f02b382dbc3, stripped
xbill@penguin:~/iap-https-rust/stdio/target/release$
What actually broke in rmcp 3.x
Going from 0.14.0 to 3.1.0 sounds like a rewrite. It was two compiler errors.
The first is that ServerInfo is now marked non_exhaustive, so it can no longer be built with a struct literal:
error[E0639]: cannot create non-exhaustive struct using struct expression
--> src/main.rs:184:9
The February version of get_info looked like this:
ServerInfo {
instructions: Some(
"A system utilities MCP that provides detailed system information.".into(),
),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
The 3.x version uses the constructor and the builder helpers instead:
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions("A system utilities MCP that provides detailed system information.")
The second is quieter, and only shows up because make lint runs cargo clippy with -D warnings:
warning: field `tool_router` is never read
In rmcp 3.x the tool_handler macro defaults to router = Self::tool_router(), which builds a fresh router on every single tool call and leaves the one cached in new() unused. Naming the cached router explicitly restores the old behaviour and silences the dead code warning:
#[tool_handler(router = self.tool_router)]
impl ServerHandler for SysUtils {
That is the entire migration. The tool_router and tool attribute macros, Parameters, the input_schema attribute, transport::stdio(), ServiceExt and the draft-07 schemars generation all compiled without changes.
Connecting Claude Code to the MCP STDIO Server
This is where the client swap actually shows up. Claude Code reads project scoped MCP servers from a .mcp.json file in the directory you launch it from. The samples repository gitignores .mcp.json so that local API keys never reach git, so create one in the stdio directory with this content:
{
"mcpServers": {
"sysutils-stdio-rust": {
"type": "stdio",
"command": "${HOME}/iap-https-rust/stdio/target/release/sysutils-stdio-rust",
"args": ["--prebuilt", "--stdio"],
"env": {
"RUST_LOG": "trace"
}
}
}
}
The shape is nearly identical to the Gemini CLI settings.json from the original article — same mcpServers map, same command/args/env keys. Two small differences are worth noting: Claude Code wants the braced ${HOME} form for variable expansion, and it states the transport explicitly with a type of stdio. Because a project scoped .mcp.json can be shared with anyone who gets a copy of the directory, Claude Code asks you to approve it once, interactively, the first time you start a session there. If you would rather keep the server registration in your own Claude config instead of a file in the project directory, the same definition can be registered from the command line:
claude mcp add-json --scope local sysutils-stdio-rust \
'{"type":"stdio","command":"${HOME}/iap-https-rust/stdio/target/release/sysutils-stdio-rust","args":["--prebuilt","--stdio"],"env":{"RUST_LOG":"trace"}}'
Either way, once the server is approved the health check confirms the connection:
xbill@penguin:~/iap-https-rust/stdio$ claude mcp list
Checking MCP server health…
sysutils-stdio-rust: ${HOME}/iap-https-rust/stdio/target/release/sysutils-stdio-rust --prebuilt --stdio - ✔ Connected
xbill@penguin:~/iap-https-rust/stdio$ claude mcp get sysutils-stdio-rust
sysutils-stdio-rust:
Scope: Local config (private to you in this project)
Status: ✔ Connected
Type: stdio
Command: ${HOME}/iap-https-rust/stdio/target/release/sysutils-stdio-rust
Args: --prebuilt --stdio
Environment:
RUST_LOG=trace
The local MCP Server (sysutils-stdio-rust) can now be used directly using Claude Code as a MCP client. This is the same Rust binary that was tested locally as a standalone build. Inside a session the /mcp slash command shows the same information, and Claude Code can also be driven headlessly with -p, which makes the round trip easy to show:
claude -p "Use the local_system_info tool from the sysutils-stdio-rust MCP server and show me its raw output." \
--allowedTools "mcp __sysutils-stdio-rust__ local_system_info"
Raw output from `local_system_info`:
System Information Report
=========================
System Information
------------------
System Name: Debian GNU/Linux
Kernel Version: 6.6.119-09283-g9c138e0af8c3
OS Version: 13
Host Name: penguin
CPU Information
---------------
Number of Cores: 12
Memory Information
------------------
Total Memory: 4424 MB
Used Memory: 4225 MB
Total Swap: 0 MB
Used Swap: 0 MB
Network Interfaces
------------------
lo : RX: 10469 bytes, TX: 10469 bytes (MAC: 00:00:00:00:00:00)
eth0 : RX: 1069820317 bytes, TX: 310221542 bytes (MAC: e6:68:83:c3:b8:3c)
dummy0 : RX: 0 bytes, TX: 0 bytes (MAC: 0a:5b:cd:04:62:01)
docker0 : RX: 0 bytes, TX: 0 bytes (MAC: 9e:3a:b3:cf:98:5c)
One thing worth noting: memory is at 4225/4424 MB (~95% used) with no swap.
Note the tool name the client uses — mcp__sysutils-stdio-rust__local_system_info. Claude Code namespaces MCP tools as mcp, then the server name, then the tool name, joined by double underscores. That is how you scope permissions to a single tool on a single server.
The unprompted comment on the last line is the part that makes this worth doing at all. The Rust binary emitted a plain text report; the model read it and noticed the machine was nearly out of memory.
Project Package Details
The stdio project has been published to crates.io:
crates.io: Rust Package Registry
Summary
The potential for using Rust for MCP development with Claude Code was validated with a incremental step by step approach.
A minimal stdio transport MCP Server was built from Rust source code and validated with Claude Code running as a MCP client in the same local environment.
The February conclusion holds up better than expected. Six months of SDK churn — a major version jump on rmcp, and a completely different coding agent on the client side — cost two lines of Rust and one config file. That is the actual argument for MCP: the protocol boundary held, so the server did not care that the client changed underneath it.
This approach can be extended to more complex deployments using other MCP transports and Cloud based options.


Top comments (0)