mcpField
M
MissingMCP

MissingMCP

リモート
missingmcp.comデータ・分析17今日更新

MissingMCP について

MissingMCPは、Garmin ConnectとWHOOPのデータをリモートMCPサーバー経由でClaudeに接続する、マルチユーザー向けゲートウェイです。ユーザーごとにGarmin workerを分離し、WHOOP公式APIを利用してトークンを暗号化します。

主な機能

  1. 01OAuth 2.1認証
  2. 02Garmin ConnectとWHOOPの接続
  3. 03ユーザーごとのトークン分離
  4. 04トークンの暗号化保存
  5. 05データベースの暗号化バックアップ

活用シーン

  1. 01ClaudeでGarmin Connectのデータを確認
  2. 02WHOOPの回復、睡眠、ワークアウトを確認
  3. 03複数ユーザー向けのヘルスデータ接続を運用

MCP 対応

HTTP経由で接続できるリモートMCPサーバーを、`/garmin/mcp`と`/whoop/mcp`で提供します。MCPクライアントはOAuth認証後、各コネクターのツールを利用できます。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://missingmcp.com/garmin/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

詳細

MissingMCP

MCP サーバーは存在します。接続を複雑にする必要はありません。

smithery badge

複数ユーザーに対応し、OAuth 2.1 で保護されたゲートウェイです。Claude に足りないコネクターをホストするため、信頼できる少人数のグループが、任意の Claude クライアント(iOS、Android、Web、デスクトップ)から各自のアカウントを接続できます。主力のコネクターは Garmin Connectです。このゲートウェイは変更されていないgarmin_mcpワーカーをラップし、OAuth、ユーザーごとのトークン分離、リバースプロキシを追加します。WHOOPも同じ方法で提供されますが、インプロセスで動作し、WHOOP 独自の公式 OAuth v2 API を使用します。コアはアダプター方式で、3 つの転送方式をサポートしています。worker(garmin)、local(whoop、インプロセス、サブプロセスなし)、remote(MCP +ヘッダー注入。独自の OAuth を持たないホスト型 MCP があるサービス向け)です。コネクターを参照してください。リファレンスデプロイはmissingmcp.comで稼働しています。

Claude → POST /garmin/mcp (Bearer) → Gateway → 127.0.0.1:<port>/mcp (per-user garmin_mcp) → connect.garmin.com

理由

garmin_mcpは優れた MCP サーバーですが、単一ユーザー向けで、stdio のみを使用します。各ユーザーは、自分の Garmin トークンを使ってローカルで実行する必要があります。このゲートウェイにより、適切な OAuth サインインフローを備えたリモート MCP サーバーとなり、どの Claude クライアントからも HTTP 経由で接続できるようになります。技術に詳しくないユーザーは「接続」をクリックして Garmin の認証情報でログインするだけでよく、ターミナルやトークンファイルに触れる必要はありません。

特長

  • OAuth 2.1 — Dynamic Client Registration を使用した Authorization Code + PKCE(S256)。任意の Claude クライアントから接続でき、トークンを手動で扱う必要はありません。
  • Garmin のパスワードは保存されません — サインイン時に一度だけ使用します(MFA 対応)。永続化されるのは、その結果得られるセッショントークンのみです。
  • 保存時に暗号化 — トークンは AES-256-GCM で暗号化されます。GATEWAY_SECRETがなければ、DB は役に立ちません。Bearer トークンは SHA-256 ハッシュとしてのみ保存されます。
  • ユーザーごとの分離 — 各アカウントには、127.0.0.1にバインドされた専用のgarmin_mcpワーカーが割り当てられ、必要に応じて起動し、アイドル状態になると終了します。
  • セキュリティ強化 — 10 分間のみ有効な一回限りの認証コード、フォームの CSRF 対策、IP/トークンごとのレート制限、レビュー済みコミットに固定されたgarmin_mcpワーカー。
  • 外部バックアップ — 暗号化された SQLite スナップショットを S3 互換バケットに定期保存します(バックアップを参照)。
  • /で提供される案内用ランディングページ。未知のパスに対する使いやすいフォールバックとしても機能します。

デプロイ

Railway(missingmcp.com の実行環境):Dockerfileからビルドした 1 つのサービス、/dataにマウントしたボリューム、単一のレプリカ(設計上、ゲートウェイはプロセスローカルの状態を保持します)、Railway のエッジで TLS を終端します。設定の環境変数をサービスに設定します。サービスを GitHub リポジトリに接続すると、mainへのプッシュで自動的にデプロイされます。オプションの Railway バケットを使うとバックアップを利用できます(railway bucket create backupsを実行し、認証情報をBACKUP_S3_*変数として設定します)。

セルフホスト(通常の Docker):

