概要
どうも、@daiki1003です!みなさん、MCPサーバー使っていますか?
設定ファイルに誰かのおまじないをコピペして、なんとなく動いている。でも「あれ結局どういう仕組み?」と聞かれると自信がない……。僕もそうでした。
先日 MCP の仕様が大きく変わったニュースを見て、「これ自分に影響あるの?」を判断しようとしたら、前提が分かってないことに気づいたんですね〜。
ということで今回は、実際に Dart でサーバーを書きながら仕組みを見ていきます。
それでは早速行ってみましょー!
執筆時環境
macOS: 26.3.1Dart: 3.12.2mcp_dart: 2.4.0
MCPサーバーってそもそも何なの?
AIは本来テキストを返すことしかできません。そこで「AIに道具を持たせる」共通規格として作られたのが MCP です。USB-Cみたいに、差し込む口の形を決めておこうという発想ですね。
登場人物は2人です。
- AIを動かすホスト(
Claude Codeなど) - 道具を提供する
MCPサーバー
その間を JSON-RPC 2.0 でつなぎます。
サーバーが公開できるのは3種類です。
Tools: AIが呼べる関数Resources: 読み取れるデータPrompts: 定型指示テンプレ
全部実装する必要はなく、Toolsだけのサーバーも普通にあります。
ここで注意点なのですが、MCP「サーバー」という名前から常時起動のWebサーバーを想像しがちなんですよね。でも実体はホストが起動する1個のプロセスであることが多いです。
この話が後半の山場になります。
MCPサーバーは3ステップで書ける
やることは「宣言する」「登録する」「繋ぐ」の3つだけです。mcp_dart を使います。
1. 宣言して、何ができるか申告する
final server = McpServer(
const Implementation(name: "example_server", version: "1.0.0"),
options: const McpServerOptions(
capabilities: ServerCapabilities(
tools: ServerCapabilitiesTools(),
resources: ServerCapabilitiesResources(),
),
),
);
capabilities は自己申告です。ここに書いていないものは使えません。
2. ツールを登録する
server.registerTool(
'calculate',
description: 'Perform basic arithmetic operations',
inputSchema: JsonSchema.object(
properties: {
'operation': JsonSchema.string(enumValues: ['add', 'subtract']),
'a': JsonSchema.number(),
'b': JsonSchema.number(),
},
required: ['operation', 'a', 'b'],
),
callback: (args, extra) async {
final result = switch (args['operation']) {
'add' => args['a'] + args['b'],
'subtract' => args['a'] - args['b'],
_ => throw Exception('Invalid operation'),
};
return CallToolResult.fromContent(
[TextContent(text: 'Result: $result')],
);
},
);
大事なのは inputSchema と description です。AIはこれを読んで使い方を理解するので、ここがそのままAIへの説明書になります。雑だとうまく使ってくれません。
3. トランスポートに繋ぐ
await server.connect(StdioServerTransport());
はい、1行で完成です。
そしてここが一番お伝えしたいポイントで、宣言とツール登録のコードはどこで動かすかに関係なく全く同じなんですね。変わるのはトランスポート接続の1行だけ。
ここが stdio か HTTP かで、サーバーの性格がまるっきり変わります。
stdio と HTTPリモートの違い
設定ファイルで "command": "dart" のように書くのが stdio です。ホストがそのコマンドを子プロセスとして起動し、標準入出力でJSONをやりとりします。ネットワークは使いません。
stdio | HTTPリモート | |
|---|---|---|
| 起動する人 | ホスト(自分のPC) | 運営者(クラウドに常駐) |
| 通信 | パイプ | インターネット越しのHTTP |
| 相手 | 常に1人 | 不特定多数 |
| 認証 | 不要(環境変数で渡す) | OAuth 2.1(任意) |
| 台数 | 1プロセス | 負荷対策で増える |
この「台数が増える」が、次で効いてきます。
なぜこの違いを知っておくと得なのか
2026年7月28日、MCPの仕様が大きく改定されました。目玉はステートレス化で、initialize という握手と Mcp-Session-Id ヘッダが廃止されています。
「自分の設定も直さなきゃ?」と思いますよね。でも、ここまで読んだ方はもう分かるはずです。これはHTTPの話なんですよ。
旧仕様はセッションIDで会話を識別していました。つまりサーバーが3台あったら同じ人は必ず1号機に戻す必要がある。負荷は偏るし、1号機が落ちれば会話も消えます。
だから握手をやめて、毎回のリクエストが必要な情報を全部持つ形にした、というわけですね。
一方 stdio の相手は自分専用の1プロセスだけ。そもそも振り分け先を選ぶ余地がありません。解決すべき問題が最初から存在しないんですね。
ちなみに実際に握手なしで叩いてみたところ、旧仕様で握手時に一度だけ渡していた情報(プロトコルバージョン、クライアント情報)が、そっくり毎回のリクエストの _meta に引っ越しているのが確認できました。
「握手をやめる」とはこういうことだったんですね〜。
僕の個人環境は全部stdioだったので、直したものはゼロでした。大事なのは「改定が大したことない」ではなく、stdioかHTTPかを見分けられれば影響の有無を自分で判断できるということです。
SDKの「Tier」に注意
MCPの仕様が配っているのはルールと型定義だけで、動く実装は入っていません(正本は TypeScript で書かれた schema.ts です)。
なので書くには言語ごとのSDKが必要で、SDKが追いついていなければ新仕様は使えません。
公式は対応の速さをティアで示しています。
| Tier 1 | Tier 2 | Tier 3 | |
|---|---|---|---|
| 言語 | TypeScript / Python / C# / Go | Java / Rust / Ruby | Swift / PHP / Kotlin |
| 新仕様への対応 | リリース前に対応 | 6ヶ月以内 | 期限なし |
| 適合テスト | 100%必須 | 80%必須 | 下限なし |
お気づきでしょうか。Dartはこの表のどこにもいません。
実際に確認したところ、Dart SDK 同梱の dart mcp-server は旧仕様の 2025-11-25 でネゴシエートされました。土台の公式 dart_mcp が 0.5.2(2026年6月29日公開)で止まっているためです。
対してサードパーティの mcp_dart は 2.4.0 が2026年7月31日公開。仕様リリースのわずか3日後で、こちらは 2026-07-28 で繋がりました。
Tierに入っていない言語では「公式だから安心」が成り立たないことがあるんですね。
まとめ
あと1つ付け加えるなら、MCPの仕様は文書にすぎず、書くにはSDKが要るということ。DartはTierの外にいるので、実装の選択にだけは注意しておきたいところです。
仕組みを一度知っておくと、こういうニュースを見たときに自分に関係あるかを自分で判断できるようになります。これが地味に効くんですよね〜。
なお今回の検証はすべてstdio上で、HTTPトランスポートでの挙動までは確認できていません。そこは今後試してみたいなと思っています。
いかがでしたか?MCPサーバーの仕組みを理解する参考になれば幸いです!
Twitterフォローお願いします
「次回以降も記事を読んでみたい!」「この辺分からなかったから質問したい!」
そんな時は、是非Twitter (@daiki1003)やInstagram (@ashdik_flutter)のフォローお願いします♪
Twitterコミュニティ参加お願いします
Twitterコミュニティ「Flutter lovers」を開設しました!参加お待ちしております😁
☕️ Buy me a coffee
また、記事がとても役に立ったと思う人はコーヒーを奢っていただけると非常に嬉しいです!





コメント