Skip to main content
Glama
benniblau

zwift-mcp

by benniblau

zwift-mcp

Zwiftのトレーニング履歴をローカルSQLiteデータベースに保存し、MCPクライアント(ClaudeやMCPに対応するその他のクライアント)およびREST API経由で公開します。

2つのコンポーネント:

  • zwift_downloader.py — cronジョブで、ZwiftゲームAPIとZwiftPowerからデータを取得し、元のFITファイルを解析して、すべてをSQLiteに保存します。

  • mcp_server.py — ステートレスなstreamable-HTTP MCPサーバーとREST APIを提供し、両方が1つのベアラートークンの背後で同じデータベースを読み取ります。

2つのソースの理由

ZwiftとZwiftPowerはそれぞれ異なる情報を保有しており、どちらも完全ではありません:

Zwift API

ZwiftPower

ソロやワークアウトを含むすべてのライド

❌ レースのみ

心拍数、ケイデンス、スピード、最大パワー

✅ 詳細エンドポイント

✅ レースごと

ラップと毎秒のストリームデータ

FITファイル内のみ

レース順位、カテゴリー、フィールド

クリティカルパワーカーブ

✅ (レースのみ)

トレーニング負荷、CTL/ATL/TSB

したがって、ダウンローダーはZwiftからアクティビティ一覧を取得し、一覧に含まれないサマリーフィールドを詳細エンドポイントから取得し、ラップとストリームのために各FITをダウンロードして解析し、ZwiftPowerからレース結果を取得し、トレーニング負荷をローカルで計算します。2つのサイトの結果は開始時刻でリンクされます。というのも、両者に共通の識別子がないためです。実際のデータでは、約3分以内に一致します。これは、スタートの旗が下りる前にペン(スタートエリア)で過ごす時間です。

セットアップ

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env       # fill in ZWIFT_USER / ZWIFT_PASS

サーバー用のベアラートークンを生成してください:

python3 -c "import secrets; print(secrets.token_urlsafe(32))"

.envZWIFT_MCP_AUTH_TOKEN として設定してください。

最初の同期の前に、エンドポイントがこのコードが想定する形のままであることを確認してください。いずれもドキュメント化されておらず、安定していません:

.venv/bin/python probe_zwift_api.py

probe_zwift.json(gitignore対象)に生のペイロードを書き出し、サマリーを出力します。セクションがエラーを報告した場合は、データベースにNULLを入れるのではなく、同期前にマッピングを修正してください。

同期

.venv/bin/python zwift_downloader.py                 # incremental
.venv/bin/python zwift_downloader.py --days 30
.venv/bin/python zwift_downloader.py --since 2024-01-01
.venv/bin/python zwift_downloader.py --full          # re-fetch everything
.venv/bin/python zwift_downloader.py --with-samples  # + per-second streams
.venv/bin/python zwift_downloader.py --backfill-detail
.venv/bin/python zwift_downloader.py --redo-detail   # re-parse cached FIT files
.venv/bin/python zwift_downloader.py --skip-fit      # no FIT pass (fast)
.venv/bin/python zwift_downloader.py --zp-only       # ZwiftPower only
.venv/bin/python zwift_downloader.py --skip-zp       # game API only
.venv/bin/python zwift_downloader.py --with-zp-fields  # + full race fields
.venv/bin/python zwift_downloader.py --summary       # print stats, sync nothing

毎晩実行するcronエントリ:

30 4 * * * cd /opt/zwift-mcp && .venv/bin/python zwift_downloader.py >> sync.log 2>&1

最初の実行は時間がかかります。アクティビティごとにFITファイルをダウンロードするためです。以降の実行では新しいものだけを取得し、キャッシュされたFITは再ダウンロードされません。

サーバーの実行

.venv/bin/python mcp_server.py                    # HTTP (default), port 8081
.venv/bin/python mcp_server.py --transport stdio  # local Claude Desktop

