Skip to main content
Glama
nutrition-mcp

nutrition-mcp

nutrition-mcp

An MCP server that gives any assistant or agent sourced nutrition, glycemic load and micronutrient data for Indian dishes and ingredients, for any amount in grams or household measures.

CI License: MIT · Case study

A lunch question answered through the server

A lunch question in Claude Desktop, with the tool approvals sped up. The model looks up each food with search_food, then sends all three to compute_meal, and every number in the table comes from that call. One line below the table misreads the server: "the rice assumes about 15 g of ghee" is the whole pot. The tool says "15 g ghee, under 4 g per serving".

Problem

Assistants answer nutrition questions about Indian food from memory, with no source and no stated portion, and they often add up a meal wrongly. That matters most for people managing diabetes or a nutrient deficiency. The authoritative Indian tables can't be redistributed, and the largest open database has few Indian dishes. Details are in the case study.

Related MCP server: nutrition-mcp-server

Solution

  • Search for Indian dishes and ingredients by name, including spelling variants.

  • Nutrition for an amount in grams or household measures such as a katori or a roti, with the source and the assumed portion stated.

  • Meal totals, and rankings of foods by any nutrient.

  • Glycemic load where an openly licensed value exists, and missing, never guessed, where it doesn't.

  • Daily calorie and protein targets, labelled as estimates.

Every feature, with what it must do, is in docs/requirements.md.

Results

Two evaluations, kept apart (ADR 0006). The rows below are copied from evals/results/summary.md, and each row names its results file.

Grounding. A Gemini model answered 30 questions twice with the same prompt: from memory (Baseline) and with this server's tools over stdio (This project). Each golden answer is the server's own total for the foods in the question, and 4 of the questions name a dish that is not in the data. The error is the mean absolute percentage error (MAPE) over the 26 questions that both runs answered with numbers. Shares run from 0 to 1, and n is the number of questions behind each value, Baseline first, or the number of tool calls for tool errors and latency.

Measure

Baseline

This project

n

Model

Date

Commit

Dataset version

Results file

Calorie error against the dataset totals, MAPE (%)

16.0

0.0

26 / 26

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Protein error against the dataset totals, MAPE (%)

13.2

0.0

26 / 26

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Carbohydrate error against the dataset totals, MAPE (%)

20.2

0.0

26 / 26

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Answers within 10% on calories (share)

0.38

1.0

26 / 26

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Missing dishes reported (share)

n/a

0.5

n/a / 4

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Failed answers (count)

0

1

30 / 30

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

API errors (count)

0

0

30 / 30

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

Tool errors (count)

n/a

0

n/a / 89

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

p95 tool latency (ms)

n/a

10.4

n/a / 89

gemini-3.1-flash-lite

2026-10-21

8262476

24143edbb56e

2026-10-21_gemini-3.1-flash-lite_8262476.json

The 0.0 is by construction. The golden answers are the server's own totals, so it shows that with the tools the model picked the right food and unit and used the computed total on every one of the 26 questions; it does not show that the totals match what the dishes contain, which the data-accuracy rows below measure against independent records. The Baseline error is also distance from those totals, not from the dishes. The 0.5 for missing dishes and the one failed answer come from the 4 questions about dishes that are not in the data; see Limitations.

Data accuracy. The suite compares the values the server computes for each dish, per 100 g, with an independent record of the same dish: a CoFID 2021 homemade dish or a USDA survey food. The value is the MAPE against those records, and n is the number of dishes compared. A gated nutrient fails CI when it gets worse than evals/thresholds.toml allows.

Suite

Measure

n

Value

Date

Commit

Dataset version

Results file

data accuracy

Energy (kcal) MAPE (%), gated

18

18.7

2026-10-20

32aa03d

24143edbb56e

2026-10-20_data-accuracy_32aa03d.json

data accuracy

Protein (g) MAPE (%), gated

18

24.8

2026-10-20

32aa03d

24143edbb56e

2026-10-20_data-accuracy_32aa03d.json

data accuracy

Available carbohydrate (g) MAPE (%), gated

18

16.7

2026-10-20

32aa03d

24143edbb56e

2026-10-20_data-accuracy_32aa03d.json

data accuracy

Iron (mg) MAPE (%), reported

18

44.2

2026-10-20

32aa03d

24143edbb56e

2026-10-20_data-accuracy_32aa03d.json

