Skip to main content
Glama
udisc
by cin

disc-golf-trends

Your disc golf rounds and practice throws, from your own exports. It has three parts: a read-only MCP server that lets Claude answer questions about your rounds, courses and scoring trends, an offline HTML dashboard of the same data, and a website where anyone can pick their own CSV and see the dashboard in the browser. (Formerly disc-golf-journal and udisc-mcp. Not affiliated with UDisc or TechDisc.)

No credentials and no network access. UDisc has no public API, and its Terms prohibit scraping, so this project never contacts UDisc or TechDisc. It only reads the CSV files their apps export. Nothing is modified.

Get your data

In the UDisc mobile app: You tab → menu (top right) → Rounds → menu (top right) → Export to CSV. Move the file into a folder of your choice. One export holds your whole history; re-export now and then to pick up new rounds. Every *.csv in the folder is read and overlapping exports are de-duplicated, so you can drop new exports next to old ones.

Related MCP server: FitNotes MCP Server

Setup

Python 3.10 or newer.

git clone https://github.com/cin/disc-golf-trends.git
cd disc-golf-trends
python -m venv .venv
.venv\Scripts\pip install -e ".[dev]"      # Linux/macOS: .venv/bin/pip
copy .env.example .env                     # then edit it

.env (see .env.example):

Variable

Meaning

UDISC_CSV_DIR

Folder holding your export(s)

UDISC_PLAYER

Your name exactly as in the PlayerName column

TECHDISC_CSV_DIR

Optional. Folder holding a TechDisc throw export; may be the same folder as UDISC_CSV_DIR (files are told apart by header). Without it the TechDisc tools report the missing setting

MIN_HOLE_ROUNDS

Optional. Rounds a hole needs before hole_problem_report ranks it: a whole number of 1 or more (default 3)

The .env file is git-ignored. It holds only paths and a name, no secrets. The server exits at startup with a clear message if UDISC_CSV_DIR or UDISC_PLAYER is missing or MIN_HOLE_ROUNDS is invalid, and on Linux/macOS if .env is readable by other users (chmod 600 .env). A setting that goes wrong later (say the folder is deleted) fails that one tool call, not the server.

Register with Claude

Claude Code:

claude mcp add udisc -- C:\path\to\disc-golf-trends\.venv\Scripts\python C:\path\to\disc-golf-trends\udisc_mcp.py

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "udisc": {
      "command": "C:\\path\\to\\disc-golf-trends\\.venv\\Scripts\\python.exe",
      "args": ["C:\\path\\to\\disc-golf-trends\\udisc_mcp.py"]
    }
  }
}

Tools (all read-only)

Tool

Answers

export_status

How fresh is the data? Newest round, export age, stale warning

list_rounds

Rounds by date range and course, newest first

get_round

Hole-by-hole scores vs par for one round

list_courses

Courses, layouts, rounds played, first/last played

course_stats

Average/best/worst rounds, per-layout stats, 3 best and worst holes; include_holes adds every hole

scoring_trends

Score vs par by month or season, optionally per course

scoring_distribution

Share of holes at eagle or better, birdie, par, bogey, double, triple or worse; overall, by month or by season

hole_problem_report

Holes that cost the most strokes over par, with blow-up counts (a blow-up is a double bogey or worse)

compare_seasons

Two calendar years side by side

throw_export_status

TechDisc: throw count, first/last throw, export age, stale warning

list_throws

TechDisc: throws by date range, Backhand/Forehand and Hyzer/Flat, newest first

throw_stats

TechDisc: speed, distance, spin and angle averages/maxima, overall and by throw style

throw_trends

TechDisc: those numbers by month or season

Dates (start, end) are YYYY-MM-DD and inclusive; anything else is rejected with a message instead of silently returning the wrong rounds. group_by accepts only month or season. list_rounds and list_throws return at most 500 rows.

Example questions: "Which holes at my home course cost me the most strokes?", "How has my scoring trended each season?", "Compare 2025 and 2026."

Dashboard

