Skip to main content

MCP server exposing principled social-choice aggregation rules (Borda, Copeland, Condorcet, approval, STV, opinion pool), with a reproducible benchmark measuring their accuracy vs majority vote over an LLM ensemble.

Project description

voting-mcp

Principled social-choice aggregation as MCP tools — with a benchmark that measures the accuracy lift over naive majority vote.

Almost every multi-agent system aggregates votes with Counter(votes).most_common(1), throwing away preference order and confidence. voting-mcp ships the real rules (Borda, Copeland, Condorcet, approval, STV, linear opinion pool) as callable MCP tools — each with its known axiomatic behavior and explicit, documented tie-breaking — plus a reproducible benchmark that aggregates a diverse ensemble of LLMs on a reasoning set and reports accuracy with bootstrap confidence intervals.

The server is pure compute: stdio transport, no network, no file writes, no secrets — clean against the OWASP MCP Top 10 by construction.

Install

# run the server directly (once published)
uvx voting-mcp

# or from source
git clone <repo> && cd voting-mcp
uv sync
uv run python -m voting_mcp.server

Add it to an MCP client (e.g. Claude Desktop claude_desktop_config.json):

{
  "mcpServers": {
    "voting": { "command": "uvx", "args": ["voting-mcp"] }
  }
}

Tools

Every tool takes a profile ({candidates, ballots}) and returns a Result with the full co-winner set (winners, so ties are never hidden), the single tie-broken winner (or null when none exists), a ranking, per-candidate scores, and a note.

Tool Ballots Notes
borda rankings positional; Condorcet-inconsistent, clone-sensitive
copeland rankings Condorcet-consistent pairwise (+1 win, +0.5 tie)
condorcet rankings returns the pairwise winner or an explicit no-winner on a cycle
approval approval sets most-approved wins
stv rankings single-winner instant-runoff; clone-resistant
opinion_pool distributions linear pool — preserves confidence, not an argmax vote
plurality rankings baseline (most first choices)
majority rankings strict >50% or no winner
aggregate_rule any dispatch by a rule enum

Tie-breaking is an explicit parameter (lexicographic default, none, or seeded random).

Benchmark

Aggregate an ensemble of 5 models (one OpenAI-compatible client via OpenRouter) on ARC-Challenge and compare each rule to the naive majority vote:

uv sync --extra bench
uv run python -m bench.fetch_arc --limit 200
# prints a cost estimate and STOPS; add --yes to actually call the API, --mock for a free dry run
uv run python -m bench.run_ensemble --dataset bench/datasets/arc_challenge.jsonl --limit 200 --yes
uv run python -m bench.compare --dataset bench/datasets/arc_challenge.jsonl --limit 200

Every raw response is cached under bench/results/raw/; re-runs never re-call the API, so aggregation tweaks are free.

Results

5-model ensemble (gpt-4o-mini · gemini-2.5-flash-lite · deepseek-v3 · claude-haiku-4.5 · glm-4.7), n = 200, bootstrap 95% CI. Two datasets of different difficulty; full write-up and both plots in RESULTS.md.

MMLU-Pro (hard, baseline 73.5%) — the informative case:

Rule Accuracy 95% CI Δ vs majority
opinion_pool 0.755 [0.695, 0.815] +0.020
majority_vote (baseline) 0.735 [0.679, 0.788]
approval 0.701 [0.640, 0.757] −0.035
stv 0.693 [0.630, 0.750] −0.043
copeland 0.647 [0.580, 0.710] −0.088
condorcet 0.620 [0.550, 0.685] −0.115
majority (strict) 0.590 [0.520, 0.655] −0.145
borda 0.472 [0.405, 0.540] −0.263

MMLU-Pro

The finding (honest): the value isn't "fancy voting beats majority." It's that the confidence-preserving rule (opinion_pool) wins when the crowd is uncertain (+2.0pp, the only rule above baseline — though its CI still overlaps, so suggestive, not conclusive), while forcing the distributions into full rankings actively hurtsborda collapses to 0.472, far below majority, because with 10 options the tail of the ranking is mostly noise. Aggregate the confidence; don't throw it away. On ARC-Challenge (baseline 96.8%, near-ceiling) nothing separates — every rule lands within overlapping CIs. See RESULTS.md.

Develop

uv run pytest -q
uv run ruff check .
uv run mypy --strict src
# exercise the tools in the MCP Inspector:
npx @modelcontextprotocol/inspector uv run python -m voting_mcp.server

Note: if you keep this repo under an iCloud-synced folder (e.g. ~/Desktop), iCloud can spawn duplicate .pth files that intermittently break the editable install. Tests use pythonpath=src; run the server with PYTHONPATH=src if an import fails, or move the repo off the synced folder.

License

MIT

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

voting_mcp-0.1.0.tar.gz (205.6 kB view details)

Uploaded Source

Built Distribution

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

voting_mcp-0.1.0-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file voting_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: voting_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 205.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voting_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6b42d14bff8f87d30cf8f10ddeac24365811474e7dc2031e4831d7424fcce504
MD5 b5962519f87d3fbd3fa62254a7f61799
BLAKE2b-256 3eb59f91741ac3d2c94dfff1e086241e5cd4d55e81b7fc30b53cdf321764d5f1

See more details on using hashes here.

File details

Details for the file voting_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: voting_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voting_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 52e987abaef1e75032f55f71288f6267816361908485085eb7cb4158dad44686
MD5 3e0c40d345c0afb21c4aa54747610551
BLAKE2b-256 d8c6329cc081f765962344743d6a4384a75565da49abe7533d4f24f90b4d6c31

See more details on using hashes here.

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