Model Council
English | 简体中文
An MCP server that seats other LLMs at your table. Your assistant asks them, reads their answers as tool results, relays those answers back and forth for critique, and gives you one merged conclusion — inside a single normal conversation, with no copy-paste.
Your assistant chairs the council. Any number of members, from any mix of OpenAI-compatible and Anthropic-compatible endpoints — a hosted API, a self-run gateway, a local server, or several of each.
Tools
| Tool | What it does |
|---|---|
ask(model, prompt) |
Ask one member by id |
ask_all(prompt, models?) |
Ask everyone (or a named subset) the same prompt in parallel, answers side by side |
list_council() |
The roster: ids, endpoints, and whether each member is ready. No network calls |
probe_models(model?) |
Ask a provider's /models route what ids it really exposes |
Members are stateless and cannot see your conversation, so the chair passes
everything they need in each call. That is exactly what makes cross-review work:
it puts one member's answer inside another's prompt.
Install
The server runs from PyPI with no clone and no virtualenv. You need uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
Claude Desktop
Edit claude_desktop_config.json (Settings → Developer → Edit Config), add the
block below, then fully quit and reopen the app — Cmd-Q, not just closing
the window. You will know it worked when the tools menu lists 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
Other MCP clients
Anything that launches a stdio server works: run uvx model-council-mcp and
pass the same environment variables.
Configuring the council
Two layers, so several models can share one endpoint without repeating its credentials:
- provider — an endpoint:
base_url+api_key+ which wire format it speaks - member — one model on some provider, addressed by a short id
Configuration comes from a JSON file if one is found, otherwise from environment
variables. list_council() always reports which source won.
Environment variables
COUNCIL_MODELS lists the ids; each id gets variables named after it, uppercased
with non-alphanumeric characters turned into underscores (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
Per member: _BASE_URL, _API_KEY, _MODEL, _FORMAT, _LABEL, _MAX_TOKENS,
_TEMPERATURE, _TIMEOUT, _HEADERS (a JSON object), _ENABLED.
Globally: COUNCIL_TIMEOUT, COUNCIL_CONFIG, COUNCIL_ENV_FILE.
Omit COUNCIL_MODELS and the roster defaults to chatgpt,glm, reading
CHATGPT_* and GLM_*.
A config file
Better once you have more than a handful of members, or when several share an
endpoint. Set COUNCIL_CONFIG=/path/to/config.json, or drop the file at
~/.config/model-council/config.json where the server finds it on its own.
{
"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" },
{ "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} is expanded from the environment, so the file carries no secrets and
can be shared or committed. A member may skip provider and inline its own
connection details, and any provider field can be overridden per member. See
examples/config.json for a fully annotated version.
Fields
| Field | Applies to | Notes |
|---|---|---|
base_url |
provider, member | Root the route hangs off — /chat/completions for openai, /v1/messages for anthropic. Usually ends in /v1 for OpenAI-compatible hosts |
api_key |
provider, member | |
format |
provider, member | openai (default) or anthropic |
model |
member | The model id sent to the endpoint |
label |
member | Display name in answers; defaults to the id |
max_tokens |
member | Anthropic format only, where it is required. Default 8192 |
temperature |
member | Sent only when set |
headers |
provider, member | Extra HTTP headers |
timeout |
provider, member | Seconds. Default 180 |
enabled |
member | false parks a member without deleting its config |
Wire format notes
formatis not inferred from the URL. Pointingbase_urlat an Anthropic-style endpoint without also settingformat: "anthropic"leaves the member on the OpenAI format, and every call fails. This is the single most common misconfiguration.- Anthropic endpoints: the server posts to
{base_url}/v1/messages, sobase_urlshould not already include the/v1. - OpenAI-compatible endpoints: the server uses
/chat/completions, never/responses. Some gateways expose both, but/responsesmay inject a provider-chosen system persona, which is wrong for a general-purpose advisor. - Model ids move fast. Run
probe_modelsto see what an endpoint actually offers today.
Using it
Things worth typing to the chair:
- "Answer this yourself, then
ask_alland give me a table of where you all agree and disagree." - "Ask gpt5 and glm this, then critique both answers and tell me which is more correct and why."
- "Round 1:
ask_all. Round 2: show each member the others' answers and ask it to revise. Then give me the merged answer." - "Ask only glm — I want a second opinion on this one file."
Local development
uv sync
Copy .env.example to .env, fill in real values, then:
uv run python tests/test_smoke.py
The smoke test checks both configuration paths offline; with a usable .env it
finishes with a live round-trip. To point a client at your working copy, use the
model-council-mcp script inside your environment instead of uvx.
Troubleshooting
- Server doesn't appear — check the client's MCP logs (Claude Desktop:
~/Library/Logs/Claude/mcp*.log). The server writes configuration warnings to stderr at startup. - A tool answers
[... is not configured]— that member is missingbase_url,api_key, ormodel. Runlist_councilfor a per-member breakdown. - HTTP 401 — wrong key, or a key the provider has disabled.
- HTTP 404 — wrong
base_url, or the wrongformatfor that endpoint. - The model id is rejected — run
probe_models.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file model_council_mcp-0.1.0.tar.gz.
File metadata
- Download URL: model_council_mcp-0.1.0.tar.gz
- Upload date:
- Size: 13.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13d30c5d7d844c815f74b50aaa045eced89d1d0db4738eef90d5004fc3668368
|
|
| MD5 |
e03858e021e2073a0bcd5e6d4297a50d
|
|
| BLAKE2b-256 |
2fccc2bc5b034e09daad47cdcc9fccce7423ea26314f07c0420b3b9fd6ded802
|
Provenance
The following attestation bundles were made for model_council_mcp-0.1.0.tar.gz:
Publisher:
release.yml on Totti0135/model-council
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
model_council_mcp-0.1.0.tar.gz -
Subject digest:
13d30c5d7d844c815f74b50aaa045eced89d1d0db4738eef90d5004fc3668368 - Sigstore transparency entry: 2460717420
- Sigstore integration time:
-
Permalink:
Totti0135/model-council@95511e02fc5f852c84441264c00618330c7a5706 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Totti0135
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95511e02fc5f852c84441264c00618330c7a5706 -
Trigger Event:
push
-
Statement type:
File details
Details for the file model_council_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: model_council_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c8bcbf3c9216a4247b7e1df87269058450400157aa742064bd8d5641397050a
|
|
| MD5 |
644df5ade9d7ebb63b8afd6e836cc036
|
|
| BLAKE2b-256 |
a3d25edf4ab60bcb0a3157df0c87474faad0e2cc7e32944385d2b6e63966ab76
|
Provenance
The following attestation bundles were made for model_council_mcp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Totti0135/model-council
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
model_council_mcp-0.1.0-py3-none-any.whl -
Subject digest:
5c8bcbf3c9216a4247b7e1df87269058450400157aa742064bd8d5641397050a - Sigstore transparency entry: 2460718184
- Sigstore integration time:
-
Permalink:
Totti0135/model-council@95511e02fc5f852c84441264c00618330c7a5706 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Totti0135
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95511e02fc5f852c84441264c00618330c7a5706 -
Trigger Event:
push
-
Statement type: