trackman-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trackman_mcp-0.1.0/.claude-plugin/marketplace.json +15 -0
- trackman_mcp-0.1.0/.claude-plugin/plugin.json +10 -0
- trackman_mcp-0.1.0/.env.example +17 -0
- trackman_mcp-0.1.0/.github/workflows/ci.yml +45 -0
- trackman_mcp-0.1.0/.github/workflows/publish.yml +55 -0
- trackman_mcp-0.1.0/.gitignore +38 -0
- trackman_mcp-0.1.0/.mcp.json +9 -0
- trackman_mcp-0.1.0/CLAUDE.md +249 -0
- trackman_mcp-0.1.0/LICENSE +21 -0
- trackman_mcp-0.1.0/PKG-INFO +186 -0
- trackman_mcp-0.1.0/PUBLISHING.md +71 -0
- trackman_mcp-0.1.0/README.md +155 -0
- trackman_mcp-0.1.0/docs/trackman-api.md +256 -0
- trackman_mcp-0.1.0/pyproject.toml +70 -0
- trackman_mcp-0.1.0/scripts/check-visualization.py +72 -0
- trackman_mcp-0.1.0/scripts/eval_analyzer.py +134 -0
- trackman_mcp-0.1.0/scripts/install-refresh-schedule.sh +114 -0
- trackman_mcp-0.1.0/scripts/refresh-token.sh +28 -0
- trackman_mcp-0.1.0/scripts/validate.py +149 -0
- trackman_mcp-0.1.0/scripts/visualize.py +17 -0
- trackman_mcp-0.1.0/server.json +27 -0
- trackman_mcp-0.1.0/skills/drill-library/SKILL.md +64 -0
- trackman_mcp-0.1.0/skills/golf-coaching/SKILL.md +143 -0
- trackman_mcp-0.1.0/skills/trackman-api-discovery/SKILL.md +69 -0
- trackman_mcp-0.1.0/skills/trackman-session-analyzer/SKILL.md +96 -0
- trackman_mcp-0.1.0/skills/trackman-stats-analysis/SKILL.md +59 -0
- trackman_mcp-0.1.0/skills/trackman-visualizer/SKILL.md +85 -0
- trackman_mcp-0.1.0/src/trackman_mcp/__init__.py +7 -0
- trackman_mcp-0.1.0/src/trackman_mcp/analysis.py +501 -0
- trackman_mcp-0.1.0/src/trackman_mcp/client.py +129 -0
- trackman_mcp-0.1.0/src/trackman_mcp/config.py +103 -0
- trackman_mcp-0.1.0/src/trackman_mcp/login.py +156 -0
- trackman_mcp-0.1.0/src/trackman_mcp/queries.py +277 -0
- trackman_mcp-0.1.0/src/trackman_mcp/server.py +579 -0
- trackman_mcp-0.1.0/src/trackman_mcp/session_store.py +75 -0
- trackman_mcp-0.1.0/src/trackman_mcp/storage.py +86 -0
- trackman_mcp-0.1.0/src/trackman_mcp/token_store.py +88 -0
- trackman_mcp-0.1.0/src/trackman_mcp/training_store.py +99 -0
- trackman_mcp-0.1.0/src/trackman_mcp/visualize.py +363 -0
- trackman_mcp-0.1.0/tests/test_analysis.py +192 -0
- trackman_mcp-0.1.0/tests/test_client.py +105 -0
- trackman_mcp-0.1.0/tests/test_config.py +80 -0
- trackman_mcp-0.1.0/tests/test_login_recovery.py +87 -0
- trackman_mcp-0.1.0/tests/test_login_stdout.py +37 -0
- trackman_mcp-0.1.0/tests/test_queries.py +23 -0
- trackman_mcp-0.1.0/tests/test_server.py +188 -0
- trackman_mcp-0.1.0/tests/test_session_store.py +93 -0
- trackman_mcp-0.1.0/tests/test_storage.py +57 -0
- trackman_mcp-0.1.0/tests/test_token_store.py +60 -0
- trackman_mcp-0.1.0/tests/test_training_store.py +82 -0
- trackman_mcp-0.1.0/tests/test_verify.py +71 -0
- trackman_mcp-0.1.0/tests/test_verify_tool.py +105 -0
- trackman_mcp-0.1.0/tests/test_visualize.py +73 -0
- trackman_mcp-0.1.0/uv.lock +1835 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trackman-golf",
|
|
3
|
+
"owner": { "name": "Bjorn Jonsson", "email": "bjorn@avo.sh" },
|
|
4
|
+
"metadata": {
|
|
5
|
+
"description": "Single-plugin marketplace for the Trackman Golf MCP + coaching skills.",
|
|
6
|
+
"version": "0.1.0"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "trackman-golf",
|
|
11
|
+
"source": "./",
|
|
12
|
+
"description": "Trackman Golf MCP server (stats as MCP tools) plus the coaching skills."
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trackman-golf",
|
|
3
|
+
"description": "Connect to Trackman Golf with your own login, expose your stats as MCP tools, and coach from them with bundled skills.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": { "name": "Bjorn Jonsson", "email": "bjorn@avo.sh" },
|
|
6
|
+
"homepage": "https://github.com/bjornj12/trackman-mcp-client",
|
|
7
|
+
"repository": "https://github.com/bjornj12/trackman-mcp-client",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": ["golf", "trackman", "coaching", "mcp"]
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Trackman Golf MCP — environment
|
|
2
|
+
|
|
3
|
+
# REQUIRED: a Bearer access token captured from an authenticated portal session.
|
|
4
|
+
# How to get it:
|
|
5
|
+
# 1. Log in at https://portal.trackmangolf.com
|
|
6
|
+
# 2. Open DevTools -> Network, filter for "graphql"
|
|
7
|
+
# 3. Click a request to api.trackmangolf.com/graphql
|
|
8
|
+
# 4. Copy the Authorization header value (the part after "Bearer ")
|
|
9
|
+
# Paste it here (with or without the leading "Bearer ").
|
|
10
|
+
# Tokens last ~7 days — re-capture when tools return a 401.
|
|
11
|
+
TRACKMAN_TOKEN=
|
|
12
|
+
|
|
13
|
+
# OPTIONAL: override the GraphQL endpoint (defaults to the production API).
|
|
14
|
+
# TRACKMAN_GRAPHQL_ENDPOINT=https://api.trackmangolf.com/graphql
|
|
15
|
+
|
|
16
|
+
# OPTIONAL: request timeout in seconds (default 30).
|
|
17
|
+
# TRACKMAN_TIMEOUT_SECONDS=30
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: true
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
run: uv python install ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev --python ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Lint (ruff)
|
|
30
|
+
run: uv run ruff check src tests
|
|
31
|
+
|
|
32
|
+
- name: Type-check (mypy)
|
|
33
|
+
run: uv run mypy
|
|
34
|
+
|
|
35
|
+
- name: Test (pytest)
|
|
36
|
+
run: uv run pytest -q
|
|
37
|
+
|
|
38
|
+
build:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- name: Install uv
|
|
43
|
+
uses: astral-sh/setup-uv@v5
|
|
44
|
+
- name: Build sdist + wheel
|
|
45
|
+
run: uv build
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI on a version tag (e.g. `v0.1.0`) using PyPI Trusted
|
|
4
|
+
# Publishing (OIDC) — no API token is stored anywhere. See PUBLISHING.md for the
|
|
5
|
+
# one-time PyPI-side setup that authorizes this exact workflow.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Verify the tag matches the package version
|
|
21
|
+
run: |
|
|
22
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
23
|
+
VER=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
24
|
+
echo "tag=$TAG pyproject=$VER"
|
|
25
|
+
test "$TAG" = "$VER" || { echo "::error::tag v$TAG does not match pyproject version $VER"; exit 1; }
|
|
26
|
+
|
|
27
|
+
- name: Lint, type-check, test (gate the release)
|
|
28
|
+
run: |
|
|
29
|
+
uv sync --extra dev
|
|
30
|
+
uv run ruff check src tests
|
|
31
|
+
uv run mypy
|
|
32
|
+
uv run pytest -q
|
|
33
|
+
|
|
34
|
+
- name: Build sdist + wheel
|
|
35
|
+
run: uv build
|
|
36
|
+
|
|
37
|
+
- uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
publish:
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment: pypi
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write # required for OIDC trusted publishing
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
|
|
54
|
+
- name: Publish to PyPI
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Secrets & credentials — NEVER commit
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
*.token
|
|
6
|
+
*token*.json
|
|
7
|
+
*credentials*.json
|
|
8
|
+
|
|
9
|
+
# Local session/auth cache
|
|
10
|
+
.cache/
|
|
11
|
+
.sessions/
|
|
12
|
+
|
|
13
|
+
# Python
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.py[cod]
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
|
|
23
|
+
# Tooling
|
|
24
|
+
.uv/
|
|
25
|
+
dist/
|
|
26
|
+
build/
|
|
27
|
+
|
|
28
|
+
# OS / editor
|
|
29
|
+
.DS_Store
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
|
|
33
|
+
# uv.lock IS committed for reproducible installs (do not ignore).
|
|
34
|
+
|
|
35
|
+
# Generated visualizations (contain personal data)
|
|
36
|
+
viz/
|
|
37
|
+
*-viz.html
|
|
38
|
+
*.viz.html
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Trackman Golf MCP
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
**Name**: Trackman Golf MCP (working name)
|
|
6
|
+
|
|
7
|
+
**Purpose**: A Model Context Protocol (MCP) server that connects to Trackman's
|
|
8
|
+
golf platform using the user's own login, fetches their stats (course rounds,
|
|
9
|
+
practice sessions, shot-level launch-monitor data, club gapping, handicap), and
|
|
10
|
+
exposes them as MCP tools. On top of that data, a set of Claude **skills** act
|
|
11
|
+
as the user's golf coach — diagnosing weaknesses and giving actionable,
|
|
12
|
+
specific practice plans with example drills and YouTube links to follow.
|
|
13
|
+
|
|
14
|
+
**Who it's for**: The individual golfer who already practices on Trackman bays /
|
|
15
|
+
ranges or plays Trackman-enabled courses, and wants their data turned into a
|
|
16
|
+
concrete "what should I work on next" plan.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The Core Boundary (read this first)
|
|
21
|
+
|
|
22
|
+
There is a **hard separation of concerns**. Respect it in every change:
|
|
23
|
+
|
|
24
|
+
- **The MCP server only fetches and returns raw data.** Authentication,
|
|
25
|
+
HTTP calls to Trackman, and shaping responses into clean JSON. It contains
|
|
26
|
+
**no coaching opinions, no drill recommendations, no analysis verdicts.**
|
|
27
|
+
- **The Claude skills do all the thinking.** They call the MCP tools to get
|
|
28
|
+
data, then diagnose, plan, and coach in prompt/markdown.
|
|
29
|
+
|
|
30
|
+
Why: coaching logic should be tunable by editing markdown, not redeploying a
|
|
31
|
+
server. Keep judgment out of the server and data out of the skills.
|
|
32
|
+
|
|
33
|
+
If you find yourself adding "recommend a drill" logic to the MCP, stop — that
|
|
34
|
+
belongs in a skill. If you find yourself hardcoding shot data in a skill, stop —
|
|
35
|
+
that belongs behind an MCP tool.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Status & Phases
|
|
40
|
+
|
|
41
|
+
This repo currently contains **documentation and skills only** — no server code
|
|
42
|
+
yet. Build in this order:
|
|
43
|
+
|
|
44
|
+
- **Phase 0 — API discovery (do this first).** Trackman has no public golf API.
|
|
45
|
+
Before writing a single tool, discover the real endpoints and auth flow the
|
|
46
|
+
web portal uses, and write them down. Use the `trackman-api-discovery` skill.
|
|
47
|
+
Output: `docs/trackman-api.md` filled in.
|
|
48
|
+
- **Phase 1 — MCP server.** Implement the tools below against the discovered
|
|
49
|
+
API. Python + FastMCP.
|
|
50
|
+
- **Phase 2 — Coaching skills.** Wire `trackman-stats-analysis` and
|
|
51
|
+
`golf-coaching` to real tool output; grow the `drill-library`.
|
|
52
|
+
|
|
53
|
+
Do not skip Phase 0. Tool names and shapes below are **provisional** and will be
|
|
54
|
+
corrected by what discovery finds.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Technology Stack
|
|
59
|
+
|
|
60
|
+
- **Runtime**: Python 3.12+
|
|
61
|
+
- **MCP framework**: [FastMCP](https://github.com/jlowin/fastmcp) / the official
|
|
62
|
+
`mcp` Python SDK
|
|
63
|
+
- **HTTP client**: `httpx` (async)
|
|
64
|
+
- **Auth/session**: `httpx` cookie/token session; tokens stored locally only
|
|
65
|
+
- **Data shaping**: plain dicts / `pydantic` models; `pandas` is allowed in
|
|
66
|
+
*skills'* helper scripts for analysis, not required in the server
|
|
67
|
+
- **Package/deps**: `uv` (preferred) or `pip` + `pyproject.toml`
|
|
68
|
+
- **Tests**: `pytest`; record real API responses as fixtures (with secrets
|
|
69
|
+
scrubbed) and test tools against those.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## MCP Tools (confirmed against the real API — see `docs/trackman-api.md`)
|
|
74
|
+
|
|
75
|
+
All tools return **raw, structured data only**. No prose, no advice. Every tool
|
|
76
|
+
calls the single GraphQL endpoint `POST https://api.trackmangolf.com/graphql`
|
|
77
|
+
under the signed-in user's `me` root.
|
|
78
|
+
|
|
79
|
+
| Tool | Backing (under `me`) | Returns |
|
|
80
|
+
|------|----------------------|---------|
|
|
81
|
+
| `authenticate` | OIDC token capture | Validates the current token; reports who you're signed in as (or that the session expired). Never echoes the token. |
|
|
82
|
+
| `login` | Browser capture | (Re)authenticate. Tries a silent refresh first; if the saved session expired, opens a browser window to sign in. The friendly recovery path when tools report an expired session. |
|
|
83
|
+
| `get_profile` | `profile` + `hcp` | Identity + current **handicap** (`hcp.currentHcp`). |
|
|
84
|
+
| `get_handicap` | `hcp.playerHistory` | Handicap record history (differentials, trend). |
|
|
85
|
+
| `list_sessions` | `activities(kinds,timeFrom,timeTo,skip,take)` | Practice + course activities (id, time, kind, summary). |
|
|
86
|
+
| `get_session` | `node(id)` / `activities` | One activity in full: range strokes or round detail. |
|
|
87
|
+
| `get_course_rounds` | `scorecards(skip,take,completed)` | Scorecards: per-hole scores, FIR/GIR, putts, `stat`. |
|
|
88
|
+
| `get_club_stats` | `equipment.clubs.findMyDistance` | Per-club **gapping**: carry/total, std-dev, dispersion. |
|
|
89
|
+
| `get_shot_data` | `*.measurement` (`Measurement`) | Shot launch metrics: ball/club speed, smash, launch, spin, carry, side, curve, landing angle (~80 fields). |
|
|
90
|
+
| `get_activity_summary` | `activitySummary(timeFrom,timeTo)` | Counts per activity kind over a window. |
|
|
91
|
+
|
|
92
|
+
### Session-analysis tools (local store, deterministic analytics)
|
|
93
|
+
|
|
94
|
+
These persist and serve a per-session *analysis*. The analytics are
|
|
95
|
+
**deterministic** (in `analysis.py`) — classification and measurement, not
|
|
96
|
+
coaching. Coaching narrative still lives in the skills. The store is JSON at
|
|
97
|
+
`~/.trackman-mcp/session-analyses.json`, capped at the **last 30**, latest first.
|
|
98
|
+
|
|
99
|
+
| Tool | Does |
|
|
100
|
+
|------|------|
|
|
101
|
+
| `analyze_and_store_session(activity_id)` | Fetch a session, classify (warm-up vs serious practice vs game), compute metrics + course difficulty, normalize vs previously stored sessions, flag used-vs-available clubs, store, return the record. |
|
|
102
|
+
| `list_session_analyses()` | Index of stored analyses (id, time, kind, category, seriousness, summary), latest first. |
|
|
103
|
+
| `get_session_analysis(activity_id)` | One full stored analysis record. |
|
|
104
|
+
|
|
105
|
+
Classification (see `analysis.py`): a session is a **warm-up** (not an
|
|
106
|
+
improvement attempt) if under ~8 strokes or ~5 minutes — even for an otherwise
|
|
107
|
+
"serious" kind; **serious practice** if it has real volume/duration/club variety
|
|
108
|
+
or is a focused kind (shot analysis, find-my-distance, sim/virtual-range, etc.);
|
|
109
|
+
**game** for played rounds. Normalization is always against sessions
|
|
110
|
+
*chronologically before* the one analyzed. Units are metric (m/s, meters).
|
|
111
|
+
|
|
112
|
+
### Training-plan tools (the coach's memory)
|
|
113
|
+
|
|
114
|
+
The coach saves prescribed practice sessions so they can be recalled later
|
|
115
|
+
("what's today's training?"). Store is JSON at `~/.trackman-mcp/training-plans.json`
|
|
116
|
+
(`training_store.py`), an ordered queue capped at the most recent 50.
|
|
117
|
+
|
|
118
|
+
| Tool | Does |
|
|
119
|
+
|------|------|
|
|
120
|
+
| `save_training_plan(plan)` | Persist a prescribed plan (title, focus, diagnosis, blocks, targets) to the pending queue. |
|
|
121
|
+
| `get_next_training()` | Return the next pending plan — the answer to "what's today's training?". |
|
|
122
|
+
| `list_training_plans(status?)` | List plans (oldest→newest), optional `pending`/`done` filter. |
|
|
123
|
+
| `mark_training_done(plan_id, result_session_id?)` | Complete a plan; the next pending one becomes current. |
|
|
124
|
+
| `verify_training_progress(plan_id, activity_id?)` | Grade a recent session's real shot metrics against the plan's structured `target_specs` (e.g. driver `clubPath` between -1 and +2). Returns per-target session-mean vs target, `all_met`, and a recommendation. |
|
|
125
|
+
|
|
126
|
+
Plans carry **`target_specs`** — machine-readable targets (`{metric, club?, op,
|
|
127
|
+
value|low/high}`, ops `< <= > >= between abs< abs<=`) graded deterministically by
|
|
128
|
+
`analysis.verify_targets` against a session's `Measurement` fields (queried via
|
|
129
|
+
`SESSION_MEASUREMENTS`, which includes face/path/spin).
|
|
130
|
+
|
|
131
|
+
`golf-coaching` writes here (Prescribe → `save_training_plan` with `target_specs`)
|
|
132
|
+
and reads here (Recall → `get_next_training` → `verify_training_progress`, then
|
|
133
|
+
`mark_training_done` once every target is met).
|
|
134
|
+
|
|
135
|
+
**Auth reality**: the web portal uses a *confidential* OIDC client (backend-for-
|
|
136
|
+
frontend), so the MCP cannot run the OAuth exchange itself. It authenticates with
|
|
137
|
+
a **Bearer access token captured from an authenticated portal session**, attached
|
|
138
|
+
as `Authorization: Bearer …`. Tokens last ~7 days (observed `iat`→`exp` =
|
|
139
|
+
604800s); on `401` the tool returns a clear "re-capture token" error.
|
|
140
|
+
|
|
141
|
+
**Recovery when expired**: data tools auto-retry once after a silent headless
|
|
142
|
+
refresh (`_try_silent_refresh`), so a stale 7-day token renews invisibly while the
|
|
143
|
+
browser session is still valid. When the browser session itself expires, tools
|
|
144
|
+
return a clear "session expired — use the `login` tool" message, and the `login`
|
|
145
|
+
tool opens a sign-in window (falling back from a fast silent attempt).
|
|
146
|
+
|
|
147
|
+
**Getting the token** — two paths (`Config.from_env`: `TRACKMAN_TOKEN` env wins,
|
|
148
|
+
else the cached token):
|
|
149
|
+
- **Browser login (recommended)**: `trackman-mcp login` opens an isolated
|
|
150
|
+
Playwright browser; the user signs in once; the token is captured from the
|
|
151
|
+
GraphQL traffic and cached at `~/.trackman-mcp/token.json` (mode `0600`). The
|
|
152
|
+
browser profile persists the session, so `trackman-mcp login --headless`
|
|
153
|
+
refreshes silently with no re-login (cron-friendly). Code: `login.py`,
|
|
154
|
+
`token_store.py`. Playwright is the optional `[login]` extra.
|
|
155
|
+
- **Manual**: set `TRACKMAN_TOKEN` from a captured portal session (`.env.example`).
|
|
156
|
+
|
|
157
|
+
Full detail and example GraphQL queries live in `docs/trackman-api.md`.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Authentication & Secrets — Rules
|
|
162
|
+
|
|
163
|
+
Treat the user's Trackman login as sensitive. **Non-negotiable:**
|
|
164
|
+
|
|
165
|
+
- **Never commit credentials, tokens, cookies, or session dumps.** They go in
|
|
166
|
+
`.env` (gitignored) or the OS keychain — never in source or fixtures.
|
|
167
|
+
- The MCP reads credentials from environment variables only
|
|
168
|
+
(`TRACKMAN_USERNAME`, `TRACKMAN_PASSWORD`, or a captured `TRACKMAN_TOKEN`).
|
|
169
|
+
See `.env.example` once Phase 1 starts.
|
|
170
|
+
- **Do not return raw auth material to the model.** Tools may say "authenticated
|
|
171
|
+
as <name>" but must not echo passwords or bearer tokens.
|
|
172
|
+
- **Scrub fixtures.** Any recorded API response saved for tests must have
|
|
173
|
+
tokens, cookies, emails, and player IDs redacted or faked.
|
|
174
|
+
- Cache sessions locally under a gitignored path (e.g. `.cache/`), not in the
|
|
175
|
+
repo tree.
|
|
176
|
+
- This MCP is for a user accessing **their own** Trackman account. Don't build
|
|
177
|
+
anything that scrapes or accesses other users' data.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Project Structure (target, once Phase 1 begins)
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
trackman-mcp-client/
|
|
185
|
+
├── CLAUDE.md # this file
|
|
186
|
+
├── README.md
|
|
187
|
+
├── pyproject.toml
|
|
188
|
+
├── .env.example
|
|
189
|
+
├── .gitignore
|
|
190
|
+
├── docs/
|
|
191
|
+
│ └── trackman-api.md # discovered endpoints (Phase 0 output)
|
|
192
|
+
├── src/
|
|
193
|
+
│ └── trackman_mcp/
|
|
194
|
+
│ ├── __init__.py
|
|
195
|
+
│ ├── server.py # FastMCP app + tool registration
|
|
196
|
+
│ ├── client.py # Trackman HTTP client + auth/session
|
|
197
|
+
│ ├── tools/ # one module per tool group
|
|
198
|
+
│ └── models.py # pydantic response models
|
|
199
|
+
├── tests/
|
|
200
|
+
│ ├── fixtures/ # scrubbed recorded responses
|
|
201
|
+
│ └── test_tools.py
|
|
202
|
+
├── .claude-plugin/ # Claude Code plugin + marketplace manifests
|
|
203
|
+
├── .mcp.json # MCP server declaration (for the plugin)
|
|
204
|
+
├── server.json # MCP Registry manifest
|
|
205
|
+
└── skills/ # coaching brain (see below); plugin-root layout
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Skills (the coaching brain)
|
|
211
|
+
|
|
212
|
+
Skills live in `skills/` (the Claude Code plugin-root layout, so they ship with
|
|
213
|
+
the plugin). Each has a `SKILL.md`.
|
|
214
|
+
|
|
215
|
+
- **`trackman-api-discovery`** — Phase 0. Reverse-engineer the portal's auth +
|
|
216
|
+
data endpoints via the browser network panel; write them into
|
|
217
|
+
`docs/trackman-api.md`.
|
|
218
|
+
- **`trackman-stats-analysis`** — Pull stats through the MCP and diagnose weak
|
|
219
|
+
areas (dispersion, gapping, scoring trends, handicap movement). Analysis only.
|
|
220
|
+
- **`golf-coaching`** — Turn the diagnosis into specific, actionable practice:
|
|
221
|
+
an example session, drills, and YouTube links. The coach persona.
|
|
222
|
+
- **`drill-library`** — Curated drills + vetted YouTube links, plus the
|
|
223
|
+
procedure for live web-searching fresh videos matched to a weakness.
|
|
224
|
+
- **`trackman-session-analyzer`** — Ingests recent sessions, stores a per-session
|
|
225
|
+
analysis (last 30) via the MCP, and returns a normalized summary of the latest
|
|
226
|
+
session. **Context-forked / data-collection skill: must run in a subagent,
|
|
227
|
+
never on the main thread.**
|
|
228
|
+
|
|
229
|
+
Typical flow: `authenticate` → `trackman-stats-analysis` (diagnose) →
|
|
230
|
+
`golf-coaching` (prescribe, pulling from `drill-library`). For per-session
|
|
231
|
+
ingest + a normalized latest-session report, dispatch `trackman-session-analyzer`
|
|
232
|
+
as a subagent.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Conventions
|
|
237
|
+
|
|
238
|
+
- Keep tools small and single-purpose; one concern per module.
|
|
239
|
+
- Tools fail loudly with clear errors (auth expired, endpoint changed) rather
|
|
240
|
+
than returning empty success.
|
|
241
|
+
- Prefer async `httpx`; don't block the event loop.
|
|
242
|
+
- When the API shape is uncertain, write a fixture-backed test from a real
|
|
243
|
+
(scrubbed) response so regressions are caught when Trackman changes things.
|
|
244
|
+
- Coaching is **specific**: "10 balls, 7-iron, alternate target 140/160y, log
|
|
245
|
+
carry dispersion" — never "practice your irons."
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
*Last updated: 2026-06-27 · Version 0.1.0 (scaffolding)*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bjorn Jonsson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trackman-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server that fetches a user's Trackman Golf stats (handicap, rounds, practice, shots, club gapping).
|
|
5
|
+
Project-URL: Homepage, https://github.com/bjornj12/trackman-mcp-client
|
|
6
|
+
Project-URL: Repository, https://github.com/bjornj12/trackman-mcp-client
|
|
7
|
+
Project-URL: Issues, https://github.com/bjornj12/trackman-mcp-client/issues
|
|
8
|
+
Author-email: Bjorn Jonsson <bjorn@avo.sh>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude,golf,llm,mcp,model-context-protocol,trackman
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Games/Entertainment
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
28
|
+
Provides-Extra: login
|
|
29
|
+
Requires-Dist: playwright>=1.40; extra == 'login'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<!-- mcp-name: io.github.bjornj12/trackman-mcp -->
|
|
33
|
+
|
|
34
|
+
# trackman-mcp
|
|
35
|
+
|
|
36
|
+
An MCP server that logs into **Trackman Golf** with your own account and exposes
|
|
37
|
+
your stats — course rounds, practice sessions, shot-level launch-monitor data,
|
|
38
|
+
club gapping, and handicap — as MCP tools. On top of that, a set of Claude
|
|
39
|
+
**skills** act as your golf coach: they diagnose your weaknesses and hand you a
|
|
40
|
+
specific practice plan with drills and YouTube links for your next session.
|
|
41
|
+
|
|
42
|
+
> [!IMPORTANT]
|
|
43
|
+
> **Unofficial.** This project is not affiliated with or endorsed by Trackman.
|
|
44
|
+
> It talks to Trackman's **private** web API using a token from *your own*
|
|
45
|
+
> authenticated session, and automates a browser login on your behalf. This may
|
|
46
|
+
> conflict with Trackman's Terms of Service — use it on your own account, at your
|
|
47
|
+
> own risk. Never use it to access anyone else's data.
|
|
48
|
+
|
|
49
|
+
## Design boundary
|
|
50
|
+
|
|
51
|
+
- **MCP server** = raw data fetch + auth only. No opinions.
|
|
52
|
+
- **Skills** = all the coaching (analysis, plans, drills).
|
|
53
|
+
|
|
54
|
+
See [`CLAUDE.md`](./CLAUDE.md) for the full architecture and auth/secret rules.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
### Option A — Claude Code plugin (server + skills together)
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
/plugin marketplace add bjornj12/trackman-mcp-client
|
|
62
|
+
/plugin install trackman-golf@trackman-golf
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This installs the MCP server (run via `uvx`) **and** the six coaching skills.
|
|
66
|
+
Then sign in once (see [Authentication](#authentication)).
|
|
67
|
+
|
|
68
|
+
### Option B — any MCP client (Claude Desktop, etc.)
|
|
69
|
+
|
|
70
|
+
Once published to PyPI, add this to your client's MCP config (Claude Desktop:
|
|
71
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"trackman-golf": {
|
|
77
|
+
"command": "uvx",
|
|
78
|
+
"args": ["trackman-mcp"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
No `env` is required: after you run `trackman-mcp login` the server loads the
|
|
85
|
+
cached token from `~/.trackman-mcp/token.json` automatically. (You can instead
|
|
86
|
+
set `TRACKMAN_TOKEN` to override it — see `.env.example`.)
|
|
87
|
+
|
|
88
|
+
To run it straight from a local checkout before publishing, use
|
|
89
|
+
`"command": "uvx", "args": ["--from", "/abs/path/to/trackman-mcp-client", "trackman-mcp"]`.
|
|
90
|
+
|
|
91
|
+
### Option C — from source (development)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uv venv && uv pip install -e '.[login,dev]' # [login] adds Playwright, [dev] adds test/lint tools
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Authentication
|
|
98
|
+
|
|
99
|
+
### Browser login (recommended)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
trackman-mcp login # opens a browser; sign in once with email+password
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A browser window opens (an **isolated** profile, not your normal Chrome). Sign
|
|
106
|
+
in once; the MCP captures the access token and caches it at
|
|
107
|
+
`~/.trackman-mcp/token.json` (mode `0600`). The session persists, so to refresh
|
|
108
|
+
later (tokens last ~7 days) just run:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
trackman-mcp login --headless # silent refresh, no window
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you don't have Google Chrome installed, the browser flow falls back to
|
|
115
|
+
Playwright's bundled Chromium — install it once with `playwright install chromium`.
|
|
116
|
+
|
|
117
|
+
### Keep it fresh automatically (optional)
|
|
118
|
+
|
|
119
|
+
Schedule the headless refresh so you never think about tokens (twice weekly,
|
|
120
|
+
margin on the ~7-day token). Portable — paths are derived at install time:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
scripts/install-refresh-schedule.sh dry-run # preview what gets installed
|
|
124
|
+
scripts/install-refresh-schedule.sh # install (macOS launchd / Linux cron)
|
|
125
|
+
scripts/install-refresh-schedule.sh uninstall # remove
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Run a headed `trackman-mcp login` **once** first to establish the browser
|
|
129
|
+
session; the schedule then refreshes it silently. Windows: schedule
|
|
130
|
+
`scripts/refresh-token.sh` via Task Scheduler.
|
|
131
|
+
|
|
132
|
+
### Alternative: paste a token manually
|
|
133
|
+
|
|
134
|
+
Set `TRACKMAN_TOKEN` (it overrides the cache). Get it from
|
|
135
|
+
portal.trackmangolf.com → DevTools → Network → a `graphql` request → the
|
|
136
|
+
`Authorization` header value. See `.env.example`.
|
|
137
|
+
|
|
138
|
+
## Run
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
trackman-mcp # start the MCP (stdio)
|
|
142
|
+
uv run python scripts/validate.py # validate stats coverage (uses cached token)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## MCP tools
|
|
146
|
+
|
|
147
|
+
All tools return **raw data only**; the skills interpret it.
|
|
148
|
+
|
|
149
|
+
**Data (read-only):** `authenticate` · `get_profile` · `get_handicap` ·
|
|
150
|
+
`list_sessions` · `get_session` · `get_course_rounds` · `get_club_stats` ·
|
|
151
|
+
`get_shot_data` · `get_activity_summary`
|
|
152
|
+
|
|
153
|
+
**Auth:** `login`
|
|
154
|
+
|
|
155
|
+
**Session analysis (local store, deterministic):** `analyze_and_store_session` ·
|
|
156
|
+
`list_session_analyses` · `get_session_analysis`
|
|
157
|
+
|
|
158
|
+
**Training-plan memory:** `save_training_plan` · `get_next_training` ·
|
|
159
|
+
`list_training_plans` · `mark_training_done` · `verify_training_progress`
|
|
160
|
+
|
|
161
|
+
**Visualization:** `build_visualization` (self-contained animated HTML artifact)
|
|
162
|
+
|
|
163
|
+
See [`CLAUDE.md`](./CLAUDE.md) for the full table and backing GraphQL.
|
|
164
|
+
|
|
165
|
+
## Skills
|
|
166
|
+
|
|
167
|
+
Bundled under [`skills/`](./skills) (installed automatically with the plugin):
|
|
168
|
+
|
|
169
|
+
- `trackman-api-discovery` — reverse-engineer the portal's API (Phase 0)
|
|
170
|
+
- `trackman-stats-analysis` — diagnose weaknesses from the data
|
|
171
|
+
- `golf-coaching` — turn the diagnosis into an actionable practice plan
|
|
172
|
+
- `drill-library` — curated drills + vetted YouTube links, plus live search
|
|
173
|
+
- `trackman-session-analyzer` — ingest + normalize recent sessions (runs forked)
|
|
174
|
+
- `trackman-visualizer` — animate a diagnosis as an HTML artifact
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv run pytest # tests
|
|
180
|
+
uv run ruff check # lint
|
|
181
|
+
uv run mypy # type-check
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
[MIT](./LICENSE)
|