.venv\Scripts\python dashboard.py        # writes dashboards\udisc-dashboard.html

One self-contained HTML file with three tabs, opened by double-clicking it: Scoring (strokes over par and round rating by month, quarter or season, with the best and worst periods ringed; every round as a dot, with new personal bests at a course in blue; the three costliest holes highlighted; birdies and blow-ups per round; the share of holes at each score by season; a courses table), Holes (where the strokes go on a chosen course, and a hole-by-season heatmap) and Throws (TechDisc speed and distance trends). Filters at the top scope the whole page by course and date range; "Highlight season" shades one season on the charts and dims the others without filtering anything. Every chart has a table view. Holes shows one course at a time, so with "All courses" it asks you to pick one (and offers your most played). Throws only uses the date range, and says so next to the greyed-out course and season controls.

Themes (menu at the top right, remembered in the browser): System follows your light or dark setting, Mocha is Catppuccin Mocha, and the third entry is the opposite of your system look (Dark on a light system, Light on a dark one). ?theme=system, ?theme=mocha, ?theme=dark or ?theme=light in the address picks one; dark or light only applies when it is the opposite of the system look. It makes no network requests and has no animation. Re-run the command after each new export.

--player "Name" builds the same dashboard for another player in your export (name matched exactly, ignoring case), written to dashboards\udisc-dashboard-name.html. The export only holds rounds you scored together, so that dashboard covers those rounds, and your TechDisc throws are left out.

The file embeds real rounds, so dashboards/ is git-ignored. Each file holds one player's rows.

Website

Live at https://cin.github.io/disc-golf-trends/. site/ is the same dashboard as a static web page: a visitor picks their own UDisc and/or TechDisc CSV export and sees the charts. Nothing is uploaded. The files are read in the browser tab, and the built page's content security policy stops the page from making network requests. It is written in TypeScript (a port of udisc_data.py and techdisc_data.py) and draws with the same dashboard_template.html as the Python dashboard, so there is one set of charts to maintain. A scorecard export includes everyone you played with; the page asks whose rounds to show and starts with the player who has the most. "Try it with sample data" loads made-up rounds and throws. The two setup cards (choose files, choose player) minimize to a one-line summary once you have picked, so the dashboard starts near the top; click a summary to change the choice, or drop new files on the picker even while it is minimized.

Working on it needs Node.js 22.12 or newer (24 recommended); the Python parts do not need Node.

cd site
npm install
npm run dev        # http://localhost:5173/, add ?sample to load made-up data
npm test           # unit tests, plus a check that the port gives the Python implementation's answers
npm run build      # static files in site/dist/

The port is held to the Python code by tests/golden.py: it runs every query over the fixtures (including a larger made-up dataset) and writes site/tests/golden.json, and the TypeScript tests must reproduce it exactly. After changing the Python statistics, run .venv\Scripts\python tests\golden.py and commit the result; a Python test fails when the file is stale. Pushes to main that touch site/, the dashboard template or the fixtures are published to GitHub Pages by .github/workflows/pages.yml.

Data notes

  • The export has a Par row before each round's player rows. Par comes from there.

  • A score of 0 means the hole was not played. Rounds with no scores are ignored, and partial rounds are left out of averages and trends.

  • Totals are the sum of the hole scores. The file's Total column is only a cross-check: when you scratch a hole ("-" in the app), UDisc still writes a placeholder score in that hole's column but leaves it out of its own total. If the holes add up to exactly one hole's score more than Total, that hole is treated as not played. If no single hole explains the gap (or several could), nothing is dropped and the gap is reported instead. get_round shows scratched_holes and total_gap, export_status counts the affected rounds, and the website notes them.

  • Hole numbers are only compared within one course layout.

  • The UDisc export has no throws, putts or penalty columns, so none are reported.

  • TechDisc throws are practice data and are not linked to rounds (the export has no course or round column). Throw times come from timeSeconds and are shown in this computer's local time zone.

  • When both exports share a folder, each loader ignores the other's files by header, and export_status only looks at scorecard files.

Development

.venv\Scripts\python -m pytest -q
.venv\Scripts\ruff check .
cd site && npm test && npm run typecheck      # the website, needs Node.js

Tests use made-up data in tests/fixtures/ (sample.csv and throws.csv, plus larger generated synthetic_*.csv). Never commit your real export: *.csv is git-ignored except for tests/fixtures/. .github/workflows/ci.yml runs all of the above on every push and pull request.

License

MIT, see LICENSE.

Available Tools

13 tools
compare_seasonsB
Read-onlyIdempotent

Compare two calendar years: rounds, average score vs par, best/worst round and the change.

Inputs: year_a (required), year_b (required), course.

course_statsA
Read-onlyIdempotent

Average, best and worst rounds, per-layout stats and the 3 best and worst holes for a course (substring match).

include_holes adds the average for every hole; that list is long for multi-layout courses.

Inputs: course (required), layout, include_holes.

export_statusB
Read-onlyIdempotent

How fresh the data is: newest round, newest export file, and a stale warning.

Inputs: no named parameters.

get_roundA
Read-onlyIdempotent

Hole-by-hole scores for one round. round_id is 'YYYY-MM-DD HHMM' from list_rounds, or a date if unambiguous.

Inputs: round_id (required).

hole_problem_reportB
Read-onlyIdempotent

Holes that have cost the most total strokes over par, with average and blow-up counts.

A blow-up is a hole played 2 or more strokes over par (double bogey or worse).

Inputs: top_n, course, layout, min_rounds. Returns: result

list_coursesC
Read-onlyIdempotent

Courses played, with rounds played, layouts and first/last played dates.

Inputs: no named parameters. Returns: result

list_roundsA
Read-onlyIdempotent

List rounds, newest first. start/end are YYYY-MM-DD (inclusive); course matches by substring.

Inputs: end, limit, start, course. Returns: result

list_throwsB
Read-onlyIdempotent

List TechDisc throws, newest first. primary_type is Backhand/Forehand; throw_type is e.g. Hyzer/Flat.

Inputs: end, limit, start, throw_type, +1 more. Returns: result

scoring_distributionB
Read-onlyIdempotent

Share of holes scored eagle or better, birdie, par, bogey, double and triple or worse.

start/end are YYYY-MM-DD (inclusive). group_by is omitted for one overall row, or 'month' or 'season'.

Inputs: end, start, course, layout, +1 more. Returns: result

throw_export_statusA
Read-onlyIdempotent

How fresh the TechDisc data is: throw count, first/last throw, export file age and a stale warning.

Inputs: no named parameters.

throw_statsC
Read-onlyIdempotent

TechDisc averages and maxima (speed, distance, spin, angles), overall and by throw style.

Inputs: end, start.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 13 tools

    Disambiguation4/5

    Tools are mostly distinct: list/get for rounds, stats/trends for scoring, and separate throw tools. Minor overlap between course_stats (includes best/worst holes) and hole_problem_report, and between scoring_trends and compare_seasons, but descriptions clarify.

    Naming Consistency3/5

    All names use snake_case, but the pattern mixes verb-first (list_rounds, get_round, compare_seasons) with noun-phrase (course_stats, scoring_distribution, throw_export_status). This is a mixed convention, though still readable.

    Tool Count5/5

    13 tools are well within the typical 3-15 range and cover both round analysis and TechDisc throw analysis without obvious bloat.

    Completeness4/5

    The tool set covers round listing/retrieval, course and hole statistics, scoring distributions/trends, season comparison, and TechDisc throw stats/trends, plus export freshness for both data sources. Minor gaps might include a dedicated tool for per-round summary or course comparison, but the surface is largely complete for analysis.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables querying and analyzing personal Strava activity data through natural language, including activities, segments, gear, and training trends.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables natural-language queries about disk usage by driving WizTree scans and analyzing cached CSV snapshots. Users can find large files, duplicate data, folder breakdowns, and more without repeatedly rescanning the disk.
      19
      MIT