mcpField
M
Model Council

Model Council

ローカル
github.com/Totti0135AI・エージェント210 週間前に更新

Model Council について

Model Council は、OpenAI 互換または Anthropic 互換の複数モデルに質問し、回答を相互に渡して結論をまとめる MCP サーバーです。回答の相互レビュー、ファイルや画像の資料、任意の反対意見席に対応します。

主な機能

  1. 01単一または複数モデルへの質問
  2. 02回答の並列収集と比較
  3. 03回答を引き継ぐ複数ラウンドのレビュー
  4. 04ファイル、テキスト、画像の資料提供
  5. 05継続的な反対意見席の追加
  6. 06メンバー設定の確認とモデルのプローブ

活用シーン

  1. 01提案に対する複数モデルの意見を比較
  2. 02モデル同士に回答を批評させる
  3. 03仕様書、ログ、差分のレビュー
  4. 04計画への反論を集めて検討

MCP 対応

MCP サーバーとして ask、ask_all、revise などのツールを提供し、ホストのアシスタントから設定済みモデルのエンドポイントを呼び出して回答をツール結果として扱えます。会話履歴にはアクセスできないため、必要な文脈は呼び出しごとに渡します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"totti0135-model-council": {
"command": "uvx",
"args": [
"model-council-mcp"
],
"env": {
"COUNCIL_MODELS": "<COUNCIL_MODELS>",
"COUNCIL_CONFIG": "<COUNCIL_CONFIG>"
}
}
}
}
起動コマンドuvx model-council-mcp
設定が必要な環境変数
COUNCIL_MODELSComma-separated short ids for the models to seat, e.g. 'gpt5,glm'. Each id then takes its own <ID>_BASE_URL, <ID>_API_KEY, <ID>_MODEL and optional <ID>_FORMAT (openai or anthropic). Omit to use a config file instead.
COUNCIL_CONFIGPath to a JSON config file, for rosters large enough that several models share one endpoint. Defaults to ~/.config/model-council/config.json when that exists.

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypimodel-council-mcp0.14.0

ツール

6

掲載時のツール一覧 · 3 週間前に更新

パラメーター型必須説明
modelenumはいchatgpt | glm
promptstringはい
systemanyいいえ
materialsanyいいえ

詳細

Model Council

English | 简体中文

ほかの LLM をあなたのテーブルに招く MCP サーバーです。あなたのアシスタントがメンバーに質問し、その回答をツールの実行結果として読み取り、回答を互いに伝えて批評させ、コピー&ペーストなしで、通常の会話の中から統合した結論を提示します。

あなたのアシスタントが評議会の議長を務めます。メンバーは何人でも追加でき、OpenAI 互換および Anthropic 互換のエンドポイントを自由に組み合わせられます。ホスト型 API、自分で運用するゲートウェイ、ローカルサーバー、またはそれらの複数を組み合わせて利用できます。

ツール

ツール機能
ask(model, prompt, materials?)ID を指定してメンバー 1 人に質問します
ask_all(prompt, models?, rounds?, guests?, materials?, steelman?)全員(または名前を指定した一部のメンバー)に同じプロンプトを並行して送り、回答を並べて表示します。rounds=2 にすると議論になり、steelman を指定すると毎ラウンド反論する常設の異論役を加え、guests ではすでに用意した回答を加え、materials では読み込ませるファイルを渡します
revise(prompt, answers, round?, materials?)自分で次のラウンドを実行します。自分にしか生成できない回答も含め、前のラウンドで出たすべての発言をメンバーに見せて、改訂版を受け取ります
revision_prompt(prompt, answers, seat, materials?)次のラウンドで自分の席に渡すプロンプトです。メンバーに渡したものと一字一句同じ内容で、ネットワーク呼び出しは行いません
list_council()評議会の名簿です。ID、エンドポイント、重み、各メンバーに提示できる内容、各メンバーが使用する送信ルート、呼び出し予算、準備状況を確認できます。ネットワーク呼び出しは行いません
probe_models(model?)プロバイダーの /models ルートに問い合わせ、実際に公開されている ID を確認します

メンバーはステートレスで、あなたの会話を見ることはできません。そのため、議長は各呼び出しで必要な情報をすべて渡します。これこそがクロスレビューを可能にする仕組みです。あるメンバーの回答を、別のメンバーの prompt に含めることができます。

評議会に読ませるものを渡す

質問は通常、仕様書、ログ、差分、スクリーンショットなど、何かについて尋ねるものです。それを含める方法としてすぐ思い浮かぶのは prompt に貼り付けることですが、これはコストの高い方法です。しかも、そのコストは誰も注目していないところで発生します。prompt は議長が 書く 引数なので、長い文書を渡すと、呼び出すたびに生成トークンとして文書全体のコピーが必要になり、メンバーに届くのは議長が再現できた内容だけです。40ページともなると、文書を確実に再現することはできません。言い換えを検討しているだけなのに、議事録にはその事実が何も記されません。

代わりに materials で指定します。

ask_all(
  prompt="Where would this break under load?",
  materials=[
    {"label": "The design", "path": "/abs/path/design.md"},
    {"label": "Latency graph", "path": "/abs/path/p99.png"},
    {"label": "My summary", "text": "…something with no file behind it…"},
  ],
  rounds=2,
)

サーバーはそれぞれを読み取り、質問の前に配置します。すべてのメンバーと各ラウンドで、常に同じ位置になります。この配置は単なる見せ方ではありません。

  • バイト列が同一。 すべてのメンバーが同じコピーを基に議論します。そのため、各メンバーに渡された版の違いではなく、文書そのものについて意見を交わせます。
  • キャッシュ可能なプレフィックス。 資料の末尾までの内容は、議論中のすべての呼び出しで同一です。Anthropic 形式では境界に cache_control の区切りを明示的に設定します。プレフィックス単位で独自にキャッシュするエンドポイントなら、どちらの場合も必要な形式になります。フィールドを受け付けないゲートウェイを使うメンバーには "cache": false を設定してください。
  • 議長の負担は一度だけ。 パスを渡すのに必要なのは議長が1行書くことだけで、文書のコピーは不要です。また、ask_all は資料をラウンド2と3にも自動で引き継ぎます。

