Skip to main content
Glama
ikeike443
by ikeike443

fatsecret-mcp

CI

Claude が FatSecret の食品・レシピデータベースを検索し、自分のフードダイアリー、体重、エクササイズログを会話の中で直接読み書きできる、個人用のリモート MCP(Model Context Protocol)サーバーです。Vercel の無料 Hobby プランにデプロイされています。fitness-mcp(Hevy)の姉妹プロジェクトで、製品ごとに 1 つの MCP サーバーを持ち、同じ認証パターンを共有しています。

ライセンス

MIT

Related MCP server: Nutrition MCP

ステータス

  • 検索(フェーズ 2): 実装済み — search_foods、get_food_detail、search_recipes、get_recipe_detail、find_food_by_barcode。FatSecret のユーザー認可は不要で、FatSecret の開発者コンソールから取得した OAuth 2.0 の Client ID/Secret のみが必要です。

  • ダイアリー/体重/エクササイズ/プロフィール(フェーズ 4): 実装済み、かつ実際の FatSecret アカウントで部分的に検証済み — get_profile、get_food_diary、get_exercise_diary はライブで確認済みです。create_exercise_entry、weight.update、find_food_by_barcode はまだ未検証のベストエフォート再構築です(詳細な内訳は下記「未検証のもの」を参照)。

  • 3-legged OAuth1 セットアップスクリプト(フェーズ 3): 実装済み(scripts/fatsecret-oauth-setup.ts)、実際の FatSecret アカウントではまだ実行していません。

2 つの認証レイヤー

このサーバーは Claude と FatSecret の間に位置し、その 2 つの関係はそれぞれ完全に異なる方法で認証されます。コードに触れる前に理解すべき主要な点です。

Claude  <──①── this server (fatsecret-mcp)  ──②──>  FatSecret API

① Claude ↔ このサーバー — 単一の共有シークレットで、fitness-mcp と同じパターンです。Claude はすべてのリクエストで Authorization: Bearer <MCP_BEARER_TOKEN> を送信し、lib/auth.ts がそれをチェックします。Claude の静的ヘッダーオプションはまだベータ版で制限されているため、このサーバーは独自の最小限の OAuth 2.1 認可サーバー(lib/oauth.ts、/api/oauth/authorize、/api/oauth/token)も実行し、Claude の標準的な OAuth Client ID/Secret フィールドが常に利用可能なフォールバックとして機能するようにしています。詳細な理由は fitness-mcp の README を参照してください。ここでも同じことが当てはまります。

このレイヤーでのすべての失敗 — 不正な/欠落した MCP_BEARER_TOKEN、認識されない OAuth client_id、誤った client_secret、不正な PKCE、許可されていない redirect_uri — はログに記録され、オプションでリアルタイムにアラートが送信されます。下記の「セキュリティイベントのログ記録とアラート」を参照してください。

② このサーバー ↔ FatSecret — ここが fitness-mcp よりも複雑な点です。なぜなら FatSecret 自体が2 種類の API メソッドに対して 2 つの異なる OAuth バージョンを使用しており、これを回避する方法はないからです。これは FatSecret の API の設計であり、ここでの選択ではありません。

FatSecret メソッドカテゴリ

例となるメソッド

このサーバーが認証する方法

署名付きリクエスト(特定のユーザーが関与しない)

foods.search、food.get、recipes.search、recipe.get、food.find_id_for_barcode

OAuth 2.0 クライアントクレデンシャル — lib/fatsecret/appAuth.ts が oauth.fatsecret.com からアプリレベルのベアラートークンを取得してキャッシュします。完全に自動で、一度の開発者登録以降は人間の操作は不要です。

署名付き&委任リクエスト(あなたの FatSecret アカウントを読み書きする)

food_entries.*、food_entry.*、weights.get_month、weight.update、exercise_entries.*、profile.get、foods.get_favorites

OAuth 1.0a、3-legged、HMAC-SHA1 署名 — lib/fatsecret/oauth1.ts。FatSecret はこれらのメソッドに対して OAuth 2.0 をまったくサポートしていないため、ここで OAuth1 を避ける方法はありません。これには一度だけの対話的な認可(下記フェーズ 3)が必要で、ブラウザで FatSecret にログインしてこのアプリを承認します。その結果得られたアクセストークン/シークレットは、その後は自動的に永久に再利用されます(フェーズ 3 の注意事項を参照)。