data accuracy

Calcium (mg) MAPE (%), reported

18

46.1

2026-10-20

32aa03d

24143edbb56e

2026-10-20_data-accuracy_32aa03d.json

The references are UK and US records, so part of each error is a difference in recipe (water, fat, an enriched grain) or in energy basis rather than wrong data; ADR 0006 names the largest cases.

Architecture

flowchart LR
  A[MCP client] -- stdio --> B[MCP server: tools and sources resource]
  B --> C[Query functions]
  C --> D[(In-memory registry)]
  E[Loader and validation] --> D
  F[Bundled and user datasets] --> E

The server reads dataset folders at startup, validates them, computes each dish's nutrition from its ingredients, and answers tool calls from memory. The longer description is in docs/architecture.md.

Tech stack

Layer

Choice

Why

Language and tooling

Python 3.12, uv, ruff, mypy, pytest

One toolchain for build, lint, types and tests

Protocol

MCP Python SDK 2.x

Typed tools over stdio for any MCP client

Data models

Pydantic

Validation at every boundary

Name matching

rapidfuzz

Fuzzy matching for spelling variants

Container

Docker

A slim image that runs as a non-root user; CI builds it and calls its tools

CI

GitHub Actions

Lint, tests, image build and secret scan on every push

Quickstart

git clone https://github.com/Varun-Kambali/nutrition-mcp && cd nutrition-mcp
make setup
make test

make run starts the server on stdio, where it waits for an MCP client. Normally the client starts it for you: see Use it.

Use it

The server runs on your machine and your MCP client starts it. It needs uv. Run it straight from GitHub with uvx, or from a clone with uv.

Claude Desktop. Add the server to claude_desktop_config.json (on macOS in ~/Library/Application Support/Claude/, on Windows in %APPDATA%\Claude\), then restart the app.

From GitHub:

{
  "mcpServers": {
    "nutrition": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/Varun-Kambali/nutrition-mcp", "nutrition-mcp"]
    }
  }
}

From a clone:

{
  "mcpServers": {
    "nutrition": {
      "command": "uv",
      "args": ["--directory", "/path/to/nutrition-mcp", "run", "nutrition-mcp"]
    }
  }
}

If Claude Desktop cannot find uv or uvx, put its full path in command (which uvx prints it).

Claude Code.

# From GitHub
claude mcp add nutrition -- uvx --from git+https://github.com/Varun-Kambali/nutrition-mcp nutrition-mcp
# From a clone
claude mcp add nutrition -- uv --directory /path/to/nutrition-mcp run nutrition-mcp

Then ask, for example, "protein and carbs in 2 rotis and a katori of rajma?". The tool descriptions tell the model to find the food ids with search_food and take the totals from compute_meal.

The five tools, all read-only, and the sources resource:

Tool or resource

What it gives

search_food(query, region?, diet?, kind?, limit?)

Food ids by name, spelling variants included, with kind, regions, diet and the units the food accepts (grams in one katori, piece, glass and so on). total counts every match before limit. Every word of the query has to match a name, so a dish that is not in the data returns no results and close names as suggestions

get_nutrition(food_id, amount, unit)

Nutrients for an amount in grams or a household measure, with the grams used, the measure and oil assumptions, glycemic load when the GI is known, and the sources

compute_meal(items)

Per-item and total nutrients and glycemic load for 1 to 30 items, each a food id, amount and unit

find_foods(rank_by, per?, region?, diet?, kind?, limit?)

Foods ranked by one nutrient or by glycemic load, per 100 kcal, 100 g or serving

compute_targets(sex, age, height_cm, weight_kg, activity, goal)

Estimated daily calorie and protein targets for an adult, from the Mifflin-St Jeor equation

nutrition://sources (resource)

Every loaded data source with its URL, license, version and attribution

Nutrient values are rounded to 2 decimals, so 0.0 can be a small amount; a value the source does not report is null, never 0. The details given to compute_targets pass through the model your client uses before they reach the server. The server stores nothing and does not log tool inputs. Logs are JSON lines on stderr; NUTRITION_MCP_LOG_LEVEL sets the level: DEBUG, INFO (the default), WARNING, ERROR or CRITICAL.

Your own data

The bundled data is a starting point. A team with a licensed food table or lab-tested recipes can load its own folders next to it, with no change to the tools. List the folders in NUTRITION_MCP_DATA_DIRS, separated by : (; on Windows), in the env of the client config:

