MCP Ruby SDKは、Model Context Protocol(MCP)のサーバーとクライアントをRubyで書くための公式SDKで、RubyGemsではmcpという名前で配布されています。2026年10月30日の公式ブログで、TypeScript・Python・C#・Go・Rustに続く6番目のTier 1 SDKになったと発表されました。この記事では、gemの導入からstdioで動く最小サーバー、RailsへのStreamable HTTP組み込み、2026-07-28仕様への接続までを実装順に追い、非公式gemとの選び分けを判断します。MCPそのものの仕組みはMCP(Model Context Protocol)の仕組みとAPIとの違いで確認できます。
まとめ|Ruby製のMCPサーバーは公式mcp gemの1.9系で組む
2026年11月10日時点の最新版は1.9.0です。要求RubyはRuby 2.7以上、実行時の依存はjson_schemerだけで、既存のRailsアプリへ足しても依存関係はほとんど増えません。Tier 1は、適合性テストの合格率100%と、新しい仕様版への公開日までの対応を約束した区分です。Rubyで新しくMCPサーバーを作るなら、非公式gemを選ぶ理由は残っていません。
注意点は2つあります。Railsに組み込む場合、従来型の接続はセッションをメモリに持つため、Pumaのworkersを増やすと壊れます。もう1つは、1.x系のマイナー版でも仕様への準拠を理由に挙動が変わる点です。版を上げる前にCHANGELOGのChanged欄を読む運用を、導入時点で決めておいてください。
MCP Ruby SDK(mcp gem)の提供元とTier 1到達で変わった位置づけ
まず、どのgemが公式で、どの程度の品質が約束されているかを確かめます。
公式gemの名前mcpと1.9.0までの版履歴、要求Ruby 2.7以上
公式SDKのソースはGitHubのmodelcontextprotocol/ruby-sdkにあり、配布名はmcpです。mcp-sdk.rbやmcp_liteなど似た名前の別実装もあるので、Gemfileに書く名前を取り違えないようにします。RubyGemsのmcpのページとAPIで2026年11月10日に確認した版の推移は次のとおりです。
| 版 | 公開日 | 主な変化 |
|---|---|---|
| 1.0.0 | 2026年7月24日 | 初の安定版 |
| 1.2.0 | 2026年8月15日 | 2026-07-28仕様に対応 |
| 1.3.0 | 2026年8月21日 | 説明を専用サイトへ移設 |
| 1.8.0 | 2026年10月16日 | OAuth 2.1のリソースサーバー機能 |
| 1.9.0 | 2026年11月3日 | Skills拡張(SEP-2640) |
1.9.0のメタデータでは、要求Rubyが2.7.0以上、ライセンスがApache-2.0です。7月の1.0.0から11月の1.9.0まで、約3か月半でマイナー版が9回出ています。更新の速さを前提に、版の固定と上げ方を決めておく必要があります。
Tier 1の5要件と3月のTier 3から9月のTier 1までの適合性スコア
MCP公式のSDK Tiersのページは、Tier 1に適合性テスト合格率100%、Issueの振り分け2営業日以内、重大バグの修正7日以内、新仕様版の公開前の対応、全機能を例つきで説明する文書を求めています。Tier 2は合格率80%、新機能への対応は6か月以内です。
MCP公式ブログのTier 1到達の発表には、評価ごとの数値が載っています。
| 評価時期 | 区分 | 評価時の版 | サーバー適合性 | クライアント適合性 |
|---|---|---|---|---|
| 2026年3月 | Tier 3 | 0.8.0 | 25/30 | 未計測 |
| 2026年7月 | Tier 2 | 1.0.0 | 30/30 | 24/24 |
| 2026年9月確定 | Tier 1 | 1.2.0 | 67/67 | 50/50 |
7か月足らずでTier 3から上がったことになります。Tier 1はテストが4週間落ち続けるとTier 2へ戻される仕組みなので、ここに載った数字は一度きりの成績ではありません。次期仕様は2027年3月下旬が目標で、Ruby SDKもリリース日までに対応すると表明しています。
gemのインストールからstdioで動くMCPサーバーを試すまでの手順
ここからは手元で動かす手順です。stdioはMCPクライアントがサーバーを子プロセスとして起動する方式で、Claude Desktopのようなデスクトップアプリとの接続や動作確認に向きます。
Gemfileへの追加とHTTPクライアントやJWT検証で別途要るgem
サーバーをstdioで動かすだけならmcpだけで足ります。公式ガイドのInstallationによると、機能によって追加のgemが要ります。
- HTTPクライアントとしてMCPサーバーへつなぐ:
faraday(2.0以上)と、SSE応答を読むためのevent_stream_parser - サーバー側でJWTのアクセストークンを検証する:
jwt(EdDSA署名ならjwt-eddsaも) - RFC 7662のトークンイントロスペクションで検証する:追加なし(標準ライブラリで動く)
SDK自身のサーバーは既定でSSEを返します。どんなサーバーにもつなぐクライアントを書くなら、faradayとevent_stream_parserの両方を入れておきます。
MCP::Toolを継承したツール定義とstdioトランスポートの起動コード
受注番号を受け取ってステータスを返す、読み取り専用のツールを1つだけ持つサーバーです。READMEのQuick Startと同じ構成で、題材だけ業務寄りに変えています。
# Gemfile
gem "mcp", "~> 1.9"
# server.rb
require "mcp"
class OrderStatusTool < MCP::Tool
title "受注ステータス照会"
description "受注番号から現在のステータスを返す"
input_schema(
properties: {
order_id: { type: "string" },
},
required: ["order_id"]
)
annotations(read_only_hint: true, destructive_hint: false)
class << self
def call(order_id:, server_context:)
statuses = { "A-100" => "出荷済み", "A-101" => "受注確認中" }
status = statuses.fetch(order_id, "該当なし")
MCP::Tool::Response.new([{ type: "text", text: "#{order_id}: #{status}" }])
end
end
end
server = MCP::Server.new(name: "order_server", version: "0.1.0", tools: [OrderStatusTool])
MCP::Server::Transports::StdioTransport.new(server).open
annotationsは省略できますが、公式ガイドのToolsではdestructive_hintの既定値がtrue、read_only_hintの既定値がfalseです。何も書かないと「破壊的な操作をするツール」としてクライアントに伝わるため、参照系のツールには明示しておきます。ツールの定義方法は、このクラス継承のほかMCP::Tool.defineとMCP::Server#define_toolのブロック形式も選べます。
標準入力からJSON-RPCでツール一覧と呼び出し結果を確認する手順
起動したら、標準入力へ1行に1リクエストずつJSON-RPCを貼り付けます。確認するメソッドはtools/listとtools/callです。MCPクライアントを用意しなくても、ツールの一覧と呼び出し結果を目で確認できます。
$ bundle exec ruby server.rb
{"jsonrpc":"2.0","id":"1","method":"ping"}
{"jsonrpc":"2.0","id":"2","method":"tools/list"}
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"order_status_tool","arguments":{"order_id":"A-100"}}}
READMEの例では、ExampleToolクラスがexample_toolという名前で呼ばれています。同じ規則ならOrderStatusToolはorder_status_toolですが、実際の名前はtools/listの応答で確かめてからtools/callに書いてください。1行の上限は既定で4MiBで、StdioTransport.newのmax_line_bytes:で変えられます。
ツール引数がシンボルキーで届く仕様とテストで見落とす落とし穴
トランスポートは受け取ったJSONをsymbolize_names: trueで読むため、入れ子のオブジェクトもキーがシンボルになります。payload["subject"]と文字列で読むとnilが返ります。
厄介なのはテストです。ツールを直接MyTool.call(payload: { "subject" => "greet" }, server_context: nil)のように呼ぶと、本番では来ない文字列キーで通ってしまいます。公式ガイドは、引数を一度JSONに変換して戻してから呼ぶ書き方を勧めています。
delivered = JSON.parse(JSON.generate(arguments), symbolize_names: true)
MyTool.call(**delivered, server_context: nil)
RSpecのヘルパーにこの2行を入れておけば、テストは通るのに実際のクライアントからは失敗する、という食い違いを防げます。
RailsアプリにStreamable HTTPでMCPエンドポイントを組み込む実装
社内の業務システムにMCPの口を足す場合は、Streamable HTTPを使います。トランスポート自体の仕組みはStreamable HTTPとSSEの違いと2026-07-28仕様の変更点にまとめています。
Railsのroutes.rbでのmount構成と単一プロセスの制約
MCP::Server::Transports::StreamableHTTPTransportはRackアプリです。Rackアプリとして渡せる理由はRackの仕様とconfig.ruの書き方で補足しています。公式のexamples/railsのサンプルは、起動時にサーバーを1つ作り、routes.rbでmountします。
# config/routes.rb
server = MCP::Server.new(
name: "order_server",
version: "1.0.0",
tools: [OrderStatusTool],
)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
server,
allowed_hosts: ["mcp.example.com"],
)
Rails.application.routes.draw do
mount transport => "/mcp"
end
サーバーをinitializersではなくroutes.rbで作るのは、Zeitwerkの自動読み込みが整う前だとapp/toolsのクラスを参照できないためです。mountすればPOST・GET・DELETEをトランスポートが振り分けるので、ルートを個別に書く必要はありません。
ただし、公式ガイドのTransportsにあるとおり、従来のハンドシェイク型の接続ではセッションとSSEの状態をプロセスのメモリに持ちます。Pumaをworkers 0で動かすか、複数台ならロードバランサーでMcp-Session-Idごとのスティッキーセッションの設定が必要です。stateless: trueならこの制約は消えますが、進捗通知とサーバーからの問いかけが使えなくなります。
コントローラ方式でログイン利用者をツールへ渡す際の必須設定と理由
current_userをserver_contextへ渡し、ログイン中の利用者ごとに渡すツールや権限を変えたいときは、リクエストのたびにサーバーを作るコントローラ方式にします。
class McpController < ActionController::API
def create
server = MCP::Server.new(
name: "order_server",
version: "1.0.0",
tools: [OrderStatusTool],
server_context: { user_id: current_user.id },
)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
server,
stateless: true,
serve_subscriptions_listen: false,
)
status, headers, body = transport.handle_request(request)
render(json: body.first, status: status, headers: headers)
end
end
stateless: trueとserve_subscriptions_listen: falseの2つは外せません。リクエストごとに作るトランスポートではセッションIDを引き継げず、renderは応答本文をまとめて返すため、通知用の開きっぱなしのストリームを扱えないからです。後者を付け忘れると、2026-07-28仕様のクライアントが通知ストリームを要求した時点で500になります。
server_contextに入れた値は、ツールのcallにserver_context:として渡ります。利用者IDに基づく絞り込みはツール側で行い、AIに渡すデータの範囲をアプリの権限と一致させます。
本番公開で403になるallowed_hostsとDNSリバインディング保護の設定
2025-11-25仕様に沿って、トランスポートは既定でHostとOriginのヘッダを検証します。許可されているHostは127.0.0.1・::1・localhostだけです。開発機では動いたのに、リバースプロキシの後ろに置いた途端にすべて403になる原因の多くはこれです。
公開用のホスト名をallowed_hosts:に、ブラウザから呼ぶ画面があるならそのオリジンをallowed_origins:に入れます。dns_rebinding_protection: falseで検証を切れますが、上流のプロキシで同じ検証をしている場合だけです。リクエスト本文の上限は既定で4MiB、initializeは64KiBで、超えると413を返します。
2026-07-28仕様の新しい接続方式とRubyクライアントからの扱い
2026-07-28仕様では、最初にinitializeで握手する手順がなくなり、リクエストごとに版を載せる方式が加わりました。仕様側の差分はMCP仕様2026-07-28版のステートレス化と移行手順で扱っています。
クライアントの自動接続判定とconnect(mode:)で固定する条件
公式ガイドのProtocol Versionsによると、ハンドシェイク型の既定の版は2025-11-25です。initializeで2026-07-28を求められても、サーバーは2025-11-25を返します。新方式はハンドシェイクでは選ばれず、クライアントがserver/discoverを送って判定します。
require "mcp"
transport = MCP::Client::HTTP.new(url: "https://mcp.example.com/mcp")
client = MCP::Client.new(transport: transport)
client.connect # 既定:server/discoverを試し、非対応ならinitialize
# client.connect(mode: :modern) # 新方式を必須にする
# client.connect(mode: :legacy) # initializeでの握手を強制する
# client.connect(protocol_version: "2025-11-25") # 版を固定し、判定を省く
tools = client.tools
puts tools.map(&:name)
相手のサーバーが分からないなら既定の自動判定のままで構いません。社内で版をそろえたサーバー同士なら、mode:を固定しておくと、接続のたびの判定通信と、判定結果の違いによる挙動差を避けられます。HTTPクライアントには前述のfaradayが要ります。
Roots・Sampling・Loggingの非推奨化とstatelessで使えない機能
2026-07-28仕様では、SEP-2577によりRoots・Sampling・Loggingが非推奨になりました。ハンドシェイク型の版では3つとも引き続き動きます。新規の設計では、これらに頼らずツールの入出力で完結させるのが無難です。
stateless: trueの接続では、sampling/createMessage・roots/list・elicitation/createといったサーバーからの問いかけはエラーになり、進捗やログの通知も届きません。利用者に確認を取ってから処理を進めるツールを作るなら、mount方式を選ぶか、確認を2回のツール呼び出しに分けて設計します。
公式mcp gemとfast-mcp・ActionMCPを比べたRubyでの採用判断
公式gemの安定版が出る前から、RubyにはMCPの非公式実装がありました。どれを使うかを判断の材料ごとに並べます。
3つのgemの最新版・要求Ruby・依存gemを並べた2026年11月時点の比較
各gemのRubyGems APIを2026年11月10日に照会した結果です。表のactionmcpが依存するrailties・activerecordは、8.1以上が条件です。
| gem | 最新版(公開日) | 要求Ruby | 主な実行時依存 |
|---|---|---|---|
| mcp(公式) | 1.9.0(2026年11月3日) | 2.7.0以上 | json_schemerのみ |
| fast-mcp | 1.6.0(2025年9月28日) | 3.0.0以上 | dry-schema・rackなど6本 |
| actionmcp | 0.201.3(2026年10月27日) | 指定なし | railties・activerecord等 |
fast-mcpはDry-Schemaで引数を検証するDSLが特徴ですが、最後のリリースから1年以上新版が出ていません。ActionMCPはRails 8.1とActive Recordを前提にしたRails専用の実装で、版は0.x系のままです。
新規開発で公式gemを選ぶ条件とfast-mcpを使い続けてよい場面
新規に作るなら、公式gemを選びます。2026-07-28仕様、OAuth 2.1のリソースサーバー、クライアント機能までそろっていて、Tier 1の要件により次期仕様への追従も約束されています。非公式gemには、この追従を誰も約束していません。
fast-mcpで動いているサーバーを急いで書き換える必要はありません。社内の限られたクライアントからだけ使っていて、2026-07-28仕様の新方式で接続する予定がないなら、そのまま運用を続けて構いません。書き換えに着手すべきなのは、新仕様のクライアントへの対応を求められたとき、または外部へ公開してOAuthで保護する必要が出たときです。ActionMCPは、Rails 8.1以上でジョブやデータベース連携まで同じ枠組みに載せたい場合に限って検討します。
マイナー版でも挙動が変わるVERSIONING方針とGemfileの固定方法
公式gemはVERSIONING.mdで、MCP仕様への準拠の修正、セキュリティ対応、明確な欠陥の修正であれば、互換性のない変更をマイナー版で出すと明記しています。実際に1.8.1ではidがnullのリクエストを拒否するようになり、1.9.0ではオブジェクトでない_metaを不正な引数として返すようになりました。
Gemfileは"~> 1.9"とし、Gemfile.lockで実際の版を固定します。上げるときはCHANGELOG.mdのChanged欄だけを先に読み、自社のクライアントや連携先が影響を受けるかを確認してからbundle updateします。依存の自動更新ツールでmcpを無条件に上げる運用は避けてください。
既存RailsシステムにMCPを足す前に棚卸しする権限とプロセス構成
MCPサーバーを足すと、AIエージェントが業務データに触れる経路が1本増えます。着手前に確かめるのは、公開するツールの一覧と各ツールが読み書きする範囲、利用者ごとの権限をどうserver_contextへ渡すか、そしてPumaのworkers数とロードバランサーの構成です。最後の点はmount方式とコントローラ方式のどちらを選ぶかを決めます。
この棚卸しで、AIに渡すデータの範囲や認可の設計が決まらない場合は、エージェント側の設計と合わせて検討したほうが手戻りは減ります。業務システムとAIエージェントの接続設計は一創のAIエージェント開発でも相談を受けています。
よくある質問
MCP Ruby SDKを導入する前によく出る疑問をまとめました。
MCP Ruby SDKは商用のシステムで無料で使えますか?
使えます。RubyGemsに登録された1.9.0のライセンスはApache-2.0です。リポジトリのREADMEでは、新しい貢献分がApache License 2.0、既存のコードがMITと説明されています。どちらも商用利用を認めるライセンスなので、社内システムに組み込んで提供しても問題ありません。再配布するときは、LICENSEファイルの表示義務を確認してください。
対応しているRubyのバージョンはどこまで古くて大丈夫ですか?
gemの要求はRuby 2.7.0以上です。ただしRuby 2.7はすでに保守が終わった系列なので、MCPサーバーを新しく置く環境では現行版を使ってください。現行の版とサポート期限はRubyの最新バージョンとサポート期限で確認できます。古いRubyで動く業務システムに組み込む場合も、MCPサーバーだけ別プロセスに切り出す選択肢があります。
SSEだけを使う旧式のトランスポートには対応していますか?
対応していません。公式ブログによると、旧来のSSEトランスポートは仕様で非推奨になっているため、SDKは意図的に実装していません。Streamable HTTPの中でSSEによる応答は使えるので、古いクライアントが旧式のSSEエンドポイントしか話せない場合に限って問題になります。その場合はクライアント側の更新を先に検討します。
MCPクライアントもRubyで書けますか?
書けます。MCP::Client::Stdioはサーバーを子プロセスとして起動し、MCP::Client::HTTPはStreamable HTTPのサーバーへ接続します。connectで接続方式を決めたあと、toolsで一覧を取得し、call_toolで呼び出す流れです。stdioの子プロセスは既定で一部の環境変数しか引き継がないため、APIキーなどはenv:で明示して渡します。
Tier 1になると、利用者にとって何が変わりますか?
次の仕様版が出たときに、Ruby SDKが置いていかれない保証が付きました。Tier 1は新仕様の公開日までの対応、重大バグの7日以内の修正を求められ、適合性テストが4週間落ち続けると格下げされます。2027年3月下旬が目標の次期仕様にも、リリース日までに対応すると表明されています。
関連記事
- MCP(Model Context Protocol)とは?仕組み・APIとの違い・MCPサーバーの作り方:MCPの基本と全体像
- MCP Python SDK v2 移行:FastMCPからMCPServerへの書き換え手順と据え置き判断:同じTier 1のPython版
- MCP TypeScript SDK v2とは?パッケージ分割とステートレス化の移行要点:TypeScript版の新仕様対応
- MCP Go SDKの使い方|v1.8.0でサーバーを作る手順とmcp-goとの違い:公式と非公式を比べたGo版
- MCP C# SDKの使い方|v2.2でサーバー・クライアントを作る手順と1.xからの移行:.NETでの実装