MCPクライアントの仕組み — ローカル接続とリモート接続の違い
MCPのホストとクライアントの違い、Elicitation/Roots/Samplingという3つの機能、stdioとStreamable HTTPで接続方法がどう変わるかを整理する。
ホストとクライアントは何が違うのか
MCPの用語では「ホスト」と「クライアント」は別物です。ホストはClaude DesktopやIDE、Claude Codeのようにユーザーが直接操作するアプリケーション本体です。クライアントは、そのホストが生成する、1台のMCPサーバーと通信するためのプロトコル部品を指します。
ホストが複数のサーバーに同時接続する場合、サーバーの数だけクライアントが作られ、各クライアントの通信相手は常に1つのサーバーです。ユーザーに見えるのはホストの画面だけですが、裏では「filesystem用」「Slack用」のように接続ごとに独立した通信路が動いています。
この単位は、トラブルの切り分けにそのまま効きます。Claude Codeは、通信が途切れたリモートサーバーを自動で再接続し、HTTPやSSEサーバーの初回接続は一時的なエラーのあとにやり直します。一方でstdioサーバーは自動再接続の対象外です。疑う対象が「ホスト全体」ではなく「問題のサーバーに対応する1本」だと分かっていれば、見るログも絞れます。
クライアントがサーバーに提供する3つの機能
サーバーからツールやリソースを受け取るだけでなく、クライアントの側もサーバーに機能を提供します。コア機能は3つです。
| 機能 | 何をするか | 2026-07-28リビジョンでの状態 |
|---|---|---|
| Elicitation | 何をするか対話の途中でサーバーがユーザーに追加情報を求める | 2026-07-28リビジョンでの状態現役 |
| Roots | 何をするかクライアントがサーバーに操作対象のディレクトリ境界を伝える | 2026-07-28リビジョンでの状態非推奨(移行先あり) |
| Sampling | 何をするかサーバーがクライアント経由でLLM補完をリクエストする | 2026-07-28リビジョンでの状態非推奨(移行先あり) |
RootsとSamplingは2026-07-28リビジョンで非推奨になりました。廃止の対象になるのは、2027-07-28以降に最初に公開されるリビジョンからです。実際に消すかどうかはコアメンテナーが公開準備の段階で決めるので、それより遅れることもあります。
移行先は機能ごとに違います。Rootsの代わりは、ツールパラメータ、リソースURI、サーバー設定でのディレクトリ指定です。Samplingの代わりは、サーバーがLLMプロバイダーのAPIへ直接つなぐ構成です。同じリビジョンでは、Logging(サーバー側のログ通知)とDynamic Client Registration(OAuthのクライアント登録方式)も非推奨に加わりました。
Elicitationの2つのモード
フォームモードは、JSON Schemaで定義した構造化データをクライアント側のUIで集めます。URLモードは、サーバーが示したURLをユーザーが開き、その先のやり取りがクライアントを通りません。
パスワード、APIキー、アクセストークン、支払い情報のような機密情報は、フォームモードで要求してはいけないと仕様が定めています。値がクライアントやLLMのコンテキストを通らないURLモードが、この種の入力の受け皿です。URLモードではクライアントがURLを全文表示して同意を得てから開き、自動で取得することはありません。詳しい使い分けはElicitationのフォームモードとurlモードで扱っています。
Rootsは境界ではなく「目安」
Rootsはfile://のURIでディレクトリを知らせる仕組みですが、仕様は「サーバーはrootの境界を尊重すべき(SHOULD)」とするだけで、強制はしません。サーバーはクライアントの制御できないコードだからです。実際の防御はOSのファイル権限やサンドボックスで行う、というのが公式の立場です。事故を防ぐ目安として働くもので、悪意あるサーバーを止める壁にはなりません。移行の具体策はRootsの非推奨と移行先にまとめています。
Claude Codeはクライアント機能にどう応えるか
Elicitationでは、サーバーが入力を求めると対話ダイアログが自動で出ます。フォームモードならサーバー定義の入力欄が、URLモードならリンクを開くかどうかの確認が表示されます。ダイアログを出さずに自動応答したいときはElicitationフックを使います。2026-07-28のリビジョンで接続した場合、Claude Codeは機能宣言にelicitation: {form: {}, url: {}}を載せます。サーバーはどちらのモードも要求できます。フックの書き方はClaude CodeのElicitationフックを参照してください。
Rootsでは、サーバーのroots/listリクエストに起動ディレクトリを返します。--add-dir・/add-dir・additionalDirectoriesで追加した作業ディレクトリも含めます。作業ディレクトリが変わるとnotifications/roots/list_changedも送ります。この挙動はv2.1.203からで、それ以前は起動ディレクトリだけを返し、変更通知も送っていませんでした。2026-07-28の仕様では、notifications/roots/list_changedがpingやlogging/setLevelとともに削除されています。仕様上は、このリビジョンで接続したときroots/listがMRTRのinputRequestsに載ります。
サーバーはなぜ処理の途中で質問できるのか — MRTR
ElicitationとSamplingは、サーバーが処理の途中でクライアントに追加情報を求める点が共通しています。2026-07-28リビジョンでは、この割り込みがMulti Round-Trip Requests(MRTR)という1つのパターンに統一されました。以前のように、サーバーが自分からクライアントへリクエストを送る方式は使えません。破壊的変更として仕様に明記されています。
MRTRで1回のツール呼び出しが完了するまで
- 1
クライアントがツールを呼ぶ
tools/callをid 1で送ります。 - 2
サーバーが「入力が要る」と返す
最終結果の代わりに、
resultType: "input_required"のInputRequiredResultを返します。inputRequestsにelicitation/createやsampling/createMessageが入り、サーバー専用のrequestStateが付くこともあります。 - 3
クライアントがユーザーに尋ねる
フォームを出す、URLを開く、LLMの補完を取るなど、リクエストの種類に応じた処理をします。
- 4
同じ呼び出しを新しいidで再送する
元の引数に
inputResponsesを添え、requestStateがあれば一字も変えずにそのまま返して、id 2で再送します。サーバーは受け取った内容から状態を組み立て直して処理を続け、最終結果を返します。
仕組みの核は、再送された2回目のリクエストが単独で完結することです。サーバーは1回目の続きを覚えておく必要がなく、requestStateに必要な文脈を詰めて、クライアントに預けます。サーバー間で共有ストレージを持たない構成や、負荷分散で次の呼び出しが別のサーバーに届く構成でも成り立つ設計です。
requestStateはクライアントを通るので、サーバーは改ざんされうる入力として扱う決まりです。認可やリソースアクセスに影響する場合は、HMACやAEADで完全性を守り、検証に失敗した値は拒否します。再利用を防ぐために、認証済みの主体、短い有効期限、元のリクエストの識別子を状態の中に含めることも勧められています。逆にクライアント側は、中身を解析も加工もしません。
InputRequiredResultを返してよい要求はtools/call、resources/read、prompts/getの3つだけです。サーバーは、クライアントが宣言していない機能のリクエストをinputRequestsに入れられません。elicitationを宣言していないクライアントには、elicitation/createを送れないということです。
Samplingでは、ユーザーが最初のプロンプトとモデルの生成結果の両方を確認・修正できるよう設計されています。ただし承認は仕様上の必須要件ではなく、確認を求めるか自動承認にするかはクライアント次第です。旅行予約サーバーが複数のフライト候補をLLMに評価させる例なら、「この内容でLLMに送るか」と「LLMの推薦をそのまま使うか」の2か所で止められる、という設計です。
ローカルサーバーにつなぐ — stdio
ローカル接続では、ホストがMCPサーバーをサブプロセスとして起動します。サーバーはstdinからJSON-RPCメッセージを読み、stdoutへ書き込みます。メッセージは改行区切りの1行1メッセージで、途中に改行を含めてはいけません。stdoutに書いてよいのは有効なMCPメッセージだけで、ログはstderrへ流します。
2026-07-28のstdio仕様には、MRTRと対になる規定があります。サーバーがstdoutにJSON-RPCの「リクエスト」を書くことは禁止で、サーバーからクライアントへの問い合わせはすべてInputRequiredResultの応答で運ばれます。
終了も単純です。クライアントは入力ストリームを閉じ、サーバーの終了を待ち、出てこなければプロセスを強制終了します。サーバー側は、標準入力が閉じたら速やかに終了するのが望ましいとされ、これが移植性のある唯一の終了合図です。サーバーが予期せず落ちた場合、仕様はクライアントに再起動を推奨します。プロトコルが状態を持たないので、処理中だったリクエストは失われるだけで、新しいプロセスに再送できるからです。前の章で見たとおり、Claude Codeはstdioサーバーの自動再接続をしないため、仕様の推奨とは挙動が分かれます。
Claude Desktopの設定ファイル
Claude Desktopでの実例はclaude_desktop_config.jsonへの追記です。設定画面の「Developer」タブで「Edit Config」を押すと開きます。場所はmacOSなら~/Library/Application Support/Claude/claude_desktop_config.jsonです。Windowsなら%APPDATA%\Claude\claude_desktop_config.jsonにあります。
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}argsに並べたディレクトリが、このサーバーに許可する範囲です。パスは相対ではなく絶対で書く必要があります。保存後はClaude Desktopを完全に終了して起動し直さないと反映されません。サーバーが出てこないときは、同じコマンドをターミナルで単体実行し、エラーが出るかを見るのが近道です。
npx -y @modelcontextprotocol/server-filesystem \
/Users/username/Desktop /Users/username/DownloadsログはmacOSなら~/Library/Logs/Claudeにあります。mcp.logが接続全般の記録で、mcp-server-SERVERNAME.logがそのサーバーのstderr出力です。stdioサーバーはstderrにログを全部流すので、後者はエラー専用のファイルではありません。
Claude Codeでの同じ設定
Claude Codeではclaude mcp addで同じ設定を作ります。v2.1.287で、空のホームディレクトリと一時プロジェクトを使い、プロジェクトスコープで2種類のサーバーを追加した実際の出力です(接続は行っていません)。
claude mcp add -s project filesystem -- npx -y @modelcontextprotocol/server-filesystem ./docs
claude mcp add -s project --transport http sentry https://mcp.sentry.dev/mcpAdded stdio MCP server filesystem with command: npx -y @modelcontextprotocol/server-filesystem ./docs to project config
File modified: <プロジェクト>/.mcp.json
Added HTTP MCP server sentry with URL: https://mcp.sentry.dev/mcp to project config
File modified: <プロジェクト>/.mcp.json生成された.mcp.jsonは次のとおりです。
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./docs"],
"env": {}
},
"sentry": {
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
}
}
}stdioの項目にurlはなく、HTTPの項目にcommandはありません。接続方式はtypeで決まります。--はClaude Code自身のオプションとサーバーに渡す引数の区切りで、これがないと-yのようなフラグをClaude Codeのオプションとして読んでしまいます。
認可仕様は、stdioの実装には従わせず、資格情報を環境から取るよう求めています。claude mcp add -e API_KEY=...の-eは、この環境経由の受け渡しに当たります。
typeの書き方には落とし穴があります。add-jsonでurlだけを書きtypeを省くと、v2.1.287ではInvalid configuration: : Invalid inputで追加できませんでした。.mcp.jsonを直接書いてtypeを落とした場合は、stdio扱いで読まれて失敗し、Claude Codeがadd "type": "http"を促すエラーを出します。MCP仕様の呼び名にあわせて"type": "streamable-http"と書くこともでき、add-jsonで試すとhttpとして保存されました。
ローカル接続はホストがサブプロセスを起動できる環境が前提で、公式のローカル接続ガイドもClaude Desktop(macOSとWindows)を対象にしています。ファイルシステム系サーバーの権限設計はFilesystem MCPサーバーの使い方、Desktop側の設定はClaude DesktopのMCP設定ガイドで扱っています。
リモートサーバーにつなぐ — Custom Connector
リモート接続では、サーバーはインターネット上で独立したプロセスとして動き、複数のクライアントからの接続を受けます。Claudeでの窓口がCustom Connectorです。
Custom Connectorの追加手順
- 1
設定のConnectorsを開く
MCPガイドの手順では、デスクトップは
Ctrl+Commaか、左上のメニューの「File」→「Settings」です。ブラウザは⌘⇧,(macOS)か、プロフィールアイコンの「Settings」から入ります。サイドバーの「Connectors」を選びます。Claudeのヘルプでは、Pro / Maxは「Customize」>「Connectors」です。Team / Enterpriseは「Organization settings」>「Connectors」で、Teamプランで追加できるのはOwnerとPrimary Ownerです。Freeで追加できるカスタムコネクタは1つまでです。
- 2
Add custom connectorを選ぶ
MCPガイドでは右上の「Add」を押し、ドロップダウンの「Add custom connector」を選びます。ヘルプでは、Pro / Maxは「+ Add」から「Add custom connector」を選びます。Team / Enterpriseは「Add」にカーソルを合わせて「Custom」→「Web」を選びます。続けてサーバーのURLを入れます。MCPガイドの例は
https://example-server.modelcontextprotocol.io/mcpで、https://から経路まで完全なURLが必要です。 - 3
認証を済ませる
MCPガイドでは、サーバーの実装によってOAuth、APIキー、ユーザー名とパスワードのいずれかになります。サードパーティの認証画面に移る場合も、Claude内にフォームが出る場合もあります。
ヘルプの設定画面では、接続のしかたを「Sign in now」「Sign in when needed」「No sign in」から選びます。OAuthクライアントの選択肢は3つです。「Use Claude's published identity (Recommended)」が推奨です。ほかに「Register automatically」と「Use your own OAuth client」があります。固定の資格情報は「Request headers」で渡します。
- 4
ツールの権限を絞る
接続したサーバーの画面で、使うツールを個別に有効・無効にできます。リソースとプロンプトは、入力欄左下の「Add files, connectors, and more /」から「Connectors」にカーソルを合わせて選びます。
設定手順の詳細と認証の中身はリモートMCPサーバーとOAuth認証で扱っています。
Claude Codeからリモートサーバーを使うなら、--transport httpで追加します。v2ランタイムでは、Claude Codeは新しいリビジョンに対応しているかをHTTPサーバーに問い合わせ、対応していれば2026-07-28で通信します。stdioサーバーには、環境変数MCP_PROTOCOL_NEGOTIATIONをautoにしたときだけ同じ問い合わせをします。legacyにすると、どのサーバーにも問い合わせず従来の方式で接続します。この変数はv2.1.221以降で使えます。
v2ランタイムはMCP TypeScript SDK 2.0を土台にしています。v2.1.232以降では機能フラグを取得したセッションで使われ、v2.1.274以降では既定になります。MCP_SDK_GENERATIONでv1かv2を選べます。
2026-07-28でStreamable HTTPは何が変わったか
通信は単一のHTTPエンドポイントへのPOSTです。クライアントは、リクエストでも通知でも、1メッセージごとに新しいPOSTを送ります。サーバーは、リクエストに対して単発のJSONか、そのリクエストに紐づくSSE(Server-Sent Events)ストリームのどちらかで答え、クライアントは両方を扱えなければなりません。2026-07-28リビジョンで、この仕組みは大きく変わりました。
Streamable HTTPの2025-11-25までと2026-07-28
2025-03-26〜2025-11-25
- サーバーが
Mcp-Session-Idヘッダーでセッションを割り当てられる(HTTP DELETEで終了) - クライアントがHTTP GETで常設のSSEストリームを開き、サーバー発のメッセージを受ける
- サーバーがSSEストリーム上でJSON-RPCの「リクエスト」を送れる
Last-Event-IDでストリームを再開できる
2026-07-28
- プロトコルレベルのセッションがなく、
tools/listなどの一覧は接続ごとに変わらない - GETのストリーム用エンドポイントがなく、変更通知は
subscriptions/listenのPOST応答ストリームで受ける - サーバーからの問い合わせは
InputRequiredResultに入れて返す - ストリームの再開はできない
セッションがなくなった分、呼び出しをまたぐ状態が要るサーバーは、サーバーが発行する明示的なハンドルをツールの普通の引数として受け渡します。旧バージョンのクライアントがGETやDELETEを送ってきたとき、新しいサーバーは405を返し、Mcp-Session-IdやLast-Event-IDは無視する扱いが仕様に書かれています。
ハンドシェイクもなくなりました。initializeとnotifications/initializedのやり取りは廃止され、リクエストごとにプロトコルのバージョンとクライアントの機能宣言が_metaに載ります。代わりにサーバーはserver/discoverの実装が必須で、対応バージョンと機能、自身の名前を答えます。クライアントは最初にこれを呼んで使うバージョンを選べます。stdioでは、旧方式のサーバーかを探る後方互換の確認にも使えます。
ストリームを閉じること自体が、そのリクエストのキャンセル合図です。stdioのようにnotifications/cancelledを送る必要はなく、Streamable HTTPでは送られないのが前提です。
POSTにはヘッダーも新しく加わりました。すべてのPOSTにMCP-Protocol-Version: 2026-07-28とMcp-Method(tools/callなど)が必須です。tools/call、resources/read、prompts/getには、呼び出し先の名前を示すMcp-Nameも付きます。値は本文の対応する値と一致しなければなりません。照合先は、Mcp-Methodがmethod、Mcp-Nameがparams.nameかparams.uri、バージョンが_metaです。食い違うとサーバーは400(HeaderMismatch)で拒否します。未対応のメソッドには404と-32601のJSON-RPCエラー、未対応のバージョンには400とUnsupportedProtocolVersionErrorが返ります。ロードバランサーやゲートウェイが、本文を読まずにルーティングできるようにする工夫です。ツール側がx-mcp-headerで指定した引数も、Mcp-Param-Region: us-west1のようにヘッダーへ写されます。
非ASCIIの値や前後に空白を含む値は、=?base64?...?=の形でBase64符号化されます。
Claude Codeは長く開くストリームの扱いも決めています。subscriptions/listenのストリームを自分で開き続け、閉じられたら開き直しますが、10秒以内に閉じる状態が続くと3回で打ち切ります。10秒を超えて開いたあとに閉じるストリームは、1時間に5回開き直した時点で、次の再開まで約6時間待ちます。その間は最後に取得した一覧を使うので、変更をすぐ拾いたいときは/mcpから再接続します。背景はMCPサーバーの動的なツール更新と再接続にあります。
ローカルとリモート、どちらを選ぶか
どちらを選ぶかは、好みよりも「責任をどこに置くか」で決まります。Claude Codeでの実務上の選び方はローカルMCPとリモートMCP、Claude Codeでどちらを選ぶかに整理しています。
| 観点 | ローカル(stdio) | リモート(Streamable HTTP) |
|---|---|---|
| 実行主体 | ローカル(stdio)ユーザーのマシン上のサブプロセス | リモート(Streamable HTTP)サーバー運営者のインフラ |
| 接続元 | ローカル(stdio)ユーザーのマシン | リモート(Streamable HTTP)Custom ConnectorはAnthropicのクラウド、Claude Codeは動いているマシン(端末なら手元) |
| 認証 | ローカル(stdio)環境変数などで資格情報を渡す | リモート(Streamable HTTP)仕様ではHTTP向けの認可フローに従うのが推奨 |
| 権限の境界 | ローカル(stdio)サーバーの実装と、起動ユーザーのOS権限 | リモート(Streamable HTTP)サーバー運営者が公開した範囲と、トークンの権限 |
| 対応ホスト | ローカル(stdio)サブプロセスを起動できるアプリ | リモート(Streamable HTTP)ブラウザ版・デスクトップ版の両方 |
| 典型用途 | ローカル(stdio)ファイルシステムなどローカルのリソース | リモート(Streamable HTTP)外部SaaS、社内システムなど |
Custom Connectorを追加すると、ClaudeはユーザーのPCではなくAnthropicのクラウドからリモートサーバーに接続します。サーバーはAnthropicのIPレンジからインターネット経由で届く必要があり、社内ネットワークやVPNの内側、ファイアウォールで遮られたサーバーにはつながりません。claude_desktop_config.jsonで設定するローカルMCPサーバーは別の仕組みで、ローカルネットワークを使えますが、CoworkとClaude.aiでは使えません。
ローカルのサーバーは、ユーザーのアカウント権限で動きます。Desktopの公式ガイドも、サーバーがユーザー自身にできるファイル操作を何でも実行できる点を警告しています。許可するディレクトリをargsに書くのは、Filesystemサーバーがその引数を操作範囲として扱うからで、任意のstdioサーバーが同じ制限を持つとは限りません。サーバーの中身を信頼できるかを、まず確かめる対象に置きます。
リモートは、サーバー運営者のインフラを信頼するかどうかという別の判断になります。公式のベストプラクティスは、接続前に提供元の真正性を確認し、認証で求められる権限を見ること、そして使わなくなったコネクタを定期的に外すことです。権限をどこまで絞るかの基準はMCPセキュリティガイドで扱っています。
Streamable HTTPの仕様は、すべてのサーバーにOriginヘッダーの検証を求め、無効なOriginには403を返させます。ローカルで動かす場合は、0.0.0.0ではなく127.0.0.1だけにバインドすることを推奨しています。リモートのWebサイトからDNSリバインディングでローカルのMCPサーバーに入られるのを防ぐためです。
まとめ
サーバーに接続するたびにホストが作るクライアントが、MCPの通信の単位です。2026-07-28で、サーバーからの問い合わせはMRTRに、Streamable HTTPはセッションなしのPOSTに変わりました。古い解説に「GETストリーム」や「Mcp-Session-Id」が出てきたら、以前のリビジョンの話です。
社内ネットワークの内側にあるサーバーは、Custom Connectorではつながらないので、ローカル接続かClaude Codeの--transport httpで使うことになります。うまくつながらないときはMCPサーバーに接続できないときの切り分け手順、MCP全体の地図はMCPとはにあります。