mcpField
B
backend-integrator

backend-integrator

github.com/massgen開発1.1k84 回インストール20 週間前に更新
リポジトリ

backend-integrator について

backend-integratorは、MassGenにLLMバックエンドを追加・監査するためのチェックリストと実装パターンです。コードベースでの実装、登録、認証、トークン管理、ツールとMCPの処理を扱います。

主な機能

  1. 01バックエンドの基底クラス選択
  2. 02ストリーミングと状態管理の実装
  3. 03バックエンドと機能の登録
  4. 04認証とトークン料金の処理
  5. 05カスタムツールとMCPサーバーの統合

活用シーン

  1. 01MassGenへのLLMプロバイダー追加
  2. 02既存バックエンド統合の監査
  3. 03バックエンド機能に伴うファイルと変更の計画

MCP 対応

APIバックエンドが設定済みのMCPサーバーを扱う方法と、CLI・SDKラッパーが基盤システムへMCPサーバー設定を渡す方法を説明します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/massgen/massgen --skill backend-integrator

インストール後、Claude Code で/backend-integratorを入力して読み込まれたか確認します。

詳細

バックエンドインテグレーター

このスキルでは、MassGen に新しい LLM バックエンドを統合するための完全なチェックリストとパターンを提供します。完全な統合では、コードベース全体で約 15 個のファイルを変更します。

このスキルを使用するタイミング

  • 新しい LLM プロバイダー/バックエンドを追加するとき
  • 既存のバックエンドに統合漏れがないか監査するとき
  • バックエンドの機能を拡張する際に変更が必要なファイルを把握するとき

統合アーキテクチャ

Backend Type Decision:
  Stateless + OpenAI-compatible API   → subclass ChatCompletionsBackend
  Stateless + custom API              → subclass CustomToolAndMCPBackend
  Stateless + Response API format     → subclass ResponseBackend
  Stateful CLI wrapper (like Codex, Gemini CLI) → subclass LLMBackend directly
  Stateful SDK wrapper (like Claude Code, Copilot) → subclass LLMBackend directly

完全なチェックリスト

フェーズ 1:コア実装(3 ファイル)
1.1 バックエンドクラス

ファイル:massgen/backend/<name>.py

基底クラスを選択します:

  • LLMBackend — 最低限の機能です。すべて自分で実装します
  • CustomToolAndMCPBackend — MCP とカスタムツールのサポートを追加します(最も一般的です)
  • ChatCompletionsBackend — OpenAI 互換 API 向けです(上記のクラスを継承します)
  • ResponseBackend — OpenAI Response API 形式向けです

必須メソッド:

async def stream_with_tools(self, messages, tools, **kwargs) -> AsyncGenerator[StreamChunk, None]:
    """Main streaming method. Yield StreamChunks."""

def get_provider_name(self) -> str:
    """Return provider name string (e.g., 'OpenAI', 'Codex')."""

def get_filesystem_support(self) -> FilesystemSupport:
    """Return NONE, NATIVE, or MCP."""

yield する StreamChunk の種類:

種類用途主なフィールド
"content"テキスト出力content="..."
"tool_calls"ツールの呼び出しtool_calls=[{id, name, arguments}]
"reasoning"思考/推論の差分reasoning_delta="..."
"reasoning_done"推論の完了reasoning_text="..."
"reasoning_summary"推論の要約の差分reasoning_summary_delta="..."
"reasoning_summary_done"推論の要約の完了reasoning_summary_text="..."
"complete_message"アシスタントの完全なメッセージcomplete_message={...}
"complete_response"API の生レスポンスresponse={...}
"done"ストリームの完了usage={prompt_tokens, completion_tokens, total_tokens}
"error"エラーの発生error="..."
"agent_status"ステータスの更新status="...", detail="..."
"backend_status"バックエンドレベルのステータスstatus="...", detail="..."
"compression_status"圧縮イベントstatus="...", detail="..."
"hook_execution"フックの実行hook_info={...}, tool_call_id="..."

すべてのチャンクに共通するフィールド:source(エージェント/オーケストレーター ID)、display(ブール値、デフォルトは True)。

トークン追跡 — 次のいずれかを呼び出します:

self._update_token_usage_from_api_response(usage_dict, model)  # If API returns usage
self._estimate_token_usage(messages, response_text, model)      # Fallback

タイミング — stream_with_tools 内で呼び出します:

self.start_api_call_timing(self.model)       # Before API call
self.record_first_token()                     # On first content chunk
self.end_api_call_timing(success=True/False)  # After completion

ステートフルなバックエンド(CLI/SDK ラッパー)の場合は、次も実装します:

def is_stateful(self) -> bool: return True
async def clear_history(self) -> None: ...
async def reset_state(self) -> None: ...

圧縮のサポート — StreamingBufferMixin を継承し、次を呼び出します:

self._clear_streaming_buffer(**kwargs)       # Start of stream
self._finalize_streaming_buffer(agent_id=id) # End of stream
1.2 フォーマッター(必要な場合)

ファイル:massgen/formatter/<name>_formatter.py

API が標準以外のメッセージ/ツール形式(OpenAI chat completions 形式以外)を使用する場合にのみ必要です。FormatterBase をサブクラス化し、format_messages()、format_tools()、format_mcp_tools() を実装します。

既存のフォーマッター:

  • _claude_formatter.py — Anthropic Messages API
  • _gemini_formatter.py — Gemini API
  • _chat_completions_formatter.py — OpenAI/汎用(互換性のある API で再利用します)
  • _response_formatter.py — OpenAI Response API 形式
1.3 API パラメーター処理(必要な場合)

ファイル:massgen/api_params_handler/<name>_api_params_handler.py

バックエンドが HTTP API を呼び出し、API に渡す前に YAML 設定パラメーターをフィルタリング/変換する必要がある場合にのみ必要です。APIParamsHandlerBase をサブクラス化します。

CLI/SDK ラッパー(Codex、Claude Code)では通常、コマンドを直接構築するため、これは必要ありません。

フェーズ 2:登録(4 ファイル)
2.1 バックエンド init.py

ファイル:massgen/backend/__init__.py

from .your_backend import YourBackend
# Add to __all__
2.2 CLI バックエンドのマッピング

ファイル:massgen/cli.py

create_backend() 関数に追加します:

