はじめに#
この記事では、大規模言語モデル(LLM)とアプリケーション間の通信を標準化するためにAnthropicによって開発されたプロトコルであるモデルコンテキストプロトコル(MCP)について探ります。この記事は、先週Gophercon UKで発表した同名の基調講演に基づいています。
明確な理解を深めるために、まず基礎から始め、次に主要なアーキテクチャコンポーネント、トランスポート、およびビルディングブロック(ツール、プロンプト、リソース)について説明します。以前に作成したサーバー(godoctorとspeedgrapher)に基づいた実践的な例を交えながら進めていきます。最後に、Gemini CLIを使用したシンプルな「バイブコーディング」の例を通して、Go SDK for MCPを使用して独自のサーバーを作成する方法を見ていきます。
このプロトコルについて初めて聞く方でも、すでに1つか2つのサーバーを作成したことがある方でも、この記事はさまざまな経験レベルの方に役立つ情報を提供することを目指しています。
新しい標準の誕生#
標準について話すとき、いつもこのXKCDのコミックが頭に浮かびます。
面白いことに、業界でこのジョークが完全には当てはまらないのは、今回が初めてかもしれません(少なくとも今のところは)。幸いなことに、業界はLLMにコンテキストを追加するための標準としてMCPにすぐに収束しました。
仕様書によると、MCPは次のとおりです。
MCPは、アプリケーションが大規模言語モデル(LLM)にコンテキストを提供する方法を標準化するオープンプロトコルです。MCPをAIアプリケーション用のUSB-Cポートのようなものだと考えてください。USB-Cがデバイスをさまざまな周辺機器やアクセサリに接続するための標準化された方法を提供するように、MCPはAIモデルをさまざまなデータソースやツールに接続するための標準化された方法を提供します。MCPを使用すると、LLMの上にエージェントや複雑なワークフローを構築し、モデルを世界と接続できます。
USB-Cとの類推は理解できますが、私はMCPを新しいHTTP/RESTと考える方が好きです。HTTPがWebサービスが通信するための普遍的な言語を提供したように、MCPはAIモデルが外部システムと対話するための共通のフレームワークを提供します。エンジニアとして、私たちは過去20年ほど、すべてを「APIファースト」にすることに費し、ソフトウェアシステムを相互接続させ、新しいレベルの自動化を実現してきました。次の20年間ではないかもしれませんが、今後5〜10年間は、これらのシステムすべてをAI対応にするために(そして新しいシステムを作成するために)かなりのエンジニアリングパワーを費やすことになると信じており、MCPはこのプロセスの重要な要素です。
MCPアーキテクチャ#
下の図を見ると、MCPアーキテクチャは実際よりも複雑に見えるかもしれません。
MCPアーキテクチャの主なコンポーネントは次のとおりです。
- MCPホスト: IDEやコーディングエージェントなどの主要なAIアプリケーション。
- MCPサーバー: 何らかの機能(ツールやプロンプトなど)へのアクセスを提供するプロセス。
- MCPクライアント: ホストを単一のサーバーに接続します。
本質的に、ホストアプリケーションは複数のクライアントを作成および管理し、各クライアントは特定のサーバーと1対1の関係を持ちます。
MCPレイヤー#
通信は2つのレイヤーで行われます。
- データレイヤー: JSON-RPCベースのプロトコルです。メッセージ形式の例は次のセクションで確認できます。
- トランスポートレイヤー: 通信チャネルを定義します。主なものは次のとおりです。
- 標準I/O: ローカルサーバー用
- ストリーマブルHTTPS: ネットワーク経由の通信用。(HTTPS+SSEを置き換えます)。
- HTTPS+SSE: セキュリティ上の懸念から、仕様の最新バージョンで非推奨になりました。
データレイヤーはSDKによって管理されており、テスト目的を除いて、手動でメッセージを構築する必要はありません。トランスポートの選択はユースケースによって異なりますが、一般的にはstdioから始めて、後でHTTPSを追加することをお勧めします。stdio MCPをHTTPSに変換したり、その逆を行ったりするオープンソースのアダプターもありますが、この機能を追加するのは非常に簡単なので、ソースコードを制御できないサーバーにのみ使用します。
初期化フロー#
クライアントとサーバーは、接続を確立するためにハンドシェイクを実行します。これには3つの主要なメッセージが含まれます。
- クライアントは、サポートするプロトコルバージョンを指定して、サーバーに
initialize
リクエストを送信します。(サーバーは初期化応答メッセージをクライアントに返信します。) - クライアントは、
notifications/initialized
メッセージで初期化を確認します。 - その後、クライアントは
tools/list
などのリクエストを開始して、サーバーの機能を発見できます。
クライアント側からのワイヤー上の初期化フローは、JSON-RPC表現を使用すると次のようになります。
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
「tools/list」や「tools/call」メッセージを直接送信することはできないことに注意してください。そうしないと、「server not ready」タイプのエラーが発生します。
たとえばGemini CLIのようなコーディングエージェントを介してMCPサーバーをコーディングしている場合、私はよく次のようにシェル経由でこれらのメッセージを送信するように指示します。
(
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}';
echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}';
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}';
) | ./bin/godoctor
私がこのフローを完全に理解する前は、私のコーディングエージェントは「サーバーの起動にもっと時間が必要なので、ツールコールの前にスリープを追加します」のような間違った仮定をすることが多かったので、実装が健全であることを確認するためにこれを行うのが好きです。開発中のMCPサーバーと適切に通信する方法をコーディングエージェントに早く教えるほど、良い結果が得られます。
MCPサーバーのビルディングブロック#
その中核において、MCPサーバーの機能は、3つの基本的なビルディングブロック(「プリミティブ」または「サーバーコンセプト」とも呼ばれる)を介して公開されます。
ビルディングブロック | 目的 | 誰が制御するか | 実世界の例 |
---|---|---|---|
ツール | AIアクション用 | モデル制御 | フライトの検索、メッセージの送信、コードのレビュー |
リソース | コンテキストデータ用 | アプリケーション制御 | ドキュメント、カレンダー、メール、天気データ |
プロンプト | インタラクションテンプレート用 | ユーザー制御 | 「休暇の計画」、「会議の要約」 |
それぞれを詳しく見ていきましょう。
ツール#
ツールは、AIモデルがアクションを実行できるようにする関数です。たとえば、API、データベース、またはコマンドラインツールを公開します。
ツールの概念を実験するために私が作成したサーバーはGoDoctorと呼ばれ、Goコードを作成する際のLLMの能力を向上させるためのツールを提供するように設計されています。GoDoctorという名前は、Goパッケージに関するドキュメントを公開するコマンドラインツール「go doc」をもじったものです。
私の仮説は、正しいドキュメントを提供することで、LLMは幻覚を見ることが少なくなり、より良いコードを作成できるようになるというものでした。あるいは、少なくとも、間違いを学習して自己修正するためのリソースを持つようになります。
ツールの実装は、MCPサーバーにツールを登録することと、ハンドラーを実装することの2つの主要なコンポーネントで構成されます。
登録はmcp.AddTool
関数を使用して行われます。
mcp.AddTool(server, &mcp.Tool{
Name: name,
Title: "Go Documentation",
Description: "Retrieves documentation for a specified Go package or a specific symbol (like a function or type). This is the primary tool for code comprehension and exploration. Use it to understand a package's public API, function signatures, and purpose before attempting to use or modify it.",
InputSchema: schema,
}, getDocumentationHandler)
ハンドラーは、API、コマンド、または関数を呼び出し、プロトコルと互換性のある方法(mcp.CallToolResult
構造体)で応答を返すアダプターです。
GoDoctorのドキュメントツールのハンドラーは次のとおりです。
func getDocumentationHandler(ctx context.Context, s *mcp.ServerSession, request *mcp.CallToolParamsFor[GetDocumentationParams]) (*mcp.CallToolResult, error) {
pkgPath := request.Arguments.PackagePath
symbolName := request.Arguments.SymbolName
if pkgPath == "" {
return result.NewError("package_path cannot be empty"), nil
}
args := []string{"doc"}
if symbolName == "" {
args = append(args, pkgPath)
} else {
args = append(args, "-short", pkgPath, symbolName)
}
cmd := exec.CommandContext(ctx, "go", args...)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err := cmd.Run()
docString := strings.TrimSpace(out.String())
if err != nil {
// If the command fails, it might be because the package doesn't exist.
// This is a valid result from the tool, not a tool execution error.
if strings.Contains(docString, "no required module provides package") {
return result.NewText(docString), nil
}
// For other errors, we'll consider it a tool execution error.
return result.NewError("`go doc` failed for package %q, symbol %q: %s", pkgPath, symbolName, docString), nil
}
if docString == "" {
docString = "documentation not found"
}
return result.NewText(docString), nil
}
プロンプト#
プロンプトは、パラメータ化できる再利用可能なユーザー制御のテンプレートを提供します。これらはAIエージェントでスラッシュコマンドとして表示されることが多く、ユーザーは簡単なコマンドで複雑なワークフローを呼び出すことができます。
これを実際に見てみるために、私が作成した別のMCPサーバーspeedgrapher
を見てみましょう。これは、私のテクニカルライティングを支援するためのプロンプトとツールのコレクションです。
speedgrapher
で最も単純なプロンプトの1つは/haiku
です。ツールと同様に、プロセスにはプロンプトの定義と、そのハンドラーの実装が含まれます。
func Haiku() *mcp.Prompt {
return &mcp.Prompt{
Name: "haiku",
Description: "Creates a haiku about a given topic, or infers the topic from the current conversation.",
Arguments: []*mcp.PromptArgument{
{
Name: "topic",
Description: "The topic for the haiku. If not provided, the model will infer it from the conversation.",
Required: false,
},
},
}
}
func HaikuHandler(ctx context.Context, session *mcp.ServerSession, params *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
prompt := "Write a haiku about the main subject of our conversation."
if topic, ok := params.Arguments["topic"]; ok && topic != "" {
prompt = fmt.Sprintf("The user wants to have some fun and has requested a haiku about the following topic: %s", topic)
}
return &mcp.GetPromptResult{
Messages: []*mcp.PromptMessage{
{
Role: "user",
Content: &mcp.TextContent{
Text: prompt,
},
},
},
}, nil
}
リソース#
リソースは、ファイル、API、またはデータベースからデータを公開し、AIがタスクを実行するために必要なコンテキストを提供します。概念的には、ツールはアクションを実行するためのものであり、リソースは情報を提供するためのものです。
そうは言っても、現実の世界では、ほとんどの開発者がデータを公開するためにツールを使用しているため(GETリクエストでAPIを使用するように)、リソースの優れた実装をまだ見たことがありません。仕様が賢すぎようとしているケースの1つだと思いますが、コミュニティがリソースに慣れてくれば、将来的にはリソースの優れた用途が見られるようになるかもしれません。
クライアントコンセプト#
サーバーのビルディングブロックに加えて、プロトコルはクライアントコンセプトも定義しています。これは、サーバーがクライアントに要求できる機能です。これらには以下が含まれます。
- サンプリング: サーバーがクライアントのモデルからLLM補完を要求できるようにします。サーバーの作成者はモデルを呼び出すために独自のAPIキーを使用する必要がないため、これはセキュリティと課金の観点から有望です。
- ルート: クライアントがファイルシステムの境界を通信するためのメカニズムで、サーバーがどのディレクトリで操作できるかを伝えます。
- 引き出し: サーバーがユーザーから特定の情報を要求するための構造化された方法で、必要に応じて入力を収集するために操作を一時停止します。
私が調査したほとんどの現実世界のアプリケーションが、サーバーとクライアントの両方を含め、まだ仕様に追いついていないもう1つのケースです。これらの機能が広く利用可能になるまでには、しばらく時間がかかるかもしれません。これは、最先端のテクノロジーを扱う際の問題の1つです…たとえば、Gemini CLIは、約1週間前にルートサポートを追加しました: https://github.com/google-gemini/gemini-cli/pull/5856
ライブデモ: MCPサーバーのバイブコーディング#
お気に入りのコーディングエージェントに「Hello World」のようなサーバーを作成させるためのプロンプトを次に示します。最近のエージェントは非決定的であるため、最初の試行で100%機能しない場合があり、最初のプロンプトの後にいくつかの追加のプロンプトでLLMをガイドする必要があるかもしれませんが、良い出発点です。
あなたのタスクは、「hello world」ツールを公開するモデルコンテキストプロトコル(MCP)サーバーを作成することです。MCPの実装には、公式のGo SDK for MCPを使用し、stdioトランスポートを使用する必要があります。
コードを作成する前に、これらのリファレンスを読んで、テクノロジーとプロジェクト構造に関する情報を収集してください。
- https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/refs/heads/main/README.md
- https://go.dev/doc/modules/layout
サーバーをテストするには、次のようなシェルコマンドを使用します。
`(
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}';
echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}';
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}';
) | ./bin/hello`
エージェントがこのタスクを正常に完了したら、新しいツールに「method tools/call」を実行して結果を確認するように依頼してください。
未来を覗き見る#
GoコミュニティはMCPエコシステムに積極的に投資しています。注目すべき2つの主要なプロジェクトは次のとおりです。
- Go SDK for MCP: デモで使用した公式SDKで、GoogleとAnthropicのパートナーシップです。まだ実験的ですが(現在のバージョンは0.20)、機能的で活発に開発されています。github.com/modelcontextprotocol/go-sdkで入手できます。
gopls
のMCPサポート: Go言語サーバーであるgopls
は、モデルに強化されたGoコーディング機能を提供するためにMCPサポートを追加しています。プロジェクトはまだ初期段階ですが、tip.golang.org/gopls/features/mcpで進捗状況を追跡できます。
便利なMCPサーバー#
コミュニティによって構築された注目すべきサーバーをいくつか紹介します。
- Playwright: Microsoftによって維持されているこのサーバーを使用すると、AIエージェントはWebページをナビゲートし、スクリーンショットを撮り、ブラウザータスクを自動化できます。https://github.com/microsoft/playwright-mcpで入手できます。
- Context7: GoDoctorと同様に、このサーバーはモデルにドキュメントを提供して、幻覚を軽減し、応答を改善します。クラウドソーシングされたリポジトリからドキュメントを取得します。詳細については、https://context7.com/をご覧ください。
自分で構築してみませんか?#
モデルコンテキストプロトコルは、AIエージェントの機能を拡張するための標準化された方法を提供します。独自のサーバーを構築することで、特定のワークフローに合わせて調整された、特殊なコンテキスト対応のアシスタントを作成できます。
始めたい場合は、独自のMCPサーバーをゼロから構築するプロセスを順を追って説明するGoogle Codelabを作成しました。
Gemini CLI、MCP、Goを使用してコーディングアシスタントを構築する方法
最後の言葉#
この記事を楽しんでいただけたでしょうか。ご質問やご意見がございましたら、下のコメント欄または私のソーシャルのいずれかでお気軽にご連絡ください。ありがとうございました!