{
  "mcpServers": {
    "nutrition": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/Varun-Kambali/nutrition-mcp", "nutrition-mcp"],
      "env": { "NUTRITION_MCP_DATA_DIRS": "/path/to/my-data" }
    }
  }
}

A folder has the same three parts as the bundled data, each with a JSON Schema:

File

Holds

Schema

sources.yaml

Each source: id, name, URL, license, version, retrieval date and attribution

schemas/sources.json

ingredients.csv

One row per ingredient: names, diet, source and record, and the 16 nutrients per 100 g, with an empty cell for a value the source does not report

schemas/ingredient.json

dishes/<id>.yaml

One recipe per file: raw grams of each ingredient, cooked weight, servings, regions, diet, household measures and the fat assumption

schemas/dish.json

A dish can use the bundled ingredients, so ingredients.csv may hold only its header. Leave water out of a recipe: cooked_weight_g accounts for it. For example, dishes/office_dal.yaml:

id: office_dal
name: Office canteen dal
aliases: [canteen dal]
regions: [south]
diet: veg
servings: 4
cooked_weight_g: 800
ingredients:
  - ingredient_id: toor_dal
    grams: 150
  - ingredient_id: ghee
    grams: 20
measures:
  katori: 180
assumptions: 5 g ghee per serving in the tadka.
source_id: my_kitchen

The server computes the dish's nutrition from its ingredients, as it does for the bundled dishes. A bad row, an unknown ingredient or an id that is already loaded stops startup with an error on stderr that names the file. The fields are described in docs/architecture.md.

Evaluation

Two evaluations, kept apart because one checks the data and the other checks a model using it (ADR 0006):

  • Data accuracy compares computed dish values with independent records of the same dishes (evals/datasets/references.csv, built from data_build/reference_map.csv by scripts/build_references.py). It needs no model and no API key. Run it with make eval-data; CI runs it on every pull request with --check against evals/thresholds.toml. The latest result is under Results.

  • Grounding asks a Gemini model each question in evals/datasets/questions.jsonl twice with the same task prompt: once from memory, and once with this server's tools over stdio and the instructions the server sends when a client connects (ADR 0007). Each question lists the food ids, amounts and units behind it, and its golden answer comes from the server's own query functions; a few ask about dishes that are not in the data, where the right answer names the dish as missing. The model ends its reply with a JSON answer, so scoring needs no judge model. A run reports the error against the dataset totals (the mean absolute percentage error on kcal, protein and available carbohydrate, over the questions both runs answered with numbers), the share of answers within 10%, the missing dishes reported, failed answers, API and tool errors and p95 tool latency, and writes them to a results file named by date, model and commit. It calls the model, so it runs only on demand: put GEMINI_API_KEY in .env (see .env.example) and run make eval. EVAL_MODEL overrides the default model set in evals/grounding.py. Requests start at least 4 seconds apart by default. That is a conservative default, not a published limit: Google shows free-tier rate limits per project in AI Studio. make eval ARGS="--min-interval 2" changes the gap, and make eval ARGS="--limit 2 --out /tmp/smoke" asks only the first two questions to check the setup; a run with --limit never replaces the headline. The first full run is under Results.

Testing

make test and make lint. CI runs both, checks data accuracy against evals/thresholds.toml, and builds the Docker image, then starts it the way an MCP client would and calls the tools (tests/integration/test_container.py, which skips unless NUTRITION_MCP_IMAGE names a built image).

Deployment

The server runs on the user's machine: the MCP client starts it and talks to it over stdio, so there is nothing to host and no demo endpoint. A remote server with authentication, rate limits and a spend cap is outside v1 (requirements).

To run it in a container, build the image with docker build -t nutrition-mcp . in a clone, then give the client docker as the command and ["run", "-i", "--rm", "nutrition-mcp"] as the arguments.

Project structure

src/nutrition_mcp/           server, loader, queries and search
src/nutrition_mcp/data/      bundled dataset, with its sources and licenses
schemas/                     JSON Schemas for a dataset folder
evals/                       evaluation runners, datasets and results
data_build/, scripts/        the reviewed maps and scripts that build the bundled data
tests/                       unit and integration tests, and the container test
docs/                        case study, requirements, architecture, data notes, decisions

