Skip to main content

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 is listed on the official MCP Registry as io.github.Totti0135/model-council, so a client that browses the registry can find and add it there. To wire it up by hand instead, read on.

It runs from PyPI with no clone and no virtualenv. You need uv:

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

Claude Desktop

Easiest is the desktop extension. Download model-council-<version>.mcpb from the latest release and drag it onto Settings → Extensions. The app asks for the endpoints and keys in a form and keeps the keys in your OS keychain, so no file on disk holds them. The form seats two models; for a larger council, point its "Config file" field at a JSON config (see below).

To wire it up by hand instead, 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 whichever source is most explicit: the file COUNCIL_CONFIG points at, else the roster COUNCIL_MODELS names, else a config file at ~/.config/model-council/config.json, else the built-in default roster. Explicit beats discovered on purpose — a config file you left lying around must not silently override settings a client just handed the server. 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-modelMY_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), _PROXY, _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. See examples/config.json for a fully annotated version.

A member gets its connection one of two ways, never a mix of both: name a provider and take that endpoint whole, or omit provider and supply base_url + api_key + format yourself (as kimi does above). Naming a provider and overriding one of those three is refused — that member is disabled and list_council says why. The reason is that a partial override would pair one endpoint's credentials with another endpoint's URL, quietly sending your key to a host it was never issued for. Per-member headers, timeout, temperature, max_tokens and label are not part of that identity and stay overridable.

Fields

The first three travel together as one unit — see the rule above.

Field Applies to Notes
base_url provider, or a member with no provider Root the route hangs off — /chat/completions for openai, /v1/messages for anthropic. Usually ends in /v1 for OpenAI-compatible hosts
api_key provider, or a member with no provider
format provider, or a member with no provider 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
proxy provider, member Omit to follow HTTP_PROXY/HTTPS_PROXY; false to connect directly; a URL to use that proxy
enabled member false parks a member without deleting its config

Wire format notes

  • format is not inferred from the URL. Pointing base_url at an Anthropic-style endpoint without also setting format: "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, so base_url should not already include the /v1.
  • OpenAI-compatible endpoints: the server uses /chat/completions, never /responses. Some gateways expose both, but /responses may inject a provider-chosen system persona, which is wrong for a general-purpose advisor.
  • A system proxy is followed by default. If a member sits on a network your proxy cannot reach — an internal gateway, typically — it fails with a bare ConnectError that never mentions a proxy. Give that member or provider "proxy": false and it connects directly, while everyone else keeps using the proxy. The error message says so too when a proxy is in play.
  • Model ids move fast. Run probe_models to see what an endpoint actually offers today.

Using it

Things worth typing to the chair:

  • "Answer this yourself, then ask_all and 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 missing base_url, api_key, or model. Run list_council for a per-member breakdown.
  • HTTP 401 — wrong key, or a key the provider has disabled.
  • HTTP 404 — wrong base_url, or the wrong format for 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

model_council_mcp-0.3.0.tar.gz (18.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

model_council_mcp-0.3.0-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file model_council_mcp-0.3.0.tar.gz.

File metadata

  • Download URL: model_council_mcp-0.3.0.tar.gz
  • Upload date:
  • Size: 18.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for model_council_mcp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 4e556ec7e444829f2fd1f499c5933f9a8eb937637fdbba0f3c717d6ff4dc72ef
MD5 ae83bf6e05299e7eb85f4b650b386f33
BLAKE2b-256 b8a17f7dd339c19ac21dc02f1bd8b7c0273ac5df3ce501d33040fa6fcd050f59

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_council_mcp-0.3.0.tar.gz:

Publisher: release.yml on Totti0135/model-council

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file model_council_mcp-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for model_council_mcp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dc744db50b8d701e2a95b3ea00c4fb95dac78a6ae5a49a526d74434c70dd7f01
MD5 cfb9af728a1c223e893d33e76888cd89
BLAKE2b-256 89c7fbec4a42894bf6904595249d954405a8c8827d5b8564748701b8ba20fd28

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_council_mcp-0.3.0-py3-none-any.whl:

Publisher: release.yml on Totti0135/model-council

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page