Skip to main content
Glama
README.md
# 🥗 MCP Nutrition - AI Meal Planner

Generate a **personalized 7-day meal plan** from your profile and goal, get **daily
calorie catch-up** when you fall short, and **track your progress** over time. It runs as a
**FastAPI web app** and, because the same engine is exposed over the **Model Context Protocol
(MCP)**, also as **AI tools, resources, and prompts any MCP client (Claude Desktop, etc.) can use**.

The intelligence is a deliberate blend:

- 🤖 **OpenAI** proposes concrete, varied meals for each day.
- 🥦 **API Ninjas** looks up macros for each food when your key returns them. **Heads-up:** the free API Ninjas tier gates `calories`/`protein`, so in practice many values fall back to the bundled ~29-food catalog or an Atwater estimate (see [Data & limits](#data--limits)).
- 🌤️ **OpenWeather** nudges calories/hydration for the day's conditions.
- 🧮 **Deterministic Python** computes the targets, keeps the LLM out of the arithmetic, reconciles each day to the **calorie** goal, and runs the catch-up math - the parts that are guaranteed and unit-tested.

> Runs offline too: with **no API keys**, it falls back to a bundled food catalog + cache, so
> `git clone && run` works immediately.

---

## Quickstart

```bash
# 1. Install (uv recommended)
uv sync --extra dev

# 2. (optional) add API keys for live LLM meals + real macros
cp .env.example .env      # then edit .env

# 3. Run the web app
uv run uvicorn app.main:app --reload
# open http://127.0.0.1:8000
```

Prefer pip? `python -m venv .venv && . .venv/Scripts/activate && pip install -e ".[dev]"`.

### Environment (all optional)

| Key | Enables | Without it |
|-----|---------|-----------|
| `OPENAI_API_KEY` | LLM-generated meals | meals come from the bundled catalog |
| `API_NINJAS_KEY` | real macro numbers | macros come from cache / catalog |
| `OPENWEATHER_API_KEY` | weather-based adjustment | adjustment is skipped |

---

## Run it as an MCP server

The planner is also a stdio MCP server named `nutrition_db`:

```bash
uv run nutrition-mcp        # or:  python -m mcp_server
```

Register it in an MCP client (e.g. Claude Desktop `claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "nutrition_db": {
      "command": "uv",
      "args": ["run", "nutrition-mcp"],
      "cwd": "/absolute/path/to/MCP Nutrition"
    }
  }
}
```

It exposes all three MCP primitives, not just tools:

- **Tools:** `get_food_nutrients`, `get_current_conditions`, `compute_targets`, `generate_weekly_meal_plan`, `log_daily_intake`, `adjust_daily_calories`.
- **Resources** (read-only context): `nutrition://catalog`, `nutrition://profile`, `nutrition://targets`, `nutrition://log/today`, `nutrition://history`.
- **Prompts** (guided flows): `plan_my_week`, `log_my_meal`, `what_should_i_eat_now`.

---

## How it works

```mermaid
flowchart LR
    UI["Web UI (form)"] -->|HTTP| API["FastAPI · app/"]
    MCP["MCP client<br/>(Claude Desktop)"] -->|tools · resources · prompts| SRV["nutrition_db · mcp_server/"]
    API --> CORE
    SRV --> CORE
    subgraph CORE["core/ engine - single source of truth"]
        direction LR
        T["targets<br/>(BMR→TDEE→goal)"] --> P["planner"]
        P --> R["reconcile ±10%"]
        ADJ["catch-up adjuster"]
        H["history"]
    end
    P -->|propose foods| OA["OpenAI"]
    P -->|ground macros| AN["API Ninjas"]
    T -->|weather adjust| OW["OpenWeather"]
```

`core/` is the single source of truth; the web app and the MCP server are thin layers over it.
See [`docs/DECISIONS.md`](docs/DECISIONS.md) for the design tradeoffs and roadmap.

**Plan generation** (`core/planner.py`), per day: OpenAI proposes foods → API Ninjas returns real
macros (cached) → deterministic code scales portions to hit the calorie target (±10%). A rolling
"avoid recently-used items" list keeps the week varied. No OpenAI key? The bundled catalog builds
the day instead.

**Calorie catch-up** (`core/adjuster.py`): compares logged intake to target. If you're under, it
suggests catch-up foods for the meals you have left today, or rolls a **capped** portion of the
shortfall into tomorrow. (This is the opposite of naively lowering the goal when you under-eat.)

**Targets** (`core/targets.py`): Mifflin-St Jeor BMR → activity TDEE → goal adjustment
(-500 weight loss / +300 muscle gain) → macro split.

---

## Project layout

```
core/         deterministic engine + service facade + API/LLM clients (the brains)
mcp_server/   nutrition_db MCP server (tools + resources + prompts over core/)
app/          FastAPI backend + minimal web UI (form, plan, catch-up, progress chart)
data/         food_catalog.json (fallback), nutrition_cache.json, state.json (runtime)
evals/        plan-quality eval harness + SCORECARD.md (python -m evals)
docs/         DECISIONS.md (design tradeoffs & roadmap)
tests/        pytest suite (targets, planner, adjuster, allergens, service, evals)
```

## Testing & evals

```bash
uv run pytest -q          # 34 tests
uv run ruff check .
uv run python -m evals    # regenerate evals/SCORECARD.md
```

Tests run the real engine in offline mode (deterministic via the catalog) and use small fakes to
exercise the LLM path without a network call.

**Plan-quality evals** ([`evals/`](evals/), scorecard: [`evals/SCORECARD.md`](evals/SCORECARD.md))
score generated plans across a golden set of profiles (goals × diets × allergies) on calorie
adherence, **100% allergen safety**, protein adequacy, diet compliance, and variety. CI gates the
non-negotiables - measuring a non-deterministic LLM system, not just unit-testing pure functions.

---

## Data & limits

Be clear-eyed about what this does and doesn't guarantee:

- **Macro source.** Authoritative macros come from API Ninjas *only when your key returns them*. On the free tier `calories`/`protein` are premium-gated, so values fall back to the curated **~29-food catalog** (`data/food_catalog.json`) or an Atwater (4/4/9) estimate. The bundled catalog is what makes offline mode work.
- **"On target" means calories.** Days are reconciled to the **calorie** target (±10%); protein/carb/fat are shown as guidance and a day is flagged when protein runs low, but macros aren't enforced.
- **Allergens.** Typed allergies are expanded to ingredient keywords (`core/allergens.py`) and excluded from catalog, LLM-proposed, and catch-up foods - but it's **best-effort keyword matching, not a medical guarantee.** Verify ingredients yourself.
- **Not medical advice.** Estimates only; not for pregnancy, medical conditions, or disordered eating. Single-user, local state; no accounts or sync.

## Provenance

This started as an MCP nutrition benchmark server and grew into a standalone product. All code here
is original work by **harmehak0173**; it has no dependency on the original benchmark framework.

## License

MIT © 2026 harmehak0173 - see [LICENSE](LICENSE).

TDQS

A3.6/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clearly distinct purpose, from computing targets to logging intake and generating meal plans. The weather tool is unrelated but still unique.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case, making them predictable and easy to understand.

Tool Count5/5

With 6 tools, the server is well-scoped for nutrition tracking without being overwhelming or underwhelming.

Completeness4/5

Core workflows are covered (targets, food lookup, logging, planning), but missing update/delete for logs and the weather tool is extraneous.

Maintenance

ActivityInactive
ResponsivenessNo issues