elif backend_type == "your_backend":
    api_key = kwargs.get("api_key") or os.getenv("YOUR_API_KEY")
    if not api_key:
        raise ConfigurationError(
            _api_key_error_message("YourBackend", "YOUR_API_KEY", config_path)
        )
    return YourBackend(api_key=api_key, **kwargs)

API キーが不要な CLI ベースのバックエンドでは、キーの確認を省略します。

2.3 機能レジストリ

ファイル:massgen/backend/capabilities.py

BACKEND_CAPABILITIES にエントリーを追加します:

"your_backend": BackendCapabilities(
    backend_type="your_backend",
    provider_name="YourProvider",
    supported_capabilities={"mcp", "web_search", ...},
    builtin_tools=["web_search"],  # Provider-native tools
    filesystem_support="mcp",      # "none", "mcp", or "native"
    models=["model-a", "model-b"], # Newest first
    default_model="model-a",
    env_var="YOUR_API_KEY",        # Or None
    notes="...",
    model_release_dates={"model-a": "2025-06"},
    base_url="https://api.example.com/v1",  # If applicable
)
2.4 設定バリデーター(必要な場合)

ファイル:massgen/config_validator.py

特別なルール(必須パラメーター、組み合わせ不可の設定など)がある場合は、_validate_backend() にバックエンド固有の検証を追加します。

フェーズ 3:トークン管理(1 ファイル)
3.1 料金

ファイル:massgen/token_manager/token_manager.py

まず LiteLLM を確認してください — モデルが LiteLLM データベースに存在しない場合に限り、PROVIDER_PRICING に追加します。プロバイダー名は get_provider_name() と完全に一致している必要があります(大文字と小文字を区別します)。

フェーズ 4:除外するパラメーター(新しい YAML パラメーターを追加する場合は 2 ファイル)
4.1 基底クラスでの除外

ファイル:massgen/backend/base.py -> get_base_excluded_config_params()

4.2 API パラメーターハンドラーでの除外

ファイル:massgen/api_params_handler/_api_params_handler_base.py -> get_base_excluded_params()

両方の設定を同期させてください。プロバイダー API に渡してはならない、新しいフレームワークレベルの YAML パラメーターを追加します。

フェーズ 5:認証
5.1 API キーバックエンド(標準)

ほとんどのバックエンドでは API キーを使用します。capabilities.py で env_var を設定し、cli.py にキーのチェックを追加します:

# cli.py
api_key = kwargs.get("api_key") or os.getenv("YOUR_API_KEY")
if not api_key:
    raise ConfigurationError(...)
return YourBackend(api_key=api_key, **kwargs)
5.2 OAuth/サブスクリプション認証(CLI/SDK ラッパー)

Codex や Claude Code のように OAuth に対応するバックエンドでは、複数段階の認証カスケードを実装します:

def __init__(self, api_key=None, **kwargs):
    # Tier 1: Explicit API key
    self.api_key = api_key or os.getenv("YOUR_API_KEY")
    # Tier 2: Check cached OAuth tokens
    self.use_oauth = not bool(self.api_key)
    self.auth_file = Path.home() / ".your_cli" / "auth.json"

async def _ensure_authenticated(self):
    if self.api_key:
        os.environ["YOUR_API_KEY"] = self.api_key
        return
    if self._has_cached_credentials():
        return
    # Tier 3: Initiate OAuth flow
    await self._initiate_oauth_flow()

async def _initiate_oauth_flow(self, use_device_flow=False):
    """Wrap the CLI's login command."""
    cmd = [self._cli_path, "login"]
    if use_device_flow:
        cmd.append("--device-auth")  # For headless/SSH
    proc = await asyncio.create_subprocess_exec(*cmd, ...)

cli.py では:API キーのチェックをスキップし、認証をバックエンドに委任します:

elif backend_type == "your_backend":
    # Auth handled by backend (API key or OAuth)
    return YourBackend(**kwargs)

capabilities.py では:API キー名を env_var に設定します(任意であり、OAuth には必須ではありません):

env_var="YOUR_API_KEY",  # Optional - can use OAuth instead
notes="Works with subscription auth (via `your-cli login`) or YOUR_API_KEY."

注:OAuth は、プロバイダーが推奨するサポート対象 SDK またはプログラムからのコマンドライン利用を通じてのみ提供します。各バックエンドで OAuth を正しくサポートできるよう、OAuth の利用方法として認められている手段を確認する必要があります(例:API を偽装した Claude Code ではなく、Claude Agent SDK を使用します)。

5.3 認証なし(ローカル推論)

ローカルサーバー(LM Studio、vLLM、SGLang)では、capabilities で env_var=None を設定します。cli.py でキーのチェックは行いません。

参考実装:

  • codex.py — ブラウザーとデバイスコードフローを使った完全な OAuth
  • claude_code.py — 3 段階:CLAUDE_CODE_API_KEY -> ANTHROPIC_API_KEY -> サブスクリプションログイン
  • lmstudio / vllm / sglang — 認証なし(env_var=None)
フェーズ 6:カスタムツールと MCP
6.1 標準パス(API バックエンド)

CustomToolAndMCPBackend を継承する場合、MCP とカスタムツールは自動的に機能します:

  • YAML の mcp_servers 設定から MCP サーバーを読み込みます
  • custom_tools / custom_tools_path 設定からカスタムツールを読み込みます
  • cwd が設定されている場合、FilesystemManager によってファイルシステム MCP が注入されます
  • ToolManager がツールの実行を処理します
6.2 マルチモーダルツール(すべてのバックエンドタイプ)

enable_multimodal_tools: true が設定されている場合、バックエンドは read_media および generate_media のカスタムツールを登録する必要があります。massgen/backend/base.py の共有ヘルパーを使用します:

from .base import get_multimodal_tool_definitions

# In __init__:
enable_multimodal = self.config.get("enable_multimodal_tools", False) or kwargs.get("enable_multimodal_tools", False)
if enable_multimodal:
    custom_tools.extend(get_multimodal_tool_definitions())
  • API バックエンド(CustomToolAndMCPBackend のサブクラス):自動的に処理されます。基底クラスが get_multimodal_tool_definitions() を呼び出し、_register_custom_tools() で登録します
  • CLI/SDK バックエンド(Codex、Claude Code などの LLMBackend のサブクラス):__init__ で明示的に実行し、MCP サーバーとしてラップする必要があります(§6.4 を参照)

