Skip to main content
Glama

intervals-mcp

Server MCP per intervals.icu: legge i dati di allenamento e crea allenamenti strutturati sul calendario, da cui intervals.icu li spinge sull'orologio (Garmin, Wahoo, Zwift, Coros, Suunto).

Usa l'API pubblica ufficiale (https://intervals.icu/api/v1, Basic auth con API key).

Setup

npm install
npm run build
cp .env.example .env    # e incolla la tua API key

L'API key si trova su intervals.icu → Settings → Developer Settings → API Key. L'athlete id (i123456) viene rilevato automaticamente dalla key; se serve forzarlo, c'è INTERVALS_ATHLETE_ID nel .env.

Verifica che tutto risponda:

npm run smoke            # solo lettura
npm run smoke -- --write # crea un allenamento di test domani, lo verifica e lo cancella

Registrazione in Claude Code

claude mcp add intervals -- node /percorso/assoluto/intervals-mcp/dist/stdio.js

La key non va nella config del client: il server legge il .env accanto al package. In alternativa, per tenerla nella config: claude mcp add intervals --env INTERVALS_API_KEY=... -- node .../dist/stdio.js.

Per Claude Desktop, in claude_desktop_config.json:

{
  "mcpServers": {
    "intervals": {
      "command": "node",
      "args": ["/percorso/assoluto/intervals-mcp/dist/stdio.js"]
    }
  }
}

In sviluppo: npm run dev (tsx, senza build).

Related MCP server: Intervals.icu MCP Server

Deploy remoto su Vercel (opzionale)

Serve solo se vuoi usarlo senza il Mac acceso — da claude.ai, dal telefono, o per condividerlo. In locale lo stdio resta più semplice e la chiave non si muove dalla tua macchina.

api/mcp.ts è una Vercel Node function che espone lo stesso server su Streamable HTTP, in modalità stateless (server e transport costruiti per richiesta, enableJsonResponse): l'unica che funziona su serverless, senza Redis né sessioni in memoria. Nessun framework: niente Next.js, solo la function e l'SDK ufficiale.

Non serve creare nulla dalla dashboard: vercel link crea il progetto. L'ordine conta, perché env add funziona solo su una directory già linkata.

npm i -g vercel
vercel login
vercel link                                  # crea/collega il progetto (scrive .vercel/, gitignorato)
npm run token                                # genera il segreto, copialo
vercel env add MCP_AUTH_TOKEN production
vercel env add INTERVALS_API_KEY production
vercel --prod                                # primo deploy

Se vuoi che funzionino anche i deploy di preview, ripeti gli env add per preview: le variabili sono per-ambiente, e un preview senza di esse risponde 500.

vercel.json dichiara outputDirectory: "public" con una pagina statica minima: senza una directory di output, un progetto con un buildCommand fa fallire il build ("No Output Directory named 'public' found").

Env var richieste sul server: INTERVALS_API_KEY, MCP_AUTH_TOKEN, opzionale INTERVALS_ATHLETE_ID. La API key di intervals.icu resta lato server: nel client finisce solo il token.

Più atleti sullo stesso deploy

Ogni utente ha il suo token, e il token sceglie quale API key di intervals.icu serve la richiesta. Il nome lega token e key:

npm run token -- simone                              # stampa il token e i comandi da lanciare
vercel env add MCP_USER_SIMONE_TOKEN production         # il token appena generato
vercel env add MCP_USER_SIMONE_INTERVALS_KEY production # la API key di Simone
vercel env add MCP_USER_SIMONE_ATHLETE_ID production    # opzionale, come INTERVALS_ATHLETE_ID
vercel --prod                                           # le env var valgono dal deploy successivo

Simone usa https://<deploy>/mcp/<suo token> e vede solo il suo account. La coppia MCP_AUTH_TOKEN + INTERVALS_API_KEY resta valida come utente default, quindi gli URL già in uso non cambiano. Revocare un utente = cancellare le sue MCP_USER_<NOME>_* + redeploy. Il server non parte (500) se la configurazione è ambigua o incompleta: un token senza key o viceversa, un token sotto i 24 caratteri, due utenti con lo stesso token. Nei log compare solo il nome dell'utente, mai token o key.