最も重要なのは画像です。 画像を渡せないと、議長はスクリーンショットを文章で説明しなければなりません。そして、すべてのメンバーが 同じ 説明を読むため、議長が読み違えた内容を評議会全体が一斉に読み違えることになり、相互レビューでも修正できません。これは、渡し方を誤ると、評議会の目的である独立性が気づかれないまま失われる唯一の入力です。PNG、JPEG、GIF、WebP は、画像を認識できるモデルを使うすべてのメンバーに渡されます。画像を認識できないメンバーには "vision": false を設定してください。そうすると、画像を含む呼び出しではそのメンバーは回答に加わりません。画像を見たかのようにテキストだけについて回答することはありません。議事録には、誰が参加しなかったかが記録されます。

どのメンバーが画像を認識できるか確かめるには: 画像を認識できないメンバーが、そのことを伝えることはほとんどありません。このサーバーの開発中にテストしたある Anthropic 互換ゲートウェイは、画像ブロックを受け付けたものの破棄し、その後モデルは色を答えました。そのケースではプロンプトで明示的に求めていたにもかかわらず、「画像は送られていない」とは答えませんでした。同じエンドポイント経由のテキスト資料は問題なく届いたため、呼び出しに異常があるようには見えませんでした。メンバーに明確な画像を1枚見せ、何が写っているか尋ねてください。自信ありげな誤答が症状であり、対処法は "vision": false を設定することです。

知っておくべき制限が2つあります。読み取れない資料があると、呼び出しは停止します。受け取っていない文書について尋ねられた評議会も、流暢に回答してしまい、文書を受け取って回答した場合と見分けがつきません。また、revision_prompt はファイルを貼り直すのではなく、メンバーに渡した位置でファイル名を 示します。そのため、残りを渡す前に、自分の席でファイルを開いてください。

サーバーがパスを読み取るかどうかは、起動方法によって異なります。HTTP 経由では、運用者がディレクトリを指定していない限り読み取りません。list_council を実行すると、テーブルの直前の行にその内容が表示されます。

ラウンド

ask_all(prompt, rounds=2) を使うと、この相互レビューを実行できます。ラウンド 1 は通常の並列質問です。ラウンド 2 では、質問に加え、ラウンド 1 のすべての回答(自身の回答と他のメンバーの回答を含む)をそのまま各メンバーに渡し、見直しを依頼します。正しい点を取り入れ、誤りを正し、なお意見が異なる場合はその点と理由を述べてもらいます。トランスクリプトはラウンドごとに返されるため、誰が考えを変え、誰が主張を曲げなかったかを確認できます。

前のラウンドを持ち越すことが、この仕組みの要です。メンバーは呼び出しと呼び出しの間に何も記憶しないため、前のラウンドを渡さなければ、2 回目のラウンドは同じ質問をもう一度するだけです。ラウンドは最大 3 回です。回数を増やすごとに、メンバーごとにもう 1 回呼び出しが必要になり、プロンプトも前のラウンドより長くなります。そのため、意見の傾向を調べるなら 1 回、意見の相違そのものが興味深い質問なら 2 回が適切です。

rounds は質問をする前に決めます。ここが唯一の難点です。最初のラウンドを読む前に 2 回目のラウンドを実施すると決める必要があり、評議会の意見が一致していた場合でも、まだ議論を続けている評議会とまったく同じコストがかかります。まず様子を見たい場合は rounds=1 で質問し、その後、必要だと判断した回数だけ revise で次のラウンドを追加してください。これはちょうど 1 回分のラウンドを実行し、回数に上限はありません。追加のラウンドではメンバーごとに呼び出しが 1 回必要ですが、revise では書き戻された回答分だけのコストで済むため、回答を読んでから追加するほうが通常は低コストです。ask_all の 3 回という上限は、1 回の呼び出しで消費するラウンド数の上限であり、議論全体のラウンド数の上限ではありません。上限に達すると、トランスクリプトにそのことが示されます。

評議会を対等なメンバーで構成しない一般的なケースでは、メンバーごとに異なる重みを設定することもできます。

常設の異議

評議会の大半が同意しますが、その同意は評議会が生み出すものの中で最も情報量が少ないものです。ここまでに説明したすべての仕組みは、収束を妨げる方向に働きます。メンバー同士は匿名で、重みも知らされず、最後の指示では安易に折れないよう伝えられます。しかし、どれも意見を分かれさせる圧力にはなりません。計画に異議を唱える最も強い意見は、計画に問題がないと思っているメンバーからは自発的に出てきません。

steelman は一席を設けます:

ask_all(prompt, rounds=3, steelman={})

各ラウンドで一人のメンバーが、評議会が収束した結論に対する最も強い反論を書きます。それは通常の匿名回答として全員に戻され、次のラウンドではメンバーがそれに対処しなければなりません。

メンバーに、自分が支持していない立場から議論するよう指示することはありません。 ここが重要な境界線であり、ディベートモードと異なる点です。メンバーが述べるのは、あくまで自分が考えていることです。追加の呼び出しが一つあることは、メンバーには知らされません。「反対側の立場で議論する」と書かれた席を与えられると、モデルはその主張に答えるのではなく、割り引いて受け取ります。そのため、出所はトランスクリプト末尾の注記であなたに伝え、主張そのものはメンバーに提示します。

一度だけではなく、毎ラウンド発言します。 これは細部に見えて、そうではありません。自分への反論に答えられない異議は、代表されるのではなく引用されるだけです。自分に対する誤解を正せないため、3ラウンド目には評議会はその言い換えと議論し、それを回答だと見なしてしまいます。tenure を設定するとデフォルトより少ないラウンドにでき、席が早期に終了した場合は、トランスクリプトに設定によって終了したと記載されます。説明のない沈黙は、まさに立場を取り下げたように映るためです。

その対称となる点にも、同じだけの価値があります。異議が最後に述べたことは、トランスクリプトの最後に記録されます。つまり、それに対処するよう求められた人はいませんし、そのこともトランスクリプトに記載されます。誰も答えなかった異議は、認められた論点ではありません。検討されなかった論点です。 rounds=2 では、その異議が述べたことはすべてこの状態になります。そのため、費用をかけた後で自分で気づくのではなく、それを解消する設定が注記に明記されています。