重要:必ず get_multimodal_tool_definitions() を使用してください。ツール辞書を直接記述してはいけません。これにより、定義を一か所にまとめられます。

6.3 CLI/SDK ラッパー — MCP サーバー

これらのバックエンドでは、CLI/SDK 独自の MCP システムを設定する必要があります:

Codex:ワークスペース内のプロジェクトスコープの .codex/config.toml を(-C ディレクトリに)書き込みます。Codex はこれを自動的に読み込みます。MassGen の mcp_servers リストを TOML 形式に変換します:

[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]

Claude Code:mcp_servers 辞書を SDK オプションに直接渡します。サーバーのライフサイクルは SDK が処理します:

options = {"mcp_servers": {"filesystem": {"command": "npx", "args": [...]}}}
6.4 CLI/SDK ラッパー — カスタムツール

LLM は外部プロセス内で実行されるため、カスタムツールには特別な対応が必要です。

推奨:MCP サーバーとしてラップする(claude_code.py の方式):

  1. ToolManager を使ってカスタムツールを読み込む(スキーマ+実行関数)
  2. 各ツールを公開する MCP サーバーを作成する
  3. MCP サーバーを CLI/SDK に登録する
  4. LLM が MCP ツールを呼び出すと、ラッパーが ToolManager を通じて実行する
# claude_code.py pattern (simplified)
def _create_sdk_mcp_server_from_custom_tools(self):
    tool_schemas = self._custom_tool_manager.fetch_tool_schemas()
    mcp_tools = []
    for schema in tool_schemas:
        name = schema["function"]["name"]
        async def wrapper(args, tool_name=name):
            return await self._execute_massgen_custom_tool(tool_name, args)
        mcp_tools.append(tool(name=name, ...)(wrapper))
    return create_sdk_mcp_server(name="massgen_custom_tools", tools=mcp_tools)

SDK の MCP に対応していない CLI ラッパーの場合(Codex など): 共通ユーティリティ massgen/mcp_tools/custom_tools_server.py を使用します。このユーティリティは、stdio トランスポート経由で ToolManager のツールをラップする、スタンドアロンの fastmcp サーバーを作成します。

from ..mcp_tools.custom_tools_server import build_server_config, write_tool_specs

def _setup_custom_tools_mcp(self, custom_tools):
    # 1. Write tool specs JSON to workspace
    specs_path = Path(self.cwd) / ".codex" / "custom_tool_specs.json"
    write_tool_specs(custom_tools, specs_path)

    # 2. Get MCP server config (fastmcp run ... --tool-specs ...)
    server_config = build_server_config(
        tool_specs_path=specs_path,
        allowed_paths=[self.cwd],
        agent_id="my_backend",
    )

    # 3. Add to mcp_servers list (written to workspace config before launch)
    self.mcp_servers.append(server_config)

サーバーは CLI によってサブプロセスとして起動され、stdio 経由で接続します。reset_state() で specs ファイルをクリーンアップしてください。

参照:codex.py ではこのパターンを使用しています。custom_tools_server.py には、すぐに使える MCP サーバーの dict を返す build_server_config() も用意されています。

フォールバック:システムプロンプトへの埋め込み

  • システムプロンプト内で、ツールを JSON スキーマとして記述する
  • 構造化出力(JSON ブロック)を解析してツール呼び出しを取得する
  • MCP によるラップが実現できない場合にのみ使用する
6.5 カスタムツールの YAML 設定
backend:
  type: your_backend
  custom_tools:
    - path: "massgen/tool/_basic"          # Directory with TOOL.md
      function: "two_num_tool"
    - path: "path/to/tool.py"             # Single file
      function: ["func_a", "func_b"]
      preset_args: [{"timeout": 30}, {}]
  custom_tools_path: "massgen/tool/"      # Auto-discover from directory
  auto_discover_custom_tools: true        # Discover from registry

MassGen ワークフローツール(new_answer、vote など):これらは実行可能なツールではなく、調整レベルのツールです。必ずシステムプロンプト経由で埋め込んでください。詳しくは §6.7 を参照してください。

6.7 ワークフローツールの統合(vote、new_answer など)

ワークフローツールはネイティブな関数呼び出しツールではありません。システムプロンプトに埋め込まれ、モデルのテキスト出力から解析されます。このパターンは Claude Code と Codex のバックエンドで共通です。

massgen/backend/base.py の共通ヘルパー:

from .base import build_workflow_instructions, parse_workflow_tool_calls, extract_structured_response

# build_workflow_instructions(tools) → str
#   Filters tools to workflow tools, returns instruction text with usage examples.
#   Returns "" if no workflow tools present.

# parse_workflow_tool_calls(text) → List[Dict]
#   Extracts JSON tool calls from text output.
#   Returns standard format: {id, type, function: {name, arguments}}

# extract_structured_response(text) → Optional[Dict]
#   Low-level: extracts {"tool_name": "...", "arguments": {...}} from text.
#   Tries: ```json blocks → regex → brace-matching → line-by-line.

API バックエンドの場合:ワークフローツールは通常の関数ツールとして渡されるため、埋め込みは不要です。

CLI/SDK バックエンドの場合(Codex、Claude Code):モデルはネイティブな関数ツール定義を受け取れないため、次のようにします。

  1. 指示を構築する:build_workflow_instructions(tools) を呼び出して指示テキストを取得する
  2. システムプロンプトに埋め込む:バックエンドがシステムプロンプトに使用する仕組みに追加する
  3. テキスト出力を蓄積する:ストリーミング中のすべての content チャンクを追跡する
  4. ストリーミング後に解析する:parse_workflow_tool_calls(accumulated_text) を呼び出してツール呼び出しを抽出する
  5. 完了チャンクとして yield する:yield StreamChunk(type="done", tool_calls=workflow_tool_calls)

Codex 固有:指示はワークスペースのルートにある AGENTS.md に記述します(Codex はこれを自動的に読み込みます)。また、Codex は CLI 経由で単一のユーザープロンプトしか受け取らないため、オーケストレーターのシステムメッセージもメッセージから抽出して含めます。こうしないと、オーケストレーターのシステムメッセージが失われてしまいます。この方法は、開発者向け指示の方法が機能しなかったため採用されました。