Design decisions

  • 0001: write the dish recipes for this project and take ingredient values only from openly licensed tables, because the Indian food tables cannot be redistributed.

  • 0002: USDA FoodData Central first, CoFID 2021 where USDA lacks the food or has it only in the wrong form.

  • 0003: a value the source does not report stays null, and a dish total that lacks one names the ingredients behind the gap.

  • 0004: the server answers questions about food and stores nothing about the person; condition-aware logic belongs to the planner.

  • 0005: calorie and protein targets from Mifflin-St Jeor, each constant cited, with the limits of the estimate.

  • 0006: two evaluations, because grounding scores a model against the bundled data and cannot catch wrong data; data accuracy compares computed dishes with independent records and gates CI.

  • 0007: an empty search result says the food is not in the data and is to be reported as missing, and the grounding run passes the server's instructions to the model as a client would.

Limitations and next steps

The grounding result is one run of one model, on the free tier. Its model runs at the provider's default temperature, as Google advises for Gemini 3 models, so its answers, and the numbers built on them, vary from run to run. Its with-tools run passed the model the server's tools but not the instructions the server sends when a client connects, which tell the model to get food ids from search_food, use get_nutrition or compute_meal, and take every number from a tool result. For clients that pass those instructions on to the model, the measured gain is a lower bound. The harness now passes them (ADR 0007), so the next run measures the server as such a client uses it.

Two behaviours with dishes that are not in the data, as observed in the recorded run. For pulao the model searched 5 times and found no pulao, then answered with the numbers for jeera rice. That is a substitution, the behaviour the requirements list as a failure. The data has no pulao, so the choice of another food was the model's, and this run did not pass the server's instructions, so it does not show whether they change it. For masala dosa the model made 7 searches for the dosa and its filling and reached the 8-turn cap without an answer, which is the one failed answer in the table. Only chicken curry and kheer were reported as missing. Since that run, an empty search result carries a note that says the food is not in the data and is to be reported as missing instead of answered with another food (ADR 0007). A new run is needed to show whether that changes either behaviour.

Search reads a plural by trying the query again without the final "s" when it finds nothing as typed, so "rotis", "plain dosas" and "batata vadas" find their dish. Irregular plurals and plurals in other languages are not handled. The data-accuracy evaluation covers only the dishes that have an independent record (n under Results); ADR 0006 lists the others and why they have none. Known limits of the design are in the case study.

What I learned

  • A tool description is read once; a tool result is read when the model decides. The search_food description already said to report a missing dish, and in the first grounding run the model still answered pulao with jeera rice after four empty searches. The fix was to put the instruction in the empty result itself (ADR 0007).

  • An evaluation scored against your own data cannot tell you the data is right. With the tools, calorie error against the server's totals was 0.0, by construction. Checking the computed dishes against independent CoFID and USDA records gave 18.7% mean error on calories over 18 dishes, which is what the data-accuracy gate in CI now tracks (ADR 0006, results file).

  • Fuzzy search needs word rules, not just a score. A plain similarity score matched "tea" inside "steamed" and returned chana masala for "chicken curry" through its alias "chickpea curry". Requiring every query word to match a word of the name fixed both, and plurals came later as a retry in the singular (search.py).

  • Two open food tables can mean different things by the same nutrient. USDA reports carbohydrate by difference, fibre included; CoFID reports available carbohydrate and gives fibre no energy. Every source value is mapped to one definition before a dish is computed (data notes).

  • Missing has to stay missing. Urad dal has no fibre value in its source, so idli's fibre is reported as a lower bound with the ingredient named, not as a smaller number that looks complete (ADR 0003).

License and data

The code is MIT. The bundled ingredient table comes from USDA FoodData Central (public domain) and CoFID 2021 (Open Government Licence v3.0); glycemic index values come from a compendium of GI values for non-Western foods (CC BY 4.0). The license and attribution for each data file are in src/nutrition_mcp/data/bundled/LICENSE.md, and the nutrient mappings are in docs/data.md.

Available Tools

5 tools
compute_mealA
Read-onlyIdempotent

Per-item and total nutrients and glycemic load for a meal of 1 to 30 items, each a food id, amount and unit as in get_nutrition. Use this for any total instead of adding numbers yourself. If an item fails, the error names it and no totals are returned. Totals listed in total_incomplete are lower bounds, and glycemic_load_missing names the items the meal's glycemic load leaves out. A dish's folate, vitamin C and vitamin B12 are totals of its raw ingredients, with no cooking losses applied; each item names them in raw_ingredient_totals, and a meal total that includes a dish carries the same caveat. Values are rounded to 2 decimals, so 0.0 can be a small amount; a value the source does not report is null, never 0.

