Metadata-Version: 2.4
Name: foo-mcp
Version: 0.1.0
Summary: MCP server for transparent FOO multiagent orchestration
Project-URL: Homepage, https://github.com/dpletta/foo-mcp
Project-URL: Repository, https://github.com/dpletta/foo-mcp
Project-URL: Issues, https://github.com/dpletta/foo-mcp/issues
Author: David Pletta
License: MIT
License-File: LICENSE
Keywords: claude,llm,mcp,model-context-protocol,multiagent,orchestration
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: anthropic<1,>=0.71
Requires-Dist: google-genai<3,>=2.1
Requires-Dist: huggingface-hub<2,>=1.1
Requires-Dist: mcp[cli]<2,>=1.20
Requires-Dist: openai<3,>=2.5
Requires-Dist: pydantic<3,>=2.11
Requires-Dist: python-dotenv<2,>=1.1
Provides-Extra: dev
Requires-Dist: mypy<2,>=1.18; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=1.2; extra == 'dev'
Requires-Dist: pytest<10,>=9.0.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.14; extra == 'dev'
Provides-Extra: mlx
Requires-Dist: mlx-lm<1,>=0.31; extra == 'mlx'
Description-Content-Type: text/markdown

# 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

```bash
# 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](#startup-quorum) for the override.

---

## Install in 60 seconds

### Option A — Add to Claude Code via PyPI (recommended)

```bash
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:

```bash
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/plugin.json),
[`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json)).
From your terminal:

```bash
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:

```bash
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](https://github.com/openai/codex) registers stdio MCP servers via
`codex mcp add`:

```bash
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:

```json
{
  "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):

```bash
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):

```bash
claude plugin list        # foo-mcp should appear as installed
```

For the Codex CLI path (Option D):

```bash
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` (or `GOOGLE_API_KEY`)
- **Hugging Face** — `HF_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:

```bash
# 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`](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`](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:

```bash
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:

```bash
# 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:

```bash
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:

```bash
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:

```bash
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

```bash
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`](.github/workflows/publish.yml) workflow,
which runs on every pushed tag matching `v*`. The workflow uses PyPI's
[trusted publisher](https://docs.pypi.org/trusted-publishers/) 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:

```bash
# 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](https://github.com/gitleaks/gitleaks) pre-commit hook that scans your
staged diff for accidentally committed API keys before they ever leave your
machine:

```bash
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`](.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](LICENSE)).