cp .env.example .env          # set GATEWAY_SECRET, PUBLIC_URL, pin GARMIN_MCP_REF
docker build -t missingmcp .
docker run -d --name missingmcp --restart unless-stopped --env-file .env \
  -p 127.0.0.1:8080:8080 -v missingmcp-data:/data missingmcp

TLS を終端するプロキシ(Caddy、nginx、Traefik)を前段に配置します。見落としやすい要件が 1 つあります。/​<adapter>/mcpは SSE をストリーミングするため、レスポンスのバッファリングを無効にし、読み取りタイムアウトを延長してください(nginx:proxy_buffering off; proxy_read_timeout 3600s;)。続いて、https://<your-domain>/garmin/mcpを Claude のリモート MCP サーバーとして追加します。

ローカル開発

uv pip install -e ".[dev]"
uv run --extra dev pytest -q                 # run the test suite

# Run the gateway locally (no Garmin account needed to exercise the OAuth surface).
# garmin-mcp isn't on PATH locally, so point GARMIN_MCP_CMD at uvx.
GATEWAY_SECRET="$(openssl rand -base64 48)" \
PUBLIC_URL=http://localhost:8088 PORT=8088 DATA_DIR=./.localdata \
GARMIN_MCP_CMD="uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp" \
  uv run missingmcp

作業ディレクトリにある.envファイルは自動的に読み込まれるため(実際の環境変数が優先されます)、代わりに同じ値をそこに記述できます。

コネクター

各コネクターは、それぞれ独自のパスプレフィックス、OAuth フロー、Bearer トークンを使ってマウントされます(/mcp単体のエンドポイントはありません)。/のランディングページに、利用可能なコネクターが一覧表示されます。

Garmin — /garmin/mcp

Garmin Connect のメールアドレスとパスワードでサインインします(MFA に対応しています)。パスワードは Garmin へのログイン時に一度だけ使用され、その後破棄されます。保存されるのは、生成された Garmin セッショントークンのみです(AES-256-GCM で暗号化)。各アカウントには専用の garmin_mcp ワーカープロセスが割り当てられ、127.0.0.1 にバインドされます。必要に応じて起動し、アイドル状態になると終了します。

WHOOP — /whoop/mcp

WHOOP 独自の OAuth ページでサインインします。ゲートウェイが WHOOP のパスワードを取得することはなく、取得するのは生成された(暗号化された読み取り専用の)トークンのみです。リカバリー、睡眠、ストレインと日次サイクル、ワークアウト、身体測定値に対応しています。WHOOP の公式 v2 API 上でプロセス内で提供されます(ワーカーのサブプロセスや共有アップストリームはありません)。運用者は WHOOP 開発者アプリを登録する必要があります。詳しくは、Configuration の WHOOP コネクターのセットアップを参照してください。

Rohlík — もう不足していません

このゲートウェイでは一時期、Rohlík コネクターを提供していました(リモート方式:ホストされている Rohlík MCP に認証情報ヘッダーを挿入)。2026-07 に Rohlík が独自の OAuth 保護された MCPを公開したため、提供を終了しました。https://mcp.rohlik.cz/mcp を Claude のカスタムコネクターとして直接追加し、ブラウザーでサインインしてください(Rohlík のガイド)。リモート転送方式は、次に必要とするサービスに備え、コアの主要機能としてテスト済みの状態で維持されています(tests/test_remote_forward.py)。

Claude から接続する

  1. 任意の Claude クライアントで、設定 → コネクター → カスタムコネクターを追加を選択します。または CLI で、claude mcp add --transport http garmin https://<your-domain>/garmin/mcp を実行します(WHOOP の場合は claude mcp add --transport http whoop https://<your-domain>/whoop/mcp)。
  2. Claude でゲートウェイのサインインページが開きます。サービスのメールアドレスとパスワードを入力してください(Garmin では必要に応じて MFA コードも求められます)。WHOOP の場合は、WHOOP 独自の OAuth ページでサインインしてください。
  3. これで完了です。サービスのツールを Claude で利用できるようになります。

設定

環境変数(または .env)で設定します。.env.exampleを参照してください。

