fatsecret-mcp
fatsecret-mcp
一个个人远程 MCP(Model Context Protocol)服务器,让 Claude 可以直接在对话中搜索 FatSecret 的食物/食谱数据库,并读写你自己的食物日记、体重和运动日志。部署在 Vercel 的免费 Hobby 层级。是 fitness-mcp(Hevy)的姊妹项目 — 每个产品对应一个 MCP 服务器,共享相同的认证模式。
许可证
状态
搜索(Phase 2):已实现 —
search_foods,get_food_detail,search_recipes,get_recipe_detail,find_food_by_barcode。无需 FatSecret 用户授权;只需要来自 FatSecret 开发者控制台的 OAuth 2.0 客户端 ID/密钥。日记/体重/运动/个人资料(Phase 4):已实现,但未经真实 FatSecret 账户验证 — 构建此项目时尚无 FatSecret API 注册(见下文“尚未验证的内容”)。在依赖它之前,请对照真实账户确认每个方法的确切字段名,如有出入请更新代码/测试。
3-legged OAuth1 设置脚本(Phase 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 的 static-header 选项仍处于 beta 限制,本服务器还运行自己的最小化 OAuth 2.1 授权服务器(lib/oauth.ts、/api/oauth/authorize、/api/oauth/token),因此 Claude 标准的 OAuth 客户端 ID/密钥字段可作为始终可用的后备方案 — 完整理由见 fitness-mcp 的 README,此处同样适用。
② 本服务器 ↔ FatSecret — 这正是比 fitness-mcp 更复杂的地方,因为 FatSecret 本身对两类 API 方法使用两种不同的 OAuth 版本,这一点无法回避 — 这是 FatSecret API 的设计,而非这里所做的选择:
FatSecret 方法类别 | 示例方法 | 本服务器的认证方式 |
签名请求(不涉及特定用户) |
| OAuth 2.0 客户端凭据 — |
签名并委派请求(读取/写入你的 FatSecret 账户) |
| OAuth 1.0a、3-legged、HMAC-SHA1 签名 — |
具体来说: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(通过运行一次设置脚本获得)。
工具列表
工具 | 类型 | 所需认证 | 描述 |
| 读取 | OAuth2(应用) | 按名称搜索 FatSecret 的食物数据库 |
| 读取 | OAuth2(应用) | 获取单个食物的完整每份营养信息 |
| 读取 | OAuth2(应用) | 搜索 FatSecret 的食谱数据库 |
| 读取 | OAuth2(应用) | 获取单个食谱的完整食材/步骤 |
| 读取 | OAuth2(应用) | 将 GTIN-13 条码解析为 foodId — 需要 |
| 读取 | OAuth1(用户) | 列出某一日期的食物日记条目 |
| 读取 | OAuth1(用户) | 列出收藏的食物 |
| 读取 | OAuth1(用户) | 列出最常吃的食物,可按餐次筛选 |
| 读取 | OAuth1(用户) | 列出最近吃过的食物,可按餐次筛选 |
| 读取 | OAuth1(用户) | 列出某一月份的体重条目 — 可能仅限 Premier |
| 读取 | OAuth1(用户) | 列出某一日期的运动日记条目 |
| 读取 | OAuth1(用户) | 获取用户的 FatSecret 个人资料摘要 |
| 写入 | OAuth1(用户) | 将食物记入日记 |
| 写入 | OAuth1(用户) | 更新现有日记条目 |
| 写入 | OAuth1(用户) | 删除日记条目 |
| 写入 | OAuth1(用户) | 记录/更新体重条目 — 可能仅限 Premier |
| 写入 | OAuth1(用户) | 记录运动条目 |
写入工具默认进行 dry-run
与 fitness-mcp 相同的设计:每个写入工具都需要 confirm: true 参数。其描述会指示调用方 LLM 先向用户准确展示将要写入的内容,并取得明确的同意。这是一种结构性的提示,而不是保证 — 决定是否调用工具的同一个 LLM 也会设置 confirm,而且在认证层没有读写工具之间的作用域分隔,因此任何持有有效 MCP_BEARER_TOKEN 的调用者都可以调用任何工具。
尚未验证的内容
构建此项目时尚无 FatSecret API 注册(这一步需要人工操作 — 见下方“设置”),因此:
search_foods/get_food_detail/search_recipes/get_recipe_detail/profile.get/food_entries.get/weights.get_month的方法名和核心参数已对照可用的第三方 FatSecret 客户端实现确认(非猜测)— 来源见 git 历史。food.find_id_for_barcode的响应结构、weight.update的参数名以及所有exercise_entries.*都是尽力而为的重构,其推断理由已在lib/fatsecret/*.ts中内联标注。请将这些视为可靠的起点,而非经过验证的事实。注册后请对照真实账户运行下面的手动验证清单,并修正你发现的任何字段名不匹配问题(
lib/fatsecret/*.test.ts中的单元测试也需要相应更新)。
设置
在 https://platform.fatsecret.com/ 注册 FatSecret Platform API 应用。你将获得:
OAuth 2.0 客户端 ID/密钥(用于
FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET)。OAuth 1.0 Consumer Key/Secret(用于
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。为 OAuth2 令牌请求将你的出口 IP 加入白名单 — FatSecret 要求这样做(最多 15 个地址/范围)。如果部署到 Vercel,则需要一个静态出口 IP(例如通过 Vercel 支持的出口代理/插件);Vercel 默认的 serverless 函数没有固定 IP。
运行一次本地开发服务器以冒烟测试搜索(Phase 2 只需要第 1 步):
npm install cp .env.example .env.local # fill in FATSECRET_CLIENT_ID/SECRET + the MCP_BEARER_TOKEN/OAuth trio vercel dev运行一次性的 3-legged OAuth1 设置(除 5 个搜索/详情工具外的每个工具都需要)— 见下方 Phase 3。
部署到 Vercel — 见下方 Deploy。
本地开发
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。
Phase 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)将:
向 FatSecret 请求一个未授权的请求令牌。
打印授权 URL — 打开它,登录 FatSecret 并批准。FatSecret 会显示一个确认码。
提示你粘贴该确认码,然后将其换取为永久的访问令牌/密钥。
将
FATSECRET_ACCESS_TOKEN/FATSECRET_ACCESS_TOKEN_SECRET写入.env.local。
然后将这两个值也添加到 Vercel 的环境变量中(.env.local 永远不会被部署)— 见下方 Deploy。
根据 FatSecret 的文档,此访问令牌不会过期。如果它被撤销(例如你在 FatSecret 账户设置中移除了该应用的访问权限),只需重新运行脚本即可获取新的令牌——这正是 fitness-mcp 的 derive() 模式所体现的精神:在这里丢失凭据并非灾难,它只是需要一条命令即可修复,只是这次是交互式的,而非确定性的重新派生。
从一个易记的密码短语生成面向 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 设置脚本,而非此密码短语。
测试
三层测试,均在每次推送/PR 时于 CI(.github/workflows/ci.yml)中运行——都不需要真实的 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/重定向 URI 白名单(包含 RFC 7636 测试向量)、FatSecret OAuth2 客户端凭据令牌获取/缓存/刷新(lib/fatsecret/appAuth.test.ts)、OAuth1 HMAC-SHA1 签名与独立重新实现的交叉验证(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路由;.well-knownOAuth 元数据路由。端到端测试(
test/e2e/*.e2e.test.mjs):启动生产构建并通过真实 HTTP 进行断言——健康检查、无效/缺失认证时返回 401、tools/list返回全部 17 个工具、OAuth 发现元数据,以及完整的授权码 + PKCE 往返。不涉及真实 FatSecret 数据(CI 设计上就没有真实凭据)。
使用真实 FatSecret 账户手动验证
CI 从不接触真实的 FatSecret 数据,而且——按照上文“尚未验证的内容”——本服务器对 FatSecret 精确响应形状的一些假设尚未针对真实账户进行过任何检查。完成注册并运行 OAuth1 设置脚本后,请按此清单逐项检查并修复你发现的任何不匹配之处:
在
.env.local中设置真实的FATSECRET_CLIENT_ID/FATSECRET_CLIENT_SECRET,运行vercel dev,并使用真实查询调用search_foods(例如通过上面的冒烟测试curl模式,使用tools/call)——确认返回了真实结果,并且对其中的一项调用get_food_detail能返回合理的营养数据。类似地调用
search_recipes和get_recipe_detail。如果你的套餐包含
barcode范围,请使用真实产品的条形码调用find_food_by_barcode,并确认响应形状与lib/fatsecret/foods.ts的RawFindIdForBarcodeResponse匹配——如果不匹配则修复它。运行
npm run fatsecret:oauth-setup,然后调用get_profile和get_food_diary——确认lib/fatsecret/profile.ts/lib/fatsecret/diary.ts中的字段名与真实响应匹配(这些字段名是根据文档重建的,并非抓取所得)。使用
confirm: true和一条明显是临时测试的条目调用create_food_diary_entry,然后对同一日期调用get_food_diary,确认它显示出正确的食物/份量/数量/餐次。然后调用update_food_diary_entry更新它,再调用delete_food_diary_entry删除它——确认每一步都能正常往返。如果你的套餐包含体重跟踪,请使用
confirm: true调用update_weight,并确认get_weight_history能反映该更新。create_exercise_entry和get_exercise_diary是此代码库中验证最少的一对(参见lib/fatsecret/exercise.ts顶部的警告)——在依赖它之前,请对照 https://platform.fatsecret.com/docs/guides 确认准确的方法名/参数;它可能需要真正的修复,而不仅仅是验证。切勿提交真实的 FatSecret 凭据,也切勿在 CI 中运行此清单。
环境变量
变量 | 用途 |
| OAuth 2.0 客户端凭据 —— Signed Request 方法(搜索/详情工具) |
| 可选。以空格分隔的 OAuth2 作用域,默认 |
| 可选。默认为 |
| OAuth 1.0 消费者密钥/消费者机密 —— 用于签署一次性设置脚本以及每次 Signed & Delegated 调用 |
| OAuth 1.0 访问令牌/机密,用于你的 FatSecret 账户——通过 |
| 本服务器在每个请求上要求的共享机密,也是我们 OAuth 流程颁发的 access_token |
| 本服务器自身极简 OAuth 授权服务器的凭据 |
| 可选。 |
在 Vercel 项目的环境变量(Production + Preview)中设置这些变量。切勿提交真实值——.env.example 仅记录这些名称。
部署
vercel linkvercel env add FATSECRET_CLIENT_ID(对上面表格中你已有值的每个变量重复此操作——至少包括FATSECRET_CLIENT_ID/SECRET、MCP_BEARER_TOKEN、OAUTH_CLIENT_ID/SECRET;一旦运行了 OAuth1 设置脚本,再添加FATSECRET_CONSUMER_*/FATSECRET_ACCESS_TOKEN*这一对)在 Vercel 仪表板中连接此 GitHub 仓库,以便在推送到
main时自动部署,或手动运行vercel --prod。记下部署后的 URL(检查项目 → 设置 → 域名,因为
fatsecret-mcp.vercel.app可能已被 Vercel 的共享命名空间占用)。在 FatSecret 开发者控制台中将该部署的出站 IP 加入白名单,用于 OAuth2 令牌请求(参见设置步骤 1)——这是在生产环境中最可能出问题的一步,因为 Vercel 无服务器函数默认没有固定 IP。
连接到 Claude
自定义连接器只能从 claude.ai(网页版)或桌面应用添加——不能从移动应用添加。一旦在那边添加,它们就会自动在移动端可用。
在 claude.ai 上:设置 → 连接器 → 添加自定义连接器。
名称:
FatSecret。URL:https://<your-deployment>/api/mcp。如果你的账户拥有“请求标头”测试版功能:在此处添加
Authorization: Bearer <MCP_BEARER_TOKEN>,然后跳到第 5 步。否则,打开“高级设置”,用 Vercel 中设置的
OAUTH_CLIENT_ID/OAUTH_CLIENT_SECRET值填写 OAuth 客户端 ID / OAuth 客户端密钥。Claude 将通过本服务器的.well-known元数据自动发现/authorize和/token端点。保存。Claude 应列出上述 17 个工具。
试试问:“バナナのカロリーを教えて”(告诉我一根香蕉的卡路里),或“今日の朝食にバナナを1本記録して”(记录一根香蕉作为今天的早餐——在第 3/4 阶段设置并验证之后)。
致谢
三方 OAuth1 流程设计的灵感来自 fcoury/fatsecret-mcp(MIT 许可),该项目将 OAuth 流程本身暴露为 MCP 工具;而本项目则将其作为一次性独立设置脚本(scripts/fatsecret-oauth-setup.ts)运行,因为本项目是为单个个人 FatSecret 账户而非多用户使用而构建的。未从中复制任何代码。
This server cannot be installed
Maintenance
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
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
MCP server for Withings health data — sleep, activity, heart, and body metrics.
GibsonAI MCP server: manage your databases with natural language
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/ikeike443/fatsecret-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server