Skip to main content
Glama
WhitGai

CourtVision Rules MCP

by WhitGai
README.md
# CourtVision Rules MCP

![CI](https://github.com/WhitGai/courtvision-rules-mcp/actions/workflows/ci.yml/badge.svg) ![License: MIT](https://img.shields.io/badge/License-MIT-green.svg) ![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)

A [Model Context Protocol](https://modelcontextprotocol.io) server that turns the
official pickleball scoring rules into callable tools, so an LLM tracks a live
match without ever improvising the rules.

Built as part of [CourtVision AI](https://github.com/WhitGai) — an AI
pickleball companion — this is the protocol-layer piece: the small, provably
correct service the model leans on so the conversation stays accurate.

---

## Preview

The same seven rallies, scored four ways — doubles/singles × side-out/rally. One engine, all correct, all unit-tested.

![CourtVision scoring across four modes](docs/courtvision-demo.gif)

## Why it exists

Pickleball's side-out scoring is exactly the kind of thing language models get
*almost* right. The one-server rule at the start, the server-1-to-server-2
rotation, win-by-two — a model that holds these in its head drifts after a few
turns. The fix is not a better prompt. It is moving the part that must be exact
out of the model and into a tool.

That is the entire MCP thesis in one server, and it doubles as a design rule
this product was built on: **accuracy is the brand.** A wrong call in front of
four players ends the app's life with that crew. So the engine never guesses —
ambiguous input is rejected with an actionable message rather than resolved
silently.

## What it does

Six tools, exposed over MCP:

| Tool | What it does |
|---|---|
| `pickleball_new_match` | Start a doubles match; returns a `match_id` and the opening `0-0-2` call |
| `pickleball_record_rally` | Apply a rally result (`server_won` / `server_lost`) with full side-out logic |
| `pickleball_record_fault` | Record a fault by either team and apply the right transition |
| `pickleball_get_score` | Return the current state and three-number score call |
| `pickleball_undo` | Revert the last rally or fault |
| `pickleball_explain_rule` | Plain-language summary of common rules topics |

State is held server-side, keyed by `match_id`, so a client runs a whole match
across many turns without re-sending the board.

## Architecture

The rules logic and the transport are deliberately separate:

```
pickleball_engine.py   pure rules engine, zero dependencies, unit-tested
                       (doubles + singles, side-out + rally scoring)
server.py              thin MCP wrapper: input validation + state registry
tests/                 the rules engine's safety net (26 cases)
evaluation/            MCP eval questions an agent must answer using the tools
```

The engine is the load-bearing part, so it lives on its own where it can be
tested in isolation and reused by any surface — the MCP server today, the Match
Mode web app and native clients later.

## Run it

```bash
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]"

# run the server (stdio transport)
python server.py

# or inspect it interactively
npx @modelcontextprotocol/inspector python server.py
```

### Use it from Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "courtvision-rules": {
      "command": "python",
      "args": ["/absolute/path/to/courtvision-rules-mcp/server.py"]
    }
  }
}
```

Then ask Claude: *"Start a pickleball match between the Reds and the Blues. The
Reds win the first rally, then lose the next one — what's the score?"* It will
call the tools and answer `0 1 1`, correctly, every time.

## Tests

```bash
pip install pytest
python -m pytest tests/ -q
```

The tricky cases are pinned: the one-server match start, the
server-1 → server-2 → side-out rotation, win-by-two, and that only the serving
team can score.

## Notes

Rules summaries defer to the current
[USA Pickleball Official Rulebook](https://usapickleball.org/) — this server
computes scoring and explains the common cases; it does not reproduce the
rulebook.

### Formats

Set `match_format` and `scoring` when you start a match:

| `match_format` | `scoring` | Call | Rule |
|---|---|---|---|
| `doubles` | `sideout` | `7 4 2` | Traditional two-server side-out with the one-server opening (the default). |
| `singles` | `sideout` | `7 4` | One server per side; every lost rally is an immediate side-out. Serve court follows score parity. |
| `doubles` | `rally` | `7 4` | A point on every rally; lose your serve and the other side scores **and** takes serve. |
| `singles` | `rally` | `7 4` | Same rally logic, one server per side. |

Doubles side-out remains the default, so existing callers are unaffected. Rally
games are commonly played to 15 or 21, win by 2 — pass `target`/`win_by` to set them.

## License

MIT — see [LICENSE](LICENSE).

## Run as a service (Docker)

```bash
docker build -t courtvision-rules-mcp .
docker run -i --rm courtvision-rules-mcp        # stdio transport
```

The server speaks MCP over stdio by default (what Claude Desktop and local MCP
clients use). For a hosted deployment, set `MCP_TRANSPORT=sse` to serve over
HTTP/SSE. Console script after `pip install -e .`: `courtvision-rules-mcp`.