具体的には、search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode は、FatSecret アプリを登録して FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET を設定すればすぐに動作します。他のすべてのツールは、さらに FATSECRET_CONSUMER_KEY/FATSECRET_CONSUMER_SECRET(OAuth1 — 同じ FatSecret アプリからの別のクレデンシャルペア)と、FATSECRET_ACCESS_TOKEN/FATSECRET_ACCESS_TOKEN_SECRET(セットアップスクリプトを一度実行して取得)が必要です。

セキュリティイベントのログ記録とアラート

上記のレイヤー①(Claude ↔ このサーバー)でのすべての失敗チェックは lib/securityAlert.ts を通じて報告され、以下の箇所をゲートします。

  • lib/auth.ts(verifyBearerToken)— ベアラートークンの欠落、誤ったベアラートークン、MCP_BEARER_TOKEN が未設定。

  • /api/oauth/authorize — 認識されない client_id、許可されていない redirect_uri(オープンリダイレクタのケースをブロックするために isAllowedRedirectUri が存在)、サポートされていない response_type、欠落または非 S256 の PKCE チャレンジ、OAUTH_CLIENT_SECRET が未設定。

  • /api/oauth/token — 誤った client_secret、無効または期限切れの認可コード、コード/PKCE/redirect_uri の不一致、MCP_BEARER_TOKEN が未設定。

2 つの独立したレイヤーがあるため、これはグレースフルに劣化します。

  1. 常にログ記録。上記の各失敗は、構造化 JSON の 1 行(event、reason、ip、userAgent、path、time)を console.error で stderr に書き込みます。セットアップは不要で、Vercel ではそのままデプロイの関数ログに表示されます。実際のベアラートークン、クライアントシークレット、PKCE ベリファイアの値は決して含まれません — 監視対象のシークレット自体を漏らす可能性のある検出メカニズムは意味をなさないため、失敗した試行に関するメタデータのみが含まれます。lib/securityAlert.test.ts と lib/auth.test.ts がこれを直接検証しています。

  2. オプションのリアルタイムアラート。SECURITY_ALERT_WEBHOOK_URL が設定されている場合(Slack または Discord の「incoming webhook」URL)、同じイベントが 1 行のメッセージとしてそこにも POST され、侵入の試みがプッシュ通知として表面化します。誰かがたまたま Vercel のログビューアを開いたときだけ見えるのではありません。Webhook の配信失敗(URL の期限切れ、ネットワークエラー)自体も security_alert_delivery_failed としてログに記録されるため、静かに壊れた Webhook が「試行なし」と読まれることはありません。

Webhook の POST は Next の after() を使用してスケジュールされ、レスポンスが送信された後に実行されます(認証チェックに追加のレイテンシは発生しません)。これは実際のリクエスト内でのみ機能するため、直接呼び出された場合(テストなど)は単純な fire-and-forget 呼び出しにフォールバックします。

これは意図的に「すべての失敗でアラート」というシンプルな設計であり、しきい値/レートベースのアラートではありません。スコープ外としたもの(カウントベースのしきい値、Vercel のプラットフォームレベルの監視、クレデンシャルのローテーション)とその理由については、lib/auth.ts/lib/securityAlert.ts の doc コメントを参照してください。

公開ツール

ツール

タイプ

必要な認証

説明

search_foods

読み取り

OAuth2(アプリ)

名前で FatSecret の食品データベースを検索

get_food_detail

読み取り

OAuth2(アプリ)

1 つの食品の完全な 1 食あたりの栄養情報

search_recipes

読み取り

OAuth2(アプリ)

FatSecret のレシピデータベースを検索

get_recipe_detail

読み取り

OAuth2(アプリ)

1 つのレシピの完全な材料/手順

find_food_by_barcode

読み取り

OAuth2(アプリ)

GTIN-13 バーコードを foodId に解決 — barcode スコープが必要で、Premier 限定の可能性あり

get_food_diary

読み取り

OAuth1(ユーザー)

指定日のフードダイアリーエントリを一覧表示

get_favorite_foods

読み取り

OAuth1(ユーザー)

お気に入りの食品を一覧表示

get_most_eaten_foods

読み取り

OAuth1(ユーザー)

よく食べる食品を一覧表示(食事別も可能)

get_recently_eaten_foods

読み取り

OAuth1(ユーザー)