Claude Code 固有:_build_system_prompt_with_workflow_tools() がツール呼び出しのセクションも追加して指示を構築し、それを system_prompt として SDK に渡します。

一般的なワークフローツールの解析(例:Claude Code)が機能するのか、MCP ツール(例:Codex)を使用する必要があるのかは、未解決の問題です。

6.7.1 MCP ベースとテキストベースのワークフローツール

CLI/SDK バックエンドで MCP ベースとテキストベースのワークフローツールを選ぶ際は、次の点を考慮してください。

MCP ベースの方法(試行済みですが、常に信頼できるとは限りません):

  • ワークフローツールを MCP サーバーとして登録する
  • SDK/CLI の MCP システムに渡す
  • 注意点:MCP ツールの命名規則は SDK によって異なります。Claude Code では mcp__{server_name}__ がプレフィックスとして付与されます。
    # If server is "massgen_workflow_tools", tool names become:
    # "new_answer" → "mcp__massgen_workflow_tools__new_answer"
    # "vote" → "mcp__massgen_workflow_tools__vote"
    
  • MCP ワークフローツールを使用する場合は、適切なプレフィックスを指定して build_workflow_mcp_instructions() を使用します。
    instructions = build_workflow_mcp_instructions(
        tools,
        mcp_prefix="mcp__massgen_workflow_tools__"
    )
    

テキストベースの方法(現在の Claude Code のフォールバック):

  • ワークフローツールの指示をシステムプロンプトに埋め込む
  • parse_workflow_tool_calls() を使用して、モデルのテキスト出力から JSON ツール呼び出しを解析する
  • より信頼性は高いものの、堅牢な解析が必要です

現在の状況:MCP ベースの方法は信頼性に欠けていたため、Claude Code ではテキストベースのワークフローツール(JSON 解析)を使用しています。Codex も AGENTS.md の指示を通じてテキストベースの方法を使用しています。

6.6 プロバイダー固有のツール:維持するか、置き換えるか

CLI/SDK ベースのエージェント(Codex、Claude Code)には、独自の組み込みツール(ファイル編集、シェル実行、ウェブ検索、サブエージェントなど)が備わっています。統合する際は、どのツールを維持し、どのツールを MassGen の同等機能で置き換えるかを決めてください。これらの判断は NativeToolBackendMixin の get_tool_category_overrides() に記録してください。詳細は §6.8 と Phase 10.2 を参照してください。

一般的なルール:MassGen が置き換える具体的な理由を持たない限り、プロバイダーのネイティブツールを優先してください。ネイティブツールはプロバイダーのモデルに最適化されており、余分な MCP オーバーヘッドを避けられます。

MassGen に利点がある場合は置き換える:

  • サブエージェント/タスクツール:MassGen のサブエージェントで置き換えます。プロバイダーのサブエージェントは MassGen の連携(投票、合意形成、インテリジェンス共有)に参加できません。
  • ファイルシステムツール:プロバイダーが提供する機能を超えて、MassGen でパスの権限適用やワークスペースの分離が必要な場合は、置き換えてもかまいません。

プロバイダーのネイティブツールを維持する:

  • ファイルの読み取り/書き込み/編集:プロバイダーのツールはモデルに最適化されています。
  • シェル/コマンドの実行:プロバイダーのサンドボックスはすでに設定されています。
  • ウェブ検索:通常、プロバイダーとの連携はシームレスです。

実装:プロバイダーのツールフィルタリング機能を使って、特定のツールを無効にします。

# Codex: via .codex/config.toml
[mcp_servers.some_server]
disabled_tools = ["task", "sub_agent"]

# Claude Code: via SDK disallowed_tools param
all_params["disallowed_tools"] = [
    "Read", "Write", "Edit", "MultiEdit",  # Use MassGen's MCP filesystem
    "Bash", "BashOutput", "KillShell",     # Use MassGen's execute_command
    "LS", "Grep", "Glob",                  # Use MassGen's filesystem tools
    "TodoWrite",                            # MassGen has its own task tracking
    "NotebookEdit", "NotebookRead",
    "ExitPlanMode",
    # Security restrictions
    "Bash(rm*)", "Bash(sudo*)", "Bash(su*)", "Bash(chmod*)", "Bash(chown*)",
]
# Conditionally keep web tools:
if not enable_web_search:
    disallowed_tools.extend(["WebSearch", "WebFetch"])

主なパターン:

  • Claude Code SDK は Bash の制限に glob パターンをサポートしています:"Bash(rm*)"、"Bash(sudo*)"
  • if "disallowed_tools" not in all_params を確認し、YAML 設定からユーザーが上書きできるようにします。
  • Codex では、.codex/config.toml の MCP サーバーごとに disabled_tools を指定します。組み込みツールには無効化する仕組みがないため、代わりに --full-auto を使って自動承認します。

capabilities.py に、バックエンドが提供するネイティブツールを記録してください。

builtin_tools=["file_edit", "shell", "web_search", "sub_agent"],
notes="Native tools: file ops, shell, web search, sub-agents. Override sub-agents with MassGen's."
6.8 NativeToolBackendMixin

ファイル:massgen/backend/native_tool_mixin.py

組み込みツールを持つバックエンド(CLI/SDK ラッパー)では、NativeToolBackendMixin を使ってツールのフィルタリングとフックの統合を標準化します。

from .native_tool_mixin import NativeToolBackendMixin

class YourBackend(NativeToolBackendMixin, LLMBackend):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.__init_native_tool_mixin__()
        # Optional: initialize hook adapter
        self._init_native_hook_adapter(
            "massgen.mcp_tools.native_hook_adapters.YourBackendAdapter"
        )

    def get_disallowed_tools(self, config):
        """Return native tools to disable (MassGen has equivalents)."""
        return ["NativeTool1", "NativeTool2"]

Mixin が提供するもの:

メソッド目的
get_disallowed_tools(config)抽象メソッド — 無効にするネイティブツールを宣言します
get_tool_category_overrides()抽象メソッド — スキップ/置き換える MCP カテゴリを宣言します
supports_native_hooks()フックアダプターが利用可能か確認します
get_native_hook_adapter()アダプターのインスタンスを取得します
set_native_hooks_config(config)MassGen のフックをネイティブ形式で設定します
_init_native_hook_adapter(path)インポートパスを指定してアダプターを初期化します

