intervals-mcp

intervals-mcp

MCP server for intervals.icu that reads training data (activities, wellness, calendar) and creates structured workouts on the calendar, which are pushed to devices like Garmin, Wahoo, Zwift, Coros, Suunto via the official API.

Category
访问服务器

README

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

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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选