🙌

MCPの公式Rust SDK(rmcp)でHello World

に公開

RustにはMCPサーバー用の公式SDKがあります。

https://github.com/modelcontextprotocol/rust-sdk

このリポジトリのREADME.mdによると、現在(2025/07/02)はファーストリリースが行われていません。
Usageを見るとrmcpというクレート名で使えるようです。
crates.ioでrmcpを検索したところ、以下のページが見つかりました。

https://crates.io/crates/rmcp

このページの記述内容は前述の公式SDKのリポジトリとそっくりですが、Repositoryのリンクが異なります。
リンク先のhttps://github.com/4t145/rmcp/を見ると、このリポジトリの内容が公式SDKに統合される予定のようです。

This crate was merged into official sdk. We will continue our development there.

また、公式リポジトリのIssueを見ると、リリース作業が進行中であることがわかります。

https://github.com/modelcontextprotocol/rust-sdk/issues/277

現在の実装とほとんど変わらずリリースされる雰囲気に見えます。
そこで、リリースより一足先に公式SDKを使ってみました。

公式のサンプルコード

公式SDKにはサンプルコードがいくつか用意されています。
サーバーとクライアントでサンプルコードのディレクトリが分かれています。

sh
$ git clone https://github.com/modelcontextprotocol/rust-sdk.git

# サーバーのサンプルコードがあるディレクトリ
$ cd ./examples/servers

リポジトリをcloneしてビルドしてみると、正常にコンパイルできました。
ただしOpenSSLがインストールされていない場合はビルドに失敗します。
そこで、Docker上でビルドできる環境を整えたリポジトリを作成しました。

https://github.com/arapower/mcp_examples_rust

ビルド用スクリプトも用意してありますので、サンプルを手軽に動かしたい場合はREADMEを参考にしてください。

実装

ここからは、上記サンプルを参考にrmcpで簡単なMCPサーバーを作成します。
最終的には、以下のリポジトリと同じ内容になります。

https://github.com/arapower/rmcp-hello-world

手順

sh
# プロジェクト作成
$ cargo new rmcp-hello-world
$ cd rmcp-hello-world

# サンプルコード作成(ファイル内容は後述)
$ vi src/main.rs
$ vi Cargo.toml

# Docker利用時(ファイル内容は後述)
$ vi Dockerfile
$ vi build.sh
$ chmod +x build.sh

# ビルド実行
$ ./build.sh
# もしくはローカル環境で
#   cargo build --release

# バイナリ生成確認
$ ls target/release/rmcp-hello-world

以下はサンプルコードの内容です。

Cargo.toml
Cargo.toml
[package]
name = "rmcp-hello-world"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk.git", rev = "8a577659f01917289064c0a3c753392b44305ef4", features = [
    "server",
    "transport-io",
] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = [
    "env-filter",
    "std",
    "fmt",
] }
schemars = "0.8"
src/main.rs
src/main.rs
use rmcp::{ServiceExt, transport::stdio, ServerHandler, model::*, tool, schemars};
use serde::Deserialize;
use tracing_subscriber::{self, EnvFilter};

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct HelloRequest {
    pub name: String,
}

#[derive(Clone)]
pub struct HelloTool;

#[tool(tool_box)]
impl HelloTool {
    #[tool(description = "Say hello world")]
    pub async fn hello_world(&self) -> Result<CallToolResult, rmcp::Error> {
        Ok(CallToolResult::success(vec![Content::text("Hello, world!".to_string())]))
    }

    #[tool(description = "Say hello to someone")]
    pub async fn hello(&self, #[tool(aggr)] req: HelloRequest) -> Result<CallToolResult, rmcp::Error> {
        Ok(CallToolResult::success(vec![Content::text(format!("Hello, {}", req.name))]))
    }
}

#[tool(tool_box)]
impl ServerHandler for HelloTool {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::V_2024_11_05,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some("This server provides 'hello_world' and 'hello' tools.".to_string()),
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::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 Hello MCP server");

    let service = HelloTool.serve(stdio()).await.inspect_err(|e| {
        tracing::error!("serving error: {:?}", e);
    })?;

    service.waiting().await?;
    Ok(())
}
Dockerfile
Dockerfile
FROM rust:latest

