Skip to main content
Glama
ikeike443
by ikeike443

fatsecret-mcp

CI

Персональный удалённый MCP-сервер (Model Context Protocol), который позволяет Claude искать в базе продуктов/рецептов FatSecret и читать/записывать ваш личный дневник питания, вес и журнал упражнений прямо в диалоге. Развёрнут на бесплатном тарифе Hobby от Vercel. Родственный проект fitness-mcp (Hevy) — один MCP-сервер на продукт, с общей схемой аутентификации.

Лицензия

MIT

Related MCP server: Nutrition MCP

Статус

  • Поиск (Фаза 2): реализовано — search_foods, get_food_detail, search_recipes, get_recipe_detail, find_food_by_barcode. Авторизация пользователя FatSecret не требуется; нужны только OAuth 2.0 Client ID/Secret из консоли разработчика FatSecret.

  • Дневник/вес/упражнения/профиль (Фаза 4): реализовано и частично проверено на реальном аккаунте FatSecret — get_profile, get_food_diary и get_exercise_diary теперь подтверждены вживую; create_exercise_entry, weight.update и find_food_by_barcode остаются непроверенными реконструкциями по принципу «наилучшего предположения» (см. «Что не проверено» ниже для полной разбивки).

  • Скрипт настройки трёхэтапного OAuth1 (Фаза 3): реализован (scripts/fatsecret-oauth-setup.ts), ещё не запускался против реального аккаунта FatSecret.

Два уровня аутентификации

Этот сервер находится между Claude и FatSecret, и каждое из этих двух отношений аутентифицируется совершенно по-разному — это главное, что нужно понять, прежде чем трогать код.

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), чтобы стандартные поля OAuth Client ID/Secret от Claude работали как всегда доступный запасной вариант — полное обоснование см. в README fitness-mcp, оно применимо здесь без изменений.

Каждый сбой на этом уровне — неверный/отсутствующий MCP_BEARER_TOKEN, нераспознанный OAuth client_id, неправильный client_secret, некорректный PKCE, запрещённый redirect_uri — логируется и, опционально, вызывает оповещение в реальном времени; см. «Логирование и оповещение о событиях безопасности» ниже.

② этот сервер ↔ FatSecret — здесь всё сложнее, чем в fitness-mcp, потому что сам FatSecret использует две разные версии OAuth для двух разных видов методов API, и обойти это невозможно — так спроектирован API FatSecret, а не выбор, сделанный здесь:

Категория методов FatSecret

Примеры методов

Как этот сервер аутентифицируется

Подписанный запрос (без участия конкретного пользователя)

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

OAuth 2.0 Client Credentials — lib/fatsecret/appAuth.ts получает и кэширует прикладной bearer-токен с oauth.fatsecret.com. Полностью автоматически; никакого ручного взаимодействия после однократной регистрации разработчика.

Подписанный и делегированный запрос (чтение/запись вашего аккаунта FatSecret)

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

OAuth 1.0a, трёхэтапный, с подписью HMAC-SHA1 — lib/fatsecret/oauth1.ts. FatSecret вообще не поддерживает OAuth 2.0 для этих методов, поэтому избежать OAuth1 здесь невозможно. Это требует однократной интерактивной авторизации (Фаза 3, ниже), при которой вы входите в FatSecret в браузере и одобряете это приложение; полученные access token/secret затем автоматически переиспользуются навсегда (см. оговорку в Фазе 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) — отсутствующий bearer-токен, неверный bearer-токен, MCP_BEARER_TOKEN не настроен.

  • /api/oauth/authorize — нераспознанный client_id, запрещённый redirect_uri (для блокировки открытого редиректа существует isAllowedRedirectUri), неподдерживаемый response_type, отсутствующий/не-S256 PKCE challenge, OAUTH_CLIENT_SECRET не настроен.

  • /api/oauth/token — неверный client_secret, недействительный/просроченный код авторизации, несовпадение code/PKCE/redirect_uri, MCP_BEARER_TOKEN не настроен.