変数必須デフォルト説明
GATEWAY_SECRETはい—トークン暗号化用の32文字以上のキーです。プレースホルダーが設定されている場合、起動を拒否します。openssl rand -base64 48 で生成してください。
PUBLIC_URLはいhttp://localhost:8080OAuth メタデータとリダイレクトで使用する公開 URL です。
PORTいいえ8080リッスンポートです。
DATA_DIRいいえ/dataSQLite DB とユーザーごとのトークンディレクトリの保存先です。
DB_PATHいいえ$DATA_DIR/gateway.dbDB パスを上書きします。
GARMIN_MCP_CMDいいえgarmin-mcpワーカーを起動するコマンドです。garmin-mcp が PATH にない場合は、uvx … の呼び出しを使用してください。
GARMIN_MCP_REFいいえDockerfile に固定された SHADocker ビルド引数です。インストールする garmin_mcp のコミットを指定します。値の更新は、意図的にレビューしたうえで行ってください。更新後は python scripts/gen_garmin_tools.py を実行して、/garmin ページのツール一覧を更新してください。
WORKER_PORT_START / WORKER_PORT_ENDいいえ9000 / 9099ユーザーごとのワーカーに使うポート範囲です。
WORKER_IDLE_TTLいいえ900アイドル状態のワーカーを終了するまでの秒数です。
WORKER_STARTUP_TIMEOUTいいえ20ワーカーが正常な状態になるまで待つ秒数です。
MAX_WORKERSいいえ10同時に実行できるユーザーごとのワーカーの最大数です。
GARMIN_MCP_STATELESS_HTTPいいえtrueMCP セッションを使わずに、Garmin ワーカーの Streamable HTTP を実行します(すべてのワーカーに明示的に渡されます)。ワーカーが置き換えられた場合(追い出し、アイドル終了、クラッシュ、デプロイ後)も、クライアントの古い Mcp-Session-Id に対して 404「Session not found」を返さず、そのまま処理します。ゲートウェイは /garmin/mcp への GET/DELETE に対して、それ自体で 405 を返します。false はロールバック用の切り替え値です(セッションありのワーカーを使用し、GET/DELETE を転送します)。true/false、1/0、yes/no、on/off(大文字小文字を区別しません)を受け付けます(空の場合はデフォルト値)。それ以外の値を指定すると、起動時にゲートウェイが停止します。
SSO_PROBE_INTERVALいいえ0(無効)Garmin の SSO 埋め込みウィジェットに対し、ブラウザーになりすました GET プローブを、サインインの各送信元から指定秒数ごとに実行します(イベント sso-probe、フィールド via、egress_ip — cdn-cgi/trace 経由で取得する送信元の実際の送信 IP。さらに、各送信元につき egress-health:前回の実行以降に発生した実際のサインイン結果。GET ではブロックされた認証情報の POST を検出できないためです。送信元の IP が変わった場合は egress-ip-changed。間隔の最小値は60秒)。送信元 IP によるレート制限を診断するための、試行とは独立したブロックの推移を記録します(信頼性チケット12)。
GARMIN_SSO_PROXYいいえ—(直接接続)ゲートウェイ自身の Garmin 通信(サインイン、MFA、トークン検証)に使用する、専用 IP 上のプロキシ URL(http://user:pass@host:port)をカンマ区切りで指定します。Garmin が SSO をレート制限する共有ホストの送信元を避けるために使用します(チケット12)。各アカウントは(アカウントのハッシュに基づいて)1つのプロキシを使い続けます。サインインのスロットリング(failing-logins マップ):ログインメールアドレスごとに、進行中のサインインは1つまでです(同時に開始されたものは拒否されます)。実際に接続した後は2分間待機します。「blocked」になったサインインは、呼び出し内で再試行せず、そのアカウントのみを5分間クールダウンします。ある送信元で、サインインに成功しないまま異なる2つ以上のアカウントがブロックされるか、1時間以内に10回以上ブロックされると、送信元のブレーカーが作動し、30分 → 2時間 → 6時間 → 24時間の順に休止します。作動を繰り返すたびに休止時間が延び、サインインに成功すると30分に戻ります。ブレーカーが作動してからサインインが成功していない送信元は suspect として扱われます。その送信元で1回ブロックされるだけで、次の段階に移行します。あるサインインのブロックによって送信元のブレーカーが作動した場合、最初の試行が35秒未満であれば、同じ呼び出し内で次のルートを使って1回再試行します。その再試行がブロックされた場合は、アカウントのみをクールダウンします。休止レベル、休止終了時刻、送信元 IP、最後に成功/ブロックした時刻は egress_state テーブルに保存されるため、デプロイしても使い切った IP の状態はリセットされません。プローブがルートの送信元 IP の変更を検知すると、その状態はリセットされます(hydra で sudo mmcp egress を実行すると確認できます)。プロキシがリクエストをまったく処理できない場合(ポート接続拒否、CONNECT 拒否 — curl の CONNECT tunnel failed / (5)/(7)/(97)。Garmin には到達していません)、結果は blocked ではなく egress になります。アカウントはクールダウンされず、送信元は5分間休止し、同じ呼び出し内で次のルートに切り替えてサインインを続行します。すべてのルートがダウンしている場合にのみ、ユーザーには「こちら側で利用できません」というメッセージが表示されます。直接接続は最後の手段として残されます(すべてのプロキシがクールダウン中の場合のみ使用されます)。プロキシ経由のサインインではモバイル SSO 戦略をスキップします(クリーンな IP でも、データセンターのフィンガープリントでは 429 が返されます)。ワーカーとその他すべての通信は直接接続のままです。イベント:garmin-login-attempt(account、egress、egress_ip、outcome、message)、login-breaker-open / login-breaker-reject(scope:account
ACCESS_TOKEN_TTL_DAYSいいえ90ベアラートークンの有効期間です。期間終了後、ユーザーは再認証します。0 にすると有効期限が無効になります。
OPERATOR_NAME / OPERATOR_EMAILいいえ—ランディングページに表示されます。
OPERATOR_URLいいえ—運営者名のリンク先となるホームページです(フッター、信頼に関する注記)。未設定の場合はプレーンテキストになります。
WHOOP_CLIENT_ID / WHOOP_CLIENT_SECRETいいえ—WHOOP 開発者アプリの認証情報です(WHOOP コネクターのセットアップを参照)。両方とも未設定の場合、whoop コネクターは無効になります。
WHOOP_API_BASEいいえhttps://api.prod.whoop.comWHOOP API のオリジンです。テスト時のみ上書きしてください。
BACKUP_S3_ENDPOINT / BACKUP_S3_BUCKET / BACKUP_S3_ACCESS_KEY / BACKUP_S3_SECRET_KEYいいえ—サーバー外への DB バックアップに使用する、S3 互換バケットです(バックアップを参照)。4つすべてが設定されていない場合、バックアップは無効になります。
BACKUP_S3_REGIONいいえautoバケットの SigV4 リージョンです。
BACKUP_S3_URL_STYLEいいえvirtual-hostvirtual-host(Railway バケット)または path です。
BACKUP_INTERVAL_HOURSいいえ6バックアップをアップロードする間隔(時間)です。最初のバックアップは起動直後に実行されます。
POSTHOG_API_KEYいいえ—公開 phc_ プロジェクトキーです。未設定の場合、PostHog のテレメトリー(イベント、ログ転送、ウェブ解析)はすべて無効になります。
POSTHOG_LOGSいいえfalsetrue にすると、構造化された stdout の各行を OTLP 経由で PostHog Logs に転送します。2026-10-19 以降はデフォルトで無効です。WAU が2,500の場合、月あたり4,300万行以上(約9 GB)になり PostHog の請求額が増える一方、Railway にもログが保持されるためです。true/false のみ指定できます。
POSTHOG_HOSTいいえhttps://eu.i.posthog.comサーバー側の取り込み先ホストです(SDK イベントと OTLP ログ転送)。
POSTHOG_WEB_HOSTいいえ$POSTHOG_HOSTposthog-js の api_host です。広告ブロッカーへの耐性を高めるには、管理下のリバースプロキシ(例:https://j.missingmcp.com)を設定してください。
POSTHOG_UI_HOSTいいえhttps://eu.posthog.comPostHog アプリのホストです。POSTHOG_WEB_HOST がプロキシの場合、posthog-js に必要です。
MAIL_API_TOKENいいえ—メールキャンペーン用の Cloudflare API トークンです。スコープは Account → Email Sending → Edit のみにしてください。これと MAIL_ACCOUNT_ID、MAIL_FROM のすべてが設定されていない場合、キャンペーンは無効になります。
MAIL_ACCOUNT_IDいいえ—送信ドメインが属する Cloudflare アカウント ID です。
MAIL_FROMいいえ—Cloudflare Email Sending に登録した(サブ)ドメインの送信者アドレスです。例:[email protected]。
MAIL_FROM_NAMEいいえ—送信者の表示名です。
MAIL_REPLY_TOいいえ—返信先です。List-Unsubscribe ヘッダーの mailto: のリンク先にもなります。
MAIL_DAILY_CAPいいえ180ローリング24時間あたりのキャンペーンメールの最大数です(Cloudflare の割り当て期間に準じます)。Cloudflare アカウントの日次割り当て量(新規アカウントでは200通。すべての送信ドメインで共有されます)を下回る値にしてください。
GATEWAY_LOG_FILEいいえ—設定すると、構造化ログと標準ライブラリのログをこのファイルに転送します。
GATEWAY_LOG_LEVELいいえinfodebug|info|warning|error|critical。debug は詳細ログを出力します(garminconnect/urllib3 の内部ログも含みます)。本番環境では使用しないでください。
WHOOP コネクターの設定
  1. https://developer-dashboard.whoop.com でアプリを作成します(すぐにセルフサービスで利用できます)。
  2. リダイレクト URI:https://<your-domain>/whoop/oauth/callback — 完全一致が必要です。
  3. スコープ:read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement offline (offline は必須です。これがないと WHOOP はリフレッシュトークンを発行せず、セッションは 1 時間後に失効します。)
  4. アプリのクライアント ID/シークレットを WHOOP_CLIENT_ID / WHOOP_CLIENT_SECRET に設定します。

注:未承認の WHOOP アプリで利用できるのは WHOOP メンバー 10 人までです。 上限を解除するには、アプリを承認申請してください:https://developer.whoop.com/docs/developing/app-approval/ (要件:API 利用規約 への準拠、1 人以上のメンバーによるテスト、ダッシュボード上の正確なアプリ名/連絡先メールアドレス/プライバシーポリシー URL、 ブランドガイドライン への準拠、および WHOOP の Typeform への申請。)

WHOOP API 利用規約に基づく運用上の義務(ゲートウェイの設計では技術的な要件にすでに対応しています。WHOOP データは保存せず、トークンは暗号化され、取り消されたアカウントは自動消去されます):

  • WHOOP メンバーデータに関するセキュリティインシデントが発生した場合、不当な遅延なく [email protected] および影響を受けたユーザーに報告します。
  • WHOOP の事前の書面による承認なしに、WHOOP に言及するプレスリリースや公表を行わないでください。
  • アプリのクライアント ID/シークレットはリポジトリに含めず(環境変数のみで管理し)、別のアプリケーションで再利用しないでください。
  • メンバーが WHOOP でアプリへのアクセスを取り消すと、ゲートウェイは次回の更新時にそれを検知し(invalid_grant)、保存済みトークンを削除して、メンバーのゲートウェイアクセストークンを無効にします(ログイベント whoop-account-revoked)。

バックアップ

BACKUP_S3_* 変数が設定されている場合、ゲートウェイは一貫性のある SQLite スナップショット(SQLite バックアップ API 経由、WAL セーフ)を起動直後と、その後は BACKUP_INTERVAL_HOURS ごとにバケットへアップロードします。キーは曜日ごとにローテーションされ(db/gateway-mon.db … db/gateway-sun.db)、クリーンアップ処理なしで 7 日分が保持されます。backup-ok / backup-failed ログイベントを確認してください。

バックアップされるのは DB のみです。DATA_DIR/users/ 内のユーザーごとのトークンディレクトリは、必要に応じて DB から再生成されます。

GATEWAY_SECRET がなければ、バックアップは役に立ちません(アカウントのデータはバックアップ内でも AES-256-GCM で暗号化されています)。これが重要な点です。GATEWAY_SECRET のコピーは、バケット内でも Railway 上でもない場所(パスワードマネージャーなど)に保管してください。シークレットを失うと、すべてのアカウントを失います。

**復元:**最新のオブジェクトをダウンロードし、$DATA_DIR/gateway.db に配置します(隣にある古い gateway.db-wal / -shm があれば削除します)。同じ GATEWAY_SECRET を設定して、ゲートウェイを起動します。ベアラートークンとログイン状態は維持され、ワーカーは必要に応じて再起動します。

監視

次の 3 つのヘルパースクリプトは、ゲートウェイの DB を直接操作します(ゲートウェイの稼働中に実行しても安全です)。

python scripts/status.py          # summary counts only (safe to paste/share)
python scripts/status.py --detail # + per-account devices (token prefixes),
                                  #   usage summary, OAuth clients, running workers
python scripts/revoke.py --list                       # accounts + token counts
python scripts/revoke.py --account [<adapter>:]<key>  # kill-switch: revoke ALL the
                                                      #   account's tokens (bare key = garmin)
python scripts/revoke.py --account <key> --purge      # + delete stored account & usage
python scripts/revoke.py --device <hash-prefix>       # revoke ONE device (prefix from status.py)
python scripts/usage.py                               # per-account tool usage + leaderboard
python scripts/usage.py --account [<adapter>:]<key>   # one account's per-tool breakdown
python scripts/subscribers.py                         # newsletter signups + suggestions
python scripts/subscribers.py --emails                # subscriber emails, one per line
python scripts/daily_report.py                        # yesterday's new/active/total users (print)
python scripts/daily_report.py --post                 # + POST it to Slack ($SLACK_WEBHOOK_URL)
python scripts/add_beer.py --email <supporter>        # record a "buy me a beer" donation (1 beer, 5 EUR)
python scripts/add_beer.py --email <supporter> --beers 3 --at 2026-07-20  # 3 beers, backdated

scripts/session_loss.py は代わりに Railway のログ(railway logs --json の出力。詳細は docstring を参照)を使い、セッション喪失(404)により失敗した garmin 呼び出しの割合を、全体とワーカー起動後 5 秒以内に分けて計測します。これは GARMIN_MCP_STATELESS_HTTP の変更前後を比較する指標です。

SLACK_WEBHOOK_URL が設定されている場合、ゲートウェイは毎朝、ユーザー統計レポートを Slack に自動投稿します(DAILY_REPORT_HOUR、デフォルトは DAILY_REPORT_TZ の 08:00)。テスト用に、scripts/daily_report.py で同じレポートを任意に実行できます。

メールキャンペーン — ユーザーとニュースレター購読者への単発のお知らせを、Cloudflare Email Sending 経由で送信します(上記の MAIL_* 環境変数を使用)。運用者がキャンペーンを作成して開始すると、ゲートウェイが自動で送信処理を行います。1 分ごとに 1 バッチ、1 日あたり最大 MAIL_DAILY_CAP 件を、アクティブ度の高いユーザーから順に送信します。そのため、リストが大きい場合は数日に分けて配信されます。

# text lives in the repo: campaigns/<slug>.txt ("Subject: ..." line, blank line, body)
python scripts/campaign.py create <slug>                  # snapshot text + enqueue everyone not unsubscribed
python scripts/campaign.py create <slug> --audience users # or: subscribers
python scripts/campaign.py test <slug> --to <you>         # one real send, not recorded
python scripts/campaign.py start <slug>                   # the gateway starts sending
python scripts/campaign.py pause <slug>
python scripts/campaign.py status [<slug>]                # counts + sent in last 24h + ETA (no addresses)
python scripts/campaign.py status <slug> --unknown        # + addresses whose send is unknown
python scripts/campaign.py requeue-unknown <slug>         # decided to send those again
python scripts/campaign.py requeue-failed <slug>          # retry the failed ones (attempts reset)
python scripts/campaign.py unsubscribe <email>            # manual opt-out (e.g. from a reply)

すべての宛先は送信台帳の行として記録されるため、同じ相手にキャンペーンが二重に送られることはありません。API 呼び出しの前に送信状態を sending として記録し、クラッシュや曖昧な API エラー(タイムアウト、5xx)が発生した場合は、むやみに再試行せず、オペレーターが対応できるよう unknown として保留します(この状態も日次上限にカウントされます)。1 件のアドレスに対する拒否(400/422)は、そのアドレスについて最大 3 回再試行し、ほかの送信を妨げません。アカウント全体に関わるエラー(無効なトークン、クォータ、レート制限)の場合は、誰にも送信枠を消費せず、メーラーを 15 分間停止します。配信停止したアドレスは、再キューされたものも含め、送信の直前に毎回除外されます。すべてのメールには配信停止リンクと RFC 8058 のワンクリック List-Unsubscribe ヘッダー(/unsubscribe — GET は確認のみ、POST は配信停止)が含まれます。配信停止は全体に適用され、そのアドレスはすべてのキャンペーンから除外されます。ハードバウンス率が 5% を超えると、キャンペーンは自動的に一時停止します(50 件の送信後に判定)。進捗はログイベント(campaign-batch、campaign-daily-cap、campaign-auto-paused、campaign-done、campaign-send-unknown、campaign-run-failed)のみで報告し、メールでは通知しません。

日次トリアージ — GitHub Actions のワークフロー(.github/workflows/daily-triage.yml)が毎朝(プラハ時間の約 07:45)scripts/daily_triage.py を実行します。ゲートウェイの Railway ログから直近 24 時間の error/warn 行を取得し、既知のエラーシグネチャを決定論的に分類します。そして、対応が必要なものが残っている場合に限り、Claude がオペレーター向けのトリアージ(何が起きたか → その意味 → 提案する対応をクラスごとに記載)を作成し、Slack に投稿します。正常な日は「異常なし」の一行(毎日のハートビート)を投稿します。分析に失敗した場合は、決定論的な集計表を投稿し、通知が途切れることはありません。分析はヘッドレスの Claude Code を介してオペレーターの Claude サブスクリプション上で実行されます(リポジトリのシークレット CLAUDE_CODE_OAUTH_TOKEN を使用。claude setup-token で発行)。フォールバックのバックエンドとして ANTHROPIC_API_KEY シークレットも使用できます。さらに RAILWAY_API_TOKEN と SLACK_WEBHOOK_URL が必要です(どちらも時間ごとのページャー用に設定済みです)。手動実行するには python scripts/daily_triage.py --dry-run を使います(RAILWAY_* 環境変数が必要です。トークンがなくても分析ステップは適切に縮退動作します)。

時間ごとの重大シグナルページャー — GitHub Actions のワークフロー(.github/workflows/hourly-digest.yml)が毎時 scripts/hourly_digest.py を実行します。Railway API を介してゲートウェイの Railway ログから直近 60 分間のログを読み取り、稼働確認プローブを実行し、重大なシグナルがある場合に限りページ通知(<!here>)します。重大なシグナルとは、プローブの失敗(サイト停止)、1 時間内に異なるアカウント 2 件以上で発生した worker-start-failed(画像破損のシグネチャ)、あらゆる 5xx、あらゆる critical、または 1 時間内のすべての sso-probe(2 件以上)が接続に失敗したサインイン用エグレスです(SSO_PROBE_INTERVAL は 1800 秒以下にする必要があります。専用のログフィルターで取得します)。それ以外の場合は完全に通知せず、通常のエラー件数は日次トリアージで扱います。リポジトリのシークレット RAILWAY_API_TOKEN と SLACK_WEBHOOK_URL が必要です(サービス ID と環境 ID はワークフローの環境変数として設定されています)。RAILWAY_API_TOKEN には Railway のプロジェクトトークン(最小スコープで、このプロジェクトのみアクセス可能。Project-Access-Token ヘッダーで送信)またはアカウント/ワークスペーストークン(Authorization: Bearer で送信)を指定できます。スクリプトは両方を試します。手動実行するには python scripts/hourly_digest.py --dry-run を使います(環境変数に RAILWAY_API_TOKEN、RAILWAY_SERVICE_ID、RAILWAY_ENVIRONMENT_ID が必要です)。 PostHog テレメトリ — POSTHOG_API_KEY を設定すると(環境変数の表を参照)、ゲートウェイは PostHog にも分析データを送信します(EU クラウド。設計: docs/superpowers/specs/2026-07-20-posthog-telemetry-design.md)。 リクエストごとの $mcp_* イベント(PostHog 組み込みの MCP 分析)、簡潔な接続ファネル/コンバージョンイベント群、構造化ログストリームの OTLP 複製(PostHog Logs。POSTHOG_LOGS=true の場合のみ。永続的なアーカイブは Railway が引き続き担当)、およびサイト上の posthog-js(UTM キャンペーンのアトリビューション。OAuth ページでは自動キャプチャを無効化)を送信します。送信データのルール:ID とメタデータのみとし、MCP 本文、認証情報、フォームの内容は送信しません。アカウントのメールアドレスは distinct_id としてのみ送信されます。すべての処理は送信後に応答を待たないため、PostHog が停止してもリクエストはブロックされません。上記の Slack レポートは変更なく引き続き実行され、PostHog はそれを補完します。ユーザーの利用を終了する際(revoke.py --purge)、GDPR 対応を完了するため、PostHog でも人物を削除してください(People → delete)。

ビールサポーター — scripts/add_beer.py は、「ビールをおごる」寄付(buymeacoffee.com/venik)をローカルの beers 監査テーブルに記録し、可能な範囲でサポーターのゲートウェイアカウントにひも付けた beer_purchased PostHog イベントを送信します(メールアドレスが既知のログイン情報の場合は matched)。このイベントは、運営者の接続→有料化ファネルと Growth ダッシュボードの「今月のビール」指標に利用することを想定しています。これらの PostHog インサイトは手動で設定する必要があり、作成するまでは存在しません。現時点では手動で取り込みます(BMC の自動化は保留中。設計: docs/superpowers/specs/2026-07-24-beer-supporters.md)。メールアドレスは distinct_id としてのみ送信し、サポーターの名前やメモは送信しないという点で、上記と同じ送信ルールが適用されます。

Docker の場合、スクリプトは /app/scripts にイメージ内蔵されています。コンテナ内で実行してください。status.py は /data 内の DB を自動的に検出します。

docker exec missingmcp python /app/scripts/status.py
docker logs -f missingmcp                   # live structured-JSON events

Railway の場合、railway ssh 経由で実行してください。ログは Railway ダッシュボードにあります(ライブ出力には railway logs --service gateway を使用します)。

railway ssh --service gateway "python3 /app/scripts/status.py"
railway ssh --service gateway "python3 /app/scripts/revoke.py --account <email>"

すべてのログは、適切な level 属性を含む構造化 JSON として標準出力に出力されます(1 行につき 1 イベント)。uvicorn/stdlib のレコード(イベント stdlib-log)と各ワーカー自身の出力(イベント worker-log、account 属性付き)も対象です。ERROR/Traceback に一致する行は、重要度が error に引き上げられます。Railway ではログエクスプローラーですべてを検索できます。たとえば、@event:worker-log @account:<email> で特定ユーザーのワーカーを追跡したり、問題を探すには @level:error で絞り込んだりできます。リクエストのレイテンシは呼び出しごとに mcp-response イベント(ttfb_ms、total_ms、bytes、tool、account)に記録されます。ログイン/検証とワーカー起動のイベントには、所要時間を示す ms が含まれます。ゲートウェイは、起動時および各カウントの変化時に stats イベント(accounts/tokens/people-with-token/clients/active-workers)も記録し、status.py には実行中のワーカーが一覧表示されます。

自動データ整理

ゲートウェイは独自のデータベースを整理するため、手動での整理は不要です。期限切れの古い認証コードやアクセストークンを削除するほか、バックグラウンドループでは(およそ1分ごとに)次の処理を行います。

  • 放棄されたOAuthクライアントを一掃します — Claudeは接続を試みるたびに新しいクライアント(DCR)を登録するため、フローを完了しなかった接続では、トークンのないクライアントが残ります。アクセストークンが0個で、作成から1時間以上経過したクライアント(コードの有効期間である10分を十分に上回るため、進行中のサインインに影響することはありません)を削除します。このため、status.pyのOAuthクライアント数は、失敗した試行のたびに増えるのではなく、接続済みのデバイス数を反映します。デバイスを失効させた場合も、そのクライアントにはトークンが残らないため、同じ方法で一掃されます。
  • 廃止されたアダプターのデータを削除します — コネクターが削除された場合、そのコネクターのすべての行(アカウント、トークン、クライアント、コード、使用状況)が一括して削除されます。廃止対象の一覧はコード内で明示的に指定され、設定から推測されることはないため、環境変数が見つからない場合でも、稼働中のコネクターが誤って消去されることはありません。

どちらの処理も、実際に何かを削除した場合にのみログに記録されます — cleanup-orphan-clients(count)および cleanup-dead-adapter(adapter とテーブルごとの件数) — そのため、ログが静かな場合は、削除するものがなかったことを意味します。1時間というしきい値は、設計上、環境変数ではなく固定定数です。

仕組み

  1. Claudeがクライアント(DCR)を登録し、OAuth 2.1(Authorization Code + PKCE)を開始します。
  2. 認可ページで、ユーザーがGarminにサインインします(メールアドレスとパスワード、求められた場合はMFAも入力します)。ゲートウェイはgarminconnect経由でログインし、取得したトークンのみ(暗号化済み)を保存して、パスワードは破棄します。
  3. ClaudeがコードをBearerトークンと交換します。
  4. /garmin/mcpへの呼び出しごとに、ゲートウェイはユーザーのgarmin_mcpワーカーが実行中であることを確認し(ワーカー独自のトークンを使用し、127.0.0.1にバインドされます)、そこへリバースプロキシします。

リモート方式を使用するアダプターでは、手順2と4を、アップストリームMCPに対するプローブ検証と、ヘッダーを直接注入した転送に置き換えます。ワーカーは使用しません。

アップストリームOAuthログインを使用するアダプター(whoop)では、手順2をプロバイダー独自のOAuthページへのリダイレクトに置き換えます。プロバイダーはコールバックでトークンを返し、ゲートウェイは同じ方法で検証して永続化します(検証後に永続化する手順は変わりません)。ローカル方式を使用するアダプター(whoop)では、手順4を置き換えます。リクエストはプロセス内で処理され、ワーカーも共有アップストリームも使用しません。また、必要に応じてゲートウェイがアカウントのローテーションするWHOOPトークンを自身で更新します。

セキュリティ

  • Garminのパスワードは永続化されません。WHOOPのパスワードは一切参照されません(サインインはWHOOP独自のOAuthページで行われ、スコープは読み取り専用です)。
  • トークンは保存時に暗号化されます(AES-256-GCM)。GATEWAY_SECRETがなければ、データベースは役に立ちません。
  • メンバーがWHOOPでアプリを失効させた場合、次回の更新時に検出され、保存済みのトークンは自動的に削除されます。
  • BearerトークンはSHA-256ハッシュのみが保存されます。
  • OAuth 2.1 PKCE(S256)、1回限りの10分コード、フォームでのCSRF対策、IPおよびトークン単位のレート制限。
  • ワーカーは127.0.0.1のみにバインドされます。garmin_mcpはレビュー済みのコミットに固定されています。

自身が管理し、信頼できるインフラストラクチャにのみデプロイしてください。DATA_DIRをバックアップし、GATEWAY_SECRETは別途保管してください。

デプロイ前

  • 実際のランダムな GATEWAY_SECRET を設定してください(openssl rand -base64 48)。アプリは .env.example のプレースホルダーが設定されていると起動を拒否します。
  • GARMIN_MCP_REF には、レビュー済みのコミット SHA を指定してください。main は予告なく変更される可能性のある追従先です(サプライチェーンリスク)。
  • アクセスの取り消し — アクセストークンは ACCESS_TOKEN_TTL_DAYS(既定値は 90 日)を経過すると期限切れになります。その場合、ユーザーは Claude で再認証するだけです。より早く取り消す場合(トークンが漏えいした場合やユーザーを削除した場合など)は、python scripts/revoke.py --account [<adapter>:]<email> を実行してください(そのアカウントのすべてのトークンを無効化するキルスイッチです)。--device <hash-prefix> を指定すると、特定のデバイスのアクセスを取り消せます(プレフィックスは status.py に表示されます)。
  • 実際の Garmin アカウントを使って、手動のエンドツーエンドのスモークテスト(MFA のフローを含む)を実行してから、実際のユーザーを接続してください。自動テストでは上流サービスをモックしています。

サポート

このゲートウェイが役に立ったら、ビールをおごってください 🍺。

ライセンス

MIT © 2026 Vaclav Slajs

謝辞

Taxuspt による優れた garmin_mcp を変更せずにラップしています。Garmin および Garmin Connect は Garmin Ltd. の商標です。このプロジェクトは Garmin と提携しておらず、Garmin の推奨を受けたものでもありません。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。