Model Context Protocol (MCP)¶
NutriTrace speaks the Model Context Protocol so any MCP-compatible AI client (Claude Desktop, Cursor, Codex, VS Code, custom agents) can read your food catalog, diary, and goals directly from your self-hosted instance.
This is different from Trace AI (which runs inside the app with 16 built-in tools). MCP lets an external agent, running on your own machine, ask questions of your NutriTrace server without any of your data going through NutriTrace's own AI provider settings.
Off by default. Opt in with one env var + one API token.
Available tools¶
Twelve tools across three tiers. Setup for each tier below; full arg/return reference in the MCP tool catalog.
Read (Phase 1)¶
Five tools, read-only, always available when MCP is on: get_goals, get_daily_totals, list_diary_entries, search_foods, get_recent_foods.
Write (Phase 2)¶
Four additive log tools: log_food, log_water, log_meal, log_body_stat. Everything they write shows up as normal entries in the diary UI, editable and deletable through the app like anything else. Off by default; turn on with MCP_WRITE_ENABLED=1 AND a token that holds mcp:write. Either missing and the write tools simply don't appear in tools/list.
Destructive (Phase 3)¶
Three destructive tools: delete_diary_entry, edit_diary_entry, create_food. Off by default. Three gates ALL required:
MCP_DESTROY_ENABLED=1on the server.- Token holds
mcp:destroy(mint separately; amcp:writetoken does NOT unlock destroy). - Every call includes
confirm: truein the arguments. Belt-and-suspenders: your MCP client already prompts per action, but the tool refuses without the arg so a hallucinated call from a client that skips prompts still gets rejected.
Delete tools return the removed content so the agent can offer undo; edit tools return before and after.
Enable it¶
Add one env var to your docker-compose.yml:
Redeploy. The endpoint at /api/mcp starts responding to authenticated MCP clients. Everything else remains untouched; existing users see no change.
Mint an MCP token¶
Log in as an admin, then:
- Open Settings → API Tokens
- New token → name it (e.g. "claude-desktop")
- Check the
mcp:readscope - Create
The raw token (nt_pat_...) is shown once. Copy it now; the server only keeps a SHA-256 hash after this.
Wire up Claude Desktop¶
Claude Desktop reads MCP servers from a JSON config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Add a mcpServers entry:
{
"mcpServers": {
"nutritrace": {
"url": "https://nutritrace.example.com/api/mcp",
"headers": {
"Authorization": "Bearer nt_pat_your_token_here"
}
}
}
}
Restart Claude Desktop. In a new chat, ask "What did I eat today?" or "What's my calorie goal?" and Claude will call the NutriTrace tools directly.
Wire up other clients¶
The transport is standard MCP Streamable HTTP (single POST endpoint). Any client that accepts a base URL + bearer header works:
- Cursor: Settings → MCP → Add server with the same URL and Authorization header.
- Codex / OpenAI Assistants: Point at the URL; pass the bearer as a header.
- Custom (Node / Python): Use the official
@modelcontextprotocol/sdkclient. The endpoint speaks JSON-RPC 2.0 framed over Streamable HTTP.
Security model¶
- Off by default. Nothing exposed without
MCP_ENABLED=1. - Scoped tokens.
mcp:readis a distinct scope; a token minted forread:foods(federation) does not unlock MCP, and vice-versa. - Rate-limited per token. Same bucket as the rest of the API tokens; overspend returns 429.
- Per-user isolation. Every tool query is scoped to the token owner's data; no cross-user leakage is possible, even for admins.
- Origin gating (DNS-rebinding defense). Server-to-server clients (Claude Desktop, Cursor, etc.) send no Origin header and pass. Browser-based MCP inspectors need to be listed in
ALLOWED_ORIGINSexplicitly. This is the MCP spec's recommended defense against DNS rebinding attacks and NutriTrace does not soften it with a "trust the Host header" fallback. - Bearer over HTTPS only. Never send the token over plain HTTP; it's equivalent to a user password for the tools it grants.
Env vars¶
| Variable | Default | Description |
|---|---|---|
MCP_ENABLED |
0 |
Set to 1 to expose /api/mcp. |
MCP_WRITE_ENABLED |
0 |
Set to 1 to allow write tools (log_food, log_water, log_meal, log_body_stat) to be registered. Also requires the calling token to hold mcp:write. |
MCP_DESTROY_ENABLED |
0 |
Set to 1 to allow destructive tools (delete_diary_entry, edit_diary_entry, create_food) to be registered. Also requires the calling token to hold mcp:destroy AND every call to include confirm: true. |
ALLOWED_ORIGINS |
(empty) | Comma-separated list of origins that browser-based MCP clients may use. Server-to-server clients (no Origin header) always pass. Leave empty unless you're specifically using the MCP Inspector in a browser. |
Verifying¶
A smoke-test script lives at scripts/mcp-smoke.mjs in the NutriTrace repo. Handshake, list tools, invoke each one, and verify negative-path (missing bearer, bad origin) gating, all in one command:
git clone https://github.com/traceapps/nutritrace && cd nutritrace
node scripts/mcp-smoke.mjs https://nutritrace.example.com nt_pat_your_token_here
Expected output ends with 9 passed, 0 failed. Add --writes to also exercise the write tools (needs mcp:write + MCP_WRITE_ENABLED=1), and --destroy on top to verify the destructive tools' gate-refusal paths (needs mcp:destroy + MCP_DESTROY_ENABLED=1; the smoke script never actually deletes anything).
Troubleshooting¶
{"error":"MCP not enabled on this server"} (404). MCP_ENABLED=1 isn't set in the container env. Redeploy after adding it.
{"error":"auth_missing"} (401). No Authorization: Bearer nt_pat_... header.
{"error":"auth_scope"} (403). The token was minted without mcp:read. Revoke and create a new one with the scope checked.
{"error":"Origin not allowed"} (403). A browser-based client is sending an Origin header that isn't in ALLOWED_ORIGINS. Server-to-server clients (Claude Desktop, Cursor CLI) don't send Origin and are unaffected.
429 rate_limited. Backoff per the Retry-After header. Same per-token rate cap as /api/v1/*.
Roadmap¶
Later: MCP prompts capability (guided workflows), additional read tools (weight history, body composition, recipes) as demand justifies them, and possibly delete_diary_day (currently blocked by tombstone-resurrection semantics; single-entry delete is the safer starting point).
Feature request and progress: issue #103 (thanks @javydekoning).