Skip to main content
Glama
sanjibani

playmetrics_mcp

by sanjibani
README.md
# playmetrics_mcp

First MCP server for the **PlayMetrics** youth-sports club management platform. Read-only access to clubs, teams, players, schedule, registrations, and payments - so Claude, Cursor, or any MCP client can answer questions about your club's day-to-day operations.

MIT licensed. Built on the [`mcp-vertical-template`](https://github.com/sanjibani/mcp-vertical-template) standards: shared `httpx.AsyncClient` with pooling + transport retries, typed exception hierarchy, `respx` tests, structlog logging, `py.typed` marker, `ruff` full-rule-set, `mypy --strict`, JSONL audit log per tool call.

## Why this exists

PlayMetrics is the dominant youth-sports club management platform in the US - purpose-built for the messy workflows of running a club, league, or tournament (registrations, rosters, schedules, payments, communications). The platform just acquired SportsEngine (the AYSO / Pop Warner stack), which makes it the de-facto default for thousands of clubs.

But PlayMetrics does not publish a first-party developer API. Club directors spend hours each week pulling rosters, schedules, and registration status by hand - exactly the kind of work an AI agent should be able to do.

This server reverse-engineers the same `api.playmetrics.com` backend the web app uses, exposes it as MCP tools, and ships the engineering discipline B2B procurement teams expect (audit logs, isError-compliance, retry with backoff, structured errors).

## Features

13 tools across 6 resource groups:

| Group | Tools |
|-------|-------|
| Diagnostic | `health_check`, `get_current_role` |
| Clubs | `list_clubs`, `get_club` |
| Teams | `list_teams`, `get_team` |
| Players | `list_players`, `get_player` |
| Schedule / events | `list_events`, `get_event`, `get_schedule`, `list_upcoming_practices` |
| Registrations & payments | `list_registrations`, `get_registration`, `list_payments` |

All tools:

- **Raise** on failure (FastMCP sets `isError=true` on the wire - agents can't get stuck retrying).
- Write one **JSONL audit record** per call (stderr by default, or `PLAYMETRICS_AUDIT_LOG=/path/to/audit.jsonl`).
- Map HTTP failures to a typed exception hierarchy (`PlaymetricsAuthError`, `PlaymetricsNotFoundError`, `PlaymetricsRateLimitError`, `PlaymetricsAPIError`, `PlaymetricsConnectionError`).
- Retry transient 5xx / 429 with exponential backoff + full jitter, honoring `Retry-After`.
- Use a shared `httpx.AsyncClient` with connection pooling and 3 transport retries.

## Install

```bash
git clone https://github.com/sanjibani/playmetrics_mcp
cd playmetrics_mcp
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
```

## Auth setup (3 minutes)

PlayMetrics auth is **session-based against `api.playmetrics.com`**, not API-key-based. You need three things from your web session:

1. **Email + password** - your PlayMetrics login.
2. **`role_id`** - a query parameter PlayMetrics uses to scope a user to one of their many possible contexts (a director might be admin of one club and parent of another player in a different club, each with its own `role_id`).

To find your `role_id`:

1. Log in to https://app.playmetrics.com in your browser.
2. Open DevTools → Network tab.
3. Reload the page or click around the Schedule tab.
4. Find any request to `api.playmetrics.com` (e.g. `/schedule` or `/events`).
5. Copy the `role_id` query parameter value from the request URL.

Then set:

```bash
export PLAYMETRICS_USERNAME='you@example.com'
export PLAYMETRICS_PASSWORD='your-password'
export PLAYMETRICS_ROLE_ID='the-role-id-from-the-url'
playmetrics_mcp
```

(Or supply `PLAYMETRICS_SESSION_TOKEN=...` if you've already minted one via the `/login` endpoint.)

## Example agent prompts

- *"List the teams I'm coaching this season."* → `list_teams`
- *"What's the schedule for team X next two weeks?"* → `list_events(start=today, end=today+14d, team_id=X)`
- *"When's our next practice?"* → `list_upcoming_practices(team_id=X, days=14)`
- *"Show me registrations still pending payment."* → `list_registrations(status=pending)`
- *"Pull today's deposits for the treasurer."* → `list_payments(status=paid, ...)` + filter client-side

## Architecture notes

### Reverse-engineered API surface

PlayMetrics does not publish an API reference. The endpoints here were observed in the web app's network traffic and follow the convention: plural nouns, `role_id` scoped, kebab-case for compound paths (e.g. `/events/upcoming`).

**If PlayMetrics renames an endpoint, point the corresponding method in `client.py` at the new path** - the rest of the stack (auth, retry, audit, typed exceptions) is unaffected. PRs welcome.

### Login flow

`PlaymetricsClient.__init__` does a synchronous `POST /login` (using the sync `httpx` client - no event loop yet) if no `PLAYMETRICS_SESSION_TOKEN` is present, then reuses the resulting bearer token for every async call that follows. The password is held in memory for re-login, but never logged (the audit redactor strips any field named `password` or `PLAYMETRICS_PASSWORD`).

### isError-compliance

Every tool body is wrapped so that any `PlaymetricsError` propagates out and FastMCP wraps it as a `ToolError` on the wire response - setting `isError=true` so the agent sees a real failure rather than an error-shaped string. The Blackwell Systems audit (54 MCPs / 20 bugs) and MCPTox benchmark both flagged the opposite pattern as the most common MCP bug class; this server does not regress.

## Development

```bash
pytest                     # run all tests (no live API)
pytest -m "not integration" # default - no live API
ruff check src tests       # lint
ruff format --check src tests
mypy src                   # type check (strict)
```

Tests use `respx` to intercept httpx calls - no live API traffic. The login flow is exercised in-process via respx mocks.

## Distribution

Distribution state file: `~/.mavis/state/vertical-mcp/dist/playmetrics-mcp.json`. Picked up daily by the `vertical-mcp-distribute` cron for auto-submission to awesome-mcp-servers, mcp.so, GitHub topics, etc.

## Sister MCPs in the same portfolio

- [`sanjibani/hawksoft-mcp`](https://github.com/sanjibani/hawksoft-mcp) - independent insurance agencies
- [`sanjibani/open-dental-mcp`](https://github.com/sanjibani/open-dental-mcp) - dental practices
- [`sanjibani/ezyvet-mcp`](https://github.com/sanjibani/ezyvet-mcp) - veterinary clinics
- [`sanjibani/jobber-mcp`](https://github.com/sanjibani/jobber-mcp) - home service businesses
- [`sanjibani/practicepanther-mcp`](https://github.com/sanjibani/practicepanther-mcp) - solo/small-firm legal
- [`sanjibani/realm-mp-mcp`](https://github.com/sanjibani/realm-mp-mcp) - church / nonprofit (Realm ACS, MinistryPlatform)
- [`sanjibani/fieldroutes-mcp`](https://github.com/sanjibani/fieldroutes-mcp) - pest control / lawn care
- [`sanjibani/cox-automotive-mcp`](https://github.com/sanjibani/cox-automotive-mcp) - auto dealerships
- [`sanjibani/qualia-mcp`](https://github.com/sanjibani/qualia-mcp) - title & escrow
- [`sanjibani/campspot-mcp`](https://github.com/sanjibani/campspot-mcp) - private campgrounds
- [`sanjibani/cleancloud-mcp`](https://github.com/sanjibani/cleancloud-mcp) - laundromat / dry cleaners
- **Custom MCP engagements**: https://sanjibani.github.io/mcp-services/

## License

MIT. See `LICENSE`. Author: Sanjibani Choudhury <schoudhury1991@gmail.com>.

## Disclaimer

This is an independent, community-built integration. It is **not** affiliated with, endorsed by, or supported by PlayMetrics, Inc. Use at your own risk - the underlying API is reverse-engineered and may change without notice.