Skip to main content

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:

  • OpenAIOPENAI_API_KEY
  • AnthropicANTHROPIC_API_KEY
  • Google GeminiGEMINI_API_KEY (or GOOGLE_API_KEY)
  • Hugging FaceHF_TOKEN (or HUGGINGFACE_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=$KEY directly on the command (recommended) or export in your shell first.
  • Claude Code plugin (Option C) — exported shell variables flow into the plugin's mcpServers.env block via ${VAR} substitution at server spawn. Export keys before launching claude.
  • codex mcp add (Option D) — pass --env KEY=$KEY on 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):

  1. On GitHub, create a pypi deployment environment for this repo: Settings → Environments → New environment → name = pypi. Optionally add a required-reviewer rule for extra release protection. (The workflow references environment: pypi; deployments fail until the environment exists.)
  2. 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)

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

foo_mcp-0.1.0.tar.gz (167.1 kB view details)

Uploaded Source

Built Distribution

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

foo_mcp-0.1.0-py3-none-any.whl (41.8 kB view details)

Uploaded Python 3

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

Hashes for foo_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 db85d45f3fd28c7a46b0e8d7f6a13d11a260624d9f4a386fa5ba78b883bee75a
MD5 f872d2461c1f24fd37da6101ae8648fd
BLAKE2b-256 a05b292a7c83f1814605585e1ff8c7a16c6370ad1e281f719addf527a9c85fb4

See more details on using hashes here.

Provenance

The following attestation bundles were made for foo_mcp-0.1.0.tar.gz:

Publisher: publish.yml on dpletta/foo-mcp

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

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

Hashes for foo_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e35bf672867e4b4cf8ada80d5fd643c6d3e39ee0ae880580881e782ec1704e5
MD5 339fd24b6c8fc9095062e0a433847858
BLAKE2b-256 f2e4ae0b7e5c7ef31d86318d5c4e5a3183a5d5755ab768f35efd6c7c04ffa9aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for foo_mcp-0.1.0-py3-none-any.whl:

Publisher: publish.yml on dpletta/foo-mcp

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 Pingdom Monitoring Sentry Error logging StatusPage Status page