NameRequiredDescriptionDefault
itemsYes

Output Schema

NameRequiredDescription
itemsYes
totalYesNutrient amounts, the sum over the items.
glycemic_loadYes
total_incompleteYes
glycemic_load_missingYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover the read-only/idempotent/closed-world profile, and the description goes well beyond them: atomic failure behavior ('If an item fails, the error names it and no totals are returned'), lower-bound semantics via `total_incomplete`, partial coverage of glycemic load, the no-cooking-loss caveat for folate/vitamin C/B12, 2-decimal rounding, and null-vs-0 convention. These are exactly the traits an agent needs to interpret results correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and scope in the first clause, then layers caveats in decreasing order of importance: failure atomicity, lower bounds, cooking-loss caveat, rounding/null convention. Dense but every sentence carries an operational fact; slightly long, though nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still pre-explains the fields an agent is most likely to misread (`total_incomplete` as lower bounds, `glycemic_load_missing`, `raw_ingredient_totals`). Combined with the error and rounding semantics, an agent can call this and correctly interpret edge-case results with no further information.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Top-level schema description coverage is reported as 0%, so the description has to compensate, and it does partially: it fixes the item count range and routes the agent to get_nutrition for argument format. However, the amount/unit/food_id semantics it relies on are only implicit ('each a food id, amount and unit'), and the nested $defs do the real work, so this is adequate rather than additive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (compute) and resource (meal nutrients and glycemic load) with explicit scope: 1 to 30 items, each a food id + amount + unit. The mention of 'as in get_nutrition' and the totals framing lets an agent distinguish it from the single-item sibling get_nutrition and from search_food/find_foods without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear directive: 'Use this for any total instead of adding numbers yourself,' which tells the agent when to reach for this over manual arithmetic or repeated get_nutrition calls. It stops short of explicitly excluding the single-item sibling or naming a failure-mode alternative, so it is clear context rather than full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

compute_targetsA
Read-onlyIdempotent

Estimated daily calorie and protein targets for an adult, from the Mifflin-St Jeor equation with an activity multiplier and a goal adjustment. Use it when asked how much someone should eat in a day. The result shows the formula and the inputs, applies a calorie floor, and is an estimate, not medical advice. Nothing is stored.

NameRequiredDescriptionDefault
ageYes
sexYes
goalYes
activityYes
height_cmYes
weight_kgYes

Output Schema

NameRequiredDescription
kcalYes
notesYes
inputsYes
formulaYes
bmr_kcalYes
protein_gYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, closed-world), and the description adds real value beyond them: the output includes the formula and inputs, a calorie floor is applied, results are estimates not medical advice, and nothing is stored. It doesn't mention validation failures (e.g., age outside 18-100) or rounding behavior, keeping it below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences: what it computes, when to use it, what the result contains, and a disclaimer. The purpose is front-loaded and no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description needn't detail the return shape, yet it still flags the formula echo, calorie floor, and disclaimer — useful for an agent deciding whether to trust the numbers. The only gap is silence on input validation boundaries defined in the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the burden; it explains that 'activity' is a multiplier and 'goal' is a goal adjustment, adding meaning beyond the enum lists. The remaining four parameters (sex, age, height_cm, weight_kg) are left to titles and min/max bounds, so compensation is partial.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Estimated daily calorie and protein targets for an adult,' plus the exact method (Mifflin-St Jeor with activity multiplier and goal adjustment). This cleanly separates it from the sibling retrieval tools (search_food, get_nutrition, find_foods) and the adjacent compute_meal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use it when asked how much someone should eat in a day' gives a clear triggering context. It stops short of naming alternatives (e.g., use compute_meal for a per-meal breakdown), so differentiation is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_foodsA
Read-onlyIdempotent

Rank foods by one nutrient or by glycemic load, per 100 kcal, per 100 g or per serving (dishes only), for questions such as high-protein vegetarian dishes or iron-rich foods. Highest first, except lowest first for kcal, sugars, saturated fat, sodium and glycemic load; order says which. Foods with no value for the ranked nutrient are left out and counted in skipped_missing, and so is a dish with an incomplete value for the ranked nutrient. Per 100 g or per 100 kcal, seasonings, fats and salt can top a ranking though nobody eats 100 g of them; kind="dish" or per="serving" ranks foods eaten as a portion. Values are rounded to 2 decimals, so 0.0 can be a small amount.