Due modi per autenticarsi, perché i client differiscono

# Claude Code: header custom (supportati)
claude mcp add --transport http intervals-remote https://<deploy>/mcp \
  --header "Authorization: Bearer <token>"

Per i client che non mandano header custom — su claude.ai l'auth via request header esiste ma è in beta/rollout — il token va nel path, che è solo un URL:

https://<deploy>/mcp/<token>

Le rewrite in vercel.json mappano /mcp e /mcp/:token sulla function. Il server accetta il token da Authorization: Bearer, x-api-key, x-auth-token, dal path o da ?token=.

Preferisci l'header: un segreto nel path finisce nei log di richiesta. Il confronto è a tempo costante su digest SHA-256, il 401 risponde con WWW-Authenticate e non rivela nulla, e il server si rifiuta di partire senza alcun token configurato (o con un token più corto di 24 caratteri): meglio un 500 che un endpoint aperto che scrive sul tuo account.

Il token vale quanto la API key — chi lo ha legge e scrive su quell'account intervals.icu. Rotazione = cambio env var + redeploy. Chi gestisce il deploy custodisce le API key di tutti gli utenti: per un servizio aperto a sconosciuti servirebbe OAuth vero, con il client_id da richiedere a david@intervals.icu.

npm test copre i casi di autenticazione (token valido/errato/assente, le varie vie, rotta non confusa per token, scelta dell'utente e configurazioni rotte).

Il coach

Le regole operative (soglie da verificare prima di scrivere, sintassi, conferma prima di scrivere in calendario) stanno nelle instructions del server in src/server.ts: vengono inviate a qualunque client all'initialize, quindi valgono anche da claude.ai e dal telefono senza ripeterle da nessuna parte.

Sopra ci va il "personaggio", che è per-client:

  • claude.ai — crea un Project, attiva il connector e incolla nelle istruzioni il contenuto di docs/coach-project-instructions.md. È in italiano perché fissa anche la lingua delle risposte.

  • Claude Code — .claude/agents/coach.md, subagent con model: opus.

Entrambi definiscono un coach che prende l'iniziativa: legge form, storico e calendario da sé, propone con il motivo di ogni seduta, e scrive sul calendario solo dopo conferma.

Nessun vincolo di metodo, per scelta. I prompt contengono solo ciò che l'agent non può dedurre — i fatti sull'atleta (rientro da infortunio, caldo, carico calcolato da HR per scelta, soglia di passo non verificata), le regole tecniche di scrittura e il limite di sicurezza sul dolore. Volumi, intensità e progressione le decide l'agent, e se gli serve un vincolo lo chiede. I numeri li rilegge ogni volta dai tool, così non invecchiano nel prompt.

Modello: Opus per pianificare e analizzare, Sonnet per le domande di tutti i giorni.

Tool disponibili

Lettura

Tool

Cosa fa

get_athlete

Profilo + soglie per sport (FTP, indoor FTP, LTHR, max HR, threshold pace, zone) + configurazione push Garmin

list_activities

Attività completate in un intervallo di date (default: ultimi 30 giorni)

get_activity

Dettaglio di una attività: load, intensità, tempo in zona, decoupling, EF, running dynamics

get_activity_intervals

Intervalli/lap rilevati, con potenza, HR, passo, cadenza, load per ripetuta

search_activities

Ricerca per nome o #tag

get_wellness

Fitness (CTL), Fatigue (ATL), Form (TSB), peso, HRV, sonno, readiness… (default: 14 giorni)

list_calendar_events

Allenamenti pianificati e altri eventi; con resolve=true mostra i target già convertiti in watt/bpm/m·s⁻¹

get_event

Un singolo evento del calendario

workout_syntax_guide

Riferimento completo della sintassi workout di intervals.icu

Scrittura

Tool

Cosa fa

create_workout

Crea un allenamento strutturato sul calendario e (default) lo rilegge con i target risolti per verifica

create_workouts

Versione bulk: una settimana o un blocco in una sola chiamata, con upsert su external_id

update_workout

Modifica un evento esistente (solo i campi passati)

delete_event

Cancella un evento

set_wellness

Aggiorna i valori wellness di una data

check_garmin_push

Diagnostica del push: upload attivo?, ultimo upload, range applicati, push_errors sugli allenamenti in arrivo

Come arrivano gli allenamenti sul Garmin

  1. create_workout scrive un evento category: "WORKOUT" sul calendario; il campo description contiene l'allenamento nella sintassi testuale di intervals.icu.

  2. intervals.icu lo interpreta e ne costruisce la struttura (workout_doc).

  3. Se su intervals.icu → Settings, nel box Garmin, è attivo Upload planned workouts (con Garmin Connect autorizzato), i prossimi giorni di calendario vengono caricati su Garmin Connect e da lì arrivano all'orologio.

  4. Eventuali problemi finiscono in push_errors sull'evento → check_garmin_push.

Scrivendo i target in percentuale (di FTP / LTHR / max HR / threshold pace) l'allenamento segue automaticamente le soglie correnti dell'atleta: la conversione in watt/bpm/passo avviene al momento del push, e Garmin riceve un range (ampiezza configurabile nelle impostazioni Garmin di intervals.icu).

Esempio di description per una sessione di corsa:

Warmup
- 12m 70% Pace

Main set 5x
- 1km 100% Pace
- 90s 60% Pace

Cooldown
- 8m 65% Pace

Attenzione: m significa minuti, i metri si scrivono mtr (400mtr). La sintassi completa è in src/workout-syntax.ts, esposta anche come tool (workout_syntax_guide) e come risorsa MCP (intervals://workout-syntax).

Troubleshooting: l'allenamento arriva sul Garmin senza target

Sintomo: sull'orologio gli step ci sono, con distanze e nomi giusti, ma ognuno mostra "No Target". Su intervals.icu il workout_doc sembra perfetto.

Causa: manca la soglia del tipo di target usato nelle sport settings di quello sport (threshold pace per il passo, FTP per la potenza). In quel caso intervals.icu elimina i target dal file che manda al dispositivo — anche quando l'allenamento usa valori assoluti, dove la soglia non servirebbe a niente. L'HR non ne soffre se LTHR o max HR sono impostate.

Verificato decodificando il FIT generato:

curl -s -u "API_KEY:$INTERVALS_API_KEY" \
  "https://intervals.icu/api/v1/athlete/0/events/<eventId>/download.fit" -o w.fit
python3 scripts/fitdump.py w.fit

Senza threshold pace ogni step esce target=open; impostata la soglia, lo stesso evento esporta target=speed con i range corretti. create_workout ora fa questo controllo da sé e restituisce un device_export_warning prima che il problema arrivi all'orologio.

Nota: l'upload parte a fronte di una modifica dell'evento. Sistemare la soglia non ricarica necessariamente un allenamento già spinto — va toccato l'evento (basta riscriverne la description) per far ripartire il push.

Struttura

src/
  index.ts             bootstrap del server MCP (stdio) + risorsa sintassi
  client.ts            client HTTP: Basic auth, athlete id, errori parlanti
  format.ts            riduzione dei payload (Activity ha 183 campi) e formattazione
  dates.ts             date locali dell'atleta
  workout-syntax.ts    cheat sheet + guida completa
  tools/               athlete, activities, wellness, events
scripts/smoke.ts       verifica end-to-end sull'API reale

Note sull'API

  • Auth: Basic, username letterale API_KEY, password = la tua key.

  • Le date sono sempre locali dell'atleta, senza timezone (2026-08-20, 2026-08-20T18:30:00).

  • Le liste accettano fields= per ridurre il payload: qui è già usato con set di campi curati.

  • Spec OpenAPI completa (117 endpoint): https://intervals.icu/api/v1/docs/.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Read/write MCP server for Intervals.icu — 51 tools spanning activities, streams, wellness, calendar, gear, and sport zones, plus structured workout generation with a built-in syntax reference for cycling, running, and swimming.
    69
    1,202 PyPI
    118
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    Local MCP server for analyzing Intervals.icu accounts, creating AI-assisted training plans, and applying managed drafts to the calendar after explicit confirmation.
    14
    AGPL 3.0
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for Intervals.icu that enables AI assistants to manage athletic training data, including activities, calendar events, wellness metrics, and workout libraries.
    19
    Apache 2.0