参考実装:

  • claude_code.py — MassGen MCP を優先し、ほとんどのネイティブツール(Read、Write、Bash など)を無効にします。
  • codex.py — すべてのネイティブツールを維持し、代わりに MassGen 側で同等の MCP の追加をスキップします。
Phase 7:テスト(2 ファイル以上)
7.1 機能テスト

capabilities.py に追加すると、massgen/tests/test_backend_capabilities.py で自動的にテストされます。

uv run pytest massgen/tests/test_backend_capabilities.py -v
7.2 統合テスト

作成先:massgen/tests/test_<name>_integration.py、またはクロスバックエンドのテストスクリプトに追加します。

クロスバックエンドテストのパターン:

BACKEND_CONFIGS = {
    "claude": {"type": "claude", "model": "claude-haiku-4-5-20251001"},
    "openai": {"type": "openai", "model": "gpt-4o-mini"},
    "your_backend": {"type": "your_backend", "model": "model-a"},
}
7.3 フック発火テスト(CLI/SDK バックエンド)⚠️ 必須

スクリプト:scripts/test_hook_backends.py

新しい CLI/SDK バックエンドでは、すべて必須です。 このスクリプトは、フックのデータ構造が正しいことだけでなく、実際のストリーミング中にフックのパイプライン全体が実際に発火することを検証します。

2 つのモードがあります。

ユニットモード(API 呼び出しなし — CI で実行):

uv run python scripts/test_hook_backends.py --backend your_backend

検証内容:バックエンドが GeneralHookManager を保持すること、MidStreamInjectionHook が注入コンテンツを返すこと、HighPriorityTaskReminderHook が発火すること、結合されたフックが正しく集約されること。

E2E モード(実際の API 呼び出し):

uv run python scripts/test_hook_backends.py --backend your_backend --e2e
uv run python scripts/test_hook_backends.py --backend your_backend --e2e --verbose

完全なライブフローを検証します。PreToolUse フックが発火 → ツールが実行 → PostToolUse フックが発火 → 注入コンテンツがモデルにフィードバックされ、認識されます。

バックエンドの追加:スクリプト内の BACKEND_CONFIGS にエントリーを追加します。

"your_backend": {
    "type": "your_backend",
    "model": "your-model",
    "description": "Your Backend description",
    "api_style": "openai",  # or "anthropic", "gemini"
},

現在の対象:claude、openai、gemini(ネイティブ SDK)、openrouter、grok 未対応:codex、claude_code、copilot、gemini_cli — これらはファイルベースの IPC または SDK ネイティブのフックを使用するため、個別の E2E フック検証が必要です。

重要な理由:ユニットテストでは、フックのデータ構造が正しいことを検証します。このスクリプトでは、実際のストリーミングターン中にフックが実際に発火することを検証します。バックエンドはすべてのユニットテストに合格しても、ライブ実行中にフックを黙って破棄してしまう可能性があります。

7.4 サンドボックス/パス権限テスト(CLI/SDK バックエンド)⚠️ 必須

スクリプト:scripts/test_native_tools_sandbox.py

組み込みのファイル/シェルツールを備えた新しい CLI/SDK バックエンドでは、すべて必須です。 このスクリプトは、固有のシークレットを含む実際のファイルシステムに対して実際のエージェントを実行し、権限の境界が実際に適用されていることを検証します。適用するコードが存在することだけを確認するものではありません。

# Test your new backend
uv run python scripts/test_native_tools_sandbox.py --backend your_backend

# Use LLM judge to detect subtle leakage
uv run python scripts/test_native_tools_sandbox.py --backend your_backend --llm-judge

このスクリプトは、権限の全マトリックスを検証します。

ゾーン想定される読み取り想定される書き込み
ワークスペース(cwd)✅ 許可✅ 許可
書き込み可能なコンテキストパス✅ 許可✅ 許可
読み取り専用のコンテキストパス✅ 許可❌ ブロック
すべてのコンテキスト外バックエンドによる❌ ブロック
親ディレクトリバックエンドによる❌ ブロック
/tmp✅ 許可バックエンドによる

固有のシークレット(各ゾーンのファイルに書き込まれる UUID)を使用して、操作が「失敗」した場合でも不正な読み取りを検出します。モデルの応答にシークレット文字列が現れた場合、エラーメッセージに関係なく情報漏えいが発生しています。

バックエンドの追加:スクリプト内の BACKEND_CONFIGS にエントリーを追加します。

"your_backend": {
    "module": "massgen.backend.your_backend",
    "class": "YourBackend",
    "model": "your-model",
    "blocks_reads_outside": True,   # Does your enforcement block reads?
    "blocks_tmp_writes": True,      # Does your enforcement block /tmp writes?
},

現在の対象:claude_code、codex 未対応:copilot、gemini_cli — これらのバックエンドが本番環境で使用される際に追加する必要があります。

重要な理由:権限コールバックとフックのユニットテストでは、適用ロジックを個別に検証します。このスクリプトでは、制限されたパスへのライブエージェントのアクセスを、適用処理が実際に阻止することを検証します。バックエンドのフックロジックが正しくても、フックが適切な実行経路に組み込まれていなければ、不正なアクセスを許してしまう可能性があります。

7.5 設定の検証
uv run python scripts/validate_all_configs.py
フェーズ 8:設定例(2 ファイル以上)
8.1 単一エージェント

作成:massgen/configs/providers/<name>/single_<name>.yaml

8.2 マルチエージェント/ツール

作成:massgen/configs/providers/<name>/<name>_with_tools.yaml

フェーズ 9:ドキュメント(3 ファイル以上)
9.1 YAML スキーマ

ファイル:docs/source/reference/yaml_schema.rst — バックエンド固有のパラメーターを記述します

9.2 バックエンドの表
uv run python docs/scripts/generate_backend_tables.py
9.3 ユーザーガイド

ファイル:docs/source/user_guide/backends.rst — 新しいバックエンドのセクションを追加します

フェーズ 10:システムプロンプトに関する考慮事項

システムプロンプトは、massgen/system_message_builder.py が massgen/system_prompt_sections.py の優先度ベースのセクションを使って組み立てます。バックエンドの機能と設定フラグに応じて、異なるセクションが条件付きで含められます。