出力は、この評議会が順番に提示できる最も強い反論として読んでください。誰かが実際にその立場を取っている証拠として読んではいけません。反論に答えた後も残った論点には価値があります。同じ論点でも、最初に示されたラウンドでは、まだ何の価値もありません。

デフォルトでは、最初に依頼されたメンバーが反論を書き、自分自身としても回答します。その二つの呼び出しは、そのメンバーにとっては無関係です。steelman={"model": "glm"} を指定すれば、別のメンバーを選べます。

すでに用意した回答を席に加える

あなたのアシスタントは議長を務めるだけでなく、自ら回答することもできます。また、Claude Code や Codex では、サブエージェントを起動して回答させることもできます。以前は、それらの回答は評議会の回答の横に並び、最後に手作業で比較していました。guests を使うと、回答を評議会の中に加えることができます:

ask_all(
  prompt="What are the traps in this plan?",
  guests=[{"label": "Subagent", "text": "<what your subagent answered>"}],
  rounds=2,
)

ラウンド1ではメンバーと並べて表示されます。ラウンド2以降は、すべてのメンバーにその文章が一字一句そのまま渡され、それについて議論するよう求められます:

--- ANSWER C (does not revise between rounds) ---
the migration has no rollback path

違いはそれだけです。これがないと、モデルはあなたのサブエージェントに意見があったことを知りません。あなた自身で比較することになります。これは、ラウンドの仕組みがすでにより優れている作業です。モデル同士が互いに回答でき、何もないところに回答する必要がないからです。

知っておくとよい点:

  • 要約ではなく、文章を一字一句そのまま渡してください。 メンバーには渡した文章がそのまま表示されます。要約は、あなたが批評してほしかったものではありません。
  • ゲストは一度だけ発言します。 修正を依頼する相手がいないため、ラウンド1の後に再登場することはありません。席が消えるだけでは立場を取り下げたように映るため、その旨がトランスクリプトに記載されます。
  • ゲストも発言者として数えられます。 メンバー一人とゲスト一人でも、実質的な第2ラウンドを行えます。サブエージェントと一つのモデルでも、議論は成立します。
  • weight はゲストにも適用されます。 メンバーと同じ尺度です。
  • ゲストは呼び出しごとに指定します。設定は行われず、状態も保持されません。
サブエージェントを正式なメンバーとして扱う

ゲストは一度だけ発言します。自分で用意した声にもメンバーと同じように、回答し、他のメンバーを読み、回答を修正し、ラウンドを重ねて議論に参加させるには、このサーバーではサブエージェントを起動できないため、ラウンドをあなたが進行する必要があります。revise は、必要に応じて1ラウンドを実行します。

ask_all(prompt)                                  → members answer round 1
  ...you spawn your subagent on the same prompt   → its round-1 answer

revise(prompt, round=1, answers=[
  {"model": "glm", "text": "<glm's round-1 answer>"},
  {"model": "sol", "text": "<sol's round-1 answer>"},
  {"label": "Subagent", "text": "<subagent's round-1 answer>"},
])                                               → members come back revised
  ...you re-run your subagent on the same material

revise(prompt, round=2, answers=[...round 2...]) → and so on

model でメンバーを指定すると、これが修正になります。そのメンバーには、自分の前回の回答が自分自身のものとして渡され、白紙から答えるのではなく、その回答をもとに先へ進むよう求められます。一方、label を指定したエントリーは、名簿に含まれない外部の声です。

メンバーは違いを見分けられません。 revise に渡された外部の声も、他のメンバーとまったく同じように提示されます。つまり、単独の文字と --- ROUND-1 ANSWER C --- です。次のラウンドでも再び回答するためです。ask_all のゲストに終了のフラグが付くのも同じ理由です。再び発言するところなのに、すでに終わった声として説明すると、議論を行うモデルに議論の状況を誤って伝えることになります。

元の質問ではなく、revision_prompt を使ってサブエージェントにプロンプトを送ってください。 ループを誤らせる方法はこれだけですが、誤っても通知されません。質問を再度渡されたサブエージェントは前回の回答をそのまま繰り返します。トランスクリプトは議論が続いているように見えますが、その席がいつ議論への参加をやめたかは、どこにも示されません。

revision_prompt(prompt, answers=[...same as revise...], seat="Subagent", round=1)

メンバーに渡されたものとまったく同じ内容が返されます。つまり、自分のものと明示された前回の回答、他の全員の回答、そして最後の指示です。これには、まだ自分が正しいと信じている立場を、少数派だからという理由だけで放棄しないよう伝える一文も含まれます。この一文が評議会の合意への収束を防ぐ主な要素であり、これを含まないプロンプトを受けたメンバーは、他のメンバーと同じ質問を受けていません。この呼び出しはローカルで実行され、ネットワークリクエストを行わないため、revise の後ではなく、並行して実行してください。

revise に回数制限はありません。ask_all に3ラウンドの上限があるのは、呼び出し自体が予算を消費するためです。一方、こちらでは、各ラウンドの費用を払うかどうかをあなたが決めます。

ask_all(guests=…)revise(answers=…)
呼び出し1回ラウンドごとに1回
あなたの声1回だけ発言毎ラウンド修正
ラウンドを進行するのはサーバーあなた
メンバーは匿名で議論する

修正ラウンドでは、他のメンバーの回答は名前ではなく文字で届きます。

--- ROUND-1 ANSWER B ---
concurrent writes race

モデル名は重みと同じ種類の手がかりであり、しかも、より強い手がかりです。モデルは互いの開発元について強い先入観を持っています。数値を伏せてもブランド名を表示すれば、より小さな手がかりを隠して、より大きな手がかりを残すことになります。メンバーに届くのは議論であり、それこそが私たちが評価してほしかったものです。

文字はテーブルでの席順から付けられるため、固定されています。B は、すべてのメンバーにとって、どのラウンドでも同じ席です。だからこそ、メンバーの1人が「Bはインデックスについて間違っていた」と言っても、次のラウンドで意味が通じます。

