Skip to main content
Glama
README.md
# usda-fdc-mcp

A stdio MCP server that answers food composition questions from USDA FoodData Central.

## What it does

The server exposes four tools over the Model Context Protocol.

| Tool | Purpose |
| --- | --- |
| `search_foods` | Search foods by name. Returns the FDC ID, description, data type, and a macro-completeness flag. |
| `get_food` | Return the nutrient panel for one FDC ID, scaled to a gram amount. |
| `food_nutrition` | Search and scale in one call. Prefers a record with a complete macro panel. |
| `recipe_nutrition` | Sum an ingredient list into per-serving totals with a per-ingredient breakdown. |

FoodData Central reports every amount per 100 g. The server scales each amount to
the grams that you request. The percent Daily Value uses the FDA adult values in
21 CFR 101.9.

### Nutrient identifiers

FoodData Central gives each nutrient two identifiers. The modern `nutrientId` for
protein is `1003`. The legacy NDB `nutrientNumber` for protein is `"203"`. A filter
on the wrong identifier returns an empty nutrient table and no error. The server
keys on `nutrientId` in every code path.

### Response shapes

The two endpoints put nutrient values in different fields.

```
/foods/search  ->  [{ nutrientId: 1003, value: 16.5 }]
/food/{id}     ->  [{ nutrient: { id: 1003 }, amount: 16.5 }]
```

The server normalizes both shapes. Results from either endpoint are
interchangeable.

### Alias identifiers

A nutrient can arrive under a different identifier in a different data type. The
value then reads as missing. The server resolves three known cases.

- Foundation cashews carry energy as Atwater factors under `2048`. The canonical
  energy identifier is `1008`.
- SR Legacy flaxseed carries omega-3 under the general PUFA 18:3 identifier
  `1270`. The ALA-specific identifier is `1404`.
- Foundation raisins carry sugars under `1063`. SR Legacy records use `2000`.

## Requirements

- Node.js 20 or later.
- An internet connection for live lookups.
- A FoodData Central API key. Read the API key section below.

## Install

```bash
npm install
npm run build
```

## Configuration

### API key

The server reads the key from the `USDA_FDC_API_KEY` environment variable. The
server falls back to `DEMO_KEY` when that variable is absent.

`DEMO_KEY` is the shared key that USDA publishes. It allows about 30 requests per
hour for each IP address. Use `DEMO_KEY` for light testing only.

Get a free personal key at <https://fdc.nal.usda.gov/api-key-signup>. A personal
key allows 1,000 requests per hour.

Pass the key through the environment. Do not commit the key to a file.

### Cache

The server writes every response to a disk cache at `~/.cache/usda-fdc-mcp`. Set
`USDA_FDC_CACHE_DIR` to move the cache. A cached food still answers after you
reach the rate limit.

### MCP client

Add the server to your MCP client configuration. Replace
`/path/to/usda-fdc-mcp` with the path to your own clone.

```json
{
  "mcpServers": {
    "usda-fdc": {
      "command": "node",
      "args": ["/path/to/usda-fdc-mcp/dist/index.js"],
      "env": {
        "USDA_FDC_API_KEY": "your_key_here"
      }
    }
  }
}
```

## Usage

### A name search can return a neighbouring food

`search_foods` ranks its results by relevance. It returns a plausible food when
the exact food falls outside the result order. It reports no error in that case.

A query for `raisins seedless` returns golden raisins (168164) before dark
seedless raisins (168165). The two records differ by nearly half on iron. Golden
raisins hold 0.98 mg per 100 g. Dark seedless raisins hold 1.79 mg per 100 g.

Read the `description` field on each hit before you use the numbers. Call
`get_food` with a known FDC ID when the exact food matters.

### Ingredients that FoodData Central does not hold

FoodData Central holds few supplement powders and few branded products. Pass such
an item through `manual_items`. Take its figures from the product label or from
published literature.

```json
{
  "items": [
    { "query": "bananas raw", "grams": 118, "label": "1 medium banana" },
    { "query": "seeds hemp seed hulled", "grams": 30 }
  ],
  "manual_items": [
    { "label": "whey isolate, 1 scoop", "nutrients": { "energy_kcal": 120, "protein_g": 30 } }
  ],
  "servings": 1
}
```

### Source citation

Every `get_food` result carries a `source` string.

```
USDA FoodData Central, FDC ID 170554 (SR Legacy), https://fdc.nal.usda.gov/food-details/170554
```

Quote that string when you publish a nutrition figure. The figure then stays
traceable to its source record.

## Tests

Run the offline test. It needs no network access and no API quota.

```bash
npm test
```

The offline test replays four cached FoodData Central records from
`test/fixtures`. It asserts that each alias case resolves to the correct number.

Run the smoke test to check the live API contract. It consumes API quota.

```bash
npm run smoke
```

The smoke test confirms that both endpoint shapes parse to the same numbers. It
confirms that gram scaling is linear. It confirms that the percent Daily Value
and the source string are populated.

## Development

```bash
npm run dev     # run the server from source through tsx
npm run build   # compile TypeScript into dist/
npm start       # run the compiled server
```

## Licence

Licensed under the PolyForm Noncommercial License 1.0.0. Copyright 2026
Seraphine Renard.

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation4/5

Each tool has a distinct role: get_food for known IDs, search_foods for discovery, food_nutrition for a name-based shortcut with nutrients, and recipe_nutrition for multi-ingredient calculations. However, the overlap between food_nutrition and search_foods/get_food could cause some initial confusion, but descriptions clarify the intended use.

Naming Consistency3/5

Names mix verb_noun (get_food, search_foods) and noun_noun (food_nutrition, recipe_nutrition) patterns, which is inconsistent and less predictable. The mixed conventions make it harder to infer a tool's function from its name alone.

Tool Count5/5

Four tools is well-scoped for a USDA food data server, covering search, ID-based lookup, convenience queries, and recipe analysis. Each tool earns its place without unnecessary bloat.

Completeness5/5

The surface covers the core workflow of finding a food, retrieving its nutrients, and combining foods into recipes. For a read-only database, there are no obvious gaps or dead ends.

Maintenance

ActivitySlowing
ResponsivenessNo issues