Build an MCP server for nutrition data
An MCP server gives an AI client a narrow, inspectable nutrition lookup tool instead of asking the model to recall product labels from memory. Wrapping DietlyAPI in the Model Context Protocol lets Claude, Cursor and other MCP hosts fetch real macros on demand, then explain exactly what they found. This guide builds a small but complete server with two tools, in Python.
Why a tool beats a guess
Language models are fluent about nutrition and frequently wrong about specific numbers. A tool call changes that: the model asks for "greek yogurt", gets back a real record with a source and a per-100 g basis, and reports the value rather than inventing one. Keep the surface small so the host can reason about when to call it.
1. Define two tools with clear intent
Start with search_foods (a query plus an optional small limit) and get_food (a Dietly food ID). Resist a single vague tool that tries to parse a meal, pick a diet and build a plan at once; narrow tools are easier for a model to use correctly.
{"name":"search_foods",
"description":"Search Dietly's food catalog by product or food name.",
"inputSchema":{"type":"object",
"properties":{"query":{"type":"string"},
"limit":{"type":"integer","maximum":10}},
"required":["query"]}}2. A complete server with the Python SDK
The official mcp package exposes a FastMCP helper. Each tool is a normal async function; return plain dictionaries and the SDK handles the protocol. This server calls GET /search and GET /food/{id} and preserves the API's meaning.
import httpx
from mcp.server.fastmcp import FastMCP
API = "https://api.getdietly.com"
mcp = FastMCP("dietly-nutrition")
@mcp.tool()
async def search_foods(query: str, limit: int = 5) -> list[dict]:
"""Search Dietly's catalog. Returns [] when nothing matches."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{API}/search",
params={"q": query, "limit": min(limit, 10)})
r.raise_for_status()
return r.json()
@mcp.tool()
async def get_food(food_id: int) -> dict | None:
"""Fetch one food by Dietly ID. Returns None if it does not exist."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{API}/food/{food_id}")
if r.status_code == 404:
return None
r.raise_for_status()
return r.json()
if __name__ == "__main__":
mcp.run()3. Preserve the API's meaning
Do not invent a match when the search is empty, and keep nullable values nullable rather than defaulting them to zero. Pass source and confidence through to the output so a person can see where a number came from and how sure Dietly is. The value of a data tool is that it does not smooth over uncertainty.
4. Register the server with a host
An MCP host launches your server over stdio. For Claude Desktop, add it to the config file; the same shape works for Cursor and other hosts.
{
"mcpServers": {
"dietly-nutrition": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}Install and run it locally
The server needs two dependencies: the MCP SDK and an HTTP client. Install them into a virtual environment, save the code above as server.py, and run it. MCP servers speak over stdio by default, so there is no port to open and nothing to expose to the internet for local use.
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]" httpx
python server.pyWhile developing, the MCP command-line inspector lets you call each tool by hand and see the raw JSON a host would receive, which is the fastest way to confirm your field passthrough is intact before you wire it into a real client. Run mcp dev server.py to open it. Because the server holds no state between calls, you can restart it freely; the host reconnects and re-lists the tools automatically.
5. Tell the model how to use the results
6. Protect keys and limits
For a local read-only tool, anonymous access is fine. If you add a key for higher limits, load it from the host environment, never from tool arguments or a prompt, so it cannot leak into a transcript. Honor 429 responses by waiting for Retry-After and cap retries. When output derived from Open Food Facts is shown publicly, include the attribution required by the API terms.
Test before you trust it
Exercise a precise product, a generic food and a deliberate typo, and confirm the model calls the tool rather than guessing and can explain an empty result. A tool that is silently skipped is worse than no tool, because the answer still sounds authoritative. If the host has a way to log tool calls, watch a few real sessions early on: you want to see the model reaching for search_foods on specific-product questions and answering from its own knowledge only on general ones. That balance, not the raw code, is what makes the integration trustworthy in daily use.