名前はあなたにはわかります。 修正ラウンドを含むトランスクリプトには、すべて最後に次のキーが付きます。

[the members saw each other as letters, not names: A = GLM-5.3, B = GPT-5.6-sol,
C = Subagent. ...]

メンバーを見分ける必要があるのはあなただけなので、これはあなたのための例外です。誰が意見を変え、誰が変えず、誰について話していたのかを確認できます。

正直に言えば、これはサーバーが付けるラベルに限られます。モデルが回答の中に自分の名前を書いた場合は対象外です。その文章はそのまま次のラウンドに引き継がれます。そうしないと、他のメンバーが批評するはずのテキストを編集することになるためです。ここでの匿名化は、肩書きから伝わる手がかりを取り除くものであり、すべての言及をなくすものではありません。

再試行

一時的なエラーで失敗した呼び出しは、通知される前に再試行されます。対象は HTTP 429 と 5xx、切断された接続、タイムアウトした接続です。バックオフはジッターを加えた 1s からの指数方式です。Retry-After ヘッダーがある場合はその値が優先されます。ただし、30s を超える時間が指定されている場合、呼び出しはその時間待たずに停止し、その旨を通知します。再度確認しても変わらない失敗(401、404、不正なレスポンス本文など)はすぐに通知されます。再試行しても同じクォータを消費して同じ結果が返るだけです。デフォルトでは 2 回再試行します。以前のように 1 回だけ実行する動作にするには、retries: 0 を設定してください。

試行を使い切ったメンバーは、そのメンバーの回答としてエラーを返すため、評議会のほかのメンバーは引き続き回答します。ただし、ほかに送信先がある場合は、そちらに送信されます。バックアップを参照してください。

インストール

このサーバーは公式の MCP Registry に io.github.Totti0135/model-council として登録されているため、レジストリを参照するクライアントから見つけて追加できます。手動で設定する場合は、以下をお読みください。

クローンや仮想環境は不要で、PyPI から実行できます。uv が必要です。

curl -LsSf https://astral.sh/uv/install.sh | sh
Claude Desktop

最も簡単なのはデスクトップ拡張機能です。最新リリースから model-council-<version>.mcpb をダウンロードし、Settings → Extensions にドラッグしてください。アプリに表示されるフォームでエンドポイントとキーを入力します。キーは OS のキーチェーンに保存されるため、ディスク上のファイルに保持されることはありません。フォームでは 2 つのモデルを設定できます。より大きな評議会を構成するには、「Config file」フィールドに JSON 設定ファイルを指定してください(下記を参照)。

手動で設定する場合は、claude_desktop_config.json を編集し(Settings → Developer → Edit Config)、以下のブロックを追加してから、アプリを完全に終了して再起動してください。ウィンドウを閉じるだけではなく、Cmd-Q を使ってください。ツールメニューに model-council が表示されれば、設定は完了です。

{
  "mcpServers": {
    "model-council": {
      "command": "uvx",
      "args": ["model-council-mcp"],
      "env": {
        "COUNCIL_MODELS": "gpt5,glm",
        "GPT5_BASE_URL": "https://your-openai-compatible-host/v1",
        "GPT5_API_KEY": "sk-xxxxxxxx",
        "GPT5_MODEL": "gpt-5",
        "GLM_BASE_URL": "https://open.bigmodel.cn/api/anthropic",
        "GLM_API_KEY": "xxxxxxxx",
        "GLM_MODEL": "glm-4.6",
        "GLM_FORMAT": "anthropic"
      }
    }
  }
}
Claude Code
claude mcp add model-council -e GPT5_BASE_URL=... -e GPT5_API_KEY=... -- uvx model-council-mcp
その他の MCP クライアント

stdio サーバーを起動できるものであれば動作します。uvx model-council-mcp を実行し、同じ環境変数を渡してください。

HTTP でチームに提供する

上記の方法では、各ユーザーが自分の MCP クライアントでサーバーを 1 つずつ起動し、それぞれのキーを設定します。もう 1 つの方法は --http です。1 つのデプロイ環境でキーをまとめて管理し、チーム全体に応答します。ユーザー側では URL を設定するだけで、シークレットは不要です。

model-council-mcp --http --host 0.0.0.0 --allow 10.20.0.0/16

同僚は、機密情報を設定に含めずにリモートサーバーとして追加できます。

claude mcp add --transport http model-council http://council.internal:8000/mcp
// Claude Desktop and other clients
{ "mcpServers": { "model-council": { "type": "http", "url": "http://council.internal:8000/mcp" } } }

deploy/ には、Dockerfile、compose ファイル、systemd ユニットがあります。

アクセスできるユーザー

stdio では、誰が呼び出せるかはオペレーティングシステムが保証します。プロセスを起動したユーザーは、すでに必要な権限を持っています。HTTP ではこの保証がなくなります。共有プロバイダーの利用枠の前に 1 つのポートが立つため、--allow でアクセス可能な相手を指定するまで、サーバーはループバック以外のアドレスでは起動を拒否します。ループバックにはフラグは不要で、常に許可されます。

フラグ
--allowCIDR、単独のアドレス、または private(RFC1918)、loopback、any の名前
--trust-proxyX-Forwarded-For を信頼できるリバースプロキシ
--allow-originブラウザーの Origin を許可します。複数回指定できます
--materials-rootmaterials のパスで指定できる唯一のディレクトリです。指定しない場合、HTTP ではパスを拒否します

--trust-proxy は、よく確認しておく価値のあるフラグです。指定しない場合、X-Forwarded-For は完全に無視され、接続元のアドレスが使われます。nginx の背後では接続元が nginx になるため、許可リストは全員に一致するか、誰にも一致しないことになります。指定すると、信頼されたプロキシではないホップのうち、チェーン上で最も右側にあるものがクライアントとして扱われます。これにより、呼び出し元が X-Forwarded-For: 10.0.0.1 と書いて、そのまま通り抜けることを防げます。

