RustでMCPサーバーを書く公式SDKの使い方
公式Rust SDK「rmcp」のマクロでツールを定義し、stdio・Streamable HTTPでMCPサーバーを動かす手順をコード付きで解説します。
RustでMCPサーバーを書くと何が手に入るか
MCP(Model Context Protocol)のRust実装はrmcpというクレート名で公式配布されており、Tier1(最上位の完成度・保守コミットメント)に分類されています。Tokioの非同期ランタイムを前提に設計されているため、すでにTokioベースのサービスを運用しているチームなら、既存のasyncコードベースにそのまま組み込めます。
このSDKは安定版のMCP仕様2026-07-28を実装しつつ、1つ前の2025-11-25リリースとも完全互換です。2026-07-28で追加された主要機能はサーバー発見・ネゴシエーション、トランスポート非依存のサブスクリプション、長時間実行タスク、レスポンスキャッシュ、複数往復のリクエスト、標準HTTPルーティングヘッダーの6つで、いずれもrmcpが対応しています。パッケージはコア機能を持つrmcpクレートと、ツールマクロを提供するrmcp-macrosクレートの2つに分かれています。
MCPの仕組み自体や他言語SDKとの比較はMCP実用ガイド、TypeScript・PythonでのMCPサーバー自作はMCPサーバー自作ガイドが扱っています。本記事はRust SDK(rmcp)固有の実装手順に絞ります。
導入からツール定義まで — 3ステップ
ステップ1: クレートを追加する
cargo addでserver機能フラグ付きのrmcpを追加します。tokio・serde・schemars(JSON Schema 2020-12生成用)が基本の依存関係です。
cargo add rmcp --features server開発版の機能を先取りしたい場合は、GitHubのmainブランチを直接指定するdevチャンネルも使えます。3.x系への移行を予定しているなら、破壊的変更が公式のmigrationガイドにまとまっているので事前に目を通しておくと手戻りが減ります。
ステップ2: マクロでツールを1つ実装する
rmcpの最大の特徴は、#[tool]・#[tool_router]・#[tool_handler]という3つのマクロが配線をほぼ自動化する点です。ツールだけを持つサーバーなら#[tool_router(server_handler)]を使い、ServerHandlerの実装を省略できます。
use rmcp::{handler::server::wrapper::Parameters, schemars, tool, tool_router, ServiceExt, transport::stdio};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
struct AddParams {
a: i32,
b: i32,
}
#[derive(Clone)]
struct Calculator;
#[tool_router(server_handler)]
impl Calculator {
#[tool(description = "Add two numbers")]
fn add(&self, Parameters(AddParams { a, b }): Parameters<AddParams>) -> String {
(a + b).to_string()
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let service = Calculator.serve(stdio()).await?;
service.waiting().await?;
Ok(())
}inputSchemaとoutputSchemaは引数の構造体AddParamsのフィールドから自動生成されます。構造体自体のドキュメントコメントは無視され、フィールド名・型・フィールドのドキュメントだけがスキーマに反映される点は覚えておいてください。
サーバー名やバージョンなど独自のメタデータを設定したい場合、あるいはツールとプロンプトのように複数のcapabilityを1つのサーバーに持たせたい場合は、#[tool_router]と#[tool_handler]を分けて明示的にServerHandlerを実装します。
#[tool_router]
impl Calculator {
#[tool(description = "Add two numbers")]
fn add(&self, Parameters(AddParams { a, b }): Parameters<AddParams>) -> String {
(a + b).to_string()
}
}
#[tool_handler(name = "calculator", version = "1.0.0", instructions = "A simple calculator")]
impl ServerHandler for Calculator {}ステップ3: stdioで起動しクライアントから呼ぶ
.serve(stdio())を呼ぶだけで標準入出力ベースのサーバーが起動します。動作確認だけなら、公開されているサーバー実装(server-everything等)をnpxで子プロセスとして起動し、Rust製クライアントから接続する構成が手早く試せます。
use rmcp::{ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}};
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ().serve(TokioChildProcess::new(Command::new("npx").configure(|cmd| {
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
}))?).await?;
Ok(())
}Claude Codeなどのホスト側からこの種のサーバーバイナリをサブプロセスとして起動しstdin/stdoutで通信させる設定は、MCPサーバー自作ガイドのClaude Code接続手順と共通です。
stdioの次はStreamable HTTPで公開する
複数クライアントから同じサーバーを使わせたい、あるいはリモートに置きたい場合はStreamable HTTPトランスポートに切り替えます。rmcpはサーバー側実装をtransport-streamable-http-server機能で、クライアント側をreqwestベースのtransport-streamable-http-client-reqwest機能で提供しており、サーバー側はTowerサービスとしてaxumやhyperのルーターにそのままマウントできます。
use rmcp::transport::StreamableHttpClientTransport;
let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp");
let client = ClientInfo::default().serve(transport).await?;クライアント側は同時に投げられるHTTP POSTの数をmax_concurrent_requestsで制御できます(デフォルトは16、1にすると順次実行、0は1として扱われます)。キャンセルや応答用には別枠のキューが1リクエスト分確保されており、そのタイムアウトはcontrol_request_timeout(デフォルト5秒)で設定します。セッション復旧は古いPOSTを最大5秒待ってから残りを打ち切る挙動で、打ち切られたPOSTは再送されません(サーバー側で処理済みの可能性があるため)。
旧HTTP+SSEトランスポートは意図的に未実装です。2024-11-05仕様で定義されていた2エンドポイント方式(GETのSSEチャンネル + POSTのメッセージエンドポイント)は2025-03-26でStreamable HTTPに置き換えられており、rmcpは現行仕様(2025-11-25・2026-07-28)のみを対象にしています。古い2024-11-05専用サーバーと通信する必要がある場合は、Streamable HTTPを話すプロキシを前段に置くか、この置き換え以前のバージョンに固定する必要があります。
2026-07-28の新機能をrmcpはどう実装しているか
2026-07-28仕様で追加されたTasks(長時間実行タスク)とCaching(レスポンスキャッシュ)は、rmcpではそれぞれ専用の仕組みとして実装されています。
Tasks: MCP Tasks拡張(SEP-2663)に対応しており、クライアントがenable_tasks()でcapabilityを宣言すると、サーバーはリクエストごとに同期実行と非同期タスク化のどちらにするかを選べます。時間のかかるtools/callをCreateTaskResultとして返し、クライアントはtasks/getでポーリング、tasks/updateで追加入力への応答、tasks/cancelで協調的キャンセルを行います。サーバー側のタスクライフサイクル管理はrmcp::task_manager::TaskManagerが担います。
Caching: SEP-2549のキャッシュヒント(ttlMs・cacheScope)が付いたレスポンスを、クライアントが透過的にキャッシュします。対象はserver/discover・tools/list・prompts/list・resources/list・resources/templates/list・resources/readの6種類です。既定で有効ですが、サーバーがttlMsを送らない限り何も保存されないため、対応していないサーバーとの互換性は保たれます。キャッシュはTTL経過で失効するほか、対応するlist_changed通知やresource updated通知が届くと自動的に無効化されます。呼び出し側のコードを変える必要はなく、peer.list_tools()やpeer.read_resource()の既存呼び出しがそのまま恩恵を受けます。挙動を調整したい場合はClientCacheConfigでTTLの上限・最大エントリ数・プリンシパルごとの分離キャッシュ・障害時のstale応答可否などを個別に設定でき、ClientCacheConfig::disabled()で丸ごと無効化することもできます。
残る4つの新機能(サーバー発見・ネゴシエーション、トランスポート非依存のサブスクリプション、複数往復のリクエスト、標準HTTPルーティングヘッダー)もrmcpは実装済みです。サブスクリプションはリソース更新の通知をトランスポートの種類を問わず配信する仕組みで、Streamable HTTPでもstdioでも同じAPIで購読できます。複数往復のリクエストは、1回のtools/callの中でサーバーからクライアントへの問い合わせ(Samplingやユーザー確認)を複数回挟めるようにする拡張で、対話的な承認フローを持つツールを作るときに関係してきます。
つまずきやすい3点
- ライフサイクルモードの選択:
serve()はレガシーのMCPライフサイクル(initialize→notifications/initialized)を使います。別のライフサイクルを明示的に選びたい場合はClientServiceExt::serve_with_lifecycleを使う必要があり、デフォルトのまま実装すると新しいネゴシエーション方式が使えません 3.x系への移行時の破壊的変更:rmcpは破壊的変更を伴うメジャーバージョンアップを行っており、3.xへの移行にはマイグレーションガイドの確認が必須です。バージョンを上げる前にリリースノートを読まずにビルドすると、マクロの呼び出し形式やトランスポートの初期化コードが一括でコンパイルエラーになります- 旧SSEトランスポートを探して見つからない:
2024-11-05仕様のHTTP+SSEトランスポートはrmcpに存在しません。これは実装漏れではなく意図的な非対応です。代替は本記事の「Streamable HTTPで公開する」節にある構成で、サーバー間のプッシュ通知やリソース更新の配信もStreamable HTTPのサブスクリプション機構に統合されています
どの構成を選ぶべきか
| 用途 | 構成 | 向き不向き |
|---|---|---|
| Claude Codeからローカルで呼ぶだけ | 構成stdio + #[tool_router(server_handler)] | 向き不向き◎。マクロだけで配線が完結する |
| 複数クライアント・リモート公開 | 構成Streamable HTTP + axum/hyperマウント | 向き不向き◎。Tower生態系にそのまま乗る |
| 既存のTokioサービスへの統合 | 構成既存の非同期ランタイムにサーバーを組み込む | 向き不向き◎。ランタイムの二重起動を避けられる |
2024-11-05専用の古いクライアントとの通信 | 構成旧HTTP+SSEトランスポート | 向き不向き×。rmcpは非対応。プロキシで吸収する |
GoやRubyとの実装感覚の違い
同じ「公式Tier1 SDK」でも、Rustは#[tool]系のマクロでスキーマ定義とハンドラ登録を1箇所に集約する設計です。JavaのアノテーションとSpring AI連携が個別のBoot Starterを組み合わせる構成なのに対し、Rust版はマクロ展開時にコンパイラがスキーマ生成コードを埋め込むため、追加ライブラリなしで完結します。一方でマクロの内部動作を追うにはマクロ展開の知識が要るため、デバッグの難易度はやや高めです。複数言語のTier判定基準を横断的に比較するならMCP SDK Tierシステムが参考になります。
まとめ
Rust SDK(rmcp)はTier1のMCP公式実装で、#[tool_router(server_handler)]を使えばマクロだけで最小サーバーが動きます。Tokioベースの非同期ランタイムを前提にしているため、すでにTokioで書かれたサービスへの組み込みが最も摩擦の少ない導入経路です。stdioで動作確認をしたあとStreamable HTTPへ切り替える流れは他言語のSDKと共通ですが、旧SSEトランスポートが意図的に非対応である点だけは実装前に把握しておく必要があります。3.x系への移行を控えているなら、マクロの呼び出し形式が変わる可能性を見込んでリリースノートを先に確認してください。