Два независимых уровня, поэтому деградация происходит корректно:

  1. Всегда логируется. Каждый сбой выше записывает одну строку структурированного JSON (event, reason, ip, userAgent, path, time) в stderr через console.error — настройка не требуется, и на Vercel это отображается в логах функций развёртывания как есть. Фактическое значение bearer-токена / client secret / PKCE verifier никогда не включается — только метаданные о неудачной попытке, поскольку механизм обнаружения, который сам мог бы утечь секрет, за которым он следит, лишён смысла; lib/securityAlert.test.ts и lib/auth.test.ts проверяют это напрямую.

  2. Опциональное оповещение в реальном времени. Если задан SECURITY_ALERT_WEBHOOK_URL (URL «входящего вебхука» Slack или Discord), то же событие также отправляется туда POST-запросом как однострочное сообщение, так что попытка вторжения всплывает как push-уведомление, а не видна только тогда, когда кто-то случайно откроет просмотрщик логов Vercel. Сбой доставки вебхука (просроченный URL, сетевая ошибка) сам логируется как security_alert_delivery_failed, чтобы молча сломанный вебхук не читался как «попыток не было».

POST вебхука планируется через after() от Next, чтобы он выполнялся после того, как ответ уже отправлен (без дополнительной задержки на проверку аутентификации); это работает только внутри реального запроса, поэтому при прямом вызове (например, из тестов) используется обычный вызов «запустил и забыл».

Это намеренно простая схема «оповещать о каждом сбое», а не пороговая/частотная — см. комментарии в lib/auth.ts/lib/securityAlert.ts о том, что было исключено из объёма (пороги на основе счётчиков, мониторинг на уровне платформы Vercel, ротация учётных данных) и почему.

Доступные инструменты

Инструмент

Тип

Требуемая аутентификация

Описание

search_foods

чтение

OAuth2 (приложение)

Поиск по базе продуктов FatSecret по названию

get_food_detail

чтение

OAuth2 (приложение)

Полная пищевая ценность на порцию для одного продукта

search_recipes

чтение

OAuth2 (приложение)

Поиск по базе рецептов FatSecret

get_recipe_detail

чтение

OAuth2 (приложение)

Полные ингредиенты/инструкции для одного рецепта

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 может вызвать любой инструмент.

Что не проверено

