【MCP】今更聞けないMCPサーバーの仕組みを、Dartで書きながら理解してみた

未分類

概要

どうも、@daiki1003です!

みなさん、MCPサーバー使っていますか?

設定ファイルに誰かのおまじないをコピペして、なんとなく動いている。でも「あれ結局どういう仕組み?」と聞かれると自信がない……。僕もそうでした。

先日 MCP の仕様が大きく変わったニュースを見て、「これ自分に影響あるの?」を判断しようとしたら、前提が分かってないことに気づいたんですね〜。

ということで今回は、実際に Dart でサーバーを書きながら仕組みを見ていきます。

それでは早速行ってみましょー!

執筆時環境

  • macOS: 26.3.1
  • Dart: 3.12.2
  • mcp_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サーバーの全体像。登場人物はホストとMCPサーバーの2人でJSON-RPC 2.0でつながる。サーバーが公開できるのはTools・Resources・Promptsの3種類。「サーバー」という名前だが実体はホストが起動する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')],
    );
  },
);

大事なのは inputSchemadescription です。AIはこれを読んで使い方を理解するので、ここがそのままAIへの説明書になります。雑だとうまく使ってくれません。

3. トランスポートに繋ぐ

await server.connect(StdioServerTransport());

はい、1行で完成です。

そしてここが一番お伝えしたいポイントで、宣言とツール登録のコードはどこで動かすかに関係なく全く同じなんですね。変わるのはトランスポート接続の1行だけ。

ここが stdioHTTP かで、サーバーの性格がまるっきり変わります。

stdio と HTTPリモートの違い

設定ファイルで "command": "dart" のように書くのが stdio です。ホストがそのコマンドを子プロセスとして起動し、標準入出力でJSONをやりとりします。ネットワークは使いません。

stdioHTTPリモート
起動する人ホスト(自分のPC)運営者(クラウドに常駐)
通信パイプインターネット越しのHTTP
相手常に1人不特定多数
認証不要(環境変数で渡す)OAuth 2.1(任意)
台数1プロセス負荷対策で増える

この「台数が増える」が、次で効いてきます。

なぜこの違いを知っておくと得なのか

2026年7月28日、MCPの仕様が大きく改定されました。目玉はステートレス化で、initialize という握手と Mcp-Session-Id ヘッダが廃止されています。

「自分の設定も直さなきゃ?」と思いますよね。でも、ここまで読んだ方はもう分かるはずです。これはHTTPの話なんですよ。

旧仕様はセッションIDで会話を識別していました。つまりサーバーが3台あったら同じ人は必ず1号機に戻す必要がある。負荷は偏るし、1号機が落ちれば会話も消えます。

だから握手をやめて、毎回のリクエストが必要な情報を全部持つ形にした、というわけですね。

MCPの握手の変化。旧仕様(2025-11-25)はinitializeで握手しMcp-Session-Idを発行するため同じ人を必ず1号機に戻す必要があり負荷が偏る。新仕様(2026-07-28)は握手とセッションIDを廃止し毎リクエストが必要な情報を全部持つため、どのサーバーに振られても成立する

一方 stdio の相手は自分専用の1プロセスだけ。そもそも振り分け先を選ぶ余地がありません。解決すべき問題が最初から存在しないんですね。

ちなみに実際に握手なしで叩いてみたところ、旧仕様で握手時に一度だけ渡していた情報(プロトコルバージョン、クライアント情報)が、そっくり毎回のリクエストの _meta に引っ越しているのが確認できました。

「握手をやめる」とはこういうことだったんですね〜。

僕の個人環境は全部stdioだったので、直したものはゼロでした。大事なのは「改定が大したことない」ではなく、stdioHTTPかを見分けられれば影響の有無を自分で判断できるということです。

SDKの「Tier」に注意

MCPの仕様が配っているのはルールと型定義だけで、動く実装は入っていません(正本は TypeScript で書かれた schema.ts です)。

なので書くには言語ごとのSDKが必要で、SDKが追いついていなければ新仕様は使えません。

公式は対応の速さをティアで示しています。

Tier 1Tier 2Tier 3
言語TypeScript / Python / C# / GoJava / Rust / RubySwift / 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に入っていない言語では「公式だから安心」が成り立たないことがあるんですね。

まとめ

MCPサーバーは宣言・ツール登録・トランスポート接続の3ステップ。宣言とツール登録は動かす場所に関係なく同じで、変わるのはトランスポート接続の1行だけ。stdioは子プロセスで1対1、HTTPは常駐で多対多。2026-07-28のステートレス化はHTTP側だけの話

あと1つ付け加えるなら、MCPの仕様は文書にすぎず、書くにはSDKが要るということ。DartはTierの外にいるので、実装の選択にだけは注意しておきたいところです。

仕組みを一度知っておくと、こういうニュースを見たときに自分に関係あるかを自分で判断できるようになります。これが地味に効くんですよね〜。

なお今回の検証はすべてstdio上で、HTTPトランスポートでの挙動までは確認できていません。そこは今後試してみたいなと思っています。

いかがでしたか?MCPサーバーの仕組みを理解する参考になれば幸いです!

誰かのお役に立てば。

Twitterフォローお願いします

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非Twitter (@daiki1003)Instagram (@ashdik_flutter)のフォローお願いします♪

Twitterコミュニティ参加お願いします

Twitterコミュニティ「Flutter lovers」を開設しました!
参加お待ちしております😁

☕️ Buy me a coffee

また、記事がとても役に立ったと思う人は
コーヒーを奢っていただけると非常に嬉しいです!
@daiki1003

コメント

タイトルとURLをコピーしました