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 → SettingsDeveloper SettingsAPI 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.

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 MCP_AUTH_TOKEN (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 sul tuo intervals.icu. Rotazione = cambio env var + redeploy. Per condividere il server con altri 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).

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/.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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.
    58
    52
    MIT
  • A
    license
    B
    quality
    B
    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
    A
    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

View all related MCP servers

Related MCP Connectors

  • MCP server for Withings health data — sleep, activity, heart, and body metrics.

  • List, fetch, create, edit (replace), delete and schedule structured workouts on Garmin Connect (runn

  • Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ale7canna/intervals-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server