Origin ヘッダーを含むリクエストは、デフォルトで拒否されます。MCP クライアントはブラウザーではないため、Origin を送信しません。一方、ウェブページは必ず送信します。許可リストによってオフィスネットワーク上のすべてのマシンがアクセスできるようになると、それぞれのマシンで動作するブラウザーが、開いているページに代わってリクエストを送信します。両者を区別するのが Origin です。

各フラグには環境変数(COUNCIL_ALLOW、COUNCIL_TRUST_PROXY、COUNCIL_HTTP_HOST など)があります。--help に一覧が表示されます。

HTTP 経由でのマテリアル

stdio では、materials はプロセスがアクセスできる任意のファイルを読み取れるため、フラグは不要です。このプロセスを起動した呼び出し元は、すでにそのアクセス権を持っているためです。これは、ループバックのバインドで --allow を省略できるのと同じ考え方です。HTTP ではこの考え方は成り立ちません。呼び出し元はポートにアクセスできる誰でもあり、パスを指定できると、共有された council がホスト上のファイルを読み取る手段になってしまいます。読み取られた内容は外部プロバイダーに送られます。そのため、HTTP のデプロイではパスを一切受け付けず、呼び出し元は内容を text として渡します。

--materials-root /srv/council/material を指定すると、解決後のパスを比較して、指定された 1 つのディレクトリだけを開きます。そのため、シンボリックリンクはリンク先の場所に基づいて判定されます。

この機能で対応しないこと

許可リストはネットワーク境界であり、ユーザーの識別手段ではありません。その範囲内にいる人は誰でも認証情報なしで呼び出せるため、利用状況を個人に紐づけたり、個人ごとにレート制限したり、特定の個人だけをアクセス不可にしたりすることはできません。これは意図的なトレードオフです。クライアント設定を URL だけにできるのはそのためですが、ネットワークが実際に信頼できる境界である必要があります。また、請負業者もアクセスできる VPN や、同じサブネット上の CI ランナーを介すると、この前提は成り立ちません。

個人ごとの利用状況の追跡や利用枠が必要な場合は、LLM ゲートウェイ(LiteLLM、one-api、または組織ですでに運用しているもの)をこのサーバーの背後に置き、各呼び出し元にそこで個別の仮想キーを割り当てるか、SSO を行うリバースプロキシの背後でこのサーバーを実行してください。

URL を告知する前に、もう 1 つ知っておくべきことがあります。このサービスは、受け取ったテキストをすべて外部プロバイダーに転送します。認証情報のない共有エンドポイントは、アクセスできるすべての人にとってデータの外部送信経路になります。

リバースプロキシの背後で実行する場合

rounds=2 を指定した ask_all は、複数のモデルに対してそれぞれ複数回試行する、時間のかかるリクエストです。各呼び出しは COUNCIL_TIMEOUT(180s)までかかる場合があります。デフォルトのプロキシタイムアウトでは、サーバーが処理を終える前に接続が切断されます。

