DEV Community

xbill
xbill

Posted on • Originally published at Medium on

MCP Development Updates with Rust and Claude Code

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

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:

Getting started

For a Linux like environment the command looks like this:

curl — proto ‘=https’ — tlsv1.2 -sSf https://sh.rustup.rs | sh
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"] }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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%)
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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$
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

The second is quieter, and only shows up because make lint runs cargo clippy with -D warnings:

warning: field `tool_router` is never read
Enter fullscreen mode Exit fullscreen mode

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 {
Enter fullscreen mode Exit fullscreen mode

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"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"}}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)