HTTPモードの場合:

/mcp  and  /mcp/     MCP streamable HTTP endpoint (both spellings work)
/api/v1/...          REST API, same bearer token
/api/v1/health       liveness probe, unauthenticated

URLと Authorization: Bearer <token> を使用してMCPクライアントを接続します。

本番環境

deploy/zwift-mcp.service は、/opt/zwift-mcp 配下へのインストール向けの堅牢化されたsystemdユニットです。代わりにホームディレクトリから実行する場合、ProtectHome を外す(これがあるとサービスの作業ディレクトリ自体が隠されてしまう)ことと、ReadWritePaths をインストール先のパスに向けることを意味します。

sudo cp deploy/zwift-mcp.service /etc/systemd/system/
sudo systemctl enable --now zwift-mcp
journalctl -u zwift-mcp -f

他の何も使用していないポート(ss -tlnp で確認)を選び、.env に設定します。ポートごとに1つのMCPサーバーです。

夜間の同期は、そのマシンで実行されている他の処理とずらして実行します:

15 9 * * * cd /opt/zwift-mcp && .venv/bin/python zwift_downloader.py --days 10 > download.log 2>&1

実行中のデプロイを更新するには:

cd /opt/zwift-mcp && ./deploy/update.sh

これにより、プル、新しい依存関係のインストール、ユニットの再起動、ヘルスエンドポイントの確認が行われ、追跡対象ファイルにローカルでの編集がある場合は実行を拒否します。

.env、データベース、fits/ はgitignore対象のため、プルでは決して変更されません。プルで schema/schema_zwift.sql が変更された場合、マイグレーションは存在しないことに注意してください。データベースを削除して再同期してください(キャッシュ済みのFITがあるためコストは低いです)。

データベースはWALモードであるため、夜間の同期と実行中のサーバーが互いにブロックすることはありません。この状態を維持してください。デフォルトのロールバックジャーナルでは、長時間の再計算が実行中のクエリに "database is locked" というエラーを返します。

MCPサーフェス

リソースzwift://athletezwift://activitieszwift://activities/recentzwift://stats/summaryzwift://stats/monthlyzwift://training/dailyzwift://power/curvezwift://racing/results

読み取りツール

ツール

説明

query_activities

スポーツ、ワールド、日付、距離、時間、パワー、レースでフィルタリング

get_activity_details

サマリー、ラップ、ゾーン内時間、そのライドのカーブ、レース結果

get_training_load

CTL / ATL / TSB 付きの日別TSS

get_training_trends

週次または月次のボリューム

get_training_zones

閾値と、そこから導出されるパワー/心拍ゾーン

get_activity_stats

全体、スポーツ別、ワールド別の合計

get_power_curve

期間ごとの最高平均パワー、ローカル vs ZwiftPower

get_race_results

カテゴリーと順位付きのZwiftPower結果

get_race_details

1つのレースと、同期済みの場合は完走者の一覧

get_athlete_profile

Zwiftプロフィール、ZwiftPowerプロフィール、現在のフォーム状態

execute_sql

データベース全体に対する読み取り専用SELECT

書き込みツールrename_activity(Zwiftにプッシュしてからローカルを更新)、set_local_annotation(ローカルタグとメモ。どこにも送信されません)、get_activity_fit_file(元のFITの保存場所を返します)。

REST API

GET    /api/v1/health                          unauthenticated
GET    /api/v1/athlete
GET    /api/v1/activities?sport=&world_id=&start_date=&races_only=&limit=
GET    /api/v1/activities/{id}?include_samples=
PATCH  /api/v1/activities/{id}                 {"name": …, "local_notes": …}
GET    /api/v1/activities/{id}/laps
GET    /api/v1/activities/{id}/samples?limit=&offset=
GET    /api/v1/stats/summary
GET    /api/v1/stats/monthly
GET    /api/v1/daily-metrics?start_date=&end_date=&limit=
GET    /api/v1/power-curve?source=local|zwiftpower|both
GET    /api/v1/races?start_date=&title_contains=&limit=
GET    /api/v1/races/{event_id}
GET    /api/v1/zwiftpower/profile
GET    /api/v1/sync-state
curl -H "Authorization: Bearer $ZWIFT_MCP_AUTH_TOKEN" \
     "http://localhost:8081/api/v1/activities?races_only=true&limit=5"