NameRequiredDescriptionDefault
perNo100kcal
dietNoKeep foods suitable for this diet: veg includes vegan.
kindNoKeep only dishes or only ingredients.
limitNo
regionNoKeep dishes from this region. Ingredients have none, so leave it out with kind=ingredient.
rank_byYes

Output Schema

NameRequiredDescription
perYes
orderYes
rank_byYes
resultsYes
skipped_missingYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only/idempotent annotations, the description discloses meaningful behavior: default sort direction, nutrient-specific exceptions, omission of missing values into skipped_missing, and rounding to 2 decimals. The reference to an `order` field that is not present in the schema is a notable transparency flaw, but the overall behavioral disclosure is strong.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but front-loaded with the core ranking behavior and then adds operational caveats. It is not bloated, though the reference to a nonexistent `order` parameter and the long run-on caveat sentence reduce structural crispness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter ranking tool with an output schema, the description covers the core ranking semantics, missing-value handling, units, rounding, and a practical caveat about misleading top results. It is nearly complete, but the mention of `order` without a corresponding schema field and the absence of guidance on limit/region leave small gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds useful meaning for rank_by, per, kind, and diet-related filtering, including caveats about per-100 g and per-100 kcal rankings. However, it leaves limit and region unaddressed and misleadingly refers to an `order` parameter that does not exist in the input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: ranking foods by one nutrient or glycemic load, with units and example questions. It is clearly distinguishable from generic search/get tools, but it does not explicitly name the sibling tools or contrast itself with them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides clear usage context through example questions such as high-protein vegetarian dishes and iron-rich foods, plus practical guidance to use kind="dish" or per="serving" when ranking portions. It does not offer explicit when-not conditions or direct alternatives among the sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_nutritionA
Read-onlyIdempotent

Nutrients for an amount of one food, with the grams used, the measure and oil assumptions, glycemic load when the GI is known, and the sources. unit is g (always valid) or a household measure such as katori, piece or tbsp; search_food lists the units each food accepts, and an unknown unit returns them. A nutrient listed in incomplete is a lower bound. A dish's folate, vitamin C and vitamin B12 are totals of its raw ingredients, with no cooking losses applied; raw_ingredient_totals names them. For more than one food, use compute_meal. Values are rounded to 2 decimals, so 0.0 can be a small amount; a value the source does not report is null, never 0.

NameRequiredDescriptionDefault
unitYesg, or a unit that search_food lists for the food, such as katori or piece.
amountYesHow many of `unit`; above 0.
food_idYesA food id from search_food, such as toor_dal.

Output Schema

NameRequiredDescription
giYes
foodYes
unitYes
gramsYes
amountYes
gi_refYes
sourcesYes
nutrientsYesNutrient amounts in `grams` of this food.
incompleteYes
glycemic_loadYes
oil_assumptionYes
measure_assumptionYes
raw_ingredient_totalsYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent, so the description earns credit by adding substantive behavior: `incomplete` means a lower bound, dish micronutrients are raw-ingredient totals without cooking losses, values are rounded to 2 decimals, and unreported values are null rather than 0. This is unusually rich, though it doesn't discuss any limits like response size or rate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose and the compute_meal boundary are front-loaded, and almost every sentence carries real information. It is a dense run-on with several semicolon-chained caveats, which costs some readability but not correctness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still explains the interpretive semantics an agent needs to read the results correctly (null vs 0, rounding, lower bounds, raw-ingredient totals). Combined with unit handling and the sibling routing, nothing essential to a correct call is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description still adds meaning beyond the schema by clarifying that g is always valid while household measures are food-specific and that an unknown unit returns the accepted list. It doesn't explain amount edge cases beyond what the schema's exclusiveMinimum already conveys.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource: nutrients for an amount of one food, including the measures used and sources. It explicitly distinguishes itself from compute_meal for multi-food queries, so an agent can pick between the two siblings without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States the single-food use case, names the alternative for multiple foods, and directs the agent to search_food for both food ids and the valid unit list. It even specifies that an unknown unit returns the accepted units, which is an explicit recovery path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_foodA
Read-onlyIdempotent

