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.
| Client | How |
|---|---|
| Claude Code | One command in a terminal: |
| 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 CLI | One command in a terminal: |
| Any other MCP client | Use 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_statsPractice statsThe 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 loggedinstruments(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 daterest_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_reviewYear in reviewThe 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 yearrest_days_per_week(integer, default 1): Missed days a streak survives per rolling 7 days (0 = every day)
compute_streaksCurrent and best streakStreaks 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 daterest_days_per_week(integer, default 1): Missed days a streak survives per rolling 7 days (0 = every day)
suggest_notesNotes auto-fill suggestionsThe 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 notesinstrument_id(string): Instrument of the new sessiontoday(string): The local date to count from (YYYY-MM-DD); defaults to the current UTC date
timer_sessionSession from timer instantsWhat 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:00Zstopped_at(string, required): ISO 8601 instant the timer stoppedpaused_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_changeHours 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 kindsessions(array, required)entries(array, default []): Equipment log entrieskind(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_importDry-run an import of session rowsThe 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 fileexisting_instruments(array, default [])existing_sessions(array, default [])typo_map(object, default {}): Misspelling → correct instrument nameunknown_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-Afterheader 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.