Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesMcp ServerScheduled programming sync through MCP

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.

  1. Call read_workouts_list with externalRefPrefix and limit: 50. Follow nextCursor while hasMore is true. The sync listing uses immutable workout IDs, so edits and deletions do not shift pages. Collect the complete inventory before any writes. Exact externalRef lookup is useful before an individual upsert. Prefix characters %, _, and backslash are literal.
  2. Call programs_sessions_list with externalRefPrefix to enumerate the source’s live sessions. Omit from and to for a complete reconciliation; use both for a bounded read. Returned published, startedCount, completedCount, loggedCount, hasActivity, and exceptionsCount show delivery and athlete state.
  3. Upsert changed workouts: one workouts_upsert (with date) or one saving workouts_parse per day, either of which replaces every section. sourceText is verbatim member text. scores holds 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 from sourceText). Resolve exercise IDs with read_exercises_resolve_batch before writing movements.
  4. Publish each session once with programs_session_create, passing the stable session externalRef, program ID, workout ID, date, and sortOrder. Resending identical programming reuses the release. Editing content cuts a new release for rows that have not been started or personalized.
  5. After the source inventory and Taikan inventory are complete, remove only refs absent from the authoritative source snapshot. Call programs_session_remove with exactly one of sessionId or externalRef. 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 with outcome: "unpublished" and reason: "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.