Scheduled programming sync through MCP
This guide is for a trusted source that owns a coaching program’s scheduled sessions. It uses deterministic MCP calls and caller-owned externalRef values. The sender makes no LLM calls and needs no Taikan API key (a workouts_parse call runs Taikan’s parser on the server).
Source text and score slots are accepted only through the MCP adapter; in-app Spotter calls must omit both so generated rewrites cannot invent coach-authored score labels or break whiteboard text anchors — in the app, the coach’s own words reach a workout through workouts.parse. Use ?orgId=<uuid> in the MCP URL so every call resolves one organization.
One workout per day
One source workout is one coach day (see the MCP server README): one workout per program and date (or per separate class); the coach’s parts (“Warm-up”, “Lift”, “Conditioning”) are section parts, the day brief is the workout description, a part’s note, clock window and optional flag live on the part. Publish one program session per day; several workouts on one date are only for genuinely separate sessions (AM and PM, separate classes), ordered by sortOrder. Either send the coach’s text verbatim to workouts_parse with the day’s date and save: { externalRef, programId } — it saves when every movement matched; otherwise re-send the same call with save.resolutions ([{ movementKey, mention, exerciseId }] from the returned unresolved and their candidates) and that call saves — or write the structure yourself with workouts_upsert (workout + parts + sections in one call, keyed by externalRef, e.g. source:<tenant>:<program>:<date>, plus date). A day without a title is named for its date and program, never its brief. MCP section writes fill omitted score slots, movement marks and workout scoring from sourceText and return warnings; fix and re-send rather than ignore them, and pass auto_confirmed_movement (matches a model confirmed) on to the coach.
Source identity and reconciliation
Assign one stable externalRef to each source workout and one to each source session. A source session is one workout on one date in one program. Use a source namespace such as source:<tenant>:<session-id> and keep it unchanged across edits and retries. Two sessions on one day need two refs and explicit sortOrder values.
- Call
read_workouts_listwithexternalRefPrefixandlimit: 50. FollownextCursorwhilehasMoreis true. The sync listing uses immutable workout IDs, so edits and deletions do not shift pages. Collect the complete inventory before any writes. ExactexternalReflookup is useful before an individual upsert. Prefix characters%,_, and backslash are literal. - Call
programs_sessions_listwithexternalRefPrefixto enumerate the source’s live sessions. Omitfromandtofor a complete reconciliation; use both for a bounded read. Returnedpublished,startedCount,completedCount,loggedCount,hasActivity, andexceptionsCountshow delivery and athlete state. - Upsert changed workouts: one
workouts_upsert(withdate) or one savingworkouts_parseper day, either of which replaces every section.sourceTextis verbatim member text.scoresholds up to four score slots per section, each with a unique stable key, label, and scoring kind — only where the coach wrote a score line. Resend all source text, scores, parts and movements on every replacement; omitted content is cleared (omitted slots and marks are re-derived fromsourceText). Resolve exercise IDs withread_exercises_resolve_batchbefore writing movements. - Publish each session once with
programs_session_create, passing the stable sessionexternalRef, program ID, workout ID, date, andsortOrder. Resending identical programming reuses the release. Editing content cuts a new release for rows that have not been started or personalized. - After the source inventory and Taikan inventory are complete, remove only refs absent from the authoritative source snapshot. Call
programs_session_removewith exactly one ofsessionIdorexternalRef. With no athlete activity, the session and untouched assignments are soft-deleted. If an athlete started, completed, skipped, personalized, or logged work, the session is unpublished withoutcome: "unpublished"andreason: "athlete_activity". Protected athlete rows retain their history; untouched rows are hidden. A later enrollee receives a hidden row, which a later publish can make visible. Retrying the same removal is safe.
Read the full result of every MCP call before continuing. An MCP response can contain structuredContent; isError: true means the tool failed even when HTTP succeeded. Never treat a truncated read as a full source inventory. The workout list uses hasMore and nextCursor; the session list currently returns every matching live session in one response, so keep source namespaces and date windows appropriately bounded for large programs.
Minimal OAuth PKCE client
scripts/mcp-programming-sync.mjs is a Node 22 example. It discovers Clerk from Taikan’s protected-resource metadata, registers a public client through DCR when no client ID is configured, uses browser authorization_code with S256 PKCE, requests profile email offline_access, and stores rotating tokens in a mode 0600 file. It locks the state file to one process and atomically replaces it after refresh before the next MCP request. A crash during the refresh response before the replacement may require a new login.
Each example command owns the lock for that command. A production sender should hold one lock across the entire read, diff, write, and verification run so two sync processes cannot act on the same source inventory at once.
export TAIKAN_MCP_URL='https://<api-host>/mcp?orgId=<org-uuid>'
node scripts/mcp-programming-sync.mjs login
node scripts/mcp-programming-sync.mjs list
node scripts/mcp-programming-sync.mjs call programs_sessions_list '{"externalRefPrefix":"source:studio-1:"}'Set TAIKAN_MCP_STATE to use another state file, TAIKAN_OAUTH_PORT to change the loopback callback port (default 8765), or TAIKAN_OAUTH_CLIENT_ID for a preconfigured public client. TAIKAN_MCP_TIMEOUT_MS bounds one tools/call (default 180000; at least 1000) — a workouts_parse runs a model for tens of seconds; OAuth, metadata and protocol handshakes keep a 30-second bound. A configured client must allow http://127.0.0.1:<port>/callback, the authorization-code grant, PKCE S256, and refresh tokens. If using DCR, enable it for the Clerk instance. The example uses the authorization_code flow; its DCR request also advertises refresh_token to renew access. It does not request device authorization. Clerk’s OAuth guide documents public clients, PKCE, scopes, DCR, and separately configured device authorization.
The example registers an OAuth application only when login runs without a configured client ID. Validate registration, authorization, token refresh, and MCP access against the target environment before scheduling unattended syncs.