Find foods by name and get their ids. Call this first: the other food tools take ids from it. Spelling variants match (sambar, sambhar), but every word of the query has to match a word of one name, so a dish that is not in the data returns no results, a note saying so and up to 5 close names as suggestions. Check that a result's name or one of its aliases is the food asked for; when no result is, report the dish as missing instead of using a suggestion or another result in its place. Each result has its kind (dish or ingredient), regions, diet and units: the units get_nutrition and compute_meal accept for that food, with the grams in one of each. total counts every match before limit; when it is larger than the results shown, raise limit or narrow with region, diet or kind.

NameRequiredDescriptionDefault
dietNoKeep foods suitable for this diet: veg includes vegan.
kindNoKeep only dishes or only ingredients.
limitNo
queryYesA food name.
regionNoKeep dishes from this region. Ingredients have none, so leave it out with kind=ingredient.

Output Schema

NameRequiredDescription
noteNoSet only when there are no results: says the food is not in the data and is to be reported as missing, not answered with a suggestion or another food, or, when a filter was given, to search again without it first.
queryYes
totalYes
resultsYes
suggestionsYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/idempotent annotations: explains spelling-variant matching (sambar/sambhar), the all-words-must-match token rule, that zero hits return a `note` plus up to 5 close-name suggestions, and that `total` counts pre-limit matches. It also names the result fields and, critically, that each food carries `units` with gram weights accepted by get_nutrition and compute_meal.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the identity and the 'call this first' instruction, then adds detail in priority order. The prose is dense and the second sentence is long and comma-chained, but nearly every clause carries operational information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers matching semantics, the empty-result contract, result field meanings, cross-tool unit compatibility, and pagination behavior — all the decision-relevant context for a search tool. An output schema exists, yet the description's explanation of `units`/grams adds genuinely useful cross-tool linkage rather than duplicating it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 80% and the individual params are already documented, but the description adds real semantics the schema lacks: the relationship between `total` and `limit` and the corrective actions (raise limit, narrow by region/diet/kind). It does not restate enum meanings, which is appropriate since the schema covers them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+payload: 'Find foods by name and get their ids.' It immediately positions itself relative to siblings by noting the other food tools take ids from it, so an agent knows this is the entry point rather than a nutrition or meal tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit sequencing guidance ('Call this first') plus concrete fallback instructions: when total exceeds shown results, raise limit or narrow by region/diet/kind, and when no result matches, report the dish missing rather than substituting a suggestion. The one gap is that the sibling `find_foods` is never mentioned, so an agent cannot tell how this differs from that similarly-named tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcompute_meal
    • First observedcompute_targets
    • First observedfind_foods
    • First observedget_nutrition
    • First observedsearch_food

TDQS

A4.2/5.0

Scored across 5 tools

Disambiguation4/5

The tools have largely distinct purposes: search_food resolves names to IDs, get_nutrition handles one food, compute_meal aggregates multiple items, find_foods ranks foods, and compute_targets estimates daily needs. The only minor overlap is get_nutrition versus compute_meal when a meal has a single item, but the descriptions clarify the intended boundary.

Naming Consistency4/5

All names use consistent snake_case with a verb_noun structure, which is readable and predictable. The verbs vary between search/find and get/compute, but the resource nouns make each tool's role clear.

Tool Count5/5

Five tools is well-scoped for a nutrition data server. Each tool covers a distinct operation—lookup, nutrient retrieval, meal aggregation, ranking, and daily targets—without obvious redundancy.

Completeness4/5

The surface covers the core workflows well: finding foods, getting nutrition, computing meals, ranking foods, and estimating targets. Minor gaps exist, such as no explicit ingredient-list tool or comprehensive vitamin/mineral target calculations, but agents can work around these.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides intelligent access to the USDA nutrition database through AI assistants, enabling users to search foods, compare nutritional content, find foods high in specific nutrients, and query authoritative nutrition data across 7,146+ food items through natural language.
    1
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables natural-language nutrition research for Indian foods, combining a curated knowledge base with hybrid RAG and specialized agents. Provides MCP tools for knowledge search, safe calculations, document retrieval, and optional live web search.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables agents to log and edit meals, query a food journal, and retrieve calorie/macro/quality KPIs with server-side nutritional grading, plus manage staples and targets.
    1
    -