10.1 セクションのカテゴリ

常に含まれます(すべてのバックエンド):

セクション目的
AgentIdentitySectionエージェントが何者か
EvaluationSectionvote/new_answer の調整プリミティブ
CoreBehaviorsSectionアクションの優先度、並列実行
OutputFirstVerificationSection品質改善の反復ループ
SkillsSectionコマンド実行による openskills read
MemorySection意思決定の記録
WorkspaceStructureSectionワークスペースのパス
ProjectInstructionsSectionCLAUDE.md/AGENTS.md
SubagentSectionMassGen のサブエージェントへの委任
BroadcastCommunicationSectionエージェント間のコミュニケーション
PostEvaluationSectionsubmit/restart
FileSearchSection汎用 CLI ツールである rg/sg
TaskContextSectionCONTEXT.md の作成
NoveltyPressureSectionエージェント間での新規性/多様性の促進
ChangedocSection変更のドキュメント化

設定フラグに応じて含まれます:

セクション条件
TaskPlanningSectionenable_task_planning=True
EvolvingSkillsSectionauto_discover_custom_tools=True AND enable_task_planning=True
PlanningModeSectionplanning_mode_enabled=True
CodeBasedToolsSectionenable_code_based_tools=True(CodeAct パラダイム)
CommandExecutionSectionenable_mcp_command_line=True
DecompositionSection分解モードが有効
MultimodalToolsSectionenable_multimodal_tools=True

モデル固有:

セクション条件
GPT5GuidanceSectionGPT-5 モデルのみ
GrokGuidanceSectionGrok モデルのみ

ネイティブバックエンド向けに調整:

セクション調整内容
FilesystemOperationsSectionhas_native_tools=True → 汎用的なツール表現
FilesystemBestPracticesSection比較ツールの表現を調整
10.2 tool_category_overrides

ファイル:massgen/backend/native_tool_mixin.py

NativeToolBackendMixin の抽象メソッド get_tool_category_overrides() は、バックエンドが MassGen のどのツールカテゴリをネイティブに処理するかを宣言します。各ネイティブバックエンドはこのメソッドを実装します:

@abstractmethod
def get_tool_category_overrides(self) -> Dict[str, str]:
    # "skip"     = backend has native equivalent
    # "override" = attach MassGen's version, disable native
    # (absent)   = attach MCP normally

カテゴリ:filesystem、command_execution、file_search、web_search、planning、subagents

現在は system_message_builder.py で以下の用途に使用されています:

  • filesystem: "skip" の場合、ファイルシステム関連セクションに has_native_tools=True を渡す
  • 各バックエンドがネイティブに処理するツールカテゴリを記載する

注:MCP サーバーの挿入フィルタリング(どの MCP サーバーを接続するか)は、各 CLI/SDK バックエンド(codex.py、claude_code.py)が個別に処理します。get_tool_category_overrides() メソッドはドキュメントとして機能し、システムプロンプトのセクションの動作を制御します。ミックスインを使用しないバックエンド(API ベース)は、system_message_builder.py のフォールバックにより、デフォルトで {} を返します。

10.3 パス権限の統合

filesystem: "skip" の場合、バックエンドのネイティブツールがファイル操作を処理します。ただし、PathPermissionManager(PPM)による MassGen のきめ細かなパスごとの権限が適用されないことがあります。各バックエンドでは、この問題に異なる方法で対応しています。

2 層の防御(権限コールバックに対応する SDK バックエンドに推奨):

  1. レイヤー 1 — 権限コールバック(大まかなゲート):SDK の権限リクエストからパスを抽出し、PPM に照らして検証します。抽出可能なパスがない場合はフェイルオープンとし、レイヤー 2 に委ねます。
  2. レイヤー 2 — PreToolUse フック(きめ細かな制御):オーケストレーターは、Claude Code 以外のネイティブバックエンドに対して PathPermissionManagerHook を PRE_TOOL_USE として自動登録します。toolName/toolArgs をすべて利用できるため、正確に検証できます。

バックエンドごとの対応方法:

バックエンドPPM の適用方法
Claude Code独自の add_dirs サンドボックスを使用 — オーケストレーターによる PPM フックの適用はスキップ
Copilot2 層の防御(権限コールバック + PPM PreToolUse フック)。Docker モードでは、コンテナーによる隔離に加え、多層防御として PPM を使用
Codexネイティブフックに非対応 — Docker/ワークスペースによる隔離に依存
API バックエンドToolManager によって PPM を自動的に適用

参考:2 層パターンについては copilot.py(_build_permission_callback)と orchestrator.py(_setup_native_hooks_for_agent)を参照してください。PPM の内部実装については、massgen/filesystem_manager/_path_permission_manager.py を参照してください。

フェーズ 11:フック

MassGen は、ツール実行やその他のライフサイクルイベントをインターセプトするフックをサポートしています。フックは透過的に実行されます(システムプロンプトには記載されません)。

一部のバックエンド(Codex など)はフックに対応していない場合があります。この場合、フックは MassGen のツール実行フレームワーク内で可能な範囲に限り適用されます。

11.1 フックの種類と組み込みフック

フックの種類(massgen/mcp_tools/hooks.py の HookType enum):

HookTypeトリガー
PRE_TOOL_USEツール実行前
POST_TOOL_USEツール実行後

組み込みフッククラス(PatternHook のサブクラス — オーケストレーターによって登録されます):

フッククラス種類目的
PathPermissionManagerHookPreToolUseパスごとの読み取り/書き込み権限を適用します(_path_permission_manager.py 内)
RoundTimeoutPreHookPreToolUseラウンドの制限時間を適用します
HumanInputHookPreToolUse人間の入力をエージェントの処理フローに注入します
MidStreamInjectionHookPostToolUse調整メッセージ(投票のリマインダーなど)を注入します
SubagentCompleteHookPostToolUseサブエージェントの完了を通知します
BackgroundToolCompleteHookPostToolUseバックグラウンドツールの完了を通知します
HighPriorityTaskReminderHookPostToolUse現在のタスクをエージェントに再通知します
MediaCallLedgerHookPostToolUseメディア生成呼び出しを追跡します
RoundTimeoutPostHookPostToolUseラウンドの制限時間を適用します
11.2 フックのアーキテクチャ
GeneralHookManager (massgen/mcp_tools/hooks/)
    ├── Registers global and per-agent hooks
    ├── Executes hooks in order for MCP-based backends
    └── For native backends → NativeHookAdapter
            ├── ClaudeCodeNativeHookAdapter
            │   └── Converts to Claude Agent SDK HookMatcher format
            └── (future) CodexNativeHookAdapter