最近食べた食品を一覧表示(食事別も可能)

get_weight_history

読み取り

OAuth1(ユーザー)

指定月の体重エントリを一覧表示 — Premier 限定の可能性あり

get_exercise_diary

読み取り

OAuth1(ユーザー)

指定日のエクササイズエントリを一覧表示

get_profile

読み取り

OAuth1(ユーザー)

ユーザーの FatSecret プロフィール概要を取得

create_food_diary_entry

書き込み

OAuth1(ユーザー)

食品をダイアリーに記録

update_food_diary_entry

書き込み

OAuth1(ユーザー)

既存のダイアリーエントリを更新

delete_food_diary_entry

書き込み

OAuth1(ユーザー)

ダイアリーエントリを削除

update_weight

書き込み

OAuth1(ユーザー)

体重エントリを記録/更新 — Premier 限定の可能性あり

create_exercise_entry

書き込み

OAuth1(ユーザー)

エクササイズエントリを記録

書き込みツールはデフォルトでドライラン

fitness-mcp と同じ設計です。すべての書き込みツールは confirm: true 引数を必要とします。その説明は、呼び出し側の LLM に、書き込まれる内容をユーザーに正確に表示し、事前に明示的な承認を得るように指示します。これは構造的な後押しであり、保証ではありません。ツールを呼び出すかどうかを決定する同じ LLM が confirm も設定し、認証レイヤーで読み取り/書き込みツールのスコープ分離がないため、有効な MCP_BEARER_TOKEN を保持する呼び出し元は任意のツールを呼び出すことができます。

未検証のもの