データベース

athletes                   — profile, FTP, weight, lifetime totals
worlds                     — world id lookup (seeded)
activities                 — one row per ride or run
activity_laps              — from the FIT lap messages
activity_samples           — per-second stream (only with --with-samples)
activity_zone_distribution — time in zone, computed from samples + FTP
power_curve                — best mean power per duration, per activity
segment_results            — segment efforts from the Zwift API
zp_profile                 — ZwiftPower category, zFTP, racing score
zp_results                 — one row per race
zp_event_results           — full finishing fields (--with-zp-fields)
zp_critical_power          — ZwiftPower's own CP curve
daily_metrics              — derived TSS, CTL, ATL, TSB per day
sync_state                 — per-dataset watermarks

Views:
  activity_summary  — km, km/h, w/kg, TSS
  monthly_stats     — by month and sport
  weekly_load       — weekly volume and TSS
  power_curve_best  — all-time best per duration, with the ride that set it
  race_results      — ZwiftPower results joined to the local activity

保存される値はすべてSI単位です。メートル、秒、ワット、bpm、m/s。換算はビューにあります。

知っておくべきこと

  • トレーニング負荷はここで計算され、取得されません。 各ライドは、当時Zwiftが保持していたFTP(profileFtp)に基づいてスケーリングされ、現在のFTPにフォールバックします。ZWIFT_FTP_OVERRIDE は古い値を置き換えます。

  • tss_source はTSSがどのように算出されたかを示します — 解析されたFITからの np、またはサマリーから推定された avg_power。推定値は大きなサージがあるライドを過小評価するため、隠されずにラベル付けされます。

  • ランのスコアリングはバイクのFTPに基づいて行われますZWIFT_RUN_FTP が設定されている場合を除きます。Zwiftのランニングパワーは同じ量ではないため、これらのTSS値は参考値として扱ってください。

  • FITファイルが詳細情報です。 それがなければ、ラップ、ストリームデータ、正規化パワー、パワーカーブはありません。

  • パワーメーターなしで記録されたランには、パワーカーブもパワーゾーンもありません。 そのFITには全ゼロのパワーチャンネルが含まれますが、それはデータがなく、存在しないことを意味します。

  • ZwiftPowerのクリティカルパワーカーブはレースのみを対象としており、しかも最近のレースだけです。カーブが空でも正常な応答であり、失敗ではありません。

  • データベースにはマイグレーションがありません。 スキーマのカラムが変更された場合は、zwift_activities.db を削除して再同期してください。fits/ にキャッシュされたFITがあるため、再ダウンロードは発生しません。

  • ZwiftPowerは別のアカウントリンクです。 APIが何も返さない場合は、ブラウザでzwiftpower.comを一度開き、Zwiftでサインインしてください。何かをクエリできる前に、プロフィールがそこに存在する必要があります。

  • ZwiftPowerの障害で同期が失敗することはありません。 レースデータはゲームデータに比べて二次的なものなので、障害は sync_state に記録され、スキップされます。

  • どちらのAPIも公開されていません。 フィールド名やエンドポイントは予告なく変更されます。probe_zwift_api.py は、どちらが壊れたかを知らせるためにあります。

-
license - not tested
-
quality - not tested
C
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 Connectors

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

  • The hockey data API. Stats, odds, and everything between. REST API and MCP server.

  • MCP server wrapping the Tesla Fleet API and TeslaMate API

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/benniblau/zwift-mcp'

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