MyFitnessPal MCP Server
MyFitnessPal MCP Service
A deployable Model Context Protocol (MCP) server for MyFitnessPal that works as a remote Claude connector: food diary, food search, exercises, body measurements, nutrition goals, water intake, and nutrition reports.
This is a fork of AdamWalt/myfitnesspal-mcp-python (MIT, tool implementations) restructured for remote deployment with the OAuth 2.1 / streamable-http transport skeleton from garmin-mcp-service. MyFitnessPal access is via coddingtonbear/python-myfitnesspal.
Tools
Tool | Type | Description |
| Read | Food diary (meals, entries, nutrition, totals, goals) for a date |
| Read | Search the MyFitnessPal food database |
| Read | Full nutrition breakdown for a food by MFP ID |
| Read | Recently used foods from the authenticated account |
| Read | Most-used foods from the authenticated account |
| Read | Foods created or saved by the authenticated account |
| Read | Body measurement history (Weight, Body Fat, ...) |
| Write | Log a body measurement for today |
| Read | Logged cardio/strength exercises for a date |
| Read | Daily nutrition goals |
| Write | Update daily nutrition goals |
| Read | Water intake for a date |
| Write | Log water intake for a date |
| Write | Add a food entry to a meal |
| Write | Create a new custom food in the MyFitnessPal database |
| Write | Update an existing diary entry by |
| Write | Delete an existing diary entry by |
| Read | Nutrition report (e.g. Net Calories) over a date range |
Food collections (recent / frequent / my foods)
mfp_get_recent_foods, mfp_get_frequent_foods, and mfp_get_my_foods each take an optional limit (recent/frequent default 10, my-foods default 100, max 100) and response_format (markdown or json). They intentionally use the legacy add-to-diary AJAX endpoints (/food/load_recent, /food/load_most_used, /food/load_my_foods) rather than the newer /food/mine, /meal/mine, or /food/new pages, which can redirect to /account/logout even when diary reads and API-token fetches still work.
Editing diary entries
mfp_get_diary with response_format=json now surfaces an entry_id for each meal entry. Pass that id to:
mfp_update_food_entry- changemeal,quantity,unit(serving-size label, e.g."350 ml"), orweight_id(raw MFP serving-size option id, overridesunit) for an entry; requiresdatefor historical entries. MyFitnessPal can rewrite an entry during edit, so the response reportscurrent_entry_idandentry_id_changedso you can keep tracking the right row.mfp_delete_food_entry- delete an entry byentry_id(requiresdatefor historical entries).
Creating a custom food
mfp_create_food submits a brand-new food to the MyFitnessPal database when mfp_search_food turns up nothing suitable. Required fields are description and the per-serving core macros (calories, fat, carbs, protein); brand, serving_size, servings_per_container, share_public, and the optional micronutrients (fiber, sugar, sodium, cholesterol, vitamins, etc.) round it out. All nutrition values are entered per single serving as defined by serving_size (e.g. serving_size="125 g" with the numbers for a 125 g portion). When the serving unit is a mass unit (g, mg, kg, oz, lb) the gram weight is recorded so MFP's gram-based scaling stays correct.
On success it returns the new food's mfp_id; pass that to mfp_add_food_to_diary to log it (it may take a short moment to also surface in mfp_search_food). Re-running with the same details creates duplicate foods.
share_public=True is irreversible — it submits the food to MyFitnessPal's shared public database, and public foods can no longer be edited or deleted. Leave it False (default) to create a private food you can still delete.
Implementation note: the
python-myfitnesspallibrary'sset_new_food()no longer works — MyFitnessPal replaced the server-rendered/food/newRails form with a client-side SPA that has noauthenticity_tokeninput, so the library's HTML scrape raisesIndexError: list index out of range.mfp_create_foodinstead POSTs to thev2/foodsAPI with the account's bearer token (the same mechanism the library still uses for goals).
Authentication: the cookie strategy
MyFitnessPal's login page is captcha-protected, so headless password login is dead - this server never asks for your MFP password. Instead it reads session cookies from one of:
Firefox profile sidecar (recommended): log into myfitnesspal.com once, interactively, in a Firefox profile; mount that profile directory read-only into the container at
/profile. The server copiescookies.sqlite(and its WAL) to a temp file on each refresh - Firefox's locks don't matter - and extracts themyfitnesspal.comcookies. The copy is cached and only re-read when the file changes, so Firefox can keep running (e.g. a headless Firefox sidecar container you occasionally VNC into to re-login).JSON cookies file:
MFP_COOKIES_FILEpointing at{"cookies": {name: value}}(AdamWalt's~/.mfp_mcp/cookies.jsonformat) or a plain{name: value}dict.
Session cookies expire eventually (~30 days); when tools start failing with auth errors, log into MFP again in that Firefox profile.
Environment Variables
Variable | Default | Description |
|
| Firefox profile dir (or parent dir of profiles) containing |
| - | JSON cookies file; used if set and no |
|
| Transport: |
|
| Bind address for HTTP mode |
|
| Port for HTTP mode |
| - | Comma-separated allowed |
| - | Shared passcode for the OAuth login page (remote connectors). Omit for unauthenticated LAN-only use |
| - | Exact public URL clients use (no path), e.g. |
|
| Access-token lifetime in seconds. When the token expires the connector re-authorizes, which means re-entering the passcode; a short value (the old 24h default) forces a daily re-login if the client doesn't silently refresh. Lower it (e.g. |
Docker
docker build -t myfitnesspal-mcp-service .
docker run -d -p 8000:8000 \
-v ~/.mozilla/firefox/abcd1234.default-release:/profile:ro \
-e MCP_ALLOWED_HOSTS=mfp.example.com \
-e MCP_RESOURCE_URL=https://mfp.example.com \
-e MCP_OAUTH_PASSCODE="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" \
ghcr.io/delize/myfitnesspal-mcp-service:latestCI builds and pushes ghcr.io/delize/myfitnesspal-mcp-service (amd64 + arm64) on pushes to main and v* tags.
Claude Connector Setup
Same flow as garmin-mcp-service:
Deploy behind HTTPS (reverse proxy) with
MCP_TRANSPORT=streamable-http,MCP_RESOURCE_URL, andMCP_OAUTH_PASSCODEset.In Claude, add a custom connector with URL
https://mfp.example.com/mcp. Leave OAuth Client ID/Secret blank - the server supports dynamic client registration (/register), authorization + PKCE (/authorize,/token).Claude redirects you to the
/loginpasscode page once; enterMCP_OAUTH_PASSCODE. After that the client holds and refreshes its own token.
The passcode proves "the caller knows the passcode", not identity - keep network-level access control (IP allowlist, VPN) in front of any internet-facing deployment. Omitting MCP_OAUTH_PASSCODE/MCP_RESOURCE_URL runs the HTTP server unauthenticated (a warning is logged); only do that on a trusted network.
For local stdio use (Claude Desktop):
{
"mcpServers": {
"myfitnesspal": {
"command": "python",
"args": ["-m", "myfitnesspal_mcp.server"],
"env": {
"MFP_FIREFOX_PROFILE_DIR": "/home/you/.mozilla/firefox/abcd1234.default-release"
}
}
}
}Troubleshooting
Tools don't appear even though the connector shows "Connected"
Problem: The connector authorizes and shows as Connected, but its tools never surface in a conversation - asking the model to use them, or searching for them, turns up nothing. No error is shown.
Cause: This is almost always a client-side tool-budget limit, not a problem with this server. Claude caps how many tools can be active in a single conversation across all connected servers combined. If another connector exposes a very large tool set, it can consume that budget and silently crowd this server's tools out of the conversation. (Seen in practice with a connector exposing ~170 tools starving this server's handful.)
Confirm / fix:
In a fresh conversation, disable the other large connector(s) and check whether these tools now appear. If they do, it was the budget.
Keep high-tool-count connectors in separate conversations, or trim their active tools if the client supports per-tool toggles.
This server always returns its full tool list regardless - you can verify independently with an authenticated
tools/listcall against/mcp. If that returns the tools but the client doesn't show them, the gap is on the client side, not here.
Attribution
Tool implementations: AdamWalt/myfitnesspal-mcp-python (MIT, this repo is a fork - full history preserved)
MyFitnessPal client library: coddingtonbear/python-myfitnesspal
OAuth/transport skeleton: garmin-mcp-service
License
MIT - see LICENSE (preserves the original copyright).
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/delize/myfitness-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server