Files
analysis_claude_code/s18_mcp_plugin/README.ja.md
2026-07-31 03:35:17 +08:00

8.3 KiB
Raw Blame History

s18: MCP Tools — 外部ツール、標準プロトコル

English · 中文 · 日本語

s01 → ... → s16 → s17 → s18s19 → s20 → s21

"外部ツール、標準プロトコル" — 発見、組み立て、呼び出し。Agent はツールを誰が書いたか知る必要がない。

Harness 層: プラグイン — 外部能力を標準プロトコルで接続。


課題

s01 から s17 まで、Agent の全ツールは手書き — bash、read、write、task、worktree。入力検証、実行ロジック、エラーハンドリング、全て一行ずつ書いた。

今、統合したい外部サービスが 3 つある:社内の Jira APIissue 検索、ticket 作成、独自のデプロイシステムdeploy トリガー、ログ閲覧)、チームの Notion ナレッジベース(ドキュメント検索、ページ作成)。各サービスのためにツールコードを書き直したくない。

標準プロトコルが必要 — 外部サービスがこのプロトコルを実装していれば、サービスが何の言語で書かれていても、Agent は直接そのツールを呼び出せる。


ソリューション

MCP Architecture

MCPModel Context Protocolは、Agent が外部ツールを発見・呼び出しする方法を定義。核心概念:

概念 目的
MCPClient Agent 側のクライアント — server に接続、ツールを発見、ツールを呼び出し
MCP Server 外部サービス側 — tools/list + tools/call を実装
assemble_tool_pool 組み込みツールと MCP ツールを一つのツールプールに組み立てる
mcp__server__tool 命名 異なる server 間のツール名衝突を防止

s17 の worktree 分離、自動認領、チームプロトコルを引き継ぐ。本章では connect_mcp ツールを追加し、サービスへの接続、ツール発見、ツールプールへの追加を行う。

本章はプロセス内の server handler を登録し、発見から呼び出しまでをオフラインで実行する。各 handler はクライアントが必要とする tools/listtools/call を提供する。


仕組み

MCPClient発見 + 呼び出し

class MCPClient:
    def __init__(self, name: str):
        self.name = name
        self.tools: list[dict] = []
        self._handlers: dict[str, callable] = {}

    def register(self, tool_defs, handlers):
        """Simulates tools/list discovery."""
        self.tools = tool_defs
        self._handlers = handlers

    def call_tool(self, tool_name: str, args: dict) -> str:
        """Simulates tools/call."""
        handler = self._handlers.get(tool_name)
        if not handler:
            return f"MCP error: unknown tool '{tool_name}'"
        return handler(**args)

登録した Python 関数が、tools/call から呼ばれる server 側のツール実装になる。

connect_mcp接続 + 発見

def connect_mcp(name: str) -> str:
    if name in mcp_clients:
        return f"MCP server '{name}' already connected"
    factory = MOCK_SERVERS.get(name)
    if not factory:
        return f"Unknown server '{name}'. Available: ..."
    mcp_client = factory()
    mcp_clients[name] = mcp_client
    return f"Connected to '{name}'. Discovered: ..."

接続後、server が提供するツールが即座に利用可能。

normalize_mcp_name名前の正規化

_DISALLOWED_CHARS = re.compile(r'[^a-zA-Z0-9_-]')

def normalize_mcp_name(name: str) -> str:
    return _DISALLOWED_CHARS.sub('_', name)

[a-zA-Z0-9_-] 以外の全文字を _ に置換。server 名やツール名の特殊文字による名前衝突やインジェクション問題を防止。

assemble_tool_poolツールプールの組み立て

def assemble_tool_pool() -> tuple[list[dict], dict]:
    tools = list(BUILTIN_TOOLS)
    handlers = dict(BUILTIN_HANDLERS)
    for server_name, mcp_client in mcp_clients.items():
        safe_server = normalize_mcp_name(server_name)
        for tool_def in mcp_client.tools:
            safe_tool = normalize_mcp_name(tool_def["name"])
            prefixed = f"mcp__{safe_server}__{safe_tool}"
            tools.append(...)
            handlers[prefixed] = (
                lambda *, c=mcp_client, t=tool_def["name"], **kw:
                    c.call_tool(t, kw))
    return tools, handlers

プレフィックス mcp__{server}__{tool} で異なる server 間のツール名衝突を防止。名前は normalize_mcp_name で正規化。

MCP ツールの description に (readOnly) または (destructive) を付け、読み取りと変更の区別をツールメタデータ上で明示する。

キャッシュなし:ツールプールが変われば、プロンプトも変わる

s10-s17 の agent_loop は prompt cache で再シリアライズを回避。s18 はキャッシュを削除:

def agent_loop(messages, context):
    tools, handlers = assemble_tool_pool()     # 毎回再構築
    system = assemble_system_prompt(context)    # 毎回再生成
    ...
    if any(b.name == "connect_mcp" ...):
        tools, handlers = assemble_tool_pool()  # 接続後に再構築
        system = assemble_system_prompt(context)

connect_mcp の後には mcp__docs__search などがツールプールへ加わる。古いシリアライズ済みツール一覧を再利用するとモデルから新しいツールが見えないため、接続後にツールプールと system prompt を再構築する。

MCP ツールは Lead のみ利用可能

connect_mcp は Lead のツールであり、assemble_tool_pool も Lead の agent loop に使われる。チームメイトはタスク、ファイル、メッセージ、プランの各ツールを保持し、Lead が外部サービスを呼び出して得た仕事を割り当てる。


s17 からの変更

コンポーネント 変更前 (s17) 変更後 (s18)
ツールソース 全て手書き builtin 手書き + MCP 外部ツール動的発見
ツールプール 固定 BUILTIN_TOOLS assemble_tool_pool が動的に mcp__ プレフィックスツールを組み立てる
名前の安全性 なし normalize_mcp_name 正規化
新規タイプ MCPClient クラスtools/list + tools/call をシミュレート)
名前空間 mcp__server__tool 衝突防止
ツール説明 アノテーションなし (readOnly)/(destructive) アノテーション
プロンプトキャッシュ ありs10 から) 削除 — ツールプールが動的、キャッシュが陳腐化
Lead ツール worktree・チームツール + connect_mcp と動的に発見した MCP ツール
チームメイトツール タスク、ファイル、メッセージ、プランのツール 変更なし
拡張方法 ツール追加のコードを書く 標準プロトコル、任意言語で server を実装

試してみる

cd learn-claude-code
python s18_mcp_plugin/code.py

以下のプロンプトを試してください:

  1. ドキュメントから worktree のクリーンアップ方針を調べてください。
  2. 現在のプロジェクトをデプロイし、結果を報告してください。
  3. 現在実行できるドキュメント操作とデプロイ操作を教えてください。

観察ポイントMCP server 接続後、ツール名に mcp__docs__mcp__deploy__ プレフィックスが付いているか?両方の server のツールが同時に利用可能かMCP ツールの description に (readOnly)/(destructive) アノテーションが付いているか?


次の章

Agent は標準プロトコルで外部ツールに接続できるようになった。前 18 章では、各境界を観察できるように仕組みを一つずつ追加してきた。

tools、permissions、hooks、todo、task graph、memory、compact、background work、cron、teams、worktree、MCP は、別々の例ではなく同じ loop に接続されるべきです。

s19 Integrated Harness → s01-s18 の仕組みを 1 つの harness に統合。仕組みは多く、loop は 1 つ。