11.3 ネイティブフックアダプター — 実装方法

ネイティブのツール実行(CLI/SDK ラッパー)を備えたバックエンドでは、MassGen のフックをバックエンドのフックシステムに橋渡しする必要があります。これは NativeHookAdapter(massgen/mcp_tools/native_hook_adapters/base.py にある基底クラス)を介して行います。

ステップ 1:アダプターのサブクラスを作成します。

# massgen/mcp_tools/native_hook_adapters/your_backend_adapter.py
from .base import NativeHookAdapter

class YourBackendNativeHookAdapter(NativeHookAdapter):
    def supports_hook_type(self, hook_type):
        return hook_type in (HookType.PRE_TOOL_USE, HookType.POST_TOOL_USE)

    def convert_hook_to_native(self, hook, hook_type, context_factory=None):
        """Wrap MassGen hook as your backend's native hook format."""
        async def native_hook(tool_name, tool_args, context):
            event = self.create_hook_event_from_native(
                {"tool_name": tool_name, "tool_input": tool_args},
                hook_type, context_factory() if context_factory else {},
            )
            result = await hook.execute(event)
            return self.convert_hook_result_to_native(result, hook_type)
        return {"pattern": hook.matcher, "handler": native_hook}

    def build_native_hooks_config(self, hook_manager, agent_id=None, context_factory=None):
        """Convert all registered hooks to native config."""
        config = {"PreToolUse": [], "PostToolUse": []}
        for hook_type, hooks in hook_manager.get_hooks(agent_id).items():
            for hook in hooks:
                native = self.convert_hook_to_native(hook, hook_type, context_factory)
                config[hook_type.value].append(native)
        return config

    def merge_native_configs(self, *configs):
        """Merge permission hooks + MassGen hooks + user hooks."""
        merged = {"PreToolUse": [], "PostToolUse": []}
        for config in configs:
            for key in merged:
                merged[key].extend(config.get(key, []))
        return merged

ステップ 2:バックエンドの __init__ で初期化します。

from ..mcp_tools.native_hook_adapters import YourBackendNativeHookAdapter
self._native_hook_adapter = YourBackendNativeHookAdapter()

ステップ 3:バックエンドのインターフェースメソッドを実装します。

def supports_native_hooks(self) -> bool:
    return self._native_hook_adapter is not None

def get_native_hook_adapter(self):
    return self._native_hook_adapter

def set_native_hooks_config(self, config):
    self._massgen_hooks_config = config

ステップ 4:バックエンドプロセスの起動時にフックをマージします。オーケストレーターは変換済みのフックを指定して set_native_hooks_config() を呼び出します。バックエンドでは、オプションの構築時に独自の権限フックとマージします。

11.4 参考資料
  • フックフレームワーク:massgen/mcp_tools/hooks/
  • アダプターベースクラス:massgen/mcp_tools/native_hook_adapters/base.py
  • Claude Code アダプター:massgen/mcp_tools/native_hook_adapters/claude_code_adapter.py
  • Copilot アダプター:massgen/mcp_tools/native_hook_adapters/copilot_adapter.py
  • パス権限:massgen/filesystem_manager/_path_permission_manager.py
  • オーケストレーターのフック設定:massgen/orchestrator.py(native_hookを検索)

共通パターン

CLI ラッパーバックエンド(Codex など)
__init__: find CLI binary, set config defaults
stream_with_tools: build command -> spawn subprocess -> parse JSONL stdout -> yield StreamChunks
MCP: write project-scoped config file in workspace before launch
Custom tools: wrap as MCP server or inject via system prompt
Cleanup: remove workspace config on reset_state/clear_history
CLI バックエンドの Docker 実行

CLI ベースのバックエンドが Docker をネイティブにサポートしていない場合は、MassGen コンテナー内で CLI を実行します。

  1. Docker イメージに CLI をインストールします(Dockerfile のnpm install -gに追加するか、command_line_docker_packages.preinstall.npmを使用します)
  2. __init__でcommand_line_execution_mode: "docker"を検出し、ホスト上の CLI の検索をスキップします
  3. stream_with_toolsでcontainer.client.api.exec_create()とexec_start(stream=True)を使用して、コンテナー内で CLI を実行します
  4. CLI 独自のサンドボックスがある場合は無効にします。コンテナーが分離を提供します
  5. CLI がホストの認証情報を必要とする場合は、_build_credential_mounts()を介して読み取り専用でマウントします

重要:一部の CLI バックエンドでは、Docker モードで実行する際にcommand_line_docker_network_mode: bridgeが必要です。これにより、コンテナーから API への外向きネットワークリクエストが可能になります。これを強制するため、設定の検証を追加してください。

# In config_validator.py
if backend_type == "your_backend":
    execution_mode = backend_config.get("command_line_execution_mode")
    if execution_mode == "docker":
        if "command_line_docker_network_mode" not in backend_config:
            result.add_error(
                "YourBackend in Docker mode requires 'command_line_docker_network_mode'",
                f"{location}.command_line_docker_network_mode",
                "Add 'command_line_docker_network_mode: bridge' (required for network access)",
            )

参考:codex.pyでは、_stream_docker()と_stream_local()を切り替えることでこのパターンを実装しています。

SDK バックエンドの Docker 実行(Copilot など)

SDK ベースのバックエンド(プロセス内の Python クライアント)は Docker 内では実行できません。SDK はホスト上にとどまります。その代わり、次の方法でファイル操作とシェル操作を分離します。

  1. Docker モードではexcluded_toolsを使用して組み込みツールを無効にし、SDK がネイティブのファイルツールやシェルツールを使用しないようにします
  2. ファイル操作とシェル操作はすべて、Docker コンテナー内で実行される MCP サーバーを経由させます
  3. working_directoryを Docker にマウントされたワークスペースのパスに設定します
