zwift-mcp
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分以内に一致します。これは、スタートの旗が下りる前にペン(スタートエリア)で過ごす時間です。
Related MCP server: catence
セットアップ
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))".env に ZWIFT_MCP_AUTH_TOKEN として設定してください。
最初の同期の前に、エンドポイントがこのコードが想定する形のままであることを確認してください。いずれもドキュメント化されておらず、安定していません:
.venv/bin/python probe_zwift_api.pyprobe_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 DesktopHTTPモードの場合:
/mcp and /mcp/ MCP streamable HTTP endpoint (both spellings work)
/api/v1/... REST API, same bearer token
/api/v1/health liveness probe, unauthenticatedURLと 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://athlete、zwift://activities、zwift://activities/recent、zwift://stats/summary、zwift://stats/monthly、zwift://training/daily、zwift://power/curve、zwift://racing/results
読み取りツール
ツール | 説明 |
| スポーツ、ワールド、日付、距離、時間、パワー、レースでフィルタリング |
| サマリー、ラップ、ゾーン内時間、そのライドのカーブ、レース結果 |
| CTL / ATL / TSB 付きの日別TSS |
| 週次または月次のボリューム |
| 閾値と、そこから導出されるパワー/心拍ゾーン |
| 全体、スポーツ別、ワールド別の合計 |
| 期間ごとの最高平均パワー、ローカル vs ZwiftPower |
| カテゴリーと順位付きのZwiftPower結果 |
| 1つのレースと、同期済みの場合は完走者の一覧 |
| Zwiftプロフィール、ZwiftPowerプロフィール、現在のフォーム状態 |
| データベース全体に対する読み取り専用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-statecurl -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は、どちらが壊れたかを知らせるためにあります。
This server cannot be deployed
Maintenance
Related MCP Connectors
Remote MCP server for training, nutrition, wellness, and performance data with OAuth 2.0.
MCP server for Withings health data — sleep, activity, heart, and body metrics.
Strava MCP tools for AI: athletes, activities, segments, clubs, routes. Powered by HAPI MCP server.
Manage your endurance training data and race preparation
Related MCP Servers
- AlicenseBqualityAmaintenancePrivacy-first MCP server for Strava activities, streams, routes and training data.29189 npm3MIT
- AlicenseBqualityAmaintenanceMCP server for local fitness-data extraction and analysis from Garmin Connect, Intervals.icu, and Strava. Provides read-only analytical tools over DuckDB and targeted Strava enrichment.35338 npmAGPL 3.0
- AlicenseNot gradedqualityAmaintenanceMCP server that mirrors your Garmin data into a personal database and exposes tools for health summaries, training load, muscle readiness, and race analysis, with optional chat-driven insights via stdio or HTTP.1MIT
- AlicenseNot gradedqualityCmaintenanceA read-only MCP server that synchronizes Garmin Connect summaries into a local SQLite cache and provides tools to query daily activity, recent activities, and recovery data.MIT