Metadata-Version: 2.5
Name: puntersedge-mcp
Version: 0.4.2
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

<!-- mcp-name: io.github.Propertyscout001/puntersedge -->
# 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 (3,000 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 races with runners and live per-bookmaker prices — every country by default, pass `country=AU` for an Australian card |
| `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 `horse_ref` on every thoroughbred runner |
| `racing_results_coverage` | 1 cr | What results coverage measurably is |
| `racing_price_history` | 5 cr | Recorded per-book price ticks for one race |
| `racing_markets` | 2 cr | Every market on one race: win, place, Same Race Multi Top 2/3/4, and the exotic dividends once it has run (Plus and above) |
| `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_clv` | 5 cr | Score up to 200 bets against the closing line: per-bet CLV and P&L, plus the averages, beat rate and ROI (Plus and above) |

**Racing — form and premierships**

| Tool | Cost | What it returns |
|---|---|---|
| `horse_form` | 3 cr | Thoroughbred form, career record and profile for one horse (Racing Australia's page) |
| `horse_runs` | 3 cr | One horse's runs in our own settled results since 1 Sep 2026, with a starts/wins/places summary |
| `greyhound_form` | 3 cr | Form history for one dog |
| `greyhound_stats` | 3 cr | One dog's record by track, distance or box |
| `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 |
| `check_overage` | free | Whether calls may run past the allowance, the rate, the ceiling and this period's estimate |
| `bulk_datasets` | free | The catalogue of pre-built dataset files (rows, bytes, digest, URL) |
| `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.
- **Join one thoroughbred across meetings on `horse_ref`, not `runner_ref`.** Every
  thoroughbred runner and placing in results, every live-board runner, every archive row
  and every form response carries `horse_ref` (`pe:<name>`), and it is the same value on
  all of them. `runner_ref` (`ra:<code>`) is the code Racing Australia published on race
  day: it joins the rows about one race (results, the closing-line, price-path and markets
  archives, the live board on race day), and it changes from meeting to meeting because
  Racing Australia issues a new code for the same horse each day. Greyhound refs
  (`grv:`/`grsa:`) are per-dog registry ids and do persist.
- **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. When
  the answer is a 503 saying Racing Australia is refusing fresh reads, `horse_runs` is our
  own record of the horse and does not depend on that page.
- **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/Propertyscout001/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.4.1** — Listed on the official MCP Registry as `io.github.Propertyscout001/puntersedge`; the
README carries the registry's ownership line. No tool changes.

**0.4.0** — New `racing_markets` tool: every market on one race — win, place, Same Race Multi
Top 2/3/4, and the exotic dividends once it has run (2 credits, Plus and above). 31 tools.
`racing_next_to_go` serves every country, and the free tier is 3,000 credits. On a
thoroughbred, `runner_ref` is the code Racing Australia published on race day and joins the
rows about one race; Racing Australia issues a new code for the same horse each day, so
`horse_ref` is the key across meetings.

**0.3.0** — `runner_ref` on thoroughbreds is not a horse identity, and this package said it
was. It is Racing Australia's code for that day's entry and changes from meeting to meeting;
`horse_ref` (`pe:<name>`), now on every thoroughbred surface, is the join key. Tool texts,
the server instructions and the notes above say so. New `horse_runs` tool: one horse's runs
in our own settled results (3 credits), the fallback when Racing Australia refuses a fresh
form read. Also new: `racing_clv` (score up to 200 bets against the closing line, 5 credits,
Plus and above — the server's first POST tool), `check_overage` and `bulk_datasets` (both
free). `greyhound_stats` no longer claims a grade dimension.

**0.2.1** — The "Rules for coding agents" link named `github.com/puntersedge/puntersedge-examples`.
That account exists and belongs to someone else; ours is `Propertyscout001`. Documentation only.

**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.