# In __init__:
self._docker_execution = (
    kwargs.get("command_line_execution_mode") == "docker"
    or self.config.get("command_line_execution_mode") == "docker"
)

# Property to check if Docker is actually active:
@property
def _is_docker_mode(self) -> bool:
    if not self._docker_execution:
        return False
    if not self.filesystem_manager:
        return False
    dm = getattr(self.filesystem_manager, "docker_manager", None)
    if dm is None:
        return False
    agent_id = self.config.get("agent_id")
    if agent_id and dm.get_container(agent_id):
        return True
    return False

# In get_disallowed_tools():
def get_disallowed_tools(self, config):
    if self._docker_execution:
        return ["editFile", "createFile", "deleteFile",
                "readFile", "listDirectory",
                "runShellCommand", "shellCommand"]
    return []

# In stream_with_tools(), merge Docker-excluded tools into session config:
if self._docker_execution:
    docker_excluded = self.get_disallowed_tools(self.config)
    if docker_excluded:
        excluded_tools = list(set((excluded_tools or []) + docker_excluded))

CLI の Docker パターンとの主な違い:

  • SDK はホスト上にとどまります(exec_create/exec_startは使用しません)
  • 組み込みツールは、コンテナー内で実行するのではなく、SDK セッション設定(excluded_tools)で無効にします
  • MCP サーバーは引き続きコンテナー内で実行されます(CLI パターンと同じです)
  • PPM 権限コールバックは多層防御として引き続き有効です

設定の検証:同じパターンで、command_line_docker_network_modeを必須にします。

if backend_type == "copilot":
    execution_mode = backend_config.get("command_line_execution_mode")
    if execution_mode == "docker":
        if "command_line_docker_network_mode" not in backend_config:
            result.add_error(...)

参考:copilot.pyではこのパターンを実装しています。

Docker MCP のパス解決

MCP サーバーの設定は、ホスト上の絶対ファイルパスを使ってオーケストレーターが作成します(例:fastmcp run /host/path/massgen/mcp_tools/planning/_server.py:create_server)。明示的にマウントしない限り、Docker 内にはこれらのパスが存在しません。

解決策:_docker_manager.pyは、massgen/パッケージディレクトリを同じホストパスでコンテナーに読み取り専用でバインドマウントします。これにより、ホストパスを使った MCP 設定をコンテナー内でもそのまま機能させられます。また、古い pip インストール済みモジュールではなく、ホスト上の最新のソースを使用できます。

ネイティブツールを持つバックエンド向けのシェルサンドボックス

このセクションは、独自のシェル/ファイルツールを備えたバックエンド(例: コマンド実行機能を内蔵した CLI ラッパー)にのみ適用されます。バックエンドがすべてのツール実行を MassGen の MCP サーバーに委譲する場合、MassGen の PathPermissionManager がサンドボックス化を自動的に処理します。

バックエンドがファイルシステムにアクセスしたりコマンドを実行したりできる独自のツールを持つ場合、MassGen の権限モデルをバックエンドのサンドボックス機構にマッピングする必要があります:

MassGen の概念期待されるアクセス
ワークスペース書き込み
一時ワークスペース読み取り専用
読み取りコンテキストパス読み取り専用
書き込みコンテキストパス書き込み — バックエンドのサンドボックス設定で明示的に許可する必要があります

ワークスペースは通常、デフォルトで書き込み可能です。ファイルシステム全体への読み取りアクセスは通常許可されています。重要なタスクは、書き込みコンテキストパスがバックエンドの書き込み可能な許可リスト(例: Codex の sandbox_workspace_write.writable_roots、Claude Code の allowed_directories)に追加されていることを確認することです。

Docker モード: バックエンドのサンドボックス設定はスキップしてください — コンテナ自体がサンドボックスです。コンテナ内では完全なアクセスを許可してください。

SDK ラッパーバックエンド(Claude Code、Copilot など)
__init__: import SDK, configure options, detect Docker mode
stream_with_tools: call SDK with messages -> iterate events -> yield StreamChunks
MCP: pass mcp_servers dict to SDK options
Custom tools: create SDK MCP server from tool definitions
State: SDK manages conversation state internally
Docker: SDK stays on host; disable built-in file/shell tools via excluded_tools;
        route file/shell ops through MCP servers in container (see §Docker Execution for SDK Backends)
API バックエンド(Claude、Gemini など)
Subclass CustomToolAndMCPBackend or ChatCompletionsBackend
Implement formatter if non-standard API format
Implement API params handler to filter/transform config -> API params
MCP + custom tools handled automatically by base class

複雑さ別のリファレンスバックエンド

バックエンド行数種類良いリファレンスとなる用途
grok.py約81ChatCompletions のサブクラス最小構成の API バックエンド
chat_completions.py約1150OpenAI 互換標準的な API バックエンド
response.py約1600Response API推論モデル
claude.py約1830カスタム APIフル機能の API
copilot.py約1250SDK ラッパーSDK ベース + PPM 権限コールバック + Docker モード
codex.py約2090CLI ラッパーCLI ベース + Docker
gemini.py約2460カスタム API非 OpenAI API
claude_code.py約3530SDK ラッパーフル機能のステートフル

ファイル一覧

#ファイル必須?目的
1massgen/backend/<name>.pyはいコアバックエンド
2massgen/formatter/<name>_formatter.py非標準 API の場合メッセージ/ツールのフォーマット
3massgen/api_params_handler/<name>_handler.pyHTTP API の場合パラメータのフィルタリング
4massgen/backend/__init__.pyはいエクスポート
5massgen/cli.pyはいバックエンドの作成
6massgen/backend/capabilities.pyはいモデルレジストリ
7massgen/config_validator.py特別なルールがある場合バリデーション
8massgen/token_manager/token_manager.pyLiteLLM にない場合価格設定
9massgen/backend/base.py新しい YAML パラメータがある場合除外パラメータ
10massgen/api_params_handler/_base.py新しい YAML パラメータがある場合除外パラメータ
11massgen/tests/test_*はいテスト
12massgen/configs/providers/<name>/はい設定例
13docs/source/reference/yaml_schema.rstはいスキーマドキュメント
14docs/source/user_guide/backends.rstはいユーザーガイド
15docs/scripts/generate_backend_tables.py実行するテーブルの再生成

コメント