location /mcp {
    proxy_pass http://127.0.0.1:8000;
    proxy_http_version 1.1;
    proxy_buffering off;          # the transport streams; buffering defeats it
    proxy_read_timeout 900s;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

次に、プロキシのアドレスを --trust-proxy に指定してサーバーを起動します。そうしないと、許可リストには常にプロキシしか表示されません。

council の設定

2 層構成にすることで、認証情報を繰り返さずに複数のモデルで 1 つのエンドポイントを共有できます。

  • provider — エンドポイントです。base_url + api_key + 対応するワイヤ形式で構成されます
  • member — いずれかの provider 上の 1 つのモデルです。短い ID で指定します

設定は、最も明示的なソースから取得されます。COUNCIL_CONFIG が指すファイル、それ以外の場合は COUNCIL_MODELS が指定するメンバー一覧、それ以外の場合は ~/.config/model-council/config.json の設定ファイル、それ以外の場合は組み込みのデフォルトのメンバー一覧が使われます。意図的に、明示的な設定が検出された設定より優先されます。置きっぱなしにした設定ファイルが、クライアントからサーバーに渡された設定を気づかないうちに上書きしてはなりません。list_council() は、どのソースが使われたかを常に報告します。

環境変数

COUNCIL_MODELS に ID を列挙します。各 ID の変数名は、大文字に変換し、英数字以外の文字をアンダースコアに置き換えます(my-model → MY_MODEL_BASE_URL)。

COUNCIL_MODELS=gpt5,glm
GPT5_BASE_URL=https://your-openai-compatible-host/v1
GPT5_API_KEY=sk-xxxxxxxx
GPT5_MODEL=gpt-5
GLM_BASE_URL=https://open.bigmodel.cn/api/anthropic
GLM_API_KEY=xxxxxxxx
GLM_MODEL=glm-4.6
GLM_FORMAT=anthropic

メンバーごとに、_BASE_URL、_API_KEY、_MODEL、_FORMAT、_LABEL、_WEIGHT、_MAX_TOKENS、_TEMPERATURE、_TIMEOUT、_RETRIES、_RETRY_BACKOFF、_HEADERS(JSON オブジェクト)、_PROXY、_VISION、_CACHE、_ENABLED を設定できます。グローバル設定は、COUNCIL_TIMEOUT、COUNCIL_RETRIES、COUNCIL_RETRY_BACKOFF、COUNCIL_PROXY、COUNCIL_CONFIG、COUNCIL_ENV_FILE、COUNCIL_MATERIALS_ROOT です。

COUNCIL_MODELS を省略すると、メンバー一覧は chatgpt,glm がデフォルトになり、CHATGPT_* と GLM_* が読み込まれます。

設定ファイル

メンバーが数人を超える場合や、複数のメンバーでエンドポイントを共有する場合は、設定ファイルを使うと便利です。COUNCIL_CONFIG=/path/to/config.json を設定するか、サーバーが自動的に検出する ~/.config/model-council/config.json にファイルを配置してください。

{
  "providers": {
    "my-relay": {
      "base_url": "https://your-openai-compatible-host/v1",
      "api_key": "${MY_RELAY_KEY}",
      "format": "openai"
    },
    "zhipu": {
      "base_url": "https://open.bigmodel.cn/api/anthropic",
      "api_key": "${GLM_KEY}",
      "format": "anthropic"
    }
  },
  "members": [
    { "id": "gpt5",  "provider": "my-relay", "model": "gpt-5", "label": "GPT-5",
      "weight": 2 },
    { "id": "codex", "provider": "my-relay", "model": "gpt-5-codex", "temperature": 0.2 },
    { "id": "glm",   "provider": "zhipu",    "model": "glm-4.6" },
    { "id": "kimi",  "base_url": "https://api.moonshot.cn/v1",
      "api_key": "${KIMI_KEY}", "model": "kimi-k2" }
  ]
}

${ENV_VAR} は環境変数から展開されるため、ファイルにシークレットを含めずに共有したり、コミットしたりできます。詳しくは、注釈付きの完全な例である examples/config.json をご覧ください。

メンバーの接続方法は、次の 2 通りのいずれかです。両方を混在させることはできません。provider を指定して、そのエンドポイント全体を使用するか、provider を省略して base_url + api_key + format を自分で指定します(上記の kimi のようにします)。provider を指定しながら、その 3 つのいずれかを上書きすると拒否されます。そのメンバーは無効になり、list_council が理由を示します。部分的な上書きを認めると、あるエンドポイントの認証情報と別のエンドポイントの URL が組み合わされ、発行先ではないホストにキーが気づかないうちに送信されるためです。メンバーごとの headers、timeout、temperature、max_tokens、label はこの ID の一部ではないため、引き続き上書きできます。

フィールド

最初の3項目は1つの単位として一緒に扱います。上記のルールを参照してください。

フィールド適用対象備考
base_urlprovider、または provider を持たないメンバールートの起点となるパスです。openai の場合は /chat/completions、anthropic の場合は /v1/messages です。OpenAI 互換ホストでは通常 /v1 で終わります
api_keyprovider、または provider を持たないメンバー
formatprovider、または provider を持たないメンバーopenai(デフォルト)または anthropic
modelメンバーエンドポイントに送信するモデル ID
labelメンバー回答での表示名です。デフォルトは ID です
weightメンバーこのメンバーの意見がどの程度重視されるかを示します。デフォルトは 1、最大は 10、0 は参考意見のみです。重みを参照してください
max_tokensメンバーAnthropic 形式のみで必須です。デフォルトは 8192 です
temperatureメンバー設定されている場合にのみ送信されます
headersprovider、メンバー追加の HTTP ヘッダー
timeoutprovider、メンバー試行ごとの秒数です。デフォルトは 180 です
retriesprovider、メンバー一時的な障害発生時の追加試行回数です。デフォルトは 2、最大は 5、0 で無効になります
retry_backoffprovider、メンバー最初の再試行までの秒数です。以降は倍増します。デフォルトは 1 です
proxyトップレベル、provider、メンバー接続経路です。省略すると HTTP_PROXY/HTTPS_PROXY に従います。false で直接接続し、URL を指定するとそのプロキシを使い、"env" で環境変数の設定に戻ります。プロキシを参照してください
visionメンバー画像を表示できないモデルの場合は false にします。その場合、画像を含む呼び出しには参加せず、テキストだけについて回答することもありません。デフォルトは true です
cacheprovider、メンバーfalse にすると、素材とともに cache_control ブレークポイントが送信されなくなります。Anthropic 形式のみです。このフィールドを拒否するゲートウェイでは無効にしてください。デフォルトは true です
enabledメンバーfalse にすると、設定を削除せずにメンバーを休止状態にします
backupsメンバー同じ枠に対する追加のエンドポイントです。直前のエンドポイントが応答しない場合に、順番に試されます。最大 4 つです。バックアップを参照してください

timeout、retries、retry_backoff、proxy は、すべてのメンバーが継承するデフォルト値として、設定ファイルのトップレベルにも指定できます。

重み

協議会で全員が対等であることはほとんどありません。weight はメンバーの意見がどの程度重視されるかを示します。指定がなければ全員が 1 で、意味を持つのは比率だけです。そのため、2 と 1 の協議会は、10 と 5 の協議会と同じです。

{ "id": "gpt5", "provider": "my-relay", "model": "gpt-5", "weight": 2 }

呼び出しの内容は変わりません。重みが異なる場合、ask_all は各回答にその重みを付け、最後に順位を含めた記録を出力します。

===== GPT-5 (gpt-5) · weight 2 =====
...
===== Local (qwen3-8b) · weight 0.5 =====
...

[WEIGHTS — GPT-5 2, GLM 1, Local 0.5]
These are this council's standing priors on its members, not votes. ...

重みはエンドポイントではなく枠に設定するため、provider には設定できません。同じリレーを使う2人のメンバーが、最先端モデルと小型の高速モデルという組み合わせになることもあります。0 は参考意見を意味します。そのメンバーの回答は読み取れますが、合意の判定には一切加算されません。使用できない値(負の数や文字列など)は、順位を気付かれないまま壊すのではなく、1 に置き換えられます。list_council は有効な値を表示します。

メンバー同士の重みが知らされることはありません。 自分より上位のメンバーがいると知らされたモデルは、反論をやめて同意し始めます。それでは、協議会を設ける目的である独立した異論が失われてしまいます。そのため、2回目のラウンドで他の回答は提示されますが、その順位は伝えられません。重みは記録を読む人のためのものであり、票ではなく事前の評価です。同点を解消し、誰が立証責任を負うかを決めます。最も重みの低いメンバーの具体的で検証可能な理由は、最も重みの高いメンバーの根拠のない断言にも勝ります。

バックアップ

同じモデルに複数の経路で接続できることはよくあります。会社のゲートウェイ、公開リレー、予備のキーなどです。それぞれを独立したメンバーとして登録するのは適切ではありません。同じモデルが何度も登場することになり、何度も回答し、何度も重み付けされ、ラウンド 2 では自分自身と議論することになります。

backups は、同じ席に割り当てる追加のエンドポイントです。先のエンドポイントから応答がない場合、順番に試します。

{
  "id": "sol", "provider": "internal", "model": "gpt-5-codex", "label": "Codex",
  "backups": [
    { "provider": "my-relay" },
    { "base_url": "https://another-relay.example/v1", "api_key": "${SPARE_KEY}",
      "model": "gpt-5-codex-latest" }
  ]
}

ID は 1 つ、ラベルは 1 つ、重みは 1 つ、投票は 1 票です。バックアップに直接アクセスすることはありません。ask(model="sol") は、3 つのうち稼働しているものに接続します。

バックアップは、モデルに関してその席で確定した設定(model、max_tokens、temperature、vision、timeout、retries)を引き継ぎます。そのため、通常のケース、つまり同じモデルを 2 つ目のリレーで使う場合は、上記の 1 行だけで済みます。別の経路でモデルが別の名前で呼ばれている場合に限り、model を指定してください。引き継がれないものは 3 つあります。

  • 接続情報(base_url、api_key、format)— これこそがバックアップを使う理由です。provider を指定して 3 つすべてをそこから取得するか、3 つすべてを自分で指定します。半分ずつ指定することは、設定ファイルで説明している理由により拒否されます。
  • headers — あるエンドポイント用に設定したヘッダーは、通常、そのエンドポイント用の認証情報でもあります。
  • proxy — 接続経路はホストに属し、席には属しません。内部ゲートウェイ用に proxy: false が固定された席では、そうしないと公開バックアップが、必要としているプロキシを迂回してしまいます。バックアップは、独自のプロバイダーから、または評議会のデフォルト設定から接続経路を取得します。

席の接続が次に進むとき。 接続が切れている場合だけではありません。キーが無効化された場合、リレーが扱わなくなったモデル ID を指定した場合、回答ではないものを返してゲートウェイが 200 で応答した場合も同様です。席から見れば、これらはすべて同じ事象です。現在の接続経路ではそのモデルに接続できず、設定では別の経路が指定されています。一時的な障害(再試行)は、まず発生した接続で再試行されるため、バックアップはエンドポイントが利用できない場合のものであり、応答が遅い場合のものではありません。待たずに切り替えたい場合は、プライマリに retries: 0 を指定してください。

どのエンドポイントが応答したかは、回答とともに表示されます。 バックアップではモデル ID が異なることが多く、モデル自体が異なる場合もあります。モデル同士の比較として評議会の結果を読む際、いつの間にか別のものを比較しているという事態は避けなければなりません。

===== Codex (gpt-5-codex-latest — backup 2, after the primary did not answer) =====

list_council では、各席の下にチェーンがインデント表示されます。

id            label        model        format  route   endpoint                       tries     status
sol           Codex        gpt-5-codex  openai  direct  https://gateway.internal/v1    3 × 180s  ready
  ↳ backup 1               gpt-5-codex  openai  env     https://your-relay/v1          3 × 180s  standing by
  ↳ backup 2               gpt-5-c...   openai  env     https://another-relay/v1       3 × 180s  standing by

probe_models はすべてのリンクをプローブするため、モデルを扱わなくなったバックアップを、必要になる前に把握できます。どの接続からも応答がない席では、試した各接続とその理由が報告されます。

バックアップは設定ファイルの機能です。環境変数によるメンバー一覧には同等の機能はありません。1 席あたり最大 4 つです。1 つの席は 1 つのモデルの意見を表すものであり、高可用性クラスターではありません。また、ask_all は最も遅いチェーンの失敗が確定するまで待機します。

プロキシ

すべてのメンバーは、このマシンの HTTP_PROXY/HTTPS_PROXY に従います。ホストをまたぐ評議会が、同じ経路を共有しないホストを含むようになるまでは、これが適切なデフォルトです。たとえば、プロキシ経由でしか応答しないパブリック API や、プロキシからは見えない内部ゲートウェイです。どちらにも適した設定を一つにすることはできないため、経路はメンバーごとに選択します。

proxy には4つの値を指定でき、メンバー、プロバイダー、または評議会独自のデフォルトとして設定ファイルのトップレベルに指定できます。

値経路
省略評議会で proxy が設定されていればそれに従い、設定されていなければ HTTP_PROXY/HTTPS_PROXY に従います
false、または "direct"どちらも無視して直接接続します
URLそのプロキシ経由で接続します。http://、https://、socks5://、socks5h:// を指定できます
"env"評議会が別の経路を使う場合に、1つのメンバーだけ HTTP_PROXY/HTTPS_PROXY に戻します

より具体的な設定が優先されます。メンバー、プロバイダー、評議会、環境変数の順です。つまり、ほとんどのメンバーがプロキシ経由で、プロキシを使ってはいけないメンバーが2つある評議会では、まず一度設定し、次に2回上書きします。

{
  "proxy": "http://127.0.0.1:7890",
  "providers": {
    "internal": { "base_url": "https://gateway.internal.example/v1",
                  "api_key": "${INTERNAL_KEY}", "proxy": false }
  },
  "members": [
    { "id": "gpt5",  "provider": "my-relay", "model": "gpt-5" },
    { "id": "inhouse", "provider": "internal", "model": "some-internal-model" },
    { "id": "local", "base_url": "http://127.0.0.1:11434/v1", "api_key": "-",
      "model": "qwen3-8b", "proxy": false }
  ]
}

環境変数を使っても、同じ3通りの設定ができます。評議会には COUNCIL_PROXY、1つのメンバーには <ID>_PROXY を指定します。

COUNCIL_PROXY=http://127.0.0.1:7890
INHOUSE_PROXY=direct
LOCAL_PROXY=direct

list_council は、確定した経路を env、direct、またはプロキシ URL として表示します。 メンバーごとに異なる経路を設定できる場合に限り、列が表示されます。

network: HTTPS_PROXY=http://127.0.0.1:7890 in this server's environment — the members whose route is 'env' go through it

id       label    model      weight  format  sees      route                  endpoint                          tries     status
gpt5     GPT-5    gpt-5      1       openai  text+img  env                    https://your-host/v1              3 × 180s  ready
inhouse  Inhouse  internal   1       openai  text+img  direct                 https://gateway.internal/v1       3 × 180s  ready
kimi     Kimi     kimi-k2    1       openai  text+img  http://127.0.0.1:7890  https://api.moonshot.cn/v1        3 × 180s  ready

プロキシ URL 内のパスワードは、テーブル、警告、接続エラーのメッセージなど、表示される場所ではすべてマスクされます。

使えないプロキシは、最初の呼び出し時ではなく、名簿を読み込むときに検出されます。 スキームを省略した 127.0.0.1:7890 は http://127.0.0.1:7890 として読み込まれ、その旨が警告に表示されます。接続できないスキームが指定されている場合、そのメンバーは停止され、理由は list_council に表示されます。リクエスト内部で ValueError が発生して毎回失敗することはありません。メンバーは別の経路にひそかに切り替わるのではなく、停止されます。プロキシを指定するのは、通信をその経路に流したいからです。

socks5:// の利用には、httpx がデフォルトではインストールしないパッケージが1つ必要です。追加パッケージをインストールしてください。pip install 'model-council-mcp[socks]' または uvx --from 'model-council-mcp[socks]' model-council-mcp を実行しない場合、そのメンバーは停止され、この内容を明記したメモが表示されます。

ワイヤーフォーマットに関する注意
  • format は URL から推測されません。 base_url に Anthropic 形式のエンドポイントを指定しても、format: "anthropic" を設定しなければ、メンバーは OpenAI 形式のままになり、すべての呼び出しが失敗します。これが最もよくある設定ミスです。
  • Anthropic エンドポイント: サーバーは {base_url}/v1/messages に POST するため、base_url に /v1 を含めないでください。
  • OpenAI 互換エンドポイント: サーバーは /chat/completions を使用し、/responses は使用しません。ゲートウェイによっては両方を公開していますが、/responses はプロバイダーが選んだシステムペルソナを注入する場合があり、汎用アドバイザーには適しません。
  • デフォルトではシステムプロキシが使われます。 メンバーがプロキシから到達できないネットワーク(通常は内部ゲートウェイ)にある場合、プロキシについての記述を含まない ConnectError が発生します。そのメンバーまたはプロバイダーに "proxy": false を指定すると、直接接続でき、ほかのメンバーは引き続きプロキシを使います。プロキシを参照してください。接続エラーにはメンバーが使用していた経路が表示されるため、3つのポリシーがすべて同じように失敗することはありません。
  • モデル ID は頻繁に変わります。 probe_models を実行して、エンドポイントが現在実際に提供しているモデルを確認してください。

使用方法

議長には、次のように入力できます。

  • 「まずあなた自身で回答してから、ask_all を実行し、全員の意見が一致している点と異なる点を表にまとめてください。」
  • 「これについて gpt5 と glm に質問し、両方の回答を批評して、どちらがより正確か、その理由も教えてください。」
  • 「これについて ask_all を2ラウンド実行して、誰が意見を変えたのか、そして最終的に何が結論を決めたのかを教えてください。」
  • 「glm だけに質問してください。この1つのファイルについて、別の意見がほしいです。」

ローカル開発

uv sync

.env.example を .env にコピーし、実際の値を入力してから、次を実行します。

uv run python tests/test_smoke.py

スモークテストでは、オフラインで両方の設定方法を確認します。使用可能な .env がある場合は、最後にライブでの往復テストを実行します。クライアントから作業コピーを指定するには、uvx の代わりに環境内の model-council-mcp スクリプトを使用してください。

トラブルシューティング

  • サーバーが表示されない — クライアントの MCP ログを確認してください(Claude Desktop の場合:~/Library/Logs/Claude/mcp*.log)。サーバーは起動時に設定に関する警告を標準エラー出力に書き込みます。
  • ツールが [... is not configured] と応答する — そのメンバーに base_url、api_key、または model が設定されていません。メンバーごとの詳細を確認するには、list_council を実行してください。
  • HTTP 401 — キーが間違っているか、プロバイダーが無効にしたキーです。
  • HTTP 404 — base_url が間違っているか、そのエンドポイントに対して format が正しくありません。
  • モデル ID が拒否される — probe_models を実行してください。
  • メンバーが gave up after N attempts と表示する — 一時的なエラーが毎回発生しています。エラーテキストには、エンドポイントから返された最後のエラーが表示されます。list_council では、各メンバーの上限が attempts × timeout として表示されます。
  • 呼び出しにタイムアウトをはるかに超える時間がかかる — リトライによって所要時間が増えます。180 秒の試行を 3 回行うと、バックオフを含めて最悪の場合は約 9 分かかります。早めに失敗させたいメンバーは、timeout または retries を小さくしてください。
  • 長時間かかる ask_all が、サーバーの処理中にタイムアウトする — その制限はクライアント側のものであり、このサーバーのものではありません。また、COUNCIL_TIMEOUT を変更しても変わりません。これはメンバーが諦めるまでの時間だけを決めます。参照 MCP SDK は、無応答が 60 秒続くとタイムアウトします。応答の遅いメンバーが 1 ついるだけでも、この制限を超えることがあります。このサーバーは 10 秒ごとに進捗を報告するため、それを処理するクライアントはタイマーをリセットします。対応しないクライアントには、個別の設定が必要です(Claude Code では MCP 設定のサーバーごとにミリ秒単位の timeout を設定するか、すべてのサーバーに MCP_TOOL_TIMEOUT を設定します)。どの制限が適用されるかに注意してください。制限を決めるのはサーバープロセスを起動したクライアントです。そのため、デスクトップアプリに登録して別のツールにブリッジされたサーバーには、入力中の場所で設定した値ではなく、デスクトップアプリ側の値が適用されます。
  • probe_models に、あるメンバーが一覧に表示されないと報告される — 表の下に表示される注記を確認してください。GET /models を拒否するエンドポイントは、チャットルートのみを提供する中継サーバーであることが多く、そのメンバーが応答できないという意味ではありません。実際に確認するには ask を使ってください。
  • ほかのメンバーは正常なのに、1 つのメンバーだけが ConnectError のみで失敗する — 通常、原因はエンドポイントではなくルートです。エラーには、そのメンバーが使用していたルートが示されます。list_council の route 列ではすべてのルートを一度に確認できます。ルートの変更方法については、プロキシを参照してください。

ライセンス

MIT

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。