このプロジェクトが最初に構築されたとき、FatSecret API の登録は存在しなかったため、ほとんどはベストエフォートの再構築として始まりました。その後、一部のツールは実際のアカウントでチェックされています。ステータスは以下のとおりです。

  • 実稼働確認済み、実装と完全に一致: search_foods (foods.search)、get_food_diary (food_entries.get、meal フィールドの実際の大文字小文字も含む。例: "Breakfast")。

  • 実稼働確認済み、確認後に修正: get_profile (profile.get) — 実際のレスポンスに height_cm が含まれていたが、まだフィールドとして公開されていなかったため、追加した。

  • 実稼働確認済み、実際の形状は想定より複雑: get_exercise_diary (exercise_entries.get)。メソッド/エンベロープは実在するが、連携したヘルスアプリから同期された実際のエントリ ({exercise_id: "184", exercise_name: "Google Health Connect", minutes: "1440", calories: "1655"} — 単一のワークアウトではなく、1日分の集計アクティビティ) には exercise_entry_id も date_int もまったくない。lib/fatsecret/exercise.ts は現在これを防御的に処理している (欠落フィールドはクラッシュや誤解を招く捏造値ではなく null になる) ほか、完全な生エントリを raw の下に保持する。依然未解決: (FatSecret アプリ経由で) 手動で記録されたエクササイズに、food_entries.get のエントリのように id/date があるかどうか — 未テスト。

  • 依然未検証 / ベストエフォートでの再構築: food.find_id_for_barcode のレスポンス形状、weight.update のパラメータ名、create_exercise_entry のメソッド名とパラメータ (上記のエクササイズ日記の発見により、「個別に作成可能なエントリ」というデータモデルの前提全体が成立しない可能性がある — lib/fatsecret/exercise.ts の警告を参照)。これらは出発点として扱い、検証済みの事実とは見なさないこと。

  • 上記2つの箇条書きについては、実際のアカウントに対して以下の手動検証チェックリストを実行し、見つかった不一致を修正すること (lib/fatsecret/*.test.ts のユニットテストもそれに合わせて更新が必要)。

セットアップ

  1. FatSecret Platform API アプリを登録する: https://platform.fatsecret.com/。以下が取得できる:

    • OAuth 2.0 クライアント ID/シークレット (FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET 用)。

    • OAuth 1.0 コンシューマーキー/シークレット (FATSECRET_CONSUMER_KEY/FATSECRET_CONSUMER_SECRET 用) — 同じアプリからの別のペアであり、上記の OAuth2 認証情報とは異なる。

    • プランに含まれるスコープを確認する (basic / premier / barcode / ...) — weights.get_month/weight.update/find_food_by_barcode は Premier または barcode/premier スコープが必要と報告されている。自分のプランで確認し、必要に応じて FATSECRET_OAUTH2_SCOPE を調整すること。

    • 送信 IP を許可リストに登録する (最大15アドレス/レンジ) — FatSecret の IP 制限はトークンエンドポイントに限定されない: 実際の Vercel デプロイで、実際の foods.search API 呼び出し自体が許可リスト外の IP から拒否されることを確認済み (エラーコード 21、「Invalid IP address detected」)。トークンが正しく発行されていても同様。したがって、一度きりの OAuth2 トークン取得と、すべての検索/詳細呼び出しの両方が許可リスト登録済み IP から発信される必要がある。ローカルではこれは単に自分のマシンのパブリック IP (curl https://ifconfig.me) である。Vercel では、サーバーレス関数はデフォルトで固定送信 IP を持たないため、下記の「Vercel の固定送信 IP」を参照 — 本番環境で Signed Request ツールが動作する前に必須。

  2. ローカル開発サーバーを一度実行して検索をスモークテストする (フェーズ2はステップ1のみ必要):

    npm install
    cp .env.example .env.local   # fill in FATSECRET_CLIENT_ID/SECRET + the MCP_BEARER_TOKEN/OAuth trio
    vercel dev
  3. 一度きりの 3-legged OAuth1 セットアップを実行する (5つの検索/詳細ツール以外のすべてのツールに必要) — 下記のフェーズ3を参照。

  4. Vercel にデプロイする — 下記の「デプロイ」を参照。ただし、先に「Vercel の固定送信 IP」を読むこと。

Vercel の固定送信 IP

Vercel のサーバーレス関数には固定送信 IP がない。これは上記の発見を踏まえると問題である — トークン取得だけでなく、search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode のすべての呼び出しが許可リスト登録済み IP から発信される必要がある。これがないと、これら5つのツールはローカルでは正常に動作するが (許可リスト登録したのは自分のマシンの IP なので)、本番環境では FatSecret API error 21: Invalid IP address detected で失敗する。

修正方法: これらのリクエストを固定 IP の HTTP プロキシ経由でルーティングする。このサーバーは Fixie を標準でサポートしている:

  1. usefixie.com でサインアップする — 無料の tricycleFree プラン (月500リクエスト/100MB、$0) で個人利用には十分。これは FatSecret の Signed Request トラフィックのみを運び、アプリ全体ではないため。プランのリクエスト枠は、アプリのみのレート制限とは異なり実際の制約であることに注意 — よく検索する場合は使用量を監視し、近づいたらアップグレードする (commuter、$5/月/2,500リクエスト)。

  2. Fixie が提供するプロキシ URL をコピーする (http://fixie:<password>@<host>:<port>)。

  3. それを FIXIE_URL として設定する — プロキシ経由のローカルテストでは .env.local に、本番用には Vercel の環境変数として。通常のローカル開発では未設定のままにする (自分の IP がすでに直接許可リスト登録されているため) — lib/fatsecret/appAuth.ts は FIXIE_URL が存在する場合にのみプロキシ経由でルーティングする。

  4. Fixie の固定 IP (Fixie ダッシュボードに表示される) を FatSecret 開発者コンソールで許可リストに登録する。ローカル開発用に許可リスト登録した IP に加えて (代わりではなく)。

このプロキシを経由する他のサーバーから FatSecret へのトラフィックはない — lib/fatsecret/oauth1.ts の OAuth1 (Signed & Delegated) リクエストは IP 制限されていないため、日記/体重/エクササイズ/プロフィールツールには FIXIE_URL はまったく不要。

ローカル開発

npm install
cp .env.example .env.local   # fill in real values
vercel dev

スモークテスト ($MCP_BEARER_TOKEN を置き換える):

curl -X POST http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

上記17ツールが返されるはず。トークンが欠落/誤っているリクエストは 401 になるはず。

フェーズ3: 一度きりの 3-legged OAuth1 セットアップ

search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode 以外のすべてのツールには、あなたの FatSecret アカウントに紐づく OAuth1 アクセストークン/シークレットが必要。一度取得する:

npm run fatsecret:oauth-setup

これ (scripts/fatsecret-oauth-setup.ts) は以下を行う:

  1. FatSecret から未承認のリクエストトークンを要求する。

  2. 認可 URL を表示する — 開いて FatSecret にログインし、承認する。FatSecret は確認コードを表示する。

  3. そのコードを貼り付けるよう促し、永続的なアクセストークン/シークレットと交換する。

  4. FATSECRET_ACCESS_TOKEN/FATSECRET_ACCESS_TOKEN_SECRET を .env.local に書き込む。

その後、同じ2つの値を Vercel の環境変数にも追加する (.env.local はデプロイされない) — 下記の「デプロイ」を参照。

FatSecret のドキュメントによると、このアクセストークンは期限切れにならない。もし失効した場合 (例: FatSecret アカウント設定からアプリのアクセスを削除した場合)、スクリプトを再実行して新しいものを取得するだけ — fitness-mcp の derive() パターンの精神に従って考えてほしい: ここで認証情報を失っても災害ではなく、ワンコマンドで修正できる。今回は決定的な再導出ではなく対話式であるだけだ。

覚えやすい1つのパスフレーズから Claude 向けシークレットを生成

MCP_BEARER_TOKEN、OAUTH_CLIENT_ID、OAUTH_CLIENT_SECRET (レイヤー ① — Claude ↔ このサーバー間。上記の FatSecret 認証情報とは無関係) はすべて単一のマスターパスフレーズから決定的に導出できる。保存された値を失っても災害にはならない — 再導出するだけ:

derive() {
  if [ -z "$MASTER_PASSPHRASE" ]; then
    printf "Master passphrase: "
    read -rs MASTER_PASSPHRASE
    echo
  fi
  echo -n "$1" | openssl dgst -sha256 -hmac "$MASTER_PASSPHRASE" -hex | awk '{print $2}'
}

derive "fatsecret-mcp:bearer-token"        # → MCP_BEARER_TOKEN
derive "fatsecret-mcp:oauth-client-id"     # → OAUTH_CLIENT_ID
derive "fatsecret-mcp:oauth-client-secret" # → OAUTH_CLIENT_SECRET

ラベル文字列は秘密ではない (この README に保持しても安全) — 秘密なのはパスフレーズのみ。同じパスフレーズで derive を再実行すると、常に同じ値が再現される。これは FatSecret 側の認証情報 (FATSECRET_CLIENT_ID/SECRET、FATSECRET_CONSUMER_KEY/SECRET、FATSECRET_ACCESS_TOKEN/SECRET) には適用されない — これらは FatSecret の開発者コンソールと OAuth1 セットアップスクリプトから取得され、このパスフレーズからは取得されない。

テスト

3つのレイヤーがあり、すべて CI (.github/workflows/ci.yml) でプッシュ/PR のたびに実行される — いずれも実際の FatSecret シークレットを必要としないため、公開リポジトリでも同じように動作する:

npm run test        # unit + integration (vitest) — pure logic, plus the real Next.js
                     # route handler exercised with fetch mocked
npm run build
npm run test:e2e     # starts a real `next start` server and hits it over real HTTP
                      # (node's built-in test runner, no extra dependency)
  • ユニット (lib/**/*.test.ts): ベアラートークン検証、OAuth2.1 コード署名/PKCE/リダイレクト URI 許可リスト (RFC 7636 テストベクターを含む)、FatSecret OAuth2 Client Credentials トークン取得/キャッシュ/リフレッシュ (lib/fatsecret/appAuth.test.ts)、独立した再実装と相互検証する OAuth1 HMAC-SHA1 署名 (lib/fatsecret/oauth1.test.ts)、およびすべての lib/fatsecret/*.ts レスポンス形状の正規化 (単一オブジェクト vs 配列、数値文字列 vs 数値、空レスポンスの癖)。

  • 統合 (test/integration/*.test.ts): 実際の app/api/mcp/route.ts ハンドラを実際の lib/fatsecret/* モジュールに接続し、fetch のみモックして、OAuth2 (Signed Request) と OAuth1 (Signed & Delegated) の両方のツールパス、およびすべての書き込みツールの確認ゲーティングをカバー。実際の /api/oauth/authorize//api/oauth/token ルート。.well-known OAuth メタデータルート。

  • E2E (test/e2e/*.e2e.test.mjs): 本番ビルドを起動し、実際の HTTP で検証 — ヘルスチェック、認証欠落/不正時の 401、tools/list が全17ツールを返すこと、OAuth ディスカバリメタデータ、完全な認可コード + PKCE ラウンドトリップ。実際の FatSecret データは使用しない (CI には設計上、実際の認証情報がない)。

実際の FatSecret アカウントでの手動検証

CI は実際の FatSecret データに触れることはなく、上記の「未検証の内容」によると、このサーバーの FatSecret の正確なレスポンス形状に関する前提の一部は実際のアカウントでまったく確認されていない。登録して OAuth1 セットアップスクリプトを実行したら、このチェックリストを実行し、見つかった不一致を修正すること:

  1. .env.local に実際の FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET を設定し、vercel dev を実行して、実際のクエリで search_foods を呼び出す — 完了、実際のアカウントで動作することを確認済み。まだの場合は get_food_detail でも同様に行うこと — 妥当な栄養数値が返ることを確認する。

  2. 同様に search_recipes と get_recipe_detail を呼び出す。依然未解決。

  3. プランに barcode スコープが含まれる場合、実際の製品のバーコードで find_food_by_barcode を呼び出し、レスポンス形状が lib/fatsecret/foods.ts の RawFindIdForBarcodeResponse と一致することを確認する — 一致しなければ修正する。依然未解決。

  4. npm run fatsecret:oauth-setup を実行し、get_profile と get_food_diary を呼び出す — 完了。get_food_diary は完全に一致。get_profile は heightCm が欠落していたため修正済み — 上記の「未検証の内容」を参照。

  5. confirm: true で明らかに使い捨てのエントリを create_food_diary_entry で作成し、同じ日付で get_food_diary を呼び出して、正しい食品/分量/数量/食事で表示されることを確認する。次に update_food_diary_entry で更新し、delete_food_diary_entry で削除する — それぞれがラウンドトリップすることを確認する。依然未解決 — get_food_diary から meal は大文字で返される ("Breakfast") ことに注意。create_food_diary_entry/update_food_diary_entry が書き込み時に同じ大文字小文字 (または FatSecret の書き込み側が実際に期待する大文字小文字) を受け入れるかどうかを、問題ないと決めつける前に再確認する価値がある。

  6. プランに体重追跡が含まれる場合、confirm: true で update_weight を呼び出し、get_weight_history がそれを反映することを確認する。依然未解決。

  7. create_exercise_entry と get_exercise_diary はこのコードベースで最も検証が進んでいないペアである。get_exercise_diary のメソッド/エンベロープは実在することが確認されたが、エクササイズ日記のデータモデルが想定より複雑であることが明らかになった (上記の「未検証の内容」を参照) — create_exercise_entry を信頼する前に、まず FatSecret アプリで手動でエクササイズを記録し、get_exercise_diary を再確認して、手動エントリに食品エントリのような exercise_entry_id/date_int があるかどうかを確認すること。そうすれば、「個別に作成可能なエントリ」がそもそも正しいモデルかどうかがわかる。その後で実際のデータに対して create_exercise_entry 自体を試すこと。

  8. 実際の FatSecret 認証情報をコミットしないこと。また、このチェックリストを CI で実行しないこと。

環境変数

変数

目的

FATSECRET_CLIENT_ID / FATSECRET_CLIENT_SECRET

OAuth 2.0 クライアント認証情報 — 署名付きリクエスト方式(検索・詳細ツール)

FATSECRET_OAUTH2_SCOPE

任意。スペース区切りの OAuth2 スコープ。デフォルトは basic。必要に応じて barcode/premier を追加

FATSECRET_FOOD_GET_METHOD

任意。デフォルトは food.get.v4。プランに v4 アクセスがない場合は上書き(例: food.get)

FIXIE_URL

任意。OAuth2 トークン取得とすべての署名付きリクエスト呼び出しに使用する固定 IP HTTP プロキシ URL(http://fixie:<password>@<host>:<port>)。Vercel はデフォルトで固定送信 IP を持たないため、Vercel では必須。上記「Vercel の固定送信 IP」を参照。ローカル開発では未設定のままにすること。

FATSECRET_CONSUMER_KEY / FATSECRET_CONSUMER_SECRET

OAuth 1.0 コンシューマキー/シークレット — ワンタイムセットアップスクリプトと、すべての署名付き・委任呼び出しの両方に署名する

FATSECRET_ACCESS_TOKEN / FATSECRET_ACCESS_TOKEN_SECRET

あなたの FatSecret アカウント用の OAuth 1.0 アクセストークン/シークレット — npm run fatsecret:oauth-setup(フェーズ 3)で取得

MCP_BEARER_TOKEN

このサーバーがすべてのリクエストで要求する共有シークレット。また、OAuth フローが発行する access_token でもある

OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET

このサーバー自身の最小限の OAuth 認可サーバー用の認証情報

OAUTH_ALLOWED_REDIRECT_HOSTS

任意。/api/oauth/authorize の redirect_uri に対するカンマ区切りの許可リスト。デフォルトは claude.ai,claude.com

SECURITY_ALERT_WEBHOOK_URL

任意。認証失敗時のリアルタイムアラート用 Slack/Discord 受信 Webhook URL — 上記「セキュリティイベントのログ記録とアラート」を参照。この設定の有無にかかわらず、失敗は常に stderr に記録される

これらは Vercel プロジェクトの環境変数(Production + Preview)に設定すること。実際の値は絶対にコミットしないこと — .env.example は名前を文書化するだけである。

デプロイ

  1. vercel link

  2. vercel env add FATSECRET_CLIENT_ID(上記の表の変数のうち、値を持つものすべてに対して繰り返す — 最低限 FATSECRET_CLIENT_ID/SECRET、MCP_BEARER_TOKEN、OAUTH_CLIENT_ID/SECRET。上記「Vercel の固定送信 IP」に従って FIXIE_URL を追加 — 実際には任意ではなく必須。OAuth1 セットアップスクリプトを実行したら FATSECRET_CONSUMER_*/FATSECRET_ACCESS_TOKEN* のペアを追加)

  3. デプロイ前に、Vercel プロジェクトの Node.js バージョンを 22.19 以降に設定すること(Project → Settings → General → Node.js Version、または現在の Vercel ダッシュボードで該当する場所)— つまり下記の手順 4 の前。このサーバーの undici@8 依存関係(Fixie プロキシに使用 — 上記「Vercel の固定送信 IP」を参照)は "engines": {"node": ">=22.19.0"} を宣言しており、package.json 自身の engines フィールドも同じ要件を文書化している — しかし、どちらも Vercel 上では単独では何も強制しないため、古い Node バージョン(例: 20.x)に固定されたプロジェクトは「成功」としてデプロイされ、実行時に失敗する。

  4. Vercel ダッシュボードでこの GitHub リポジトリを接続し、main へのプッシュで自動デプロイするか、手動で vercel --prod を実行する。

  5. デプロイされた URL をメモする(Project → Settings → Domains を確認 — このプロジェクトの本番 URL は未取得の https://fatsecret-mcp.vercel.app であることが判明したが、これは Vercel の共有名前空間なので、フォークでも空いているとは想定しないこと)。

  6. FatSecret 開発者コンソールで Fixie の固定 IP を許可リストに追加する(上記「Vercel の固定送信 IP」を参照)— これは本番環境で最も問題になりやすい手順である。これがないと search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode がすべて FatSecret API error 21 で失敗するため。

Claude に接続

カスタムコネクタは claude.ai(Web)またはデスクトップアプリからのみ追加できる — モバイルアプリからは不可。追加後はモバイルからも自動的に使用できる。

  1. claude.ai で: Settings → Connectors → Add custom connector。

  2. 名前: FatSecret。URL: https://<your-deployment>/api/mcp。

  3. アカウントに「Request headers」ベータがある場合: そこに Authorization: Bearer <MCP_BEARER_TOKEN> を追加し、手順 5 に進む。

  4. それ以外の場合、Advanced settings を開き、Vercel で設定した OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET の値を OAuth Client ID / OAuth Client Secret に入力する。Claude はこのサーバーの .well-known メタデータを介して /authorize と /token エンドポイントを自動的に検出する。

  5. 保存する。Claude は上記の 17 個のツールを一覧表示するはずだ。

次のように試してみて: 「バナナのカロリーを教えて」、または「今日の朝食にバナナを1本記録して」(フェーズ 3/4 がセットアップされ検証された後)。

謝辞

3-legged OAuth1 フローの設計は fcoury/fatsecret-mcp(MIT)に基づいている。このプロジェクトは OAuth フローを MCP ツール自体として公開しているが、本プロジェクトは単一の個人用 FatSecret アカウント向けに構築されているため、代わりにスタンドアロンのセットアップスクリプト(scripts/fatsecret-oauth-setup.ts)として一度だけ実行する。コードは一切コピーしていない。

Related MCP Connectors

Related MCP Servers