Skip to main content
Glama
megageek

Nutri Points MCP Server

by megageek

Nutri Points MCP Server

An MCP server exposing the Nutri Points API's stable, scoped recipe, food-item, and generic-ingredient draft workflow as MCP tools.

Tools search saved recipes, food items, and generic ingredients; read published items and drafts; read food, activity, and weight logs; and save, update, publish, or discard drafts. Recipe drafts can also be validated before publishing. Each write is a separate call, so callers can review the draft and its version before publication. Nutri Points calculates nutrition and points.

Every tool has the standard MCP readOnlyHint annotation: searches, detail and draft reads, log/day/weight reads, recipe validation, and ping are read-only; saving, updating, publishing, and discarding drafts are writes. Tools are also tagged read or write within FastMCP, allowing FastMCP-based deployments to filter each set independently. MCP itself does not define a separate category field, so wrappers should use readOnlyHint to apply different requirements.

The MCP server sends workflow instructions when a client connects. They direct assistants to search for reusable recipes and ingredients before creating new ones, inspect candidate details, and prefer generic ingredients for reusable categories. A specific food item is appropriate when an exact product or its nutrition matters. Assistants should request missing nutrition facts and use Nutri Points' draft validation and calculated values; these instructions guide the assistant and do not block tool calls.

API contract

The server is pinned to Nutri Points stable-rw-v20 through the public nutripoints-api-contracts v20.0.0 release. The pinned version and wheel SHA-256 are recorded in contract-version.json. Its OpenAPI document is checked into src/nutripoints_mcp/contracts/openapi.json and included in the Python package and Docker image, so development and runtime do not depend on GitHub availability. The snapshot is the source for future MCP tool schemas; the detailed contract and generation changelog are in docs/dev/api.md.

To refresh the snapshot after deliberately updating the pin and reviewing that generation's changelog, run python scripts/sync_contract.py. The script verifies the release wheel's SHA-256 before extracting OpenAPI. For local cross-repository development, pass --wheel path/to/nutripoints_api_contracts-<version>-py3-none-any.whl. Update affected tools and tests in the same change when advancing the generation.

Related MCP server: mealie-mcp

Development

Open this repository in the Dev Container (VS Code "Reopen in Container", or GitHub Codespaces). It provisions Python 3.11 with uv and Node.js (for the MCP Inspector via npx, and for Commitlint).

The Dev Container also installs Docker Engine, the Docker CLI, Buildx, and Compose through the Docker-in-Docker feature. Rebuild the Dev Container after changing this configuration, then run docker version and docker compose version inside it. Its Docker daemon is separate from the host daemon and requires a host that allows privileged Dev Containers. To deploy beside an existing Nutri Points container on another Docker host, run Compose on that host or select a Docker context for it. Dev Container CI builds the Compose service and checks /health.

Copy .env.example to .env and fill in a Nutri Points base URL and scoped API key before running anything against a real instance. For draft and catalog tools, the key needs recipes:read, recipes:write, foods:read, foods:write, ingredient-types:read, and ingredient-types:write; a narrower key works for the tools in its domain. The configured key must also be permitted by Nutri Points to read the selected log, weight, and day routes. API errors, including version conflicts and missing items, are returned as tool errors. Mutating tools accept an optional idempotency_key for replay-safe retries.

Search tools pass q to Nutri Points (up to 120 characters). Food-item and generic-ingredient searches also accept include_archived. To edit a published item, first call its get_*_draft_for_item tool. If it returns a draft, update that draft using its returned id and version; if no draft exists, call save_*_draft with the published item ID to begin one. To create a new item, omit the item ID. Agents may create needed ingredients, but should first confirm the user can obtain them. Prefer generic ingredients unless an exact product, brand, preparation, nutrition, or multiple serving variants needs a specific food. Recipe drafts can reference published or caller-owned draft ingredients; Nutri Points validation reports required next actions before publication.

list_food_logs, list_activity_logs, and list_weight_logs accept optional date_from/date_to ISO dates, start_at/end_at ISO datetimes, and limit (1–2,000). get_today reads the current day, while get_day reads a specific ISO date. A day result can be status: "ready" with the server-calculated ledger, or status: "setup_blocked" when daily-budget prerequisites are incomplete; both are returned unchanged. get_weight_overview reads calculated trends and coaching for 30d, 90d, 1y, or all, and get_pending_weight_recap reads without acknowledging a recap.

For example, search_generic_ingredients with {"q":"basil"} finds saved generic ingredients. save_generic_ingredient_draft with a complete payload returns a draft id and version; publish_generic_ingredient_draft then takes those as draft_id and expected_version. The same tool naming applies to recipe and food drafts. Search results and published detail reads come directly from Nutri Points.

Draft write payloads

