Cronometer MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Cronometer MCP What did I eat yesterday and how many calories was that?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Cronometer MCP
One MCP server for Cronometer that works in Claude (Claude Code, Claude Desktop, claude.ai web and mobile) and ChatGPT. It merges what every public Cronometer MCP could do (see Credits) into 65 tools, all tested against a live account.
Cronometer has no public API. This server uses the two private ones the official apps use:
The mobile API (
mobile.cronometer.com, plain JSON) for almost everything.The web app's GWT-RPC for what only the website does: repeating foods, macro templates and the weekly schedule, copying between any two days, deleting notes, cancelling a recurring fast, and the CSV exports.
Both log in with your Cronometer email and password. Either API can change without notice when Cronometer ships an update.
Tools
Area | Tools |
Diary |
|
Nutrition |
|
Foods |
|
Recipes |
|
Repeating foods |
|
Targets |
|
Biometrics and sleep |
|
Exercise |
|
Notes |
|
Fasting |
|
Account and export |
|
ChatGPT search |
|
Paging |
|
Tools are annotated read-only or destructive, so Claude and ChatGPT ask before anything that deletes.
Related MCP server: cronometer-api-mcp
Three ways to use it
Connect to a hosted server: nothing to install. Works in Claude (web, desktop, mobile) and ChatGPT.
Run it on your own computer: for Claude Code and Claude Desktop.
Host your own server: for yourself, or open to anyone.
1. Connect to a hosted server
A public server runs at https://cronometer.chrisvanek.com/mcp, or use the address of any server set up as in part 3.
Claude (claude.ai, Claude Desktop, mobile): Settings → Connectors → Add custom connector. Paste the URL, then Connect.
ChatGPT: Plugins → Add → Add custom MCP server. Paste the URL and choose Authentication OAuth. Custom connectors depend on your ChatGPT plan.
A Connect Cronometer window opens. Sign in with your own Cronometer email and password, and the tools appear in your chats, working on your own diary.
Before you sign in, know that the server keeps your Cronometer password, encrypted, so it can stay signed in for you. Its operator could read it, so only use a server run by someone you trust. Accounts with two-factor login on can't connect.
2. Run it on your own computer
Needs uv and Python 3.12+.
git clone https://github.com/ChrisJVanek/cronometer-mcp.git && cd cronometer-mcp
uv sync
cp .env.example .env # then fill in CRONOMETER_USERNAME and CRONOMETER_PASSWORD.env is gitignored. If your account uses two-factor login, also set CRONOMETER_TOTP_SECRET and run uv sync --extra totp.
Then add this to the mcpServers section of ~/.claude.json (Claude Code) or ~/Library/Application Support/Claude/claude_desktop_config.json (Claude Desktop):
"cronometer": {
"command": "uv",
"args": ["run", "--frozen", "--directory", "/path/to/cronometer-mcp", "cronometer-mcp"]
}If you also added a hosted connector to your Claude account, Claude Desktop already gets the tools from it; skip the local entry there so the tools are not listed twice.
3. Host your own server
Hosted, the server runs over HTTPS and is its own OAuth server, so Claude and ChatGPT can connect as in part 1. It runs one of two ways:
Just you | Open to anyone | |
Set |
|
|
The connect page asks for | the server password ( | the person's own Cronometer email and password |
Tools work on | your account | each person's own account |
On Railway (or any Docker host)
The Dockerfile here runs the server in HTTP mode.
Build: point a Railway service (or any Docker host) at this repo. The
Dockerfileneeds no build arguments.Variables: the ones in the table above, plus
MCP_PUBLIC_URL(your public HTTPS address, e.g.https://cronometer.example.com). Optional:MCP_API_KEY,CRONOMETER_ACCOUNT_TZ, andMCP_ALLOWED_HOSTSfor any other domain it should answer on (Railway's own*.up.railway.appname is accepted automatically).Volume: mount one at
/data. It holds the Cronometer sessions, the OAuth clients and tokens, and in open mode the encrypted accounts, so redeploys don't disconnect anyone.Health check:
/health.
The "approve on this computer" shortcut on the connect page only works when the server runs on your own machine.
When moving from one host to another, set MCP_OAUTH_IMPORT to the old oauth.json, base64 encoded, for one deploy. Connected apps then keep working without signing in again. Remove the variable once the log says Imported OAuth state.
Open it to anyone with a Cronometer account
Set MCP_SECRET_KEY (16+ characters, e.g. from openssl rand -base64 32). Nothing about the people using it goes in the server's settings: each person adds https://<your server>/mcp in Claude or ChatGPT and signs in on the connect page with their own Cronometer email and password. The server checks them by signing in to Cronometer, and from then on every tool their app calls reads and writes their own diary.
CRONOMETER_USERNAMEandCRONOMETER_PASSWORDbecome optional. If set, that account signs in like anyone else, and apps connected to it before keep working.MCP_ALLOWED_EMAILS(optional) limits who may connect, e.g.mum@example.com,dad@example.com. Unset or*means anyone.MCP_AUTH_PASSWORDis not needed.MCP_API_KEYand the local "approve on this computer" shortcut still mean the owner account.
Good to know:
Keep
MCP_SECRET_KEYand never change it: it encrypts the Cronometer passwords the server keeps (inaccounts.jsonon the volume) so it can sign people in again when Cronometer's session expires. A new key means everyone connects again. You, as operator, could read those passwords, so people are trusting you.Five wrong passwords for one email lock that email out for 15 minutes; other people are unaffected.
Accounts with two-factor login on can't connect: the server has no way to get their code. They can turn two-factor off in Cronometer.
On your own Mac, through a tunnel
Set up
.envas in part 2, and addMCP_AUTH_PASSWORD(8+ characters) orMCP_SECRET_KEY.Point a public hostname at
http://localhost:8430. With a Cloudflare named tunnel, add this to~/.cloudflared/config.ymlbefore the catch-all rule, then runcloudflared tunnel route dns <tunnel> cronometer.example.com:- hostname: cronometer.example.com service: http://localhost:8430Run the server at login (macOS launchd, restarts if it stops):
scripts/install-macos-service.sh https://cronometer.example.comOr run it in the foreground:
uv run cronometer-mcp --transport http --public-url https://cronometer.example.comConnect as in part 1 with
https://cronometer.example.com/mcp. On the Mac running the server you can also click Approve there without a password. That approval only works onlocalhost, so nothing coming in through the tunnel can use it.
Tokens last 24 hours and refresh for 90 days. They are kept in ~/.local/share/cronometer-mcp/oauth.json, so a restart does not disconnect anything. Clients that can send headers can use Authorization: Bearer $MCP_API_KEY instead of OAuth.
Logs: ~/Library/Logs/cronometer-mcp.log. Health check: GET /health.
Development
uv run pytest # 87 tests, offline
uv run ruff check srctests/test_oauth.py runs the whole connector flow (discovery, registration, consent, code exchange, refresh, tools/list) without touching Cronometer.
Notes from live testing (Nov 2026)
get_fasting_with_date_rangereturns an empty list for every range. History now reads/api/v2/get_fastingand filters by date. A fast's end must be sent asfinish;endis silently dropped.The v3 diary-entries DELETE rejects notes. Notes go through the web app's
removeNoteinstead.The web app's repeat items label the mobile food id as
food_source_idand the measure id asfood_id. The tools translate both ways.GWT back-references (the same weekday shared by two repeat items) are resolved instead of being dropped.
Credits
Built on rollecode/cronometer-mcp (MIT, itself from rwestergren/cronometer-api-mcp) for the mobile API. The web-app client in src/cronometer_mcp/web.py is from cphoskins/cronometer-mcp (MIT). Recipe import comes from rwestergren, sleep logging from firaskudsy/cronometer-api-mcp, barcode and activity search from ekelly95/cronometer-mcp, and the "can I eat it" and day-analysis ideas from LucasHenriqueDiniz/gocronometer-mcp. Also reviewed: atticoos, Pop101, DiegoDeLaCruz220, DoubleAV, maxperron and DanielBrahano.
Not affiliated with Cronometer. See DISCLAIMER.md.
Available Tools
65 toolsadd_biometricAIdempotent
Record a biometric measurement, such as weight or body fat.
Use list_biometrics to find metric IDs and their valid unit IDs. Correct a wrong value with edit_biometric, or drop the reading entirely with remove_biometric when the measurement never happened.
Args: metric_id: Metric to record, from list_biometrics. unit_id: Unit the amount is in, from that metric's units. amount: The measured value. date: Date as YYYY-MM-DD (defaults to today).
Inputs: amount (required), unit_id (required), metric_id (required), date. Returns: result
add_custom_foodA
Create a custom food with any nutrients you have, from 1 to all 94.
Amounts are for one whole serving of serving_grams, not per 100 g, each in that nutrient's own unit. Call list_nutrients for the accepted names and their units. Only pass the nutrients you actually know: a nutrient you leave out stays blank in Cronometer, while passing 0 states the food contains none of it, and the app treats those differently. An unrecognised name is an error, so nothing is silently dropped from a food that then looks complete.
Two label conveniences: energy_kj is converted to calories, salt_g to sodium. Pass either one or its underlying nutrient, not both.
After creation, use the returned food_id with add_food_entry to log it.
Args: name: Food name. nutrients: Nutrient name to amount per serving, e.g. {"energy": 250, "protein": 12.5, "vitamin_c": 30, "b12_cobalamin": 1.2}. serving_name: Name for the serving size (default "1 serving"). serving_grams: Weight of one serving in grams (default 100). label_type: "AMERICAN_2016" or "EUROPEAN". notes: Free-text note stored on the food.
Inputs: name (required), nutrients (required), notes, label_type, +2 more. Returns: result
add_exerciseAIdempotent
Log an exercise entry.
A wrong entry can be corrected with edit_exercise or deleted outright with remove_exercise.
Args:
name: What the exercise was called.
minutes: Duration in minutes.
calories_burned: Calories burned, as a positive number.
date: Date as YYYY-MM-DD (defaults to today).
activity_id: Optional Cronometer activity, from search_activities.
Leave 0 for a free-form entry named name.
Inputs: name (required), minutes (required), calories_burned (required), date, +1 more. Returns: result
add_fastAIdempotent
Record a fast.
Args: start: When the fast started, as YYYY-MM-DD HH:MM. end: When it ended, same format. Omit for an ongoing fast. goal_hours: Target length in hours.
Inputs: start (required), end, goal_hours. Returns: result
add_food_entryA
Add a food entry to the Cronometer diary.
Use search_foods to find food_id and measure_id, then get_food_details to confirm serving sizes and gram weights.
Args: food_id: Numeric food ID from search_foods results. measure_id: Measure/unit ID from get_food_details. grams: Weight of the serving in grams. Real grams for recipes too: the conversion to Cronometer's batch units happens here. date: Date to log as YYYY-MM-DD (defaults to today). translation_id: Translation ID from search results (usually 0). diary_group: Meal slot -- one of "auto", "breakfast", "lunch", "dinner", "snacks" (case-insensitive, default "auto"). time: Time of day as HH:MM or HH:MM:SS. Defaults to now. Pass the real eating time when logging after the fact; an "auto" diary_group then follows that hour instead of the current one.
Inputs: grams (required), food_id (required), measure_id (required), date, +3 more. Returns: result
add_noteAIdempotent
Add a note to a day in the Cronometer diary.
Cronometer cannot delete notes, only rewrite them, so a note added here can be changed but only removed in the Cronometer app.
Args: text: The note text. date: Date as YYYY-MM-DD (defaults to today).
Inputs: text (required), date. Returns: result
add_repeat_itemAIdempotent
Make a food log itself automatically on chosen days of the week.
Give food_id (from search_foods) and optionally measure_id; without a measure the food's default serving is used. Or give only food_name and the best search match is used: its name is echoed back, so check it.
Args: quantity: How many of the measure, e.g. 1 for one medium banana. food_id: From search_foods. measure_id: A measure of that food, from get_food_details. food_name: Search text, when food_id is not known. days: "all", "weekdays", "weekends", or e.g. "Mon,Wed,Fri". diary_group: Meal to log into, e.g. "Breakfast" (defaults by time of day).
Inputs: quantity (required), days, food_id, food_name, +2 more. Returns: result
analyze_dayBRead-onlyIdempotent
A day at a glance: calories and macros per meal, and the foods that contributed the most energy and protein.
Args: date: YYYY-MM-DD (defaults to today). top: How many top foods to list.
Inputs: top, date. Returns: result
cancel_active_fastADestructiveIdempotent
Cancel a fast that is still running. A recurring fast keeps its schedule; a one-off fast is removed.
To end it with the time fasted kept instead, use edit_fast with an end.
Args: fast_id: The running fast's id, from get_fasting_history.
Inputs: fast_id (required). Returns: result
check_food_fitARead-onlyIdempotent
Would eating this food still fit today's targets? Shows the day's calories and macros now, what the food adds, and what would be left.
Args: food_id: From search_foods. grams: Amount you plan to eat, in grams. date: YYYY-MM-DD (defaults to today).
Inputs: grams (required), food_id (required), date. Returns: result
connection_statusARead-onlyIdempotent
Check the Cronometer login works, and which account it is.
Inputs: no named parameters. Returns: result
copy_dayA
Copy all diary entries from one day onto another.
Additive -- does not remove existing entries on the destination date, so running it twice duplicates them.
Args: date: Destination date as YYYY-MM-DD (defaults to today). source_date: Day to copy from, as YYYY-MM-DD. Defaults to the day before the destination.
Inputs: date, source_date. Returns: result
create_macro_templateAIdempotent
Save a new macro target template, and optionally schedule it.
Args: template_name: Name for the template; must not already exist. calories: Energy target in kcal. protein_grams: Protein in grams. carbs_grams: Net carbs in grams. fat_grams: Fat in grams. days: Also make it the weekly default on these days: "all", "weekdays", "weekends" or e.g. "Mon,Wed,Fri".
Inputs: calories (required), fat_grams (required), carbs_grams (required), protein_grams (required), +2 more. Returns: result
create_recipeAIdempotent
Create a recipe from ingredients, or update one by passing recipe_id.
Cronometer sums the nutrients from the ingredients itself, so unlike add_custom_food you give foods and amounts, not nutrient values. Use search_foods to find each ingredient's food_id and measure_id first.
Cronometer locks a recipe's serving type when it is created and it cannot be changed afterwards, so choose deliberately:
"weight" (default) measures the recipe in grams. Portions log as plain grams in every client, including the mobile app's own entry screen. The ingredient list is recorded in the notes rather than as editable ingredients, because Cronometer will not compute a correct weight from ingredients that use non-weight measures.
"servings" keeps an editable ingredient list in Cronometer, but the mobile app then offers only a 1 g serving and hides the amount field. Logging through this server is still correct in grams.
cooked_grams is the weight of the finished dish. Give it whenever the food was baked or simmered: nutrients are stored per 100 g, and a dish that lost water is denser than its raw ingredients, so without it a portion weighed off the plate logs too little.
Updating with recipe_id replaces the whole ingredient list, so pass every ingredient, not only new ones. Recipes are removed with retire_custom_food, the same as custom foods.
Args: name: Recipe name. ingredients: List of {"food_id": int, "grams": float, "measure_id": int (optional)}. servings: How many portions the batch makes. notes: Free-text note stored on the recipe. recipe_id: Existing recipe to update in place. serving_type: "weight" or "servings". Cannot be changed later. cooked_grams: Weight of the finished dish, if it lost water in cooking.
Inputs: name (required), ingredients (required), notes, servings, +3 more. Returns: result
delete_fastADestructiveIdempotent
Remove a recorded fast.
Args: fast_id: The fast ID, from get_fasting_history.
Inputs: fast_id (required). Returns: result
delete_macro_templateBDestructiveIdempotent
Delete a saved macro target template.
Args: template_id: From list_macro_templates.
Inputs: template_id (required). Returns: result
delete_repeat_itemADestructiveIdempotent
Stop a food from repeating. Entries it already logged are kept.
Args: repeat_item_id: From get_repeated_items.
Inputs: repeat_item_id (required). Returns: result
edit_biometricBIdempotent
Change the value of a recorded biometric.
Args: biometric_id: The biometric ID, from get_food_log. amount: The corrected value, in the unit the entry already uses. date: Date the entry is on as YYYY-MM-DD (defaults to today).
Inputs: amount (required), biometric_id (required), date. Returns: result
edit_exerciseAIdempotent
Change the duration or calorie burn of a logged exercise.
Args: exercise_id: The exercise ID, from get_food_log. minutes: New duration in minutes. calories_burned: New burn, as a positive number. date: Date the entry is on as YYYY-MM-DD (defaults to today).
Inputs: exercise_id (required), date, minutes, calories_burned. Returns: result
edit_fastAIdempotent
Change a recorded fast, including ending one that is still open.
Args: fast_id: The fast ID, from get_fasting_history. start: New start as YYYY-MM-DD HH:MM. end: New end as YYYY-MM-DD HH:MM. goal_hours: New target length in hours.
Inputs: fast_id (required), end, start, goal_hours. Returns: result
edit_food_entryAIdempotent
Change the amount or time of an already logged food entry.
Use get_food_log to find entry IDs.
Args: entry_id: The serving ID of the entry to change. grams: New amount in grams. time: New time as HH:MM or HH:MM:SS. date: Date the entry is on as YYYY-MM-DD (defaults to today).
Inputs: entry_id (required), date, time, grams. Returns: result
edit_noteAIdempotent
Replace the text of an existing diary note.
Args: note_id: The note ID, from get_food_log. text: The replacement text. date: Date the note is on as YYYY-MM-DD (defaults to today).
Inputs: text (required), note_id (required), date. Returns: result
export_dataARead-onlyIdempotent
Cronometer's own data export, for long date ranges.
data_type is one of:
servings: every food logged, with its full nutrients
daily_summary: one row of nutrient totals per day
exercises, biometrics, notes
The rows are the columns of Cronometer's CSV export. Large results are paged; follow the get_result_page hint.
Args: data_type: servings, daily_summary, exercises, biometrics or notes. start_date: First day, YYYY-MM-DD. end_date: Last day, YYYY-MM-DD (defaults to today). format: "json" for parsed rows or "csv" for the raw text.
Inputs: data_type (required), start_date (required), format, end_date. Returns: result
fetchARead-onlyIdempotent
Read one search result in full: a food's nutrients, or a diary day.
Args: id: An id from search, "food:" or "diary:".
Inputs: id (required). Returns: result
find_entries_by_foodARead-onlyIdempotent
Every diary entry that references a food, with dates and amounts.
Use before replacing or retiring a food, so its entries can be moved rather than left pointing at something retired.
Cronometer cannot search entries by food, so this reads the diary one day at a time and the range costs a request per day. It defaults to the last 30 days; widen it deliberately.
Args: food_id: The food to look for. start_date: First day as YYYY-MM-DD (defaults to 30 days back). end_date: Last day as YYYY-MM-DD (defaults to today).
Inputs: food_id (required), end_date, start_date. Returns: result
get_biometricsARead-onlyIdempotent
Get a biometric time series such as weight or body fat from Cronometer.
Returns the recorded values over the date range as a list of {day, value} points.
Use list_biometrics to find metric_id and unit_id (e.g. Weight is metric_id 1, with unit_id 1 for kg or 2 for lbs).
Args: metric_id: Numeric metric ID from list_biometrics. unit_id: Numeric unit ID from the metric's units in list_biometrics. start_date: Start date as YYYY-MM-DD (defaults to 30 days ago). end_date: End date as YYYY-MM-DD (defaults to today).
Inputs: unit_id (required), metric_id (required), end_date, start_date. Returns: result
get_daily_macro_targetsBRead-onlyIdempotent
Calorie, protein, carb and fat targets that apply on one day.
Args: date: YYYY-MM-DD (defaults to today).
Inputs: date. Returns: result
get_daily_nutritionARead-onlyIdempotent
Get daily nutrition totals for every nutrient eaten that day.
The response has:
summary: flat macro totals (energy, protein, carbs, net_carbs, fat, fiber, alcohol). A value is null if nothing that day contained it.
nutrients: each with id, name, amount, unit, category, confidence and
tracked.tracked_count and untracked_count.
tracked says whether that nutrient has a target set in Cronometer.
Untracked nutrients are still eaten and still counted here; they simply have
nothing to measure against, and Cronometer's own summary leaves them out. So
an absent nutrient means none was eaten, rather than none being tracked.
Report an untracked amount as a plain figure. It has no target, so never describe it as over, under or on track, and offer set_nutrient_target if a target would be useful.
Args: date: Date as YYYY-MM-DD (defaults to today). include_untracked: Leave true for everything eaten. False restricts the response to nutrients with targets, matching the app's own summary.
Inputs: date, include_untracked. Returns: result
get_fasting_historyARead-onlyIdempotent
Get fasting history from Cronometer.
Returns fasts within the date range including status, timestamps, and duration.
Args: start_date: Start date as YYYY-MM-DD (defaults to 30 days ago). end_date: End date as YYYY-MM-DD (defaults to today).
Inputs: end_date, start_date. Returns: result
get_fasting_statsARead-onlyIdempotent
Get aggregate fasting statistics.
Returns total fasting hours, longest fast, average fast duration, and completed fast count.
Inputs: no named parameters. Returns: result
get_food_detailsARead-onlyIdempotent
Get detailed food information including nutrition and serving sizes.
Use this after search_foods to get the full nutrient profile and available measure_ids needed for add_food_entry.
nutrients is for one serving of serving_grams, keyed and scaled exactly
as add_custom_food and update_custom_food take them, so a value read here
can be written straight back unchanged. Cronometer's own stored rows, per
100 g and by numeric id, are alongside as nutrients_per_100g.
Args: food_id: Food ID from search_foods results.
Inputs: food_id (required). Returns: result
get_food_logARead-onlyIdempotent
Get all diary entries for a given date.
Returns every food entry logged for the day. Each "Serving" entry is enriched (best-effort) with the food's name, source, the serving measure (unit name and grams per unit), the number of servings, and that food's own nutrient profile scaled to the amount eaten. Non-food entries (exercise, biometrics) carry their own name.
Note: the per-entry "nutrients" are each food's individual contribution, which is distinct from the day-level nutrition_summary aggregate below.
Also returns a top-level energy_summary field with pre-computed values most relevant to the user:
total_target_kcal: daily calorie target dynamically adjusted for expenditure and weight goal (equivalent to Cronometer's "Total Target" in the Energy Summary screen)
consumed_kcal: total calories consumed
remaining_kcal: calories remaining to stay on target (total_target_kcal - consumed_kcal). Always report this when summarizing the user's day. Prefer this over manually deriving values from the burn breakdown fields.
Also returns a nutrition_summary field with consumed totals for every nutrient eaten that day:
macros: flat macro totals (energy, protein, carbs, net_carbs, fat, fiber, alcohol)
nutrients: every nutrient with amount, unit and
tracked, which says whether it has a target set in Cronometer. Untracked ones were still eaten; they just have nothing to measure against, so report them as plain figures and never as over or under target.
Args: date: Date as YYYY-MM-DD (defaults to today). include_untracked: Leave true for everything eaten. False restricts the summary to nutrients with targets, matching the app's own summary.
Inputs: date, include_untracked. Returns: result
get_macro_targetsARead-onlyIdempotent
Get current macro targets including weekly schedule and templates.
Returns the weekly macro schedule (which template applies to each day) and all saved macro target templates with their values.
Inputs: no named parameters. Returns: result
get_nutrition_scoresARead-onlyIdempotent
Get nutrition scores with per-nutrient consumed amounts and category grades.
Returns category scores (All Targets, Vitamins, Minerals, Electrolytes, Antioxidants, Immune Support, Metabolism, Bone Health) with the actual consumed amount and confidence level for each nutrient.
This is Cronometer's own scoring, so it covers only nutrients with a target; scoring one without a target would have nothing to score against. Use it to see how close each nutrient is to its target, and get_daily_nutrition when you need everything that was actually eaten.
Args: date: Date as YYYY-MM-DD (defaults to today).
Inputs: date. Returns: result
get_nutrition_summaryARead-onlyIdempotent
Daily macro totals over a range of days, with averages.
Each day carries energy, protein, carbs, net carbs, fat, fiber and alcohol. Days with nothing logged are listed but left out of the averages. Pass nutrient names (e.g. ["vitamin_d", "iron"]) to add those as well. At most 62 days per call.
Args: start_date: First day, YYYY-MM-DD. end_date: Last day, YYYY-MM-DD (defaults to today). nutrients: Extra nutrient names to include, as in list_nutrients.
Inputs: start_date (required), end_date, nutrients. Returns: result
get_profileARead-onlyIdempotent
The account profile: birthdate, gender, timezone and language.
Inputs: no named parameters. Returns: result
get_recent_foodsARead-onlyIdempotent
Recently logged foods with how often each was logged.
The fastest route for "log my usual": find the food here, then add_food_entry. Recipes are flagged so a whole batch is not logged as one portion by mistake.
Inputs: no named parameters. Returns: result
get_repeated_itemsBRead-onlyIdempotent
Foods set to log themselves automatically on chosen days of the week.
Inputs: no named parameters. Returns: result
get_result_pageARead-onlyIdempotent
Read more of a result that was too large to return in one piece.
Any tool whose answer is too large returns its first page with a
paging block. Pass that block's result_id here. Nothing is fetched
again: the stored result is paged, filtered and narrowed.
Args:
result_id: From the paging block of the earlier answer.
offset: First item to return, usually the previous next_offset.
limit: At most this many items. Fewer come back if they would not fit.
fields: Only these keys of each item. Dots reach nested keys, such as
"statistics.sizeOnDisk". The paging.fields list shows what exists.
match: Keep only items whose field contains this text, ignoring case,
for example {"title": "alien", "year": "1979"}.
path: Which list to page when the result holds several, as named in
paging.lists. Defaults to the largest.
Inputs: result_id (required), path, limit, match, +2 more. Returns: result
get_streakARead-onlyIdempotent
Diary logging streaks: current run of fully logged days, and the record.
Args: date: Day to count back from as YYYY-MM-DD (defaults to today).
Inputs: date. Returns: result
get_targetsBRead-onlyIdempotent
Nutrient targets for the account, as shown beside the diary totals.
Inputs: no named parameters. Returns: result
import_recipeAIdempotent
Create a recipe from a free-text ingredient list.
Cronometer's own parser matches each line ("2 tbsp ketchup", "1 cup rice") to a database food and works out the grams, the same as Import Recipe in the app. Matching is fuzzy: show the matched ingredients to the user, and run with save=false first when unsure. create_recipe is the exact alternative when food ids and grams are already known.
Args: ingredients: One ingredient per line. name: Recipe name. Defaults to the one Cronometer suggests. servings: How many portions the batch makes. save: False to only parse and preview the matches.
Inputs: ingredients (required), name, save, servings. Returns: result
list_biometricsARead-onlyIdempotent
List the biometric metrics tracked in Cronometer.
Returns every metric type the account can record (Weight, Body Fat, Heart Rate, Blood Glucose, Waist Size, Sleep, blood panels, body measurements, etc.). Use the metric_id and a unit_id from the results with get_biometrics.
Inputs: no named parameters. Returns: result
list_custom_foodsARead-onlyIdempotent
Every custom food and recipe you own, with no database results mixed in.
Leave query empty for the whole library. This is the way to audit it: to find, say, every food whose name lost its Finnish characters, list them all and read the names, then fix each with update_custom_food.
Retired foods are hidden by Cronometer's own library listing, so include_retired cannot bring them back; it only affects rows already returned.
Args: query: Narrow the list by name. Empty lists everything. include_retired: Keep retired foods in the result when they appear.
Inputs: query, include_retired. Returns: result
list_diary_groupsARead-onlyIdempotent
The diary groups (meals) this account uses, e.g. Breakfast, Lunch.
Inputs: no named parameters. Returns: result
list_macro_templatesBRead-onlyIdempotent
Saved macro target templates, with their ids and values.
Inputs: no named parameters. Returns: result
list_nutrientsARead-onlyIdempotent
Every nutrient add_custom_food accepts, with its unit and category.
Read from the account's own catalog, so it stays right as Cronometer adds nutrients. Use the returned keys for the add_custom_food nutrients dict.
Inputs: no named parameters. Returns: result
list_recipesARead-onlyIdempotent
Every recipe you own, with its serving type.
serving_type says which kind each one is: "weight" logs in grams everywhere, "servings" keeps an editable ingredient list but shows a 1 g serving in the mobile app. It is fixed at creation and cannot be changed.
Args: include_retired: Keep retired recipes in the result when they appear.
Inputs: include_retired. Returns: result
log_sleepAIdempotent
Record a night's sleep, optionally with its stages and a sleep score.
Each value is stored as its own biometric (Sleep, and the Deep, Light, REM and Score metrics when the account has them), so they show in Cronometer's trends. Fix or remove one with edit_biometric or remove_biometric.
Args: hours: Total sleep in hours, e.g. 7.5. date: The diary day it belongs to, as YYYY-MM-DD (defaults to today). deep_hours: Deep sleep in hours. light_hours: Light sleep in hours. rem_hours: REM sleep in hours. score: Sleep score from 0 to 100.
Inputs: hours (required), date, score, rem_hours, +2 more. Returns: result
lookup_barcodeARead-onlyIdempotent
Find a packaged food by its UPC/EAN barcode.
Returns matching foods with the same fields as search_foods, so a hit can go straight to get_food_details or add_food_entry.
Args: barcode: The digits printed under the barcode.
Inputs: barcode (required). Returns: result
mark_day_completeAIdempotent
Mark a diary day as complete or incomplete.
Args: date: Date to mark as YYYY-MM-DD. complete: True to mark complete, False for incomplete.
Inputs: date (required), complete. Returns: result
nutrient_reportARead-onlyIdempotent
Every tracked nutrient for a day against its target: amount, target, upper limit and percent of target, lowest first.
Good for spotting gaps (what to eat more of) and anything over a limit.
Args: date: YYYY-MM-DD (defaults to today).
Inputs: date. Returns: result
remove_biometricADestructiveIdempotent
Delete recorded biometrics, such as a reading from a misbehaving scale.
Deleting is right when the measurement never happened or is impossible; edit_biometric is for a value that is merely wrong, since editing keeps the day's entry and its history.
Args: biometric_ids: List of biometric IDs to remove, from get_food_log. date: Date the entries belong to as YYYY-MM-DD (defaults to today).
Inputs: biometric_ids (required), date. Returns: result
remove_exerciseADestructiveIdempotent
Delete logged exercise entries.
Use this for an entry that should not be there at all, such as a duplicate from a tracker sync. edit_exercise is for one whose duration or burn is merely wrong.
Args: exercise_ids: List of exercise IDs to remove, from get_food_log. date: Date the entries belong to as YYYY-MM-DD (defaults to today).
Inputs: exercise_ids (required), date. Returns: result
remove_food_entryADestructiveIdempotent
Remove diary entries of any kind: foods, exercise, biometrics, notes.
Use get_food_log to find entry IDs (servingId, exerciseId, biometricId or noteId). Deleting cannot be undone.
Args: entry_ids: List of entry IDs to remove. date: Date the entries belong to as YYYY-MM-DD (defaults to today).
Inputs: entry_ids (required), date. Returns: result
remove_noteADestructiveIdempotent
Delete a note from the diary.
Args: note_id: The noteId, from get_food_log. date: The note's day, YYYY-MM-DD (defaults to today); used to confirm it went.
Inputs: note_id (required), date. Returns: result
retire_custom_foodADestructiveIdempotent
Retire a custom food so it stops being offered for new entries.
This is how Cronometer removes a food; there is no delete. Diary entries that already use it keep working. Pass retired=False to bring it back.
Args: food_id: The custom food's ID. retired: True to retire, False to restore.
Inputs: food_id (required), retired. Returns: result
searchARead-onlyIdempotent
Search Cronometer: foods by name, or a diary day by date.
A date (YYYY-MM-DD, "today", "yesterday") finds that day's diary; anything else searches the food database. Read a result in full with fetch.
Args: query: Food name, or a date.
Inputs: query (required). Returns: result
search_activitiesARead-onlyIdempotent
Search Cronometer's exercise activities by name, for add_exercise.
Every word of the query must appear in the name, e.g. "running 6 mph" or "cycling". Returns up to 50 activity ids.
Args: query: Words to look for in the activity name.
Inputs: query (required). Returns: result
search_foodsARead-onlyIdempotent
Search Cronometer's food database by name.
Returns matching foods with their IDs and source information. Use the food_id and measure_id from results with add_food_entry, or pass food_id to get_food_details for full nutrition info.
Args: query: Food name or keyword (e.g. "eggs", "chicken breast").
Inputs: query (required). Returns: result
set_macro_targetsAIdempotent
Change the calorie and macro targets for one day.
Only the values given change; the rest are read first and kept. For a lasting change across the week, use create_macro_template and set_weekly_macro_schedule instead.
Args: date: YYYY-MM-DD (defaults to today). calories: Energy target in kcal. protein_grams: Protein target in grams. carbs_grams: Net carbs target in grams. fat_grams: Fat target in grams. template_name: Label for the day's targets (defaults to "Custom Targets").
Inputs: date, calories, fat_grams, carbs_grams, +2 more. Returns: result
set_nutrient_targetAIdempotent
Set a nutrient's daily target, its upper limit, or whether it is tracked.
Giving a minimum or maximum makes it a custom target, replacing the default Cronometer works out from the profile. Setting visible turns tracking of that nutrient on or off, which is how a micronutrient starts showing up in the diary at all.
Only what you pass changes: the rest of the nutrient's settings are read first and kept, so turning on visibility never disturbs an existing target.
Going back to Cronometer's own default is done in the app, under Settings then Targets. Report the current value from get_targets before overwriting one, so it can be put back by hand if wanted.
Args: nutrient: Nutrient name, e.g. "protein", "iodine", "choline", "biotin". Call list_nutrients for the accepted names. minimum: Daily target, in that nutrient's own unit. maximum: Upper limit, in that nutrient's own unit. visible: True to track the nutrient, False to hide it.
Inputs: nutrient (required), maximum, minimum, visible. Returns: result
set_weekly_macro_scheduleAIdempotent
Make a saved template the default targets on chosen days of the week.
This changes the recurring schedule for every future week.
Args: template_name: A template from list_macro_templates, or "default" to put those days back on the profile's default targets. days: "all", "weekdays", "weekends" or e.g. "Mon,Wed,Fri".
Inputs: template_name (required), days. Returns: result
update_custom_foodAIdempotent
Edit one of your custom foods in place, keeping its diary entries.
Only what you pass changes. Entries already logged stay attached to this same food and their nutrition follows the edit, so a typo or a wrong nutrient can be fixed without re-logging anything.
Nutrients are merged into the existing profile, so correcting one value leaves the rest alone. Call list_nutrients for the accepted names.
Amounts are for one serving, the same basis get_food_details reports and
returns as serving_grams. That is the food's default measure, which is
not always 100 g: a juice measured in millilitres takes per-millilitre
values. Read the food first and the two always agree.
Args: food_id: The custom food to edit. name: New name. notes: New note text. nutrients: Nutrient name to amount per serving, merged in. measures: [{"measure_id": int, "name": str, "grams": float}] to fix a wrongly weighted measure. name and grams are each optional. Leave measure_id out to ADD a serving size instead, giving name and grams: that is how a food gets a per-piece measure such as "1 karkki" or "1 viipale" alongside plain grams, so it can be logged by the count as well as by weight.
Inputs: food_id (required), name, notes, measures, +1 more. Returns: result
update_recipeAIdempotent
Edit one of your recipes in place, keeping its diary entries.
Only what you pass changes, and the serving type never does: Cronometer fixes it at creation. Passing ingredients replaces the whole list, so send every ingredient rather than only the new ones.
Args: food_id: The recipe to edit. name: New name. notes: New note text. ingredients: Full replacement list of {"food_id": int, "grams": float, "measure_id": int (optional)}. cooked_grams: New finished weight. Weight-based recipes only, and it needs the ingredients too, since the nutrition is recomputed.
Inputs: food_id (required), name, notes, ingredients, +1 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 65 tools
Several tools overlap significantly: remove_food_entry can delete foods, exercise, biometrics, or notes even though dedicated remove_note, remove_exercise, and remove_biometric tools exist; generic search/fetch duplicate search_foods/get_food_details; and create_recipe can update recipes while update_recipe also edits them. Nutrition reporting is split across get_food_log, get_daily_nutrition, analyze_day, get_nutrition_summary, nutrient_report, and get_nutrition_scores, whose boundaries are described but still easy to confuse.
All names use snake_case and mostly follow verb_noun patterns. Minor inconsistencies exist in verb choice, such as remove versus delete, add versus create, and generic search/fetch names that are less descriptive than the rest.
With 65 tools, the surface is far beyond a well-scoped set, even for a rich domain like Cronometer. The large count is compounded by overlapping removal, nutrition-reporting, and recipe-editing tools.
Coverage is broad: diary entries, custom foods, recipes, biometrics, exercise, fasting, nutrient and macro targets, repeated items, notes, exports, and account status are all represented. There are minor gaps and inconsistencies, such as contradictory note-deletion guidance and no explicit water or supplement logging surface.
Maintenance
Related MCP Connectors
Calorie tracker app for ChatGPT and Claude: read meals, macros, weight and log meals from chat.
Log meals and read your nutrition diary from Claude or ChatGPT.
Calorie, workout and body-weight log inside ChatGPT and Claude. Log meals by text, photo or voice.
Log meals, water, and weight to Garmin Connect from Claude or ChatGPT.
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server that provides access to Cronometer nutrition data, enabling users to pull food logs, macro and micronutrient summaries, and biometric data into Claude or Cursor. It supports daily nutrition tracking and raw CSV exports by interfacing with the Cronometer web protocol.27606 PyPI28MIT
- AlicenseAqualityBmaintenanceEnables nutrition tracking with Cronometer, including food logging, food search, diary management, and nutrition data retrieval via natural language.13MIT
- AlicenseAqualityBmaintenanceEnables reading and writing your Cronometer food diary from Claude.ai and Claude Code, including food entries, notes, biometrics, exercises, and fasts.4117 npm2MIT
- AlicenseAqualityBmaintenanceUnofficial, self-hosted MCP server that connects Claude or ChatGPT to your own FatSecret food diary. It drafts entries from a description, a plate photo or a nutrition label, saves them only after you confirm, and changes or removes entries the same way. It also reads a day or several weeks against your calorie and macro targets.2564 PyPI6MIT