loading…
Search for a command to run...
loading…
MCP server integrating Foodvisor nutrition API for food search, meal logging, daily summaries, and progress tracking via LLM agents like Claude.
MCP server integrating Foodvisor nutrition API for food search, meal logging, daily summaries, and progress tracking via LLM agents like Claude.
A remote Model Context Protocol server that exposes the Foodvisor nutrition API to LLM agents (Claude, Cursor, …). Search foods, log meals, fetch progress and macros — all from your assistant.
Disclaimer. This project is unofficial. It uses Foodvisor's private mobile API by reverse-engineering its requests and is not endorsed by Foodvisor. Use at your own risk; endpoints may change without notice.
| Tool | Description |
|---|---|
search_food |
Search the Foodvisor catalog by free-text query. |
get_food_details |
Full nutritional info (macros, vitamins, units) for one or more food_ids. |
log_meal |
Add foods to a meal slot on a given date. |
list_meals |
Logged meals on a date range. |
get_daily_summary |
Total calories/macros for a day vs. your targets. |
get_progress |
Daily calories, weight and Foodvisor grade for ~90 days. |
get_fv_grade_distribution |
Share of A/B/C/D meals over rolling 7/30/90 day windows. |
get_streak |
Current logging streak and freezes. |
get_water_log |
Daily water intake on a date range. |
get_profile |
Profile and nutritional goals. |
git clone https://github.com/cldt-fr/foodvisor-mcp.git
cd foodvisor-mcp
docker compose up -d
The server now listens on http://localhost:3000/mcp. Health probe at /health.
To run behind a reverse proxy (Caddy, Traefik, nginx) on a public domain, just terminate TLS in front of port 3000.
foodvisor-mcp supports two ways to authenticate, both backed by the same underlying credential — your Foodvisor refresh token:
Authorization: Bearer …. Useful for scripts or quick tests. The server detects the token shape and proxies as before.Either way, no per-user state is stored on the server: the OAuth-issued JWT is self-contained and the legacy mode is purely passthrough.
Foodvisor only authenticates via Apple Sign-In on iOS — there is no public OAuth or password endpoint. Capture the POST /user/auth/ response on a real iPhone with Charles Proxy (or Proxyman, mitmproxy, …) configured as an HTTPS man-in-the-middle:
POST https://api.foodvisor.io/api/6.0/ios/FR/fr_FR/user/auth/ request. The JSON response contains tokens.refresh — that's your long-lived credential (≈ 6 months).Refresh tokens give full access to your nutrition history. Treat them like passwords.
Add the server as a connector with its public /mcp URL (e.g. https://foodvisor-mcp.example.com/mcp). Claude will:
/.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server.POST /register./authorize page in your browser. Paste your Foodvisor refresh token in the form and submit.After that you can use the tools directly from Claude. When the access token expires, Claude re-runs the flow.
{
"mcpServers": {
"foodvisor": {
"url": "https://foodvisor-mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer <YOUR_FOODVISOR_REFRESH_TOKEN>"
}
}
}
}
Requires Node ≥ 22.
npm install
npm run dev # tsx watch on $PORT (default 3000)
npm run typecheck
npm run build && npm start
src/
├── index.ts # Node http server + per-request MCP transport + OAuth routes
├── env.ts # zod-validated env vars
├── auth/
│ ├── extract.ts # Bearer parsing — accepts OAuth JWT and legacy Foodvisor refresh
│ └── token-cache.ts # Foodvisor access-token cache + refresh
├── oauth/
│ ├── jwt.ts # HS256 sign/verify + AES-256-GCM encrypt/decrypt
│ ├── store.ts # in-memory clients + auth codes (TTL)
│ ├── login.ts # HTML login page (paste refresh token)
│ ├── handlers.ts # /register, /authorize, /token handlers
│ └── metadata.ts # /.well-known/* metadata builders
├── foodvisor/
│ ├── client.ts # fetch wrapper with 401 retry
│ ├── endpoints.ts # typed endpoint helpers
│ └── types.ts # response shapes
└── mcp/
├── server.ts # createMcpServer(ctx)
└── tools/ # one file per tool group
├── food.ts
├── meal.ts
├── progress.ts
├── trackers.ts
└── profile.ts
The HTTP server is intentionally minimal (no Express/Hono) — each POST /mcp spins up a fresh McpServer bound to the caller's userId/refreshToken and a stateless StreamableHTTPServerTransport.
| Var | Default | Purpose |
|---|---|---|
PORT |
3000 |
HTTP listen port |
LOG_LEVEL |
info |
debug | info | warn | error |
MCP_PUBLIC_URL |
derived | Public origin (e.g. https://foodvisor-mcp.example.com). Used in OAuth metadata. If unset, derived per-request from Host + X-Forwarded-Proto. |
MCP_JWT_SECRET |
random | HMAC secret used to sign OAuth-issued tokens. Min 32 chars. Set explicitly in production — otherwise tokens are invalidated on every restart. |
MCP_ACCESS_TOKEN_TTL |
2592000 |
Lifetime of OAuth access tokens, in seconds (default 30 days). |
FOODVISOR_BASE_URL |
https://api.foodvisor.io |
Override only for testing |
FOODVISOR_LOCALE_PATH |
/api/6.0/ios/FR/fr_FR |
Locale path prefix used by upstream |
Generate a stable secret with:
openssl rand -base64 48
/mcp endpoint with HTTPS in production and consider IP allowlists if it is exposed publicly.user_id, so concurrent requests from the same user share a single access token; concurrent refresh attempts coalesce via an in-flight map.Issues and PRs welcome at https://github.com/cldt-fr/foodvisor-mcp. Please don't open issues asking for help reverse-engineering Foodvisor endpoints; capture them yourself with Charles/Proxyman and contribute a typed wrapper.
MIT — see LICENSE.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"foodvisor-mcp": {
"command": "npx",
"args": []
}
}
}