Skip to content
parley.

Guide

Run Parley as an MCP tool, on your own machine.

The base install ships a stdio MCP server. Any MCP host, including Claude Desktop, Claude Code and Cursor, can launch it as a local process and get three tools: decide among parties with conflicting interests, verify the receipt, and replay one party's red lines against the outcome. No API key, no hosted service, no third-party dependencies. This page is the setup, one checked call, and the limits.

Commands and values here were checked against version 0.2.0 from PyPI; the server is parley/mcp.py in the source repository. Companion guides: fair multi-agent negotiation, red lines for LLM agents, verifying a decision receipt and a fair group decision with private limits.

1. Install

The distribution is parley-consensus; the import name is parley. The server needs nothing beyond the base package, so the console script parley-mcp is part of the plain install.

pip install parley-consensus

The same server is also registered under the distribution name, so a host that runs Python tools through uv can start it without a separate install step:

uvx parley-consensus

2. Point your host at it

Add one stdio server entry to the host's MCP configuration. This is the entry from the README:

{"mcpServers": {"parley": {"command": "parley-mcp"}}}

If the console script is not on the host's PATH, launch it through the interpreter that has the package:

{"mcpServers": {"parley": {"command": "python", "args": ["-m", "parley.mcp"]}}}

Or let uv fetch and run it:

{"mcpServers": {"parley": {"command": "uvx", "args": ["parley-consensus"]}}}

Once connected, three tools appear in the host's tool list. The README's example prompt is a venue decision: "Three of us are picking a venue: rooftop (900), garden (600), diner (300). Bob will not go over 700; Ana prefers the rooftop, Cara the garden, Bob the cheapest. Use parley_decide, then verify the receipt hash and check that Bob's red line held."

3. Check it without a host

The wire format is one JSON-RPC 2.0 message per line, so a shell pipe is a complete client. Send an initialize request and the server answers with its protocol version and name, then exits when stdin closes. Logs go to stderr; stdout carries replies only.

echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}}' | parley-mcp
{"jsonrpc": "2.0", "id": 0, "result": {"protocolVersion": "2025-06-18", "capabilities": {"tools": {}}, "serverInfo": {"name": "parley", "version": "0.2.0"}}}

The three tools

The input is the same JSON as the recipes underexamples/demo/ in the repository. A red line is an object with attr, op (one of ==, !=, <, <=, >, >=, in, not_in) and value; a missing attribute fails closed. A soft term is categorical (prefer) or numeric (directionwith lo and hi), and a party's score is the weighted average of its terms.

ToolTakesReturns
parley_decidespec: the shared options plus every party's red lines (hard) and soft utility terms. Optional rule, where only "egalitarian" exists.status ("agreed" or "deadlock"), decision (the winning option object or null), transcript (every option with each party's masked verdict), transcript_sha256.
parley_verify_receipttranscript and sha256 from a previous parley_decide call. Optional expected_owners, the roster you know took part.match (the transcript hashes to the sha256 you passed), max_min_verified (the announced decision is the max-min option over the recorded verdicts), expected_sha256, recomputed_sha256.
parley_check_partyparty: one party object, the same shape as in the spec. decision: the decision from parley_decide, or null for a deadlock.owner and holds. holds is true when every red line of that party holds on the decision; soft preferences are not judged.

One decision, start to finish

The venue decision as a parley_decide spec. Bob's budget is a red line, not a preference; the other two parties only have soft terms.

{
  "title": "Pick a venue",
  "options": [
    {"venue": "rooftop", "cost": 900},
    {"venue": "garden",  "cost": 600},
    {"venue": "diner",   "cost": 300}
  ],
  "parties": [
    {"owner": "Ana",  "utility": [{"attr": "venue", "prefer": "rooftop"}]},
    {"owner": "Bob",  "hard": [{"attr": "cost", "op": "<=", "value": 700}],
                      "utility": [{"attr": "cost", "direction": "lower", "lo": 0, "hi": 1000}]},
    {"owner": "Cara", "utility": [{"attr": "venue", "prefer": "garden"}]}
  ]
}

What came back, with the transcript shortened to the rooftop entry and scores rounded to one decimal:

status:            agreed
decision:          {"cost": 600, "venue": "garden"}
transcript_sha256: 8904d3475702417dfea9faa29cba1a8fb3f9fd001037d6b371717be2757385a1

transcript.entries[0]:
  option: {"cost": 900, "venue": "rooftop"}
  Ana   acceptable=true   reason="ok"        score=1.0
  Bob   acceptable=false  reason="red-line"  score=0.1
  Cara  acceptable=true   reason="ok"        score=0.0

The rooftop is Ana's favourite and loses anyway: it crosses Bob's budget line, so it is rejected before any score is weighed. The verdict saysred-line, not which line. Then the two checks, passing the transcript and hash straight back, and Bob's party object with the decision:

parley_verify_receipt(transcript, sha256, expected_owners=["Ana","Bob","Cara"])
  {"match": true, "max_min_verified": true, "expected_sha256": "8904d3...85a1", "recomputed_sha256": "8904d3...85a1"}

parley_check_party(party=Bob, decision={"cost": 600, "venue": "garden"})
  {"owner": "Bob", "holds": true}

parley_check_party(party=Bob, decision={"cost": 900, "venue": "rooftop"})
  {"owner": "Bob", "holds": false}

What this mode guarantees, and what it does not

  • No privacy from the host. The host holds every party's spec and passes them all in one call. Each tool description carries this note so the model reads it too. For private sheets, run one process per owner over HTTP (examples/run_env.py); the coordinator then sees only masked verdicts.
  • The red line still holds in code. An option that crosses any party's red line cannot win, whatever its scores. No option acceptable to everyone yields status: "deadlock"with decision: null, never a forced choice.
  • The hash is unsigned and not stored.transcript_sha256 detects a later edit only when someone other than the editor kept the hash. The server keeps nothing between calls. max_min_verified is the independent check: it recomputes the winner from the recorded verdicts.
  • Verdicts are unsigned in this mode. Whoever can rewrite the record can rewrite the verdicts to fit a swapped winner. Ed25519-signed verdicts exist in the HTTP layer as an optional extra, not here.

Limits, as set in the server

Transportstdio, newline-delimited JSON-RPC 2.0, at most 1 MB per line
Protocol versions2025-06-18 (default) and 2024-11-05
Options per call500
Parties per call50
Option times party evaluations per call10,000
Values in one in / not_in constraint1,000
Selection ruleegalitarian (max-min) only
Dependenciesnone; the base install is standard-library Python

A spec over a limit is refused before it runs, with a tool error naming the limit. The bounds exist because a spec is evaluated options times parties times, and every constraint list is scanned per option.

Questions

Do I need an API key or a hosted service?

No. parley-mcp is a local process that reads JSON-RPC from stdin and writes replies to stdout. It makes no network calls, needs no key, and the base install has no third-party dependencies. The host launches it and talks to it over a pipe.

Is my party's preference sheet private from the host?

No, and the tool description says so. In MCP mode the host holds every party's spec and passes them all in one parley_decide call, so there is no privacy between parties or from the host. What still holds is what the engine enforces in code: a red line rejects an option deterministically, the max-min rule picks among options feasible for everyone, and the transcript hash makes later edits visible. For private sheets, run one process per owner over HTTP with examples/run_env.py.

What does transcript_sha256 prove?

Only that a transcript hashes to that value. The hash is an unsigned SHA-256 over the canonical transcript and the server does not store it. If the transcript and the hash come from the same hand, match=true proves nothing, because whoever edits the record can re-hash it. It detects tampering when the hash was kept by someone other than whoever could edit the transcript. max_min_verified does not depend on the hash: it recomputes the winner from the recorded verdicts.

What happens when the spec is too large or malformed?

The server refuses before running. A spec over 500 options, 50 parties, 10,000 option times party evaluations, or a constraint list over 1,000 values is rejected with a tool error naming the limit. A malformed spec or transcript is also a tool error with the exception text, not a silent mismatch. A line over 1 MB gets a parse error and the session continues.

See a pre-computed run first, then wire the engine into your host.

The demo replays pre-computed runs of the same engine, no signup. The repository is Apache-2.0, andparley/mcp.py is the whole server.

Back to the overview · Next: how the whole negotiation stays fair · Then: verify the receipt afterwards · Worked case: a group decision with private limits