Skip to main content

clubmanager365cli

PyPI version Python versions License

A command-line tool to log in to clubmanager365.com and book courts from the terminal — and an MCP server (local stdio or remote HTTP) exposing the same actions so AI agents can book courts for you.

Personal automation for your own account. Use responsibly and within your club's terms of use.

Booking rules

Each club configures its own booking rules, so your club may differ. By default this tool assumes the same rules as the club it was developed against:

  • Every booking requires at least one opponent (book --with <name|id>).
  • One slot per person per day — booking a day you already have fails.
  • Booking needs no payment at the time of booking (covered by membership / court credits), so there's no checkout step.

Install

The quickest way — run the CLI without installing anything, via uv (it fetches the package on demand):

uvx --from clubmanager365cli cm365 --help

Or install it as a persistent global cm365 command with pipx:

pipx install clubmanager365cli
cm365 --help

Or from source, for development (Python 3.10+):

git clone https://github.com/jerry-shao/clubmanager365cli
cd clubmanager365cli
uv venv --python 3.12 && uv pip install -e ".[mcp]"
# or: python3 -m venv .venv && .venv/bin/pip install -e ".[mcp]"

Credentials

Provide your credentials (kept local, git-ignored):

cp credentials.env.example credentials.env

Then open credentials.env in an editor and fill in your username and password.

Alternatively, export them as environment variables (quote both values — usernames and passwords can contain spaces):

export CM365_USERNAME="your username"
export CM365_PASSWORD="your password"

If your club's match type differs from the default (4 = Friendly), set CM365_MATCH_TYPE too — run cm365 match-types to find your club's ids. This applies to both the CLI and the MCP server.

Usage

The examples below assume cm365 is on your PATH (pipx or source install). If you use uvx, prefix each command with uvx --from clubmanager365cli.

cm365 login                       # verify your credentials work
cm365 mybookings                  # list your upcoming bookings
cm365 slots tomorrow -a           # free slots tomorrow
cm365 slots 2026-07-04 -t 18:00   # all courts at 18:00 on a date
cm365 slots today --type indoor   # restrict to indoor courts
cm365 players "pat smith"        # find an opponent's id by name (quote names with spaces)
cm365 match-types                # list your club's match-type ids (e.g. Friendly)

# Booking requires an opponent and is a dry run unless you pass --yes:
cm365 book tomorrow 18:00 --with "Pat Smith"             # dry run (finds slot)
cm365 book tomorrow 18:00 -c "Indoor Court 1" --with "Pat Smith" --yes
cm365 book tomorrow 18:00 --with 100001 --with 100002 --yes   # doubles, by id

cm365 cancel 12345678 --yes       # cancel a booking by id (from mybookings)

Dates accept today, tomorrow, YYYY-MM-DD, or 27 Jun 2026. Times accept 18, 18:00, or 6pm. Opponents accept names (fuzzy) or ids.

Diagnostics

cm365 whoami             # post-login landing page + nav links
cm365 explore [PATH] -o page.local.html   # dump a page's HTML/forms/links

MCP server

The same actions are exposed as an MCP server so an AI assistant (Claude Desktop, Codex, OpenClaw, …) can book courts for you. By default it runs locally over stdio with your own credentials — no shared/hosted server, so your login never leaves your machine. It can also run as a remote HTTP server with per-request credentials.

Tools: list_slots, my_bookings, search_players, list_match_types, book_court, cancel_booking. book_court and cancel_booking are a dry run unless you pass confirm: true, so the assistant can't book or cancel without an explicit confirmation step.

Run it

No manual install needed — uv runs it (and provisions a suitable Python; the MCP SDK needs 3.10+):

uvx --from "clubmanager365cli[mcp]" clubmanager365-mcp

Connect a client

All clients need the same three things: command: uvx, the args below, and your credentials in env. A few examples; other MCP clients follow the same pattern.

OpenClaw — add with the CLI:

openclaw mcp add clubmanager365 \
  --command uvx \
  --arg --from --arg "clubmanager365cli[mcp]" --arg clubmanager365-mcp \
  --env CM365_USERNAME=your-username \
  --env CM365_PASSWORD=your-password

or edit ~/.openclaw/openclaw.json directly (servers live under mcp.servers):

{
  "mcp": {
    "servers": {
      "clubmanager365": {
        "command": "uvx",
        "args": ["--from", "clubmanager365cli[mcp]", "clubmanager365-mcp"],
        "env": {
          "CM365_USERNAME": "your-username",
          "CM365_PASSWORD": "your-password"
        }
      }
    }
  }
}

Verify with openclaw mcp doctor clubmanager365 --probe.

Hermes Agent — add to ~/.hermes/hermes-agent/config.yaml (top-level key mcp_servers), then run /reload-mcp:

mcp_servers:
  clubmanager365:
    command: "uvx"
    args: ["--from", "clubmanager365cli[mcp]", "clubmanager365-mcp"]
    env:
      CM365_USERNAME: "your-username"
      CM365_PASSWORD: "your-password"

Claude Desktop — add to claude_desktop_config.json:

{
  "mcpServers": {
    "clubmanager365": {
      "command": "uvx",
      "args": ["--from", "clubmanager365cli[mcp]", "clubmanager365-mcp"],
      "env": {
        "CM365_USERNAME": "your-username",
        "CM365_PASSWORD": "your-password"
      }
    }
  }
}

