MCP server for transparent FOO multiagent orchestration
Reason this release was yanked:
Apparently there are patents pending
Project description
FOO MCP
foo-mcp exposes FOO-style multiagent orchestration as a Model Context Protocol
server. Peer agents critique each other, harmonizer agents organize the
critiques, and the target agent revises based on the consensus — every step
written to a hash-chained, JSONL trace ledger so the full reasoning history is
reproducible and auditable. Supports OpenAI, Anthropic, Google Gemini, Hugging
Face, and Apple Silicon MLX local inference behind a single config schema.
Quick start
# 1. Install
pip install foo-mcp # or: uvx foo-mcp
# 2. Export the API keys for the providers you want to use
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
# 3. Pick at least two providers + models interactively
foo-mcp-init # writes ~/.config/foo-mcp/agents.json
export FOO_MCP_CONFIG=~/.config/foo-mcp/agents.json
# 4. Smoke-test the full multi-agent pipeline against real LLMs
foo-mcp-consensus-demo --question "What is the half-life of caffeine?"
The server requires at least two usable non-mock agents at startup; without
them it exits with a clear error pointing back at foo-mcp-init. See
Startup quorum for the override.
Install in 60 seconds
Option A — Add to Claude Code via PyPI (recommended)
claude mcp add --scope user --transport stdio foo-mcp \
--env OPENAI_API_KEY=$OPENAI_API_KEY \
--env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--env GEMINI_API_KEY=$GEMINI_API_KEY \
--env HF_TOKEN=$HF_TOKEN \
--env FOO_MCP_CONFIG=$HOME/.config/foo-mcp/agents.json \
-- uvx foo-mcp
Pass keys as bare values — KEY=sk-abc123, not KEY="sk-abc123". Wrapping
quote characters are passed through to provider SDKs and cause silent 401s.
Only pass --env lines for the providers whose keys you actually have.
Use --scope local instead of --scope user to limit registration to the
current project.
Option B — Add to Claude Code via Git (track main directly)
If you want the bleeding-edge main branch rather than the latest PyPI
release:
claude mcp add --scope user --transport stdio foo-mcp \
--env OPENAI_API_KEY=$OPENAI_API_KEY \
--env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--env GEMINI_API_KEY=$GEMINI_API_KEY \
--env HF_TOKEN=$HF_TOKEN \
-- uvx --from git+https://github.com/dpletta/foo-mcp foo-mcp
Option C — Install as a Claude Code plugin
The repo ships a Claude Code plugin manifest plus a single-plugin marketplace
(.claude-plugin/plugin.json,
.claude-plugin/marketplace.json).
From your terminal:
claude plugin marketplace add dpletta/foo-mcp
claude plugin install foo-mcp@foo-mcp
Or, inside a Claude Code session:
/plugin marketplace add dpletta/foo-mcp
/plugin install foo-mcp@foo-mcp
The plugin's mcpServers block uses ${CLAUDE_PLUGIN_ROOT} so uvx installs
directly from the cloned plugin source. Provider keys are picked up from your
shell environment at Claude Code launch time via ${VAR} substitution; export
them before starting Claude Code:
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GEMINI_API_KEY=...
export HF_TOKEN=hf_...
claude
Option D — Add to Codex CLI
Codex CLI registers stdio MCP servers via
codex mcp add:
codex mcp add foo-mcp \
--env OPENAI_API_KEY=$OPENAI_API_KEY \
--env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--env GEMINI_API_KEY=$GEMINI_API_KEY \
--env HF_TOKEN=$HF_TOKEN \
--env FOO_MCP_CONFIG=$HOME/.config/foo-mcp/agents.json \
-- uvx foo-mcp
Replace uvx foo-mcp with
uvx --from git+https://github.com/dpletta/foo-mcp foo-mcp to pin to main
instead of the latest PyPI release.
Verify with codex mcp list and codex mcp get foo-mcp. Same bare-value
quoting rules apply — KEY=sk-abc123, not KEY="sk-abc123".
Option E — Manual JSON config
If you prefer to edit ~/.claude.json (or .mcp.json for a project scope) by
hand, the equivalent stanza is:
{
"mcpServers": {
"foo-mcp": {
"type": "stdio",
"command": "uvx",
"args": ["foo-mcp"],
"env": {
"OPENAI_API_KEY": "sk-...",
"ANTHROPIC_API_KEY": "sk-ant-...",
"GEMINI_API_KEY": "...",
"HF_TOKEN": "hf_...",
"FOO_MCP_CONFIG": "/absolute/path/to/agents.json"
}
}
}
}
Replace ["foo-mcp"] with
["--from", "git+https://github.com/dpletta/foo-mcp", "foo-mcp"] to pin to
main instead of the PyPI release.
In the JSON snippet above, the " around each value are JSON syntax — the
actual key value is the bare string between them (e.g. sk-..., not
"sk-..."). Do not add extra quote characters inside the value.
Verify the install
For the claude mcp add path (Option A / B):
claude mcp list # foo-mcp should appear with ✓ Connected
claude mcp get foo-mcp # shows the spawn command + env vars
For the plugin path (Option C):
claude plugin list # foo-mcp should appear as installed
For the Codex CLI path (Option D):
codex mcp list
codex mcp get foo-mcp
Inside a Claude Code session, /mcp shows the server as CONNECTED and
exposes all 11 tools. Inside Codex, the tools appear under the foo-mcp
namespace.
Tools exposed
| Tool | Purpose |
|---|---|
list_agents |
Configured agents with status + env diagnostics |
send_message_to_agent |
Send a single message to one agent |
broadcast_message |
Send the same message to every active agent |
run_vulnerability_analysis |
Peers critique a source agent's response |
run_judgment_analysis |
Harmonizers organize peer critiques |
run_reflection_analysis |
Source agent revises using the harmonized judgment |
get_conversation_history |
Full JSON history for one agent |
reset_agent_history |
Clear one or all conversation histories |
get_trace_record |
Fetch a trace event by ID or event hash |
verify_trace_chain |
Re-validate the hash chain |
export_run_bundle |
Manifest + trace + verification report on disk |
Provider API keys
The server reads, in order of precedence:
- OpenAI —
OPENAI_API_KEY - Anthropic —
ANTHROPIC_API_KEY - Google Gemini —
GEMINI_API_KEY(orGOOGLE_API_KEY) - Hugging Face —
HF_TOKEN(orHUGGINGFACE_API_KEY/HUGGINGFACE_HUB_TOKEN) - Apple Silicon MLX — no key required; runs locally
How keys are supplied depends on the install path:
claude mcp add(Options A/B) — pass--env KEY=$KEYdirectly on the command (recommended) or export in your shell first.- Claude Code plugin (Option C) — exported shell variables flow into the
plugin's
mcpServers.envblock via${VAR}substitution at server spawn. Export keys before launchingclaude. codex mcp add(Option D) — pass--env KEY=$KEYon the command.- Manual JSON config (Option E) — fill in literal values inside the JSON
string (the surrounding
"are JSON syntax, not part of the value).
Do not wrap key values in quote characters. The MCP server expects
KEY=sk-abc123, not KEY="sk-abc123". Wrapping quotes are passed through
to provider SDKs and cause silent 401s. foo-mcp strips matched wrapping
quotes defensively (clean_api_key in src/foo_mcp/config.py), but other
tooling along the chain may not.
Configure your own agents
Interactive setup with foo-mcp-init (recommended)
The package installs a foo-mcp-init CLI that scans your env for provider
keys, lets you pick at least two providers and a model for each, picks a
harmonizer (Anthropic Claude Sonnet 4.6 by default when available, otherwise
the picked-provider's premium model), and writes a fully-validated config
JSON. It works interactively or via flags:
# Interactive — numbered menus
foo-mcp-init
# Scripted — pass providers, models, harmonizer up front
foo-mcp-init \
--providers anthropic,openai \
--models claude-haiku-4-5-20251001,gpt-4.1-mini \
--harmonizer anthropic:claude-sonnet-4-6 \
--output ~/.config/foo-mcp/agents.json --force
The default output path is ~/.config/foo-mcp/agents.json. After the file
is written, the CLI prints the export FOO_MCP_CONFIG=... line you need.
Startup quorum
foo-mcp (and foo-mcp-http) refuse to start unless at least two
active non-mock agents have valid API keys present in env. The check is
skipped for the bundled example (so the zero-key default flow keeps
working) and can be bypassed for testing/CI with
FOO_MCP_ALLOW_SINGLE_PROVIDER=1. The error message is actionable — it
lists the agents that are blocked and points back at foo-mcp-init.
Hand-edit the config
If you prefer to write the JSON yourself, the schema is documented inline
in
src/foo_mcp/_data/foo.agents.example.json.
The top-level keys are CONFIG (instructions, user name, output dir) and
MODELS (an array of agent specs). Each agent picks one of:
openai, anthropic, google, huggingface, mlx, or mock.
If FOO_MCP_CONFIG is set but points at a missing file, the server prints
a stderr warning and falls back to the bundled example so it still boots.
Working config with all providers
For a hand-editable reference, the repo also ships
config/foo.agents.example.json — one
agent per provider so every supported backend appears in list_agents.
Copy it and edit, or use it as a structural template alongside the
foo-mcp-init output:
cp config/foo.agents.example.json config/my-agents.json
export FOO_MCP_CONFIG=/absolute/path/to/foo-mcp/config/my-agents.json
Set this in your shell before launching Claude Code (or add it to
~/.claude/settings.local.json as an env var).
Supported providers
| Provider | Env var(s) | Example model_code |
|---|---|---|
anthropic |
ANTHROPIC_API_KEY |
claude-haiku-4-5-20251001, claude-sonnet-4-6, claude-opus-4-7 |
openai |
OPENAI_API_KEY |
gpt-4.1-mini, gpt-4.1, o4-mini |
google |
GEMINI_API_KEY (or GOOGLE_API_KEY) |
gemini-2.0-flash, gemini-2.5-pro |
huggingface |
HF_TOKEN (or HUGGINGFACE_API_KEY) |
Qwen/Qwen2.5-7B-Instruct, meta-llama/Llama-3.1-8B-Instruct |
mlx |
(none — runs locally) | mlx-community/Llama-3.2-3B-Instruct-4bit |
mock |
(none) | Any string — responses are deterministic hashes |
Agents for providers whose keys are missing still appear in list_agents
with their missing_env_keys populated. Set "active": false to hide an
agent you don't intend to use.
Agent fields
Each entry in MODELS accepts:
| Field | Required | Description |
|---|---|---|
agent_name |
yes | Unique name, shown in list_agents and used by send_message_to_agent |
provider |
yes | One of anthropic, openai, google, huggingface, mlx, mock |
model_code |
yes | Provider-specific model identifier passed to the SDK |
model_name |
no | Human-readable label for list_agents |
temperature |
no | 0.0–1.0 (omitted for reasoning models like o1/o3/o4) |
max_completion_tokens |
no | Output token cap |
active |
no | true (default) to enable; false to skip at startup |
harmonizer |
no | true to designate this agent as a harmonizer |
agent_directive |
no | Appended to the system prompt for this agent |
harmonizer_directive |
no | Overrides agent_directive when acting as harmonizer |
top_p, top_k |
no | Provider-specific sampling controls |
max_kv_size |
no | MLX cache size in tokens |
endpoint_url |
no | Custom endpoint for Hugging Face TGI or compatible APIs |
Apple Silicon (MLX local inference)
Add the mlx extra so mlx-lm installs alongside the server:
# PyPI
claude mcp add --scope user --transport stdio foo-mcp \
-- uvx --with 'foo-mcp[mlx]' foo-mcp
# Or from main
claude mcp add --scope user --transport stdio foo-mcp \
-- uvx --from 'git+https://github.com/dpletta/foo-mcp[mlx]' foo-mcp
Then configure an agent with "provider": "mlx" and an mlx-community/*
model code, e.g. mlx-community/Qwen2.5-0.5B-Instruct-4bit. First-run
downloads the model from Hugging Face Hub; subsequent runs hit the local
cache.
Streamable HTTP transport
A streamable HTTP variant ships as foo-mcp-http. Useful when the server
should run on a long-lived host instead of being respawned per Claude Code
session:
foo-mcp-http # binds to 127.0.0.1:8000 by default
claude mcp add --transport http foo-mcp-http http://127.0.0.1:8000/mcp
FOO_MCP_HOST and FOO_MCP_PORT override the bind address. The
FOO_MCP_ALLOW_SINGLE_PROVIDER=1 escape hatch and the bundled-example
exemption apply here too.
Standalone CLIs
The package installs five console scripts:
| CLI | Purpose |
|---|---|
foo-mcp |
Stdio MCP server (the one MCP clients talk to) |
foo-mcp-http |
Streamable HTTP variant on 127.0.0.1:8000 |
foo-mcp-init |
Interactive provider/model picker → writes the agents config |
foo-mcp-consensus-demo |
One-shot full-pipeline run, prints a consensus report |
foo-mcp-smoke |
Per-provider round-trip smoke test |
End-to-end consensus demo
foo-mcp-consensus-demo runs the full pipeline (broadcast → vulnerability
→ judgment → reflection → trace verification → bundle export) against the
configured agents and prints a structured report:
export FOO_MCP_CONFIG=~/.config/foo-mcp/agents.json
foo-mcp-consensus-demo --question "What is the half-life of caffeine?"
Use --json-only to emit one machine-readable blob with every step, or
--max-chars to tune the per-response snippet size. The full content of
every response is always preserved in runs/<run_id>/trace.jsonl and the
exported bundle regardless of snippet truncation.
Per-provider smoke test
foo-mcp-smoke is a single-command provider round-trip:
foo-mcp-smoke all
# or one provider at a time:
foo-mcp-smoke openai
Each provider reports [OK] with latency and a one-line reply, or
[FAIL] with the error.
Trace ledger
Every tool call writes an append-only event to runs/<run_id>/trace.jsonl.
Each event carries a previous_hash and event_hash where the event hash
is SHA-256 of canonical JSON with sorted keys and stable separators —
catches edits, insertions, deletions, and reordering. verify_trace_chain
re-validates the chain on demand; export_run_bundle packages the run
directory plus a verification report for archival.
Raw prompts and responses are stored by default. Set
FOO_MCP_TRACE_INCLUDE_RAW_CONTENT=false to substitute deterministic
hashes for the raw content while keeping the chain verifiable.
Development
git clone https://github.com/dpletta/foo-mcp
cd foo-mcp
uv sync --extra dev
cp .env.example .env # fill in provider keys you have
uv run pytest # 63 unit tests (mocked, fast)
uv run pytest -m live # opt-in: hits real provider APIs
uv run foo-mcp-smoke all # opt-in: single round-trip per provider
uv run foo-mcp-consensus-demo --question "..." # opt-in: full pipeline live run
uv run ruff check . && uv run ruff format --check . && uv run mypy src
Live tests under tests/ (marked @pytest.mark.live) exercise the
real provider APIs end-to-end; run them with uv run pytest -m live.
Releases (maintainers)
Releases are published to PyPI by the
.github/workflows/publish.yml workflow,
which runs on every pushed tag matching v*. The workflow uses PyPI's
trusted publisher flow over
OIDC — no long-lived API token is stored.
First-time setup (one-time):
- On GitHub, create a
pypideployment environment for this repo: Settings → Environments → New environment → name =pypi. Optionally add a required-reviewer rule for extra release protection. (The workflow referencesenvironment: pypi; deployments fail until the environment exists.) - On PyPI, sign in at
https://pypi.org/manage/account/publishing/ and add a pending
trusted publisher with:
- PyPI Project Name:
foo-mcp - Owner:
dpletta - Repository:
foo-mcp - Workflow filename:
publish.yml - Environment:
pypi(must match step 1's environment name)
- PyPI Project Name:
Cutting a release:
# 1. Bump the version in pyproject.toml
# 2. Commit + push to main
# 3. Tag and push
git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0
The workflow then builds, runs all checks, and uploads the wheel + sdist.
Secret scanning (optional)
If you fork this repo and plan to commit changes, opt into a local gitleaks pre-commit hook that scans your staged diff for accidentally committed API keys before they ever leave your machine:
pip install pre-commit # or: brew install pre-commit
pre-commit install # installs the hook in .git/hooks/pre-commit
After that, every git commit runs gitleaks on the staged diff and blocks the
commit on any finding. Allowlists for the test stubs (tests/*.py) and
.env.example are already configured in .gitleaks.toml, so
the existing repo passes cleanly — you'll only see findings on actually
suspicious diffs.
To bypass the hook for a single commit (not recommended), use
git commit --no-verify.
License
MIT (see LICENSE).
Project details
Release history Release notifications | RSS feed
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 foo_mcp-0.1.0.tar.gz.
File metadata
- Download URL: foo_mcp-0.1.0.tar.gz
- Upload date:
- Size: 167.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db85d45f3fd28c7a46b0e8d7f6a13d11a260624d9f4a386fa5ba78b883bee75a
|
|
| MD5 |
f872d2461c1f24fd37da6101ae8648fd
|
|
| BLAKE2b-256 |
a05b292a7c83f1814605585e1ff8c7a16c6370ad1e281f719addf527a9c85fb4
|
Provenance
The following attestation bundles were made for foo_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on dpletta/foo-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
foo_mcp-0.1.0.tar.gz -
Subject digest:
db85d45f3fd28c7a46b0e8d7f6a13d11a260624d9f4a386fa5ba78b883bee75a - Sigstore transparency entry: 1607171109
- Sigstore integration time:
-
Permalink:
dpletta/foo-mcp@e8edef426872b63c20dbee4c41860fc11633d827 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/dpletta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e8edef426872b63c20dbee4c41860fc11633d827 -
Trigger Event:
push
-
Statement type:
File details
Details for the file foo_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: foo_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 41.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e35bf672867e4b4cf8ada80d5fd643c6d3e39ee0ae880580881e782ec1704e5
|
|
| MD5 |
339fd24b6c8fc9095062e0a433847858
|
|
| BLAKE2b-256 |
f2e4ae0b7e5c7ef31d86318d5c4e5a3183a5d5755ab768f35efd6c7c04ffa9aa
|
Provenance
The following attestation bundles were made for foo_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on dpletta/foo-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
foo_mcp-0.1.0-py3-none-any.whl -
Subject digest:
0e35bf672867e4b4cf8ada80d5fd643c6d3e39ee0ae880580881e782ec1704e5 - Sigstore transparency entry: 1607171223
- Sigstore integration time:
-
Permalink:
dpletta/foo-mcp@e8edef426872b63c20dbee4c41860fc11633d827 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/dpletta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e8edef426872b63c20dbee4c41860fc11633d827 -
Trigger Event:
push
-
Statement type: