For AI assistants

CTDS Practice Log

The arithmetic behind Practice Log, offered to AI assistants as 7 tools over the Model Context Protocol.

The Practice Log diary arithmetic (Candidate #21): stats, year in review, streaks with rest days, notes suggestions, the timer's session math, hours since a string/head/reed change and the import dry run. Dates are local calendar dates (YYYY-MM-DD), times HH:MM, durations whole minutes; pass `today` to pin the day. The numbers are computed from the sessions given in the call and are a diary, not advice about an instrument or a person's health. No stored account data, recording, rhythm analysis or AI summary is reachable: the server is offline.

Connect

The server is at https://practicelog.chimneytopdigital.com/api/mcp. It is free, asks for no sign-in and keeps nothing between calls. Rate limits apply per address.

ClientHow
Claude CodeOne command in a terminal:
claude mcp add --transport http ctds-practice-log https://practicelog.chimneytopdigital.com/api/mcp
Claude (web and desktop apps)Settings → Connectors → Add custom connector, with the URL https://practicelog.chimneytopdigital.com/api/mcp. No sign-in is asked for.
Codex CLIOne command in a terminal:
codex mcp add ctds-practice-log --url https://practicelog.chimneytopdigital.com/api/mcp
Any other MCP clientUse the Streamable HTTP transport with the URL https://practicelog.chimneytopdigital.com/api/mcp: one JSON-RPC message per POST, JSON answers, no session and no authentication.

To see the raw protocol, list the tools and call one from a terminal:

curl -s https://practicelog.chimneytopdigital.com/api/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

curl -s https://practicelog.chimneytopdigital.com/api/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"compute_stats","arguments":{"sessions":[]}}}'

Tools

Every tool is read-only and idempotent, so a client may run it without asking you first. Arguments are checked before a tool runs; a wrong one comes back as a message naming the field.

compute_stats Practice stats

The dashboard numbers for a list of sessions as of a day: totals for today, this week (Monday to Sunday), this month and all time; time per instrument and per instrument type with shares; the current and best streak; 26 weekly totals; and, on request, the 365-day heatmap. Sessions dated after `today` are ignored.

  • sessions (array, required): The sessions logged
  • instruments (array, default []): Instruments, for names and type splits; unknown ids show as "Unknown instrument"
  • today (string): The local date to count from (YYYY-MM-DD); defaults to the current UTC date
  • rest_days_per_week (integer, default 1): Missed days a streak survives per rolling 7 days (0 = every day)
  • include_heatmap (boolean, default false): Also return the 365 daily heatmap entries (long)
year_in_review Year in review

The year-in-review model for one calendar year: total minutes, sessions, active days, top three instruments, longest session, minutes per month and the best month, top five tags, average mood and the best streak of the year.

  • sessions (array, required)
  • instruments (array, default [])
  • year (integer, required): Calendar year
  • rest_days_per_week (integer, default 1): Missed days a streak survives per rolling 7 days (0 = every day)
compute_streaks Current and best streak

Streaks with rest days from a list of practice dates: a span counts while every rolling 7-day window has at most rest_days_per_week days without practice. Current is the streak still alive as of today (today itself never counts as missed); best is the longest in the history. Lengths are calendar days; practice_days counts the days played.

  • practice_dates (array, required): Dates with at least one session (duplicates are fine)
  • today (string): The local date to count from (YYYY-MM-DD); defaults to the current UTC date
  • rest_days_per_week (integer, default 1): Missed days a streak survives per rolling 7 days (0 = every day)
suggest_notes Notes auto-fill suggestions

The notes the form would offer for a new session: first the most recent note within 7 days for the same instrument (else for any instrument), then up to four frequent notes ranked by frequency × 0.5^(age in days / 14). Deterministic: the same history, instrument and day give the same list.

  • history (array, required): Past sessions with their notes
  • instrument_id (string): Instrument of the new session
  • today (string): The local date to count from (YYYY-MM-DD); defaults to the current UTC date
timer_session Session from timer instants

What the practice timer saves for a session started and stopped at two instants, less the time paused: the local date and start time in the account's zone, the whole minutes practiced (at least 1) and the end time (start plus minutes, so end − start equals the duration).

  • instrument_id (string, required)
  • started_at (string, required): ISO 8601 instant the timer started, e.g. 2026-10-29T18:05:00Z
  • stopped_at (string, required): ISO 8601 instant the timer stopped
  • paused_ms (integer, default 0): Total time paused, in ms (not counted as practice)
  • time_zone (string, default "America/New_York"): IANA time zone for the local date and times
hours_since_change Hours since the last string, head or reed change (or tuning)

Hours played on an instrument since its last consumable change: strings for stringed types, drum heads for drums, reeds for wind, tuning for piano (or the kind you name). With no change logged, hours count from the first session. Sessions dated on the change day count. Hours are to one decimal.

  • instrument (object, required): The instrument; its type picks the default kind
  • sessions (array, required)
  • entries (array, default []): Equipment log entries
  • kind (string-change | head-change | reed-change | tuning | setup | repair | battery | other): Entry kind to count from; defaults from the instrument type (string-change when unknown)
plan_import Dry-run an import of session rows

The import planner over rows already read from a file (one object per row): the reconciliation report (sessions to create, duplicates skipped, rows rejected and why, instruments detected and merged by spelling, minutes before and after), the instruments to create and, on request, the planned sessions. Rows with source_id dedupe by id; others by date, start and instrument. Give the account's existing instruments and sessions to dedupe against them; with none, every valid row is new.

  • rows (array, required): Session rows as read from the file
  • existing_instruments (array, default [])
  • existing_sessions (array, default [])
  • typo_map (object, default {}): Misspelling → correct instrument name
  • unknown_instrument (string): Name for rows with no instrument (default "Other")
  • tag_rules (array, default []): Words in notes that become tags (id, tag, group, words)
  • include_sessions (boolean, default false): Also return the planned sessions (long)

Resources

  • ctds://practice-log/notice: Practice Log diary notice. The app's own statement of what its numbers are; repeat it with stats and streaks.

Limits

  • The tools compute from the values the assistant passes in. They cannot read or change anything in your Practice Log account; for that, use the app.
  • Results carry the same notices the app shows. They are estimates and working aids, not professional advice; ask your assistant to show the notice with the figure.
  • Rate limits apply per address. A request over the limit gets a plain error the assistant can read.
  • The tool list may grow. This page is generated from the running server, so it shows the tools as they are today.

If something fails

"Couldn't reach the MCP server", or a 404
Check the address character by character: the server answers only at https://practicelog.chimneytopdigital.com/api/mcp, over HTTPS. Nothing else on this host speaks MCP.
405 Method Not Allowed
The client sent a GET. The server speaks the Streamable HTTP transport without an event stream: one JSON-RPC message per POST, a JSON answer, no session. A notification gets an empty 202.
429 Too Many Requests
Your address passed the fair-use limit. The Retry-After header says how many seconds to wait; the limit resets on its own.
413 Payload Too Large
One request may carry 256 KiB. Send fewer rows per call, or summarize before you send.
A tool answers with an error naming a field
The argument did not match the tool's schema above: money is integer cents (or plain dollars where the tool says so), dates are YYYY-MM-DD, and lists are JSON arrays. Fix the named field and call again; nothing was stored.
Still stuck
Write to support@chimneytopdigital.com with the tool name, the arguments you sent (with nothing personal in them), the time in UTC and the exact message. We keep no record of calls, so what you send us is all we can look at.

How we built the servers, and the rules every one follows: our guide. Questions: hello@chimneytopdigital.com.