На момент первой разработки этого проекта регистрации в API FatSecret не существовало, поэтому большая часть начиналась как реконструкции по принципу «наилучшего предположения». С тех пор часть инструментов была проверена на реальном аккаунте — статус ниже:

  • Подтверждено вживую, точно соответствует реализации: 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"} — агрегированная активность за целый день, а не отдельная тренировка), не содержит exercise_entry_id и вообще не имеет date_int. lib/fatsecret/exercise.ts теперь обрабатывает это защитно (отсутствующие поля становятся null, а не вызывают сбой или вводящее в заблуждение сфабрикованное значение) и сохраняет полную исходную запись в raw. Остаётся открытым: есть ли у вручную добавленного упражнения (через приложение FatSecret) id/дата, как у записей food_entries.get — не проверено.

  • Всё ещё не проверено / реконструкции по принципу «наилучшего усилия»: форма ответа food.find_id_for_barcode, имена параметров weight.update, а также имя метода и параметры create_exercise_entry (обнаружение выше по дневнику упражнений означает, что его предположение о модели данных «отдельная создаваемая запись» может не выполняться — см. предупреждение в lib/fatsecret/exercise.ts). Относитесь к этому как к отправной точке, а не как к проверенной истине.

  • Прогоните приведённый ниже чек-лист ручной проверки на реальном аккаунте для всего, что указано в двух пунктах выше, и исправьте любые несоответствия, которые найдёте (модульные тесты в lib/fatsecret/*.test.ts потребуют соответствующих обновлений).

Настройка

  1. Зарегистрируйте приложение FatSecret Platform API на https://platform.fatsecret.com/. Вы получите:

    • OAuth 2.0 Client ID/Secret (для FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET).

    • OAuth 1.0 Consumer Key/Secret (для FATSECRET_CONSUMER_KEY/FATSECRET_CONSUMER_SECRET) — отдельная пара из того же приложения, не та же, что учётные данные OAuth2 выше.

    • Проверьте, какие области (scopes) включает ваш тариф (basic / premier / barcode / ...) — сообщается, что weights.get_month/weight.update/find_food_by_barcode требуют Premier или областей barcode/premier; сверьтесь со своим тарифом и при необходимости скорректируйте FATSECRET_OAUTH2_SCOPE.

    • Внесите в белый список ваш исходящий IP-адрес(а) (до 15 адресов/диапазонов) — ограничение по IP в FatSecret не ограничивается только конечной точкой токена: подтверждено на реальном развёртывании Vercel, что сам вызов API foods.search был отклонён (код ошибки 21, «Обнаружен недопустимый IP-адрес») с IP, не входящего в белый список, даже при действительном выпущенном токене. Таким образом, и одноразовое получение токена OAuth2, и каждый отдельный вызов поиска/детализации должны исходить с IP из белого списка. Локально это просто публичный IP вашей машины (curl https://ifconfig.me). На Vercel, где серверless-функции по умолчанию не имеют фиксированного исходящего IP, см. «Фиксированный исходящий IP для Vercel» ниже — это требуется до того, как любой инструмент Signed Request заработает в продакшене.

  2. Один раз запустите локальный dev-сервер для быстрой проверки поиска (для фазы 2 нужен только шаг 1):

    npm install
    cp .env.example .env.local   # fill in FATSECRET_CLIENT_ID/SECRET + the MCP_BEARER_TOKEN/OAuth trio
    vercel dev
  3. Выполните одноразовую настройку трёхэтапного OAuth1 (нужна для всех инструментов, кроме 5 поисковых/детальных) — см. Фазу 3 ниже.

  4. Разверните на Vercel — см. «Развёртывание» ниже, но сначала прочтите «Фиксированный исходящий IP для Vercel».

Фиксированный исходящий IP для Vercel

Серверless-функции Vercel не имеют фиксированного исходящего IP, что является проблемой с учётом приведённого выше вывода — каждый вызов search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode, а не только получение токена, должен исходить с IP из белого списка. Без этого эти пять инструментов отлично работают локально (ваш IP — это то, что вы внесли в белый список), но в продакшене завершаются ошибкой FatSecret API error 21: Invalid IP address detected.

Решение: маршрутизируйте эти запросы через HTTP-прокси с фиксированным IP. Этот сервер поддерживает Fixie из коробки:

  1. Зарегистрируйтесь на usefixie.com — бесплатного тарифа tricycleFree (500 запросов/100 МБ в месяц, $0) достаточно для личного использования, поскольку он несёт только трафик Signed Request FatSecret, а не всё ваше приложение. Учтите, что квота запросов тарифа — реальное ограничение, в отличие от лимита скорости только для приложения — если вы много ищете, следите за использованием и переходите на более высокий тариф (commuter, $5/мес/2500 запросов), если приблизитесь к лимиту.

  2. Скопируйте URL прокси, который даёт Fixie (http://fixie:<password>@<host>:<port>).

  3. Установите его как FIXIE_URL — в .env.local для локального тестирования через прокси и как переменную окружения Vercel для продакшена. Оставьте его неустановленным для обычной локальной разработки (где ваш собственный IP уже напрямую внесён в белый список) — lib/fatsecret/appAuth.ts маршрутизирует через прокси только при наличии FIXIE_URL.

  4. Внесите фиксированный IP Fixie (показан на панели управления Fixie) в консоль разработчика FatSecret, в дополнение к (а не вместо) любым IP, которые вы внесли в белый список для локальной разработки.

Никакой другой трафик сервер→FatSecret не проходит через этот прокси — запросы OAuth1 (Signed & Delegated) в lib/fatsecret/oauth1.ts не ограничены по 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: одноразовая настройка трёхэтапного OAuth1

Каждому инструменту, кроме search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode, нужен токен доступа/секрет OAuth1, привязанный к вашему аккаунту FatSecret. Получите его один раз:

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.

Затем добавьте эти же два значения в переменные окружения Vercel (.env.local никогда не развёртывается) — см. «Развёртывание» ниже.

Согласно документации FatSecret, этот токен доступа не истекает. Если он когда-либо будет отозван (например, вы удалите доступ приложения в настройках аккаунта FatSecret), просто повторно запустите скрипт, чтобы получить новый — следуйте духу паттерна derive() из fitness-mcp: потеря учётных данных здесь не катастрофа, это исправление одной командой, просто на этот раз интерактивной, а не детерминированным повторным выводом.

Генерация секретов для 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, а не из этой парольной фразы.

Тестирование

Три уровня, все выполняются в CI (.github/workflows/ci.yml) при каждом push/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): проверка bearer-токена, подпись кода OAuth2.1/PKCE/разрешение redirect-URI (включая тестовый вектор RFC 7636), получение/кэширование/обновление токена OAuth2 Client Credentials FatSecret (lib/fatsecret/appAuth.test.ts), подпись HMAC-SHA1 OAuth1, перекрёстно проверенная с независимой реализацией (lib/fatsecret/oauth1.test.ts), и нормализация формы ответа для каждого lib/fatsecret/*.ts (одиночный объект против массива, числовая строка против числа, особенности пустых ответов).

  • Интеграционные (test/integration/*.test.ts): реальный обработчик app/api/mcp/route.ts, подключённый к реальным модулям lib/fatsecret/* с мокированным только fetch, покрывающий как пути инструментов OAuth2 (Signed Request), так и OAuth1 (Signed & Delegated), а также подтверждение-гейтинг для каждого инструмента записи; реальные маршруты /api/oauth/authorize//api/oauth/token; маршруты метаданных OAuth .well-known.

  • E2E (test/e2e/*.e2e.test.mjs): запускает продакшен-сборку и проверяет по реальному HTTP — проверка работоспособности, 401 при неверной/отсутствующей аутентификации, tools/list возвращает все 17 инструментов, метаданные обнаружения OAuth и полный цикл авторизационного кода + PKCE. Не обращается к реальным данным FatSecret (в CI по замыслу нет реальных учётных данных).

Ручная проверка на реальном аккаунте FatSecret

CI никогда не касается реальных данных FatSecret, и — согласно разделу «Что не проверено» выше — некоторые предположения этого сервера о точных формах ответов FatSecret вообще не проверялись на реальном аккаунте. После регистрации и запуска скрипта настройки OAuth1 пройдитесь по этому чек-листу и исправьте любые несоответствия, которые найдёте:

  1. Установите реальные FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET в .env.local, запустите vercel dev и вызовите search_foods с реальным запросом — готово, подтверждено работающим на реальном аккаунте. Всё же сделайте это для get_food_detail, если ещё не сделали — убедитесь, что он возвращает разумные значения питательных веществ.

  2. Вызовите search_recipes и get_recipe_detail аналогично. Всё ещё открыто.

  3. Если ваш тариф включает область barcode, вызовите find_food_by_barcode со штрих-кодом реального продукта и подтвердите, что форма ответа соответствует RawFindIdForBarcodeResponse из lib/fatsecret/foods.ts — исправьте, если нет. Всё ещё открыто.

  4. Запустите npm run fatsecret:oauth-setup, затем вызовите get_profile и get_food_diary — готово. get_food_diary совпал точно; в get_profile отсутствовал heightCm, теперь исправлено — см. «Что не проверено» выше.

  5. Вызовите create_food_diary_entry с confirm: true и заведомо одноразовой записью, затем get_food_diary за ту же дату и подтвердите, что запись появляется с правильной едой/порцией/количеством/приёмом пищи. Затем обновите её через update_food_diary_entry и удалите через delete_food_diary_entry — подтвердите, что каждый цикл работает. Всё ещё открыто — обратите внимание, что meal возвращается с заглавной буквы ("Breakfast") из get_food_diary; стоит перепроверить, принимает ли create_food_diary_entry/update_food_diary_entry тот же регистр при записи (или какой регистр на самом деле ожидает сторона записи FatSecret), прежде чем предполагать, что всё в порядке.

  6. Если ваш тариф включает отслеживание веса, вызовите update_weight с confirm: true и подтвердите, что 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 Client Credentials — подписывают методы Signed Request (инструменты поиска/детализации)

FATSECRET_OAUTH2_SCOPE

Необязательно. OAuth2-области через пробел, по умолчанию basic. Добавьте barcode/premier по мере необходимости

FATSECRET_FOOD_GET_METHOD

Необязательно. По умолчанию food.get.v4; переопределите (например, food.get), если ваш тариф не включает доступ к v4

FIXIE_URL

Необязательно. URL фиксированного IP-прокси HTTP (http://fixie:<password>@<host>:<port>) для получения токена OAuth2 и каждого вызова Signed Request — обязательно на Vercel, так как у него по умолчанию нет фиксированного исходящего IP. См. «Фиксированный исходящий IP для Vercel» выше. Для локальной разработки оставьте пустым.

FATSECRET_CONSUMER_KEY / FATSECRET_CONSUMER_SECRET

Consumer Key/Secret OAuth 1.0 — подписывают и одноразовый скрипт настройки, и каждый вызов Signed & Delegated

FATSECRET_ACCESS_TOKEN / FATSECRET_ACCESS_TOKEN_SECRET

Токен доступа/секрет OAuth 1.0 для вашего аккаунта FatSecret — получаются через npm run fatsecret:oauth-setup (этап 3)

MCP_BEARER_TOKEN

Общий секрет, который этот сервер требует при каждом запросе, а также access_token, выдаваемый нашим OAuth-процессом

OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET

Учётные данные для собственного минимального OAuth-сервера авторизации этого сервера

OAUTH_ALLOWED_REDIRECT_HOSTS

Необязательно. Разрешённый список через запятую для redirect_uri в /api/oauth/authorize. По умолчанию claude.ai,claude.com

SECURITY_ALERT_WEBHOOK_URL

Необязательно. URL входящего вебхука Slack/Discord для оповещений в реальном времени о сбоях аутентификации — см. «Логирование событий безопасности и оповещения» выше. Сбои всегда записываются в stderr независимо от того, задана ли эта переменная

Задайте их в разделе Environment Variables проекта 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; добавьте FIXIE_URL согласно разделу «Фиксированный исходящий IP для Vercel» выше — на практике это обязательно, а не опционально; добавьте пару FATSECRET_CONSUMER_*/FATSECRET_ACCESS_TOKEN*, как только запустите скрипт настройки OAuth1)

  3. Установите версию Node.js проекта Vercel на 22.19 или новее (Project → Settings → General → Node.js Version или там, где это сейчас находится в панели Vercel) до развёртывания — то есть до шага 4 ниже. Зависимость undici@8 этого сервера (используется для прокси Fixie — см. «Фиксированный исходящий IP для Vercel» выше) объявляет "engines": {"node": ">=22.19.0"}, и собственное поле engines в package.json здесь документирует то же требование — но ни то, ни другое само по себе ничего не принуждает на Vercel, поэтому проект, всё ещё закреплённый за старой версией Node (например, 20.x), развернётся «успешно», а затем упадёт во время выполнения.

  4. Подключите этот GitHub-репозиторий в панели Vercel для автоматического развёртывания при пуше в main, либо запустите vercel --prod вручную.

  5. Запишите URL развёртывания (проверьте Project → Settings → Domains — продакшен-URL этого проекта оказался незанятым https://fatsecret-mcp.vercel.app, но это общее пространство имён Vercel, так что не рассчитывайте, что он будет свободен для форка).

  6. Внесите фиксированный IP Fixie в белый список в консоли разработчика FatSecret (см. «Фиксированный исходящий IP для Vercel» выше) — это шаг, который с наибольшей вероятностью подведёт в продакшене, поскольку без него search_foods/get_food_detail/search_recipes/get_recipe_detail/find_food_by_barcode — все завершаются ошибкой FatSecret API error 21.

Подключение к Claude

Пользовательские коннекторы можно добавить только из claude.ai (веб) или десктопного приложения — не из мобильного. После добавления они автоматически доступны и на мобильных устройствах.

  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 и заполните OAuth Client ID / OAuth Client Secret значениями OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET, заданными в Vercel. Claude автоматически обнаружит конечные точки /authorize и /token через метаданные .well-known этого сервера.

  5. Сохраните. Claude должен показать 17 инструментов, перечисленных выше.

Попробуйте спросить: «バナナのカロリーを教えて» (скажи, сколько калорий в банане) или «今日の朝食にバナナを1本記録して» (запиши один банан на завтрак сегодня — после того как этапы 3/4 настроены и проверены).

Благодарности

На дизайн трёхэтапного OAuth1-процесса повлиял fcoury/fatsecret-mcp (MIT), который предоставляет OAuth-процесс в виде самих MCP-инструментов; этот проект вместо этого запускает его один раз как отдельный скрипт настройки (scripts/fatsecret-oauth-setup.ts), поскольку он рассчитан на один личный аккаунт FatSecret, а не на многопользовательское использование. Код из него не копировался.

Related MCP Connectors

Related MCP Servers