# Install Dependencies
RUN apt-get update \
  && apt-get install -y --no-install-recommends \
     pkg-config \
     libssl-dev \
     bash \
     git \
  && rm -rf /var/lib/apt/lists/*

ARG USER_NAME=rustuser
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN groupadd -g ${GROUP_ID} ${USER_NAME} \
    && useradd -m -u ${USER_ID} -g ${USER_NAME} ${USER_NAME}

WORKDIR /workspace

USER ${USER_NAME}

CMD ["/bin/bash"]
build.sh
build.sh
#!/bin/sh

docker build -t rust-dev .
docker run --rm -v "$(pwd)":/workspace rust-dev cargo build --release

# ./target/release/rmcp-hello-world

動作確認

MCPサーバーの動作確認にはMCP Inspectorを使うと便利です。

https://github.com/modelcontextprotocol/inspector

MCP Inspectorを使うことで、AIエージェントの代わりにブラウザ上からMCPサーバーへアクセスし、動作確認ができます。
起動コマンド例は以下の通りです。

$ npx @modelcontextprotocol/inspector
Need to install the following packages:
@modelcontextprotocol/inspector@0.15.0
Ok to proceed? (y)

Starting MCP inspector...
⚙️ Proxy server listening on 127.0.0.1:6277
🔑 Session token: 99f3b3f167d34e7f21b6d9bc5e4a7f120f7674a3f788e57cd921f3011d119045
Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth

🔗 Open inspector with token pre-filled:
   http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=99f3b3f167d34e7f21b6d9bc5e4a7f120f7674a3f788e57cd921f3011d119045

🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀

表示されたトークン付きURL(例: http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...)にブラウザでアクセスすると、MCP Inspectorの画面が開きます。

MCPサーバーに接続する

ブラウザ上で以下の設定を入力します。

  • Transport Type: STDIO
  • Command: ビルドしたバイナリファイルへの絶対パス(例: /home/user/git/rmcp-hello-world/target/release/rmcp-hello-world

ConnectボタンをクリックするとMCPサーバーに接続されます。
成功するとTools欄にList Toolsボタンが表示されます。

Tools

hello

helloを選択すると右側にname欄が表示されます。
name欄に任意の文字列を入力し、Run ToolボタンをクリックするとHello, というレスポンスが得られます。

hello_world

hello_worldは入力欄がなく、Run ToolボタンをクリックするとHello, world!というレスポンスが返ります。

サンプルコードの解説

Cargo.toml

rmcpはGitHubリポジトリとコミットハッシュを指定しています。

[dependencies]
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk.git", rev = "8a577659f01917289064c0a3c753392b44305ef4", features = [
    "server",
    "transport-io",
] }

これはmainブランチの実装が変わってもサンプルコードが動作することを保証するためです。

src/main.rs

use rmcp::{ServiceExt, transport::stdio, ServerHandler, model::*, tool, schemars};

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct HelloRequest {
    pub name: String,
}

#[derive(Clone)]
pub struct HelloTool;

#[tool(tool_box)]
impl HelloTool {
    #[tool(description = "Say hello world")]
    pub async fn hello_world(&self) -> Result<CallToolResult, rmcp::Error> {
        Ok(CallToolResult::success(vec![Content::text("Hello, world!".to_string())]))
    }

    #[tool(description = "Say hello to someone")]
    pub async fn hello(&self, #[tool(aggr)] req: HelloRequest) -> Result<CallToolResult, rmcp::Error> {
        Ok(CallToolResult::success(vec![Content::text(format!("Hello, {}", req.name))]))
    }
}

#[tool(tool_box)]
impl ServerHandler for HelloTool {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::V_2024_11_05,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some("This server provides 'hello_world' and 'hello' tools.".to_string()),
        }
    }
}

impl HelloTool内にMCPサーバーの機能を実装しています。
#[tool(tool_box)]を付与することでToolとして必要な実装が自動で追加されます。
各メソッドに#[tool]を付けることでToolとして公開されます。

fn helloは引数を受け取り、(&self, #[tool(aggr)] req: HelloRequest)の形でリクエスト内の引数を受け取れます。

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // --snip--

    let service = HelloTool.serve(stdio()).await.inspect_err(|e| {
        tracing::error!("serving error: {:?}", e);
    })?;

    service.waiting().await?;
    Ok(())
}

main関数ではMCPサービスの起動処理を行っています。

その他のサンプル

最後に、私がRustのコーディング時に使うために自作したMCPサーバーを紹介します。
よければ参考にしてください。

https://github.com/arapower/webfetch-mcp-server

Discussion