Write only fields advertised by a save or update tool's payload schema. Published and draft reads include server-generated IDs, timestamps, calculated/display nutrition and points, archive/origin metadata, and basis fields; do not copy those fields back into a draft payload unless the write schema explicitly includes them.

  • A recipe ingredient is either {"kind":"fixed_food","food_item_id":12,"quantity":{"mode":"grams","value":100}} or {"kind":"generic","ingredient_type_id":34,"resolution_policy":"generic_allowed","quantity":{"mode":"grams","value":10}}. Generic ingredients can also retain a named serving with {"kind":"generic","ingredient_type_id":34,"quantity":{"mode":"serving_variant","ingredient_type_serving_id":56,"multiplier":2}}. A draft ingredient may use the corresponding food_draft_id or ingredient_type_draft_id instead. Quantity modes are grams, milliliters, serving_variant, and base_servings. For serving_variant, provide the selected food_item_serving_id for a food or ingredient_type_serving_id for a generic ingredient; multiplier defaults to 1. Published recipe reads return that serving ID, so it can be retained when preparing a draft payload.

  • Prefer a named serving when it describes the recipe naturally—such as one egg rather than 120 g. Use serving_variant for either a food or a generic ingredient; a food selection needs its food_item_serving_id, while a generic selection needs its ingredient_type_serving_id (and may set multiplier). Use base_servings for generic ingredients, or grams/milliliters when no suitable named serving exists. Both generic ingredients and specific foods can add multiple serving_variants; add sensible options such as pinch, teaspoon, and tablespoon when they are useful for a spice. Generic ingredients can also set a base serving label and amount.

  • Put only {"section":"cook",...} steps in instruction_steps. Put reheat steps in reheat_steps_fridge or reheat_steps_freezer, with sections reheat_fridge or reheat_freezer respectively. The matching reheat_instructions_fridge and reheat_instructions_freezer text fields are also available.

  • Recipe payloads support prep_time_minutes, cook_time_minutes, and passive_time_minutes, each from 0 to 10,080. Include known values when saving or updating. update_recipe_draft replaces the full recipe payload, so timing fields cannot be sent as a standalone partial update.

  • Recipe payloads also support image_url, storage_life_fridge_days, and storage_life_freezer_days. Storage life is nullable and, when set, must be a whole number of days from 1 to 3,650. Use null to clear a value; do not invent storage life or image URLs.

  • When the recipe explicitly gives a timed cooking action, add it to that step's automation_actions: use {"action_kind":"timer","duration_seconds":...} or {"action_kind":"rest","duration_seconds":...}; oven with temperature_c and duration_seconds, hob with level (1–9) and duration_seconds, microwave with power_watts and duration_seconds, or air fryer with temperature_c and duration_seconds. preheat (oven) and shake (air fryer) are optional booleans. Durations are whole seconds; do not invent missing times, temperatures, power, or hob levels.

  • A food payload needs name, nutrition_input_mode, protein_g, carbs_g, fat_g, and fiber_g; for example, {"name":"Basil","nutrition_input_mode":"per_100g","protein_g":3,"carbs_g":2,"fat_g":1,"fiber_g":2}. Optional serving variants use writable fields such as {"label":"tbsp","grams":4}.

  • A generic-ingredient payload has the same required nutrition fields. It can additionally provide base_serving_label with base_serving_grams or base_serving_milliliters when applicable.

update_*_draft requires the draft's current expected_version. Use idempotency_key on writes that might be retried. validate_recipe_draft accepts only a saved draft_id; it does not accept recipe data or save changes. Call save_recipe_draft first, then validate the returned draft ID before publishing a recipe.

The API key stays in process memory and is sent only in the Bearer header to the configured base URL; the server does not log it. Existing CI runs Semgrep, Gitleaks, Trivy, and pip-audit, so this change adds no separate security-scanning dependency. An HTTP base URL transmits the key without TLS; use HTTPS for a remote instance.

uv sync --extra dev   # install dependencies
uv run pytest         # run tests
uv run nutripoints-mcp  # run the server locally (HTTP transport on :8000)

Running with Docker

The server is distributed as a Docker image. For local development:

cp .env.example .env   # fill in NUTRIPOINTS_BASE_URL / NUTRIPOINTS_API_KEY
docker compose up --build

This starts the MCP server on http://localhost:8000/mcp, with http://localhost:8000/health as a plain HTTP health check.

Released versions are published to ghcr.io/megageek/nutripoints-mcp via Release Please.

Releases

Release Please tracks Conventional Commits on main and maintains a release pull request. Merging that PR creates the version bump, CHANGELOG.md entry, GitHub release and tag, and triggers the GHCR image build/scan/publish. Do not create release tags manually.

Related MCP Connectors

Related MCP Servers