Metadata-Version: 2.5
Name: puntersedge-mcp
Version: 0.2.0
Summary: Official MCP server for the PuntersEdge odds API — Australian and New Zealand racing odds and Australian sports odds for AI agents: live per-bookmaker prices, the change feed, results, form, premierships, the closing-line archive and feed health, each tool stating its credit cost.
Project-URL: Homepage, https://puntersedge.online/api
Project-URL: Documentation, https://puntersedge.online/developers
Project-URL: Get an API key, https://puntersedge.online/api#signup
Author-email: PuntersEdge <hello@puntersedge.online>
License: MIT
Keywords: ai-agents,australia,mcp,new-zealand,odds,racing,sports-betting
Requires-Python: >=3.10
Requires-Dist: mcp>=2.0.0
Description-Content-Type: text/markdown

# PuntersEdge MCP Server

Official [MCP](https://modelcontextprotocol.io) server for the
[PuntersEdge odds API](https://puntersedge.online/api) — Australian and New Zealand
racing odds (AU thoroughbred, greyhound and harness; NZ thoroughbred and harness) and
Australian sports odds: live per-bookmaker prices, the change feed, best-price
comparison, full-field results, form, premiership tables, the closing-line archive and
feed health, for AI agents. Bookmaker coverage is measured live at
[puntersedge.online/api/coverage](https://puntersedge.online/api/coverage) rather than
fixed here.

## Install

```bash
pip install puntersedge-mcp
```

## Configure

Get a free API key (1,500 credits/month, no card) at
[puntersedge.online/api](https://puntersedge.online/api#signup), then add it to your MCP
client config (Claude Desktop, Claude Code, Cursor, or any MCP host):

```json
{
  "mcpServers": {
    "puntersedge": {
      "command": "puntersedge-mcp",
      "env": { "PUNTERSEDGE_API_KEY": "your-key-here" }
    }
  }
}
```

Claude Code, in one line:

```bash
claude mcp add puntersedge --env PUNTERSEDGE_API_KEY=your-key-here -- puntersedge-mcp
```

## Tools

Read only. Nothing here mutates — no key rotation, no billing, no webhook creation. An
agent should not be able to rotate its own credentials, change what is billed, or
register a URL that will then receive data. Those endpoints exist and are documented;
they are reached deliberately, from application code, not from a chat turn.

**Racing — live**

| Tool | Cost | What it returns |
|---|---|---|
| `racing_next_to_go` | 2 cr | Next AU/NZ races with runners and live per-bookmaker prices |
| `racing_best_odds` | 3 cr | Best win/place/tote per runner, with the book offering it and the market percentage |
| `racing_movers` | 3 cr | Consensus firmers and drifters right now |
| `racing_changes` | 2 cr | Only what moved since a timestamp — **the right way to watch a market** |
| `racing_events` | 1 cr | The forward card without prices |
| `racing_track_conditions` | 1 cr | Going, weather and rail, plus the day's change log |
| `racing_acceptances` | 2 cr | Full-day AU thoroughbred acceptance card |
| `racing_venues` | 1 cr | Canonical venue directory — `venue_id` for joining across endpoints |

**Racing — results and history**

| Tool | Cost | What it returns |
|---|---|---|
| `racing_results` | 2 cr | Settled results: dividends **and** the full finishing order with stable registry ids |
| `racing_results_coverage` | 1 cr | What results coverage measurably is |
| `racing_price_history` | 5 cr | Recorded per-book price ticks for one race |
| `racing_closing_lines` | 5 cr | The permanent closing-line archive (Standard plan and above) |
| `racing_closing_lines_coverage` | 1 cr | What the archive actually holds |

**Racing — form and premierships**

| Tool | Cost | What it returns |
|---|---|---|
| `horse_form` | 3 cr | Thoroughbred form, career record and profile for one horse |
| `greyhound_form` | 3 cr | Form history for one dog |
| `greyhound_stats` | 3 cr | One dog's record by track, distance, box or grade |
| `jockey_stats` | 2 cr | Jockey premiership tables, or one jockey by name |
| `trainer_stats` | 2 cr | Trainer premiership tables, or one trainer by name |

**Sports**

| Tool | Cost | What it returns |
|---|---|---|
| `list_sports` | 1 cr | The sport catalogue |
| `sports_odds` | **1 cr per market** | Per-bookmaker prices for a sport's fixtures |
| `best_odds` | 3 cr | Best price per outcome for one sport |
| `arb_best_prices` | 2 cr | Cross-book comparison per selection |

**Account, health, sandbox**

| Tool | Cost | What it returns |
|---|---|---|
| `check_usage` | free | Plan, allowance, credits used, reset date |
| `connector_health` | free | Per-bookmaker feed freshness — a quiet market vs a broken feed |
| `demo_next_to_go` | free | No-key sandbox sample, to explore the shape |
| `demo_best_odds` | free | No-key sandbox sports sample |

Every tool response includes `credits_remaining`, and `credits_warning` once you cross
the threshold, so an agent can budget without spending a call on `check_usage`. Costs are
enforced server-side; a 402 is a hard stop, never overage billing.

`sports_odds` is billed per market requested — `markets="h2h,spreads,totals"` is three
credits, not one — so it has no single figure and the tool description says so rather
than naming one that is right only for the default.

## Notes for agents

- **Racing is not a sport key.** There is no `sport_key` of `horse-racing`. Racing is
  under the `racing_*` tools; categories are horse, greyhound and harness. `country`
  filters by ISO code (`AU`, `NZ` or `AU,NZ`); omit it to get every meeting. New Zealand
  is thoroughbred and harness only — there are no NZ greyhounds in the feed.
- **Do not poll `racing_next_to_go`.** It re-downloads every race every time and bills 2
  credits for it. `racing_changes` returns only what moved and hands back the cursor for
  the next call. The cursor sits 30 seconds behind the server clock so nothing in flight
  is skipped, which makes the feed at-least-once — expect to see a race twice.
- **A stale price looks exactly like a live one.** A stalled scraper does not error; it
  keeps serving its last value. Every racing quote carries `age_seconds` and `stale`, and
  `connector_health` reports `last_ok` per bookmaker. Check it before telling someone a
  market has settled.
- **Prices are decimal odds** in AUD markets. Every sports market carries a `quality`
  object; trust it over assumptions.
- **Settle only against `status: "final"`.** Interim placings can change on protest.
- **`runner_ref`** in results is a stable registry identifier that persists across
  meetings — use it, not the name, for longitudinal joins.
- **Do not loop `horse_form`** over a field or a list. The upstream rate-limits hard. For
  many horses there is a paced bulk backfill queue, reached from application code.
- **On `racing_acceptances`, several counts are a floor, not an exact figure**, wherever a
  race reports `runner_set_status` other than `checks_passed`. The response's
  `completeness` block states the bound. Say "at least N" rather than reporting a field
  size.
- **One book quoting both legs is not an arb.** A large apparent edge on a two-outcome
  market that only one bookmaker is pricing is a data artefact.

## Also available

- **Rules for coding agents** — [`AGENTS.md`](https://github.com/puntersedge/puntersedge-examples/blob/main/agents/AGENTS.md),
  a drop-in instructions file so Claude Code, Cursor or Copilot write correct PuntersEdge
  code. The MCP server lets an agent *read* live odds; that file makes it *write* better
  clients.
- **SDKs** — `npm install puntersedge` (TypeScript/JavaScript),
  `pip install puntersedge` (Python).
- **Schema** — <https://api.puntersedge.online/openapi.json>.

## Development

```bash
python3 tests/check_contract.py .        # checks every tool against the live OpenAPI document
```

That test drives each tool through a stub and verifies three things the runtime will not
tell you about: that the path exists, that every query parameter is one the endpoint
actually declares, and that the credit cost in the docstring matches what the API
charges. It is the test that catches a tool quietly sending a filter the server ignores.

## Changelog

**0.2.0** — 16 new tools (change feed, events, track conditions, acceptances, venues,
results coverage, closing lines and coverage, horse and greyhound form, greyhound stats,
jockey and trainer premierships, cross-book comparison, connector health, sports sandbox).
Corrected three understated credit costs: `best_odds` said 2 and is 3,
`racing_price_history` said 3 and is 5, `sports_odds` said 2 and is one per market.
Removed `num_races` from `racing_movers` — that endpoint has no such parameter, so the
filter had silently done nothing since 0.1.0; use `limit`. Status-specific error hints,
and a contract test to stop all of that recurring.

**0.1.1** — Package text says Australian *and New Zealand* racing, with no fixed book
count.

**0.1.0** — First release.
