odds-mcp
by jigonyoo
README.md
# odds-mcp
**An MCP server over odds data where every read names its evidence and
the one write is impossible without an approval token.**
[Model Context Protocol](https://modelcontextprotocol.io) is how an LLM
agent gets tools. The dangerous part is never the reads - it is the one
tool that *does* something. This server demonstrates the pattern that
makes side-effectful agent tools safe enough to exist: a risk envelope
checked before a human ever sees the request, a single-use approval
token issued by a separate step, a publish that cannot happen without
it, and an append-only audit log that records every attempt -
especially the refused ones.
Data comes from [odds-consensus](https://github.com/jigonyoo/odds-consensus):
5 books normalized into one schema, de-margined fair prices, and a
cross-book consensus view. This repo is the integration surface on top.
```
MCP client (Claude, any agent)
│ tools
▼
odds-mcp server ──── reads ────> store.py (consensus / fair price / outliers)
│ ▲
└──── the one write ────> gate.py propose → approve → publish
│ (envelope · token · audit)
└──────> out/audit_log.jsonl
```
## Tools
| Tool | Kind | Behavior |
|------|------|----------|
| `list_events` | read | events in the snapshot |
| `get_consensus` | read | books per market, price ranges, median fair odds, outliers |
| `get_fair_price` | read | one market's fair price — or an **explicit not-found with the valid alternatives**. Ambiguous line? It asks. Single book? "A single book is not a consensus." Never a guess. |
| `find_outliers` | read | books off the consensus median, with exact deviation % |
| `get_pipeline_health` | read | counts **plus every upstream devig refusal** — what the snapshot does *not* cover |
| `propose_boost` | write-intent | checked against the risk envelope (max % over fair, stake cap) *before* a human sees it; refusals carry the reason |
| `approve_proposal` | human gate | issues a **single-use** approval token (production: a trader's Telegram approval card; same gate logic) |
| `publish_boost` | write | requires proposal id + matching token; token dies on use; replay fails |
| `get_audit_trail` | read | the append-only log of every attempt and outcome |
## Run it
```bash
pip install -r requirements.txt
python -m pytest tests/ -q # 13 tests
PYTHONPATH=src python -m odds_mcp.server # stdio MCP server
```
Claude Desktop config:
```json
{"mcpServers": {"odds": {"command": "python", "args": ["-m", "odds_mcp.server"],
"cwd": "<this repo>", "env": {"PYTHONPATH": "src"}}}}
```
## What the tests pin down
- Publishing **without** approval: refused. Wrong token: refused.
Replaying a used token: refused. (Tokens are single-use and compared
constant-time.)
- A boost priced above the envelope (default: 5% over consensus fair)
is refused **at propose time**, with the arithmetic in the reason.
- `get_fair_price` on a market with two live lines demands an explicit
`line` instead of picking one; on a single-book market it refuses to
call one price a "consensus".
- Every attempt - including every refusal - lands in the audit log.
- All 9 tools are actually registered on the server (test asserts the
exact set).
## Why this shape
An agent that can only read is safe but useless; an agent that can
write is useful but dangerous. The resolution is not "trust the model" -
it is structure: **envelope, then human, then token, then audit.** The
model can draft; only the gate can act; and the log never forgets.
MIT licensed. Part of a set of small, honest data gates:
[jigonyoo.com](https://jigonyoo.com).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues