Repository navigation
Stage 3A: compact tool catalogue (42k -> 22.6k tokens), tool sets, guides, prompts, follow-ups - #28
Merged
Merged
Conversation
…ronment Tools and the private helpers of the tools package no longer take or pass an API key; make_intervals_request falls back to API_KEY as before. The tool guard and the athlete time zone resolver no longer forward a key. Breaking for Python callers that passed api_key to a tool function; all other positional and keyword arguments are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in phase-6 URLs get_activity_data_audit showed a failed streams request as "no streams" (the endpoint returned none of the listed streams); it now reports the API error and leaves the coverage unchecked (JSON streams.error). The phase-6 code (data audit, fueling, long-ride fatigue, submax tests, route history, multi-ride power comparison) formats every id into an API path with seg(); a test checks every API path f-string of the package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Garmin Endurance Score, Hill Score / Strength / Endurance, (achievable) Fitness Age, predicted race times, the acute training load, VO2max and daily goals are device values computed once a day, not overnight values. today_completeness groups them as daily_metric (between night/morning values and day totals) and the completeness line lists them on a line of their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, instructions, guides - @tool registers every tool with structured_output=False: FastMCP no longer adds an output schema and a {"result": ...} structured copy of the same text. The cleaned docstring is the description. - IntervalsFastMCP.add_tool compacts the input schema: no generated titles, optional parameters with a null default without the anyOf/null wrapper. Workout fields that Intervals.icu fills itself are hidden from the schema (still accepted). - MCP_TOOLSET=full (default) or core (19 curated tools; toolsets.py); get_server_status shows the tool set and the tools outside it. - Server instructions (initialize) with the usage rules. - Guides as resources intervals://guide, intervals://workout-syntax and intervals://methods/<topic>, and get_guide(topic) for clients that only call tools; the usage guide covers the phase-6 tools and today's completeness. - utils/params.py: shared Annotated parameter types with descriptions and case-insensitive Literal enums (an empty value means the default). - catalogue(permissions, toolset) builds the tools/list of any configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- race_week: race and remaining sessions (get_events), form on race day with get_load_projection(target_date=...), recovery, a fueling plan per hour from the athlete's get_fueling_analysis history, weather (no forecast in this server; past conditions on the same route) and a logistics checklist. - fueling_review: one activity or the long sessions of the last weeks. - plan_health_check: get_load_projection of the planned weeks with its plan checks and a what-if scenario that is never written. - coach_handoff: compact summary with ids for another coach or session. - workout_planning_validation points to the workout syntax first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hod guides Every tool description is at most 900 characters and its first sentence says when to use the tool (write tools: only on the athlete's request). Parameters are described in the input schema (Annotated + Field) instead of an Args block; parameters with fixed values are Literal enums (output_format, detail_level, sort_by, group_by, zone_type, zone_basis, threshold_as, metric, target, environment, temperature_source, threshold_unit, category, item_type, visibility, topic), integer scales (wellness 1-4, feel 1-5, RPE 1-10) carry their range. The long method explanations moved, complete, into guides/methods/<topic>.md (intervals://methods/<topic>, get_guide). Tool names, permission classes, parameter names, order and defaults and the tool behaviour are unchanged. tools/list at MCP_PERMISSIONS=read,write: 42.0k -> 22.6k tokens (cl100k); MCP_TOOLSET=core: 8.1k. tests/test_catalogue.py checks the limits, the budget per permission set and tool set, enums, descriptions, no api_key, no output schema and an MCP round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ettings MCP_TOOL_MAX_REQUESTS, MCP_TOOL_TIMEOUT_S, MCP_MAX_OUTPUT_CHARS, ATHLETE_TIMEZONE and MCP_TOOLSET are checked one by one (the server itself still falls back to the default for a wrong limit or time zone); OAUTH_REFRESH_REUSE_GRACE, OAUTH_REFRESH_REUSE_REVOKE, OAUTH_LOGIN_GLOBAL_RATE_LIMIT and OAUTH_REQUIRE_PRIVATE_KEY_JWT are errors with MCP_AUTH=oauth and warnings otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…by type README: tool sets (MCP_TOOLSET), the compact catalogue, get_guide, the new prompts and resources, refreshing the tool list after an update. .env.example: MCP_TOOLSET. AGENTS/CONTRIBUTING: the description and parameter rules. CHANGELOG 1.0.0b1: regrouped into Security / Added / Changed / Fixed with the phase labels kept as subsections (no entry lost), plus the stage 3 entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r parity, no network in tests - R28-1: output_format accepts every value the tools always took: anything but json (or application/json) means text; get_activity_streams maps text to summary and csv/table to full. The schema still shows the enum. - R28-2: get_guide accepts intervals://methods/load, methods/load, workout_syntax, "Workout Syntax", guide, syntax ... - R28-3: the Value schema of the workout_doc states the MINS_KM/MINS_MILE value format (seconds or decimal minutes; 5.35 is 5:21, not 5:35). - R28-4: race_week asks for or confirms the race date instead of assuming the next A race; get_load_projection accepts target_date = today (start of race day, no taper search). - R28-5: core set + get_event_by_id, get_training_plan, preview_workout and get_wellness_trends (23 tools, 9.4k tokens at read,write); with a tool set other than full, tool descriptions, prompts and guides mark the tools outside it "(full tool set)". - R28-6: item_type offers FITNESS_TABLE (OpenAPI CustomItem.type). - R28-8: --doctor and the server share the parsers of MCP_TOOL_MAX_REQUESTS, MCP_TOOL_TIMEOUT_S and MCP_MAX_OUTPUT_CHARS and name the value really used; inf/nan/non-positive limits fall back to the default (inf broke every call). - Tests: conftest blocks every non-loopback network access (a leak fails the test); test_get_activities mocks the gear catalogue instead of calling intervals.icu. Small wording fixes (monotony > 2.0, API key error text, pace_units no longer suggested in the workout syntax). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he sign-in page and README - serverInfo carries websiteUrl (GitHub) and icons: with MCP_PUBLIC_URL the SVG and PNGs served by this server, without it (stdio) the SVG as a data: URI. - The HTTP transports serve /favicon.ico (48 and 128 px), /favicon.png, /icon.png, /icon.svg and /apple-touch-icon.png without authentication, with cache headers, nosniff and a locked-down CSP for the SVG. - The sign-in/consent page shows the logo (CSP img-src 'self') and links the favicon; the README shows the logo from docs/assets. - The icon files live in intervals_mcp_server/assets (part of the wheel); CI checks that the installed wheel can read the guides and the icons. - Docs: README and CHANGELOG for the icon and the review fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
futureweb
added a commit
that referenced
this pull request
Oct 10, 2026
…echo) and six fixes from #28 (#29) Stage 3B: make every write predictable and verifiable for the athlete, and fix the six bugs noted in #28. The existing safety stays as it is (validation before writes, range delete only with `confirm_ids` from a preview, `replace_workout=true` before text replaces a structured workout, empty texts never wipe, `clear_*` flags). Nothing is created, renamed, paired or deleted automatically. ## Write safety ### 1. `dry_run` on every create/update tool New parameter `dry_run` (default `false`, so existing calls behave exactly as before) on `add_or_update_event`, `add_or_update_note`, `add_events_bulk`, `add_event_from_library`, `create_library_workout`, `create_custom_item`, `update_activity`, `update_wellness`, `update_custom_item`, `update_sport_settings`, `add_activity_message`. - A dry run runs every check of the real call, including the reads it needs (duplicate check, the current event before a date change, the custom item content to merge, the sport settings), and returns compact JSON: `{"dry_run":true,"message":...,"request":{"method","path","params","body"},"validation":{"ok":true,"warnings":[...]}, ...}`. `body` is exactly what would be sent after all defaults and merges (e.g. the merged custom item content, the rendered workout text, `color: green` of a new note); `update_sport_settings` also shows the current values, `add_events_bulk` the indexes sent and the refused entries. - A refusal (invalid workout, duplicate, nothing to change) is returned exactly as the real call would return it. - Second line of defence: the tool guard runs a call with `dry_run=true` (also one nested in another tool call) inside `api.client.read_only_requests()`, so `make_intervals_request` refuses every non-GET request during a dry run (also covers `delete_events_by_date_range`, whose default is `dry_run=true`). ### 2. Duplicate check before creating events `add_or_update_event` / `add_or_update_note` without `event_id`, `add_events_bulk` and `add_event_from_library` read the events of the dates first (one GET over the date range, checked against the tool call's request budget) and refuse a create when that day already has an event of the same category and the same sport (when both have one) with the same name (case/whitespace-insensitive) or the same non-trivial content (workout text with at least two step lines or a repeat: its step and repeat lines are compared; plain text with at least two lines). A single generic line (`- 45m Z2 HR`, "Rest day") is never compared, so a brick day and AM/PM sessions with different names are created. The answer names the existing event id and name and how to proceed (`event_id=...` or `allow_duplicate=true`). Bulk decides per entry: an entry duplicating an existing event or repeating an earlier entry exactly is listed under `refused` and not written; entries with the same day, sport and name but different content are planned double sessions and are created (with a warning). If the events cannot be read, nothing is written and the answer says to try again (no hint to skip the check). ### 3. Read-back after every event / library write After a successful write the stored object is read (`GET /events/{id}`, `GET /workouts/{id}`; bulk: one `GET /events` over the dates of the created events) and the answer reports what Intervals.icu stored and parsed: date (time only when not midnight), name, category, sport, planned duration, load, timed steps parsed vs sent, and parse warnings: steps dropped/merged/added, repeat counts changed (labelled headers such as "Main set 3x" count), a step's duration or distance changed, a target missing or in other units (absolute paces sent as `MINS_KM` … and stored as `secs/km` … are the same unit and are compared in seconds, 2 s tolerance), no planned duration, fields stored with another value (e.g. a planned moving time replaced by the steps' total). Notes, holidays, sick and injured days: date, category, name and text length only. Text answers get one `Read-back:` paragraph (≤ 600 characters). `add_events_bulk` answers compact JSON with status, id, date, name and short warnings per entry; `detail_level="full"` adds what was stored. If the read-back fails: "the write succeeded but event N could not be verified (...)". Example (live data, read-only: the comparison applied to a stored event): `Read-back: Intervals.icu stored 2026-10-06T00:00:00 WORKOUT Ride 'Ultimate – 3×12 min Sweet Spot', 1:08:00, load 79, 7 steps parsed (7 sent), paired with i194378945. Parse warnings: none.` ### 4. Deletions echo what was deleted `delete_event` reads the event first and answers e.g. `Deleted event 5: 2026-10-13 WORKOUT Ride 'Done ride' (paired with activity i9).` A missing id (404, verified live read-only: Intervals.icu answers 404 for a missing event and custom item, `{}` for a missing library workout) answers `Event 5 not found; nothing was deleted.` without a DELETE; a DELETE answered with 404 says it was already gone. `delete_library_workout` and `delete_custom_item` do the same (library: id, name, sport, folder). ### Requests added per write | Write | Added requests | | --- | --- | | create one event (`add_or_update_event`, `add_or_update_note`, `add_event_from_library`) | +1 GET duplicate check, +1 GET read-back (`allow_duplicate=true`: only the read-back) | | update one event | +1 GET read-back | | `add_events_bulk` | +1 GET duplicate check over the date range, +1 GET read-back | | `create_library_workout` | +1 GET read-back | | `delete_event` | +1 GET before the DELETE | | `delete_library_workout`, `delete_custom_item` | none (they read first already) | | other writes (`create_custom_item`, `update_activity`, `update_wellness`, `update_custom_item`, `update_sport_settings`, `add_activity_message`) | none | | any dry run | only the reads of the real call, no write | ## Fixes (noted in #28) - **a** `get_durability`: the recent efficiency-factor mean is labelled with `recent_days` (was always "last 7 d"); JSON `efficiency.<sport>.recent_days`. - **b** `compare_workouts`: the reference is fetched by id, always takes one of the `limit` places and is always the pattern (it was cut when older than the newest `limit` activities and the newest activity silently became the pattern). A reference without a main set says so. - **c** `compare_workouts` with `query` + `end_date` + reference and no `start_date`: the listed window is now the one the note names (365 days before the reference up to `end_date`; it was 90 days before `end_date`). A name search with a reference and no dates lists that window too instead of the newest search results. - **d** `compare_best_efforts` with `activity_ids` de-duplicates and caps to `limit` before any request (`cap_ids`/`ids_note`) and says what was dropped. - **e** The JSON of `get_coach_context` and `get_training_summary` follows an explicit `detail_level` (compact / standard / full, with `detail_level` in the payload). Without `detail_level` the JSON keeps the previous full structure (backwards compatible; listed under Changed). Live: coach context compact 8.6k characters vs 14.5k full, training summary 3.5k vs 30k. - **f** `get_activities` / `get_wellness_data` with a past `end_date` and no `start_date`: the default start is 30 days before the earlier of `end_date` and today (it was 30 days before today, after the end). ## Catalogue (`tools/list`, tiktoken cl100k, chars/4 in brackets) | `MCP_PERMISSIONS` | Tool set | Before (main b6758ac) | After | Budget (chars/4) | | --- | --- | --- | --- | --- | | `read` | full | 19,247 (20,993) | 19,267 (21,011) | 23,000 | | `read,write` (live connector) | full | 22,711 (24,445) | **23,085** (24,884), +374 | 26,500 | | `all` | full | 24,919 (26,845) | 25,527 (27,557), +608 | 29,000 | | `read,write` | core | 9,356 (10,010) | 9,501 (10,177), +145 | 11,000 | New parameters have short descriptions (`dry_run`: "true = check and return the exact request, write nothing"; `allow_duplicate`: "Create even if that day has the same event (name or workout)"); the details are in the new **Write safety** section of `intervals://guide` (`get_guide`), the workout syntax guide and the README. All descriptions stay ≤ 900 characters; server instructions 1,303 characters. ## Review fixes (commit 4cb9a29) R29-1 absolute pace units (`MINS_KM` ≡ `secs/km` …, values compared in seconds); R29-2 labelled repeat headers ("Main set 3x"); R29-3 duplicates need the same sport and non-trivial content; R29-4 bulk refuses only exact repeats within the list, double sessions are created; R29-5 no `allow_duplicate` hint when the check could not run; R29-6 one range GET for the duplicate check; R29-7 compact bulk answer (`detail_level`); R29-8 notes read back with date, category, name, text length; R29-9 `create_custom_item` has `dry_run`, docs complete; R29-10 nested dry runs read-only too; R29-11 JSON default unchanged without `detail_level`. ## Tests - `tests/test_write_safety.py` (41): every dry run end to end through the real API client with a recording HTTP client (only GETs leave, body/path/method checked), dry-run bodies after defaults and merges, refusals identical to the real call, the guard refusing a write of a buggy tool during a dry run, the range-delete preview running read-only, duplicate detection (name case/whitespace, same workout with another intro, other category not refused, `allow_duplicate`, failed day read, notes, library, bulk per entry incl. repeats within the list, all refused, request budget), read-back (summary, parse warnings for dropped steps/repeats/units/durations/missing duration/fields, ≤ 600 characters, failed read-back, updates, library workouts, library events without steps), deletions (echo, not found, already gone, read error) and a tools/call round trip of the new parameters. - `tests/test_stage3b_fixes.py` (9): one regression test per bug; all 9 fail on `main`. - Existing tests adapted where answers now carry the read-back or the mocks have to answer the duplicate-check GET. ## Checks - `ruff check .`, `mypy src tests` (121 files): clean. - `pytest`: 952 passed (902 before); network access stays blocked by `tests/conftest.py`. - Pylint gate `pylint --disable=C0301 --fail-under=0 --fail-on=E,F $(git ls-files '*.py')`: exit 0, no new messages compared with `main`. - Live, read-only only (credentials loaded in a subshell, key never printed, a runner that blocks every non-GET request before it leaves the process): dry runs of all eleven tools (all requests GET, 0 blocked attempts: the tools never tried to write), duplicate refusal of a real event (2026-10-06, event 142029202) by name and by steps, while a Run with the same name or text is not refused; bulk with an exact repeat (refused) and a double session (created); the read-back comparison applied to all 85 stored events of the calendar, both their own text and their own workout_doc (0 false parse warnings), 404 answers for missing ids, the JSON defaults and the six fixes. ## Not verified - No write was made against Intervals.icu (by design): the read-back after a real write, the bulk read-back and the parse warnings were verified with mocks and by applying the comparison to existing stored events, not on a freshly written event. The stored spelling of absolute paces other than `secs/km` (`secs/mile`, `secs/100m` …) and the parse of `power_zone`, `hr_zone`, `%hr`, `%mmp`, `maxeffort`, `freeride` targets are not confirmed (the calendar has none). - `add_event_from_library` dry run live only up to "No library workout found" (the athlete's library has no workouts); the full path is covered by tests. ## For connected clients After the deploy the clients should refresh the tool list (ChatGPT: refresh the connector; Claude: reconnect) to see `dry_run` / `allow_duplicate`. Answers of the write tools now end with a `Read-back:` paragraph; `delete_event` answers with text instead of the raw API JSON; `add_events_bulk` answers compact JSON with `status`/`date` per entry. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 3A: make the tool catalogue much cheaper for the AI client and easier to use correctly, without losing capability.
Token size of
tools/listMeasured with tiktoken
cl100k_baseon the compact JSON of thetools/listresult ({"tools": [...]}as FastMCP serialises it: name, description, input schema, output schema, annotations), built withcatalogue(permissions, toolset)for every configuration. chars/4 in brackets. "Before" ismaind951844 (dumped frommcp.list_tools()with the same counter).MCP_PERMISSIONSreadread,write(live ChatGPT connector)allreadread,writeBreakdown at
read,write, before → after: descriptions 23,598 → 7,997; input schemas 14,001 → 12,963 (every parameter now has a description, constrained ones an enum; titles andnullwrappers removed); output schemas 1,729 → 0. Server instructions: 1,255 characters (264 tokens) in theinitializeanswer. Longest description before: 6,608 characters (add_or_update_event; 48 tools over 900, 24 over 1,800); after: 895, all ≤ 900.Per tool (all classes, full tool set): description chars, description / input / output schema tokens, total tokens before → after
add_or_update_event(core)create_library_workoutcompare_workoutsfind_similar_intervalsanalyze_workout_executionanalyze_climbsget_durabilityget_power_hr_efficiencyget_load_projection(core)get_training_summary(core)get_activity_report(core)get_intensity_distributionget_long_ride_fatigue_profileupdate_wellness(core)compare_power_streamsget_activity_intervals(core)get_activity_streamsget_coach_context(core)get_wellness_trends(core)get_training_loadadd_events_bulkdelete_events_by_date_rangeget_best_efforts(core)update_sport_settingsget_recovery_snapshot(core)get_fatigue_resistancecompare_best_effortsget_activities(core)get_submax_test_trendsget_nutrition_summaryget_pace_curvesget_athlete_power_curvesvalidate_workout(core)get_activity_details(core)update_custom_itemget_hr_curvesadd_or_update_noteget_fueling_analysis(core)get_wellness_data(core)update_activity(core)create_custom_itemget_activity_data_auditget_plan_complianceget_sport_settings(core)get_events(core)get_training_zonesget_training_plan(core)get_activity_histogramget_gear_detailsget_weekly_summaryadd_event_from_libraryget_event_by_id(core)get_athlete_profilepreview_workout(core)list_activity_streamsget_library_workoutdelete_library_workoutdelete_custom_itemget_gear_listget_server_status(core)get_workout_libraryget_custom_item_by_iddelete_eventget_custom_itemsadd_activity_messageget_activity_messagesget_guide(core)What changed
tests/test_catalogue.pykeeps budgets per permission set and tool set (chars/4 with ~10 % headroom).@toolregisters every tool withstructured_output=False(centrally inmcp_instance.tool); no tool returns a JSON object on purpose (JSON output is JSON text). On the wire (streamable HTTP viabuild_http_app, auth off, live read-only call ofget_coach_context):outputSchemaon 58 of 58 tools, the result carried the text andstructuredContent: {"result": <same text>};outputSchema,content: [{type: text}]only,structuredContentabsent,isError: false. ChatGPT and Claude read the text content as before.api_key: removed from every tool function and its schema (and from the private helpers of the tools package, the tool guard and the time zone resolver);make_intervals_requestusesAPI_KEYfrom the environment as before. A client that still sendsapi_keyhas it ignored.athlete_idstays, described as "Default: the configured athlete".Annotated[..., Field(description=...)], shared aliases inutils/params.py); constrained parameters areLiteralenums (output_format,detail_level,sort_by,group_by,zone_type,zone_basis,threshold_as,metric,target,environment/indoor_outdoor,temperature_source,threshold_unit,category,item_type,visibility,topic), normalised case-insensitively, an empty value means the default; integer scales carryminimum/maximum(wellness 1-4, feel 1-5, RPE 1-10) while the tools keep their own range messages. The long explanations moved, complete, intoguides/methods/<topic>.md, served asintervals://methods/<topic>(activity-data, execution, climbs, power-meters, load, intensity, durability, summary, comparisons, fatigue, wellness, fueling); the workout syntax intointervals://workout-syntax, referenced from the write and validate tools. Workout fields Intervals.icu fills itself are hidden from the workout_doc schema (still accepted).get_coach_contextfor weekly analysis,get_activity_reportfor one activity, compact first, missing values (today's wellness) are not normal, writes only on explicit request, preview/dry-run first, read the workout syntax before writing workouts.MCP_TOOLSET=full(default; the live connector keeps every tool) orcore(23 tools):get_server_status,get_guide,get_coach_context,get_training_summary,get_load_projection,get_events,get_event_by_id,get_training_plan,get_activities,get_activity_report,get_activity_details,get_activity_intervals,get_best_efforts,get_fueling_analysis,get_recovery_snapshot,get_wellness_data,get_wellness_trends,get_sport_settings,validate_workout,preview_workout,add_or_update_event✎,update_wellness✎,update_activity✎ (MCP_PERMISSIONSstill applies; descriptions, prompts and guides mark tools outside the set "(full tool set)"). Documented in the README;get_server_statusand--doctorshow the active tool set.tests/test_catalogue.py): description ≤ 900 and starts with "Use", token budget for core and full per permission set, noapi_keyin any schema or signature, no output schema, every parameter described, every constrained parameter has an enum, schema compaction keeps meaningful nulls, core set registration in a fresh process, instructions, guides complete and everyintervals://reference resolvable, every tool named in prompts/guides exists, the new prompts, and an MCP round trip (initialize, tools/list, tools/call, prompts/list, resources/list, resources/read) through a real client session.race_week,fueling_review,plan_health_check,coach_handoff;intervals://guiderewritten (phase-6 tools,include_route_history,scenario/target_date, today's wellness completeness); new read toolget_guide(topic)returns the guides for clients that cannot read resources (ChatGPT connectors only call tools), so no capability is lost by moving text out of the descriptions.get_activity_data_auditreports a failed streams request as the API error ("stream coverage not checked", JSONstreams.error) instead of "no streams"; (b) every id in a phase-6 API path goes throughseg()(a test scans every API path f-string of the package); (c)--doctorvalidatesMCP_TOOL_MAX_REQUESTS,MCP_TOOL_TIMEOUT_S,MCP_MAX_OUTPUT_CHARS,ATHLETE_TIMEZONE,MCP_TOOLSET,OAUTH_REFRESH_REUSE_GRACE/_REVOKE,OAUTH_LOGIN_GLOBAL_RATE_LIMIT,OAUTH_REQUIRE_PRIVATE_KEY_JWT(each problem listed; OAuth ones are warnings withoutMCP_AUTH=oauth); (d) daily metrics (Endurance Score, Hill Score/Strength/Endurance, Fitness Age, race predictions, acute training load, VO2max, daily goals) are a group of their own in today's completeness (daily_metric, own line) viautils/wellness_completeness.py; (e) CHANGELOG 1.0.0b1 regrouped into Security / Added / Changed / Fixed with the phase labels as subsections (a script checked that no line was lost), plus the stage 3 entries.Changed tool signatures
api_keyremoved from the 65 tools that had it (all butpreview_workout). For Python callers this is breaking: in 45 tool functionsapi_keywas not the last parameter, so positional arguments after it move one place to the left (get_activities("i1", None, "2026-01-01")would now bind the date toend_date); call tool functions with keyword arguments. No in-repo caller relies on that, and MCP clients are not affected (a strayapi_keyargument is ignored).get_server_status(api_key, output_format)→get_server_status(output_format).get_guide(topic="usage")(read); topics also acceptintervals://methods/load,methods/load,workout_syntax...output_formatkeeps the old leniency (anything but json = text); other enums reject unknown values on MCP calls (main rejected most of them with a tool error already, see the review table).item_typeincludesFITNESS_TABLE.get_load_projection(target_date=...)now also accepts today (start of race day).main).Review fixes (second round, commits 2ced45e and bcc305a)
output_format(non-json = text; streams: text → summary, csv → full); R28-2get_guidetopic forms; R28-3 MINS_KM/MINS_MILE value format in the workout_docValueschema; R28-4race_weekasks for/confirms the race date,target_date= today allowed; R28-5 core set + 4 tools and "(full tool set)" marks; R28-6FITNESS_TABLE; R28-7 wording above; R28-8 one parser for--doctorand the server (inf/nan/non-positive limits fall back to the default; the doctor names the value really used).tests/conftest.pyblocks every non-loopback network access (a leak fails the test);test_get_activitiesno longer calls intervals.icu for the gear catalogue.serverInfo.websiteUrlandicons(served URLs withMCP_PUBLIC_URL, else the SVG asdata:URI); unauthenticated/favicon.ico,/favicon.png,/icon.png,/icon.svg,/apple-touch-icon.png; logo on the sign-in page (CSPimg-src 'self') and in the README; assets inintervals_mcp_server/assets(wheel) anddocs/assets; CI checks guides and icons from the installed wheel.For connected clients (ChatGPT / Claude)
After the deploy the clients must refresh the tool list (ChatGPT: refresh the connector in the app settings; Claude: reconnect the connector) to get the new descriptions, schemas,
get_guideand the new prompts. The OAuth grant stays valid; no re-consent is needed. The live connector keeps every tool (MCP_TOOLSETdefaults tofull).Checks
ruff check .,mypy src tests(116 files): clean.pytest: 902 passed (no network access: guarded in conftest).pylint --disable=C0301 --fail-under=0 --fail-on=E,F $(git ls-files '*.py'): exit 0 (no new messages in the changed modules).build_http_app(mcp, "streamable-http"), auth off, live Intervals.icu read-only (non-GET requests blocked, key never printed): initialize (instructions 1,255 chars), tools/list (59 tools at read,write, 97,444 chars, no output schema, noapi_key), tools/callget_coach_context(detail_level="Compact")(one text block, nostructuredContent), prompts/list (13), resources/list (15).Notes found while rewriting (not changed here)
get_durabilitytext labels the efficiency-factor recent mean "last 7 d" even with anotherrecent_days.compare_workouts: a reference activity older than the newestlimitactivities is cut and the pattern silently falls back to the newest activity; withquery+end_date+ reference and nostart_dateonly 90 days are fetched although the note says 365.compare_best_effortswithactivity_idsdoes not de-duplicate ids, and fetches all ids beforelimitapplies.get_coach_contextandget_training_summaryignoresdetail_level.get_activities/get_wellness_data: a pastend_datewithoutstart_dategives an inverted range (start defaults to 30 days before today).🤖 Generated with Claude Code