Codex CLI — add to ~/.codex/config.toml:

[mcp_servers.clubmanager365]
command = "uvx"
args = ["--from", "clubmanager365cli[mcp]", "clubmanager365-mcp"]
env = { CM365_USERNAME = "your-username", CM365_PASSWORD = "your-password" }

Remote HTTP mode

Set CM365_MCP_TRANSPORT=http and the server speaks streamable HTTP instead of stdio:

CM365_MCP_TRANSPORT=http uvx --from "clubmanager365cli[mcp]" clubmanager365-mcp
# serves http://127.0.0.1:8000/mcp  (CM365_MCP_HOST / CM365_MCP_PORT to change)

In HTTP mode each request must carry the caller's own credentials in headers — environment credentials are deliberately ignored (set CM365_HTTP_ENV_FALLBACK=1 to opt back in for a private single-user server):

Header Meaning
x-cm365-username clubmanager365 username (required)
x-cm365-password clubmanager365 password (required)
x-cm365-base-url optional, defaults to https://clubmanager365.com

The SDK's DNS-rebinding Host check is disabled in HTTP mode (a tunnel's public hostname isn't known in advance, and sensitive calls need credentials anyway). To lock the server to specific hostnames, set CM365_MCP_ALLOWED_HOSTS=mcp.example.com (comma-separated).

Credentials are used per-request to log in and are never stored server-side.

To expose it publicly without opening ports, use a Cloudflare Tunnel:

cloudflared tunnel --url http://127.0.0.1:8000   # quick tunnel for testing

Deploying to a serverless container platform

The included Dockerfile runs the server in HTTP mode with CM365_MCP_STATELESS=1 (self-contained requests — required when instances scale to zero) and honours the platform's injected PORT. No credentials go into the image; they arrive per-request via headers. E.g. Google Cloud Run:

gcloud run deploy cm365-mcp --source . --region europe-west2 --allow-unauthenticated

The resulting https://….run.app/mcp URL is stable — point Smithery at it.

Publishing to Smithery

The header scheme matches Smithery session config, so the server can be published to Smithery as a remote server where each user fills in their own credentials. Point it at your public …/mcp URL — a Cloud Run service or a tunnel host both work:

smithery mcp publish "https://your-server-host/mcp" --config-schema '{
  "type": "object",
  "required": ["username", "password"],
  "properties": {
    "username": {"type": "string", "title": "clubmanager365 username",
                 "x-from": {"header": "x-cm365-username"}},
    "password": {"type": "string", "format": "password",
                 "title": "clubmanager365 password",
                 "x-from": {"header": "x-cm365-password"}}
  }
}'

The booking API

All booking actions go through /Club/ActionHandler.ashx, with the request object serialised as a JSON string carried on the request, matching the calls the site's own front-end makes:

/Club/ActionHandler.ashx?siteCallback=CourtCallback&action=GetCourtDay&_=<ts>&{"Date":"27 Jun 2026",...}

When a booking takes no payment at booking time (e.g. it's covered by membership or court credits), booking is a single MakeBooking call — no preliminary hold and no BookingPlayerID are needed. GetCourtDay lists the slots (each cell carries a CourtSlotID; each column a CourtID), and MakeBooking takes CourtsRequired: [{c: CourtID, s: CourtSlotID}] plus OpponentPlayerIDs, SelectedMatchType, MatchDate, etc. Clubs that take payment instead go through SaveNewPreliminaryBooking (a short-lived hold) before confirming — that path isn't exercised here. See cm365/bookings.py.

How login works

The site is ASP.NET WebForms. Logging in is a "postback" on the homepage:

  1. GET /Homepage.aspx → session cookie + hidden __VIEWSTATE, __VIEWSTATEGENERATOR, __EVENTVALIDATION.
  2. POST /Homepage.aspx echoing those hidden fields plus …UserLogin$UserName, …UserLogin$Password, …UserLogin$LoginSubmitButton.
  3. The <asp:LoginView> widget swaps to its authenticated template; cookies now carry the session.

See cm365/client.py.

Metadata

Release files for clubmanager365cli 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clubmanager365cli 0.2.0
File Size Uploaded Provenance
clubmanager365cli-0.2.0.tar.gz 23.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for clubmanager365cli 0.2.0
File Interpreter ABI Platform
clubmanager365cli-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 49.7 kB

Release files / clubmanager365cli-0.2.0.tar.gz

Download URL clubmanager365cli-0.2.0.tar.gz
Size 23.7 kB
Tags Source
SHA-256 checksum
How to use checksums
f401ce54bd7c51ae0749556c64e2bcb3104f7a07a7075317ad149b943135c4f8
BLAKE2b-256 checksum
How to use checksums
5f6140d07300064180a3aa0383ffd3b1254615b9f3f1131486632ed98d1b5d0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.16

Release files / clubmanager365cli-0.2.0-py3-none-any.whl

Download URL clubmanager365cli-0.2.0-py3-none-any.whl
Size 26.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7ea73fd254915fd11d1db9a7345d11c60ce4d545c2a71efccf5b17e5f5e392f5
BLAKE2b-256 checksum
How to use checksums
4a2f3405c74756a587e6f849233c442518951ab2cfa98aa482b36aa5118899d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.16

Security for clubmanager365cli 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page