tandoor-mcp
{
"answer": "This MCP server bridges an AI assistant to a Tandoor Recipes instance, enabling full management of recipes and their associated entities (foods, units, keywords). Here's what you can do:\n\n### Recipe Operations\n- Search recipes — Full-text and filtered search with support for keywords (AND/OR/NOT logic), foods, ratings, cooking history, "make now" filtering, recent additions, random ordering, and pagination.\n- Get a recipe — Fetch a complete recipe with all steps, ingredients, and entity IDs.\n- Create a recipe — Build a full recipe (name, servings, prep/cook times, source URL, description, keywords) with multiple steps and ingredients in one call. Supports Jinja-templated step instructions ({{ ingredients[0] }} placeholders) that are plural-aware, scale amounts with servings, and handle inline scaling via {{ scale(200) }}.\n- Update a recipe — Read-modify-write partial updates; only specified fields are touched. Steps and ingredients keep their IDs, omitted ones are deleted, and out-of-range template indexes are validated.\n- Delete a recipe — Cleanly removes ingredients and steps first, then the recipe, preventing orphaned database rows.\n- Log a cooking event — Record that a recipe was cooked.\n\n### Food (Ingredient) Operations\n- Search foods — Fuzzy/trigram-ranked lookup; search up to 40 terms at once, with tree-path display.\n- Create a food — Add a new food entity with plural name and category decisions (validation guidance prevents duplicate pollution).\n\n### Unit Operations\n- Search units — Look up units of measure or list the full vocabulary.\n- Create a unit — Add a new unit of measure.\n\n### Keyword (Tag) Operations\n- Search keywords — Look up tags by name, or list all existing tags (useful for matching recipes to existing vocabulary); tree paths are shown.\n- Create a keyword — Add a new keyword/tag.\n\n### Safety & Design Principles\n- Entities (foods, units, keywords) are always referenced by id+name in recipe payloads, preventing silent duplicate creation.\n- Ingredient amounts can be flagged as "no amount", steps can be headers or embed other recipes, and ingredient notes capture preparation details.\n- All entities are validated with max lengths and required fields."
}
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@tandoor-mcpsearch my recipes for chicken curry"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
tandoor-mcp
An MCP server for Tandoor Recipes, built for creating and editing recipes from the Claude app.
Setup
uv sync
cp .env.example .env # fill in TANDOOR_URL and TANDOOR_TOKEN
uv run tandoor-mcpThe API token comes from Tandoor under Settings → API → Access Tokens.
Related MCP server: Tandoor MCP Server
Docker
export TANDOOR_URL=... TANDOOR_TOKEN=... \
TANDOOR_AUTH_TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
docker compose up -dAlpine-based, ~136 MB. The container defaults to the HTTP transport bound to
0.0.0.0, since stdio has nothing to talk to inside a container.
Two things bite when connecting to a containerised Tandoor:
ALLOWED_HOSTS. Tandoor answers a request whose
Hostheader it does not recognise with a bareBad Request (400)and no explanation. The hostname inTANDOOR_URLmust be listed in Tandoor'sALLOWED_HOSTS.Reachability. Either attach to Tandoor's compose network and use the service name, or reach the host — both need the hostname allowed above.
Transport
TANDOOR_TRANSPORT=stdio (default) for local clients, or http with
TANDOOR_HOST / TANDOOR_PORT to expose it over the network. HTTP is served
at /mcp, or wherever TANDOOR_PATH points.
TANDOOR_PATH exists for reverse proxies that route by prefix without
stripping it — the server then has to answer on the same path the client asks
for, e.g. TANDOOR_PATH=/tandoor/mcp behind
PathPrefix(/tandoor). Like TANDOOR_HOST and TANDOOR_PORT it describes
this server, not the Tandoor instance.
The HTTP transport requires TANDOOR_AUTH_TOKEN; clients send it as
Authorization: Bearer <token> and anything else gets a 401. The server
refuses to start without it, because an open port hands write access to the
recipe database to whoever can reach it. TANDOOR_ALLOW_UNAUTHENTICATED=true
waives this deliberately. stdio needs no token — the transport is a pipe to a
process the user started.
For Claude Desktop with stdio:
{
"mcpServers": {
"tandoor": {
"command": "uv",
"args": ["run", "--directory", "/path/to/tandoor-mcp", "tandoor-mcp"],
"env": {
"TANDOOR_URL": "https://tandoor.example.com",
"TANDOOR_TOKEN": "tda_..."
}
}
}
}Tools
Tool | Purpose |
| Compact hits, with Tandoor's full filter set |
| One recipe in full, with all ids |
| Whole recipe incl. steps and ingredients |
| Read-modify-write update |
| Delete, including its parts |
| Record that a recipe was cooked |
| Look up entity ids, many terms per call, scored |
| Add entities deliberately, several at a time |
| Fix an entity in place |
| Remove entities, guarded |
| Fold a duplicate into the entry that stays |
| Nest an entity into a tree |
* is foods, units, keywords and food_categories.
Search hits are id, name and a match score. Everything else — a food's category
or plural name, a keyword's position in the tree — is opt-in per call via
options, because a batch of twenty ingredients multiplies whatever a single
hit costs and the model is usually only picking an id.
search_recipes exposes Tandoor's own filters rather than a subset:
keywords_and / _or / _and_not / _or_not and the same four for foods,
plus rating, cooking history (cookedon_lte, timescooked_gte), makenow and
sort_order. The four flavours matter — Tandoor's plain keywords parameter is
an OR, so a tool that describes it as "must carry all of them" promises
something the API does not deliver.
Keywords and foods are both trees, and filtering follows them. A recipe
tagged only Vorspeise is found by filtering on its parent Gang; a recipe
using only Büffelmozzarella is found by filtering on Käse. Both verified
against a live instance, and include_children=false turns it off.
That makes specificity free: referencing the exact food costs nothing in
findability, as long as it hangs in the right place. It is also the answer to a
database that grew flat — move_food re-parents without touching any recipe.
Tandoor ignores parent in a create payload (the response comes back with
parent null), so creation happens in parallel and nesting afterwards, strictly
one at a time: each move rewrites the whole nested-set tree, and two at once
answer 500.
Layout
src/tandoor_mcp/
├── config.py # TANDOOR_* settings
├── client.py # httpx wrapper, DRF errors -> readable messages
├── types.py # shared JSON alias
├── templating.py # step-templating reference text (see below)
├── payloads.py # input -> Tandoor, read-only stripping, response slimming
├── server.py # assembly; iterates tools.DOMAINS
├── models/ # entities.py, recipes.py — tool input validation
└── tools/ # one module per domain, each exposing register(mcp, client)
├── _entity_tools.py # shared search and delete machinery
├── recipes.py
├── foods.py
├── units.py
├── keywords.py
└── categories.pytemplating.py holds no logic. TEMPLATING_REFERENCE is a plain string that
gets interpolated into the description= of create_recipe and
update_recipe, so it becomes part of what the model reads when it lists the
tools. Editing that one constant changes the guidance everywhere it appears.
The four entity domains differ only in endpoint and wording, so their search and
delete tools are built once in _entity_tools.py and registered per domain via
EntitySpec and MCPServer.add_tool, which takes the tool name and
description as arguments. Each spec lists the options its domain supports, so
every search tool advertises exactly its own enum.
Requires fuzzy lookup
Tandoor only uses trigram similarity for food/unit/keyword search when the
calling user has lookup enabled in their search preferences
(cookbook/views/api.py, FuzzyFilterMixin). Without it the search falls back
to a plain substring match, and Kartoffeln then fails to find Kartoffel
while happily returning Süßkartoffelnudel.
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$TANDOOR_URL/api/search-preference/<user-id>/" -d '{"lookup": true}'Design notes
Entities are referenced by id, never by name. Tandoor's serializers accept a
nested food or unit without an id and create a new one, which is how duplicate
ingredients accumulate. EntityRef in models.py makes the id mandatory, so a
name-only reference fails validation instead of silently polluting the database.
Tandoor additionally requires name next to id on nested entities — id alone
is a 400.
No ingredient parser. Tandoor's /api/ingredient-parser/ splits a written
line well, but it creates every food and unit it fails to recognise. Measured
on 20 realistic German lines it silently added four entities — geriebener Parmesan next to the existing Parmesan, rote as a unit. Since fuzzy search
resolves the same lines without writing anything, the parser is not exposed.
Search is batched. One call takes every ingredient of a recipe, one request per term in parallel — 12 terms in ~230 ms against a local instance. Ranking is left entirely to Tandoor.
Search hits are scored, because Tandoor has no "not found". Its fuzzy lookup
annotates by trigram similarity and orders by it, but never filters, so every
query returns a full page whatever was asked: Xylophon comes back with Bacon,
Honig and Natron, and Mozzarella with Mehl and Mandel behind the real hit.
Each hit therefore carries a score — the returned name measured against the
query, singular and plural — and anything under min_score is dropped, so an
empty result finally means something. The default of 0.55 is calibrated on that
noise: it removes Mehl (0.43) and Mandel (0.50) from Mozzarella while keeping
Süßkartoffel (0.83) and Karotte (0.67) for Kartoffeln. The ordering is still
Tandoor's; this only says how far down the list stopped being relevant.
Out-of-range template indexes are rejected client-side. Tandoor renders
{{ ingredients[9] }} as an empty string with no error, so the mistake would
only surface as a missing word in the finished recipe. StepInput validates
every index against the step's own ingredient count.
Updates are read-modify-write. Tandoor treats the steps array as the whole
truth: omitted steps get deleted, id-less ones get created. update_recipe
therefore fetches the recipe, strips the read-only fields, merges the requested
changes and writes the complete object back.
Steps and ingredients keep their ids through an update, which is what makes it
an edit rather than a rebuild. StepInput and IngredientInput carry an
optional id and pass it through; without it Tandoor discards every step and
creates replacements, so one update of a two-step recipe left two dead step
rows behind and renumbered everything.
Removing a step is the other half of that. Tandoor detaches it from the recipe
without deleting the row, and /api/step/<id>/ answers 404 the moment it is
detached, so nothing can ever clear it up afterwards. update_recipe deletes
what the new step list drops before writing the update, while those rows are
still reachable.
New foods must decide on a plural and a category. Both are required
parameters of create_food with no default, though either may be null. A food
without a plural reads wrong at every amount other than one, and one without a
category falls to the bottom of every shopping list — and neither is visible
once the food is in place.
Deleting a food is guarded. Tandoor deletes a food that recipes still use
with a plain 204 and cascades to their ingredient lines: verified on 2.6, a
food used by one recipe deleted cleanly and left that recipe's step with zero
ingredients. delete_food therefore asks /api/recipe/?foods=<id> first and
refuses while anything uses it, naming the recipes. delete_keyword does the
same. Units are not guarded — deleting one only sets the field to null — but
that too happens across every recipe at once, so the tool says so.
The dependency check deliberately ignores Tandoor's own cascading entries for
ingredients and steps. Deleting a recipe orphans both: after deleting the only
recipe in a fresh instance, the database still held its 4 steps and 3
ingredients, unreachable through any endpoint and still claiming their food was
in use. delete_recipe therefore takes a recipe apart from the inside out —
ingredients, then steps, then the recipe — and leaves nothing behind.
Merging is the repair that deleting is not. merge_foods(source, target)
repoints every recipe from the source to the target and deletes the source, so
a duplicate disappears without any recipe losing an ingredient. It is guarded
like deletion, and for one reason more than usual: Tandoor keeps the target
exactly as it is, so anything only the source knew is discarded silently.
Verified on 2.6 — merging a food that carried a category and a description into
one that had neither left the target with neither. The refusal therefore lists
both the recipes that will move and the fields that will be lost, and says which
direction the merge should run: the target is the better-maintained entry, not
the better-named one. Whatever was lost can be put back with update_food,
which is why the tool does not try to carry it over itself.
Responses are slimmed. A fully expanded recipe carries nutrition, properties,
conversions and user objects. payloads.py reduces both search hits and full
recipes to what the model can actually act on.
Templating is verifiable. For templated steps the write tools return
instruction_rendered: Tandoor's own rendering, collapsed from its Vue markup
back to plain text ("200 EL Mehl mit 2 Zehen Knoblauch verrühren"). The syntax
reference lives in templating.py, verified against Tandoor 2.6.13.
Development
uv run ruff check .
uv run ruff format .
uv run ty checkA Tandoor to develop against
docker compose -f dev/docker-compose.yml up -d
dev/bootstrap.sh # user, space, search preferences, API token -> .env.dev
uv run python dev/seed.py # ~120 German foods, 8 categories, units, keywordsdev/bootstrap.sh writes .env.dev; both it and dev/data/ are gitignored.
Data survives docker compose down and disappears only with dev/data/. The
web UI is on http://localhost:8090, login dev / dev.
Postgres rather than SQLite on purpose: fuzzy entity lookup is trigram
similarity from pg_trgm, so on SQLite the search tools would quietly behave
like a substring match and anything measured against it would be worthless. A
fresh Tandoor also has an empty food table, which is why the seed exists —
ranking cannot be judged against nothing.
Available Tools
11 toolscreate_foodA
Create a new food (ingredient). Search first. Tandoor has no duplicate detection, so a second 'Zwiebel' would sit next to the existing one forever with nothing to flag it. Store the plain noun ('Parmesan'), never a prepared form ('geriebener Parmesan') — preparation belongs in the ingredient's note.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Singular name | |
| description | No | Optional note | |
| plural_name | No | Plural name, if it differs |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden. It discloses a key behavioral trait: Tandoor lacks duplicate detection, so creating a duplicate will silently succeed. It also adds domain-specific rules about naming, which are not visible in the schema or annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no fluff. The first sentence is the core purpose; the second packs essential usage warnings and naming rules. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple create tool with an output schema, the description covers all necessary context: what it does, how to avoid duplicates, and naming conventions. It is fully sufficient for an agent to use it correctly without additional info.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3. The description adds meaning to the 'name' parameter by specifying it should be a plain noun and not a prepared form, and suggests that preparation belongs in the note (likely the 'description' parameter). This adds value beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states 'Create a new food (ingredient)' with a specific verb and resource. The description distinguishes itself from siblings by emphasizing the need to search first due to missing duplicate detection, making it clear this is for creating new entries only.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs 'Search first' and explains why (no duplicate detection), effectively guiding the agent to use search_foods before invoking this tool. Also provides naming conventions to avoid incorrect usage, such as storing plain nouns rather than prepared forms.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_keywordA
Create a new keyword (tag). Search first. Tandoor has no duplicate detection, so a second 'Vegan' would sit next to the existing one forever with nothing to flag it.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Keyword name | |
| description | No | Optional note |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description discloses the critical behavioral aspect that Tandoor lacks duplicate detection and that duplicates persist indefinitely. This goes beyond the schema and helps the agent understand the consequences, though it doesn't cover all potential outcomes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler. The purpose is immediately stated, and the warning is a valuable addition without bloat.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the key duplicate risk, and the output schema covers return values. For a simple creation tool with an output schema, this is nearly complete, though it could mention auth or error cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with clear descriptions for name and description, and the description doesn't add parameter-level detail. Baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-resource pair ('Create a new keyword (tag)') and adds a distinct warning that differentiates it from the search_keywords sibling, making the tool's purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly instructs 'Search first,' telling the agent to use a search tool before this create tool to avoid duplicates. This provides clear usage context and implies the alternative (search_keywords).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_recipeA
Create a new recipe with all its steps and ingredients in one call.
Every food, unit and keyword must be referenced by id and name, looked up with search_foods / search_units / search_keywords beforehand. Reduce each written ingredient line to a singular base noun for the search and put adjectives and preparation into the ingredient's note: '100 g geriebener Parmesan' becomes food 'Parmesan', unit 'g', amount 100, note 'gerieben'.
STEP TEMPLATING Instructions are Jinja templates. Placeholders are replaced when the recipe is viewed, and amounts inside them rescale automatically when the user changes the serving count. Literal numbers you type do not rescale.
{{ ingredients[0] }} amount + unit + food, plural-aware ("2 Chilischoten", "30 Gramm Ingwerwurzel", "2 Zehen Knoblauch") {{ ingredients[0].food }} just the food name, plural-aware {{ ingredients[0].amount }} just the number, scaling {{ ingredients[0].unit }} just the unit {{ ingredients[0].note }} the note; notes are NOT part of {{ ingredients[0] }} {{ scale(200) }} any other number that should scale with servings
The index is ZERO-BASED and refers to the ingredient list of the SAME step, in the order you supply it. Indexes are not shared across steps.
CRITICAL: an out-of-range index renders as an empty string, silently — the sentence simply loses a word and nothing reports an error. Count the step's own ingredients before writing an index. This server rejects out-of-range indexes, so a rejection means your index was wrong, not that the syntax was.
Convention worth following: annotate each reference with a Jinja comment so the template stays readable, e.g. {{ ingredients[1] }}{# Ingwerwurzel #} schälen und grob würfeln. Comments render to nothing.
Write prose and put a placeholder wherever an amount or an ingredient name occurs, instead of repeating the numbers literally.
| Name | Required | Description | Default |
|---|---|---|---|
| recipe | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly. It explains the required id+name references, the silent empty-string rendering for out-of-range template indexes, the server's rejection of such indexes, and the zero-based per-step indexing behavior. This gives agents a precise mental model of how the tool behaves, including failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place: the lookup prerequisite, ingredient normalization rule, templating reference, critical out-of-range warning, and convention example. Headings and the CRITICAL callout make the dense information scannable and actionable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with nested recipe/step/ingredient inputs, templating behavior, and no annotations, the description covers the essential operational details completely. It addresses the key gotchas an agent would face: entity lookup, zero-based indexing, scaling behavior, and silent failures. The output schema can handle return-value documentation, so its omission is not a gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage at the top level, but the description heavily compensates. It explains how to reduce ingredient lines, how to separate notes from food names, and every templating placeholder with examples. This adds substantial meaning that the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Create a new recipe with all its steps and ingredients in one call,' using a specific verb and resource while clarifying the all-in-one scope. This clearly distinguishes it from siblings like update_recipe, delete_recipe, and get_recipe. The purpose is unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context: use this to create a full recipe after looking up foods, units, and keywords via the search tools. It does not explicitly mention alternatives like 'use update_recipe to modify an existing recipe,' but the create-oriented purpose and the prerequisite search steps provide strong usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_unitA
Create a new unit of measure. Search first. Tandoor has no duplicate detection, so a second 'Gramm' would sit next to the existing one forever with nothing to flag it. List the units first: the vocabulary is short and the one you want almost certainly exists.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Short form, e.g. 'g' or 'EL' | |
| description | No | Optional note | |
| plural_name | No | Plural form, if it differs |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description discloses a key behavioral trait: no duplicate detection, so duplicate units persist silently. This adds significant context beyond the basic 'create' action, though it doesn't cover other potential behaviors like permissions or failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three concise sentences deliver purpose, usage guidance, and a concrete example. Every sentence adds value with no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple create tool with an output schema, the description covers purpose, usage guidelines, and a critical risk. The presence of an output schema means return values need not be described, making this complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for all parameters, so the baseline is 3. The description adds no additional parameter semantics beyond what the schema already specifies for name, description, and plural_name.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Create a new unit of measure' with a specific verb and resource, distinguishing it from sibling tools like search_units and other create_* tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly instructs to 'Search first' and 'List the units first', providing the alternative action and reasoning—'Tandoor has no duplicate detection'—which gives clear when-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_recipeA
Permanently delete a recipe. This cannot be undone.
| Name | Required | Description | Default |
|---|---|---|---|
| recipe_id | Yes | Recipe id |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It explicitly discloses the irreversible nature of the operation ('This cannot be undone'), which is a critical behavioral trait. It doesn't mention other side effects, but for a simple delete this is valuable and above baseline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short, front-loaded sentences. The first sentence states the action, and the second reinforces permanence. While slightly redundant, it is concise and free of fluff, earning a high score.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter delete tool with an output schema present, the description covers the essential purpose and the key risk (permanence). It doesn't address edge cases like non-existent recipes, but the output schema likely handles return behavior. Overall, it is sufficiently complete for the tool's simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% coverage for recipe_id, so the baseline is 3. The description adds no additional meaning about the parameter (e.g., where to find the ID, constraints, or how it's used). The schema itself only says 'Recipe id', which is minimal but sufficient per the coverage guideline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool 'Permanently delete a recipe', using a specific verb ('delete') and resource ('recipe'). This distinguishes it from sibling tools like search_recipes, get_recipe, create_recipe, and update_recipe.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage when a recipe needs to be removed, but it provides no explicit guidance on when to use this tool versus alternatives (e.g., update_recipe for deactivation) or any exclusions. It's not misleading, but the context is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_recipeA
Read one recipe in full: steps, ingredients, and all entity ids.
Always call this before update_recipe. The ids in the result are what you pass back to keep existing steps, ingredients, foods and units intact.
| Name | Required | Description | Default |
|---|---|---|---|
| recipe_id | Yes | Recipe id |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the burden. It discloses that the tool returns entity ids necessary for preserving related data during updates, which is valuable behavioral context. While it doesn't mention error handling or other traits, it adequately covers the key read-only semantics and the importance of preserving ids.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: the first sentence states the core function, the second provides essential usage guidance. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with one parameter and an output schema, the description covers the essential context: what is returned, why it matters, and how it relates to update_recipe. The presence of an output schema handles return value details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already fully describes the sole parameter (recipe_id) with 100% coverage, so the description doesn't need to add much. It doesn't go beyond the schema, but the baseline of 3 applies given high schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Read one recipe in full: steps, ingredients, and all entity ids.' This specific verb+resource+scope distinguishes it from siblings like search_recipes (search) and update_recipe (write).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance is provided: 'Always call this before update_recipe.' This directly tells the agent when to use the tool and implies it is a prerequisite for updates, with further instruction on passing ids back.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_foodsA
Look up foods (ingredients) by name — all of a recipe's ingredients at once.
Results are ranked by similarity, best first, so the top hit is usually right but the tail may be unrelated. Pick the entry that means the ingredient itself: prefer 'Parmesan' over 'Parmesankäse' unless the recipe really calls for the latter.
Search for the ingredient, not the whole written line. Amount, unit and preparation are separate fields, so "100 g geriebener Parmesan" is one query for 'Parmesan', with 'gerieben' going into the ingredient's note.
Foods form a tree, so 'full_name' shows the path ('Gemüse > Zwiebel'). Called without queries this lists foods, which is rarely useful — the database holds thousands.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Hits per query | |
| queries | No | One term per entity you are looking for; all are searched at once. Omit to list everything. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full behavioral disclosure. It reveals ranking by similarity with potential unrelated tail results, advises selecting the exact ingredient entry (e.g., 'Parmesan' over 'Parmesankäse'), explains the food tree path in 'full_name', and notes the rarely useful empty-query listing behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise yet rich: about six sentences, front-loaded with purpose, and every sentence contributes unique guidance (ranking, selection, examples, tree structure, empty query). No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema and the absence of annotations, the description fully covers essential aspects for a search tool: purpose, ranking behavior, selection tips, tree structure, and parameter use. It leaves no significant gaps for an agent to misuse the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and parameter descriptions already explain the queries and limit. The description adds practical semantic value with the example '100 g geriebener Parmesan' -> query 'Parmesan', clarifying how to think about query terms, which goes beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb ('Look up') and resource ('foods (ingredients)'), and adds batch capability ('all of a recipe's ingredients at once'). This clearly distinguishes it from sibling search tools like search_recipes and search_units, which target different entity types.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear usage guidance: search for the ingredient rather than the whole written line, and prefer exact ingredient names. It also warns that omitting queries lists foods, which is rarely useful. However, it does not explicitly name alternative tools or state when not to use this tool, though the context is strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_keywordsA
Look up keywords (tags) by name, or list them all.
Keywords form a tree like foods, so 'full_name' shows the path. Called without queries this lists the existing tags, which is a good way to match a recipe to the vocabulary already in use rather than inventing a parallel one.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Hits per query | |
| queries | No | One term per entity you are looking for; all are searched at once. Omit to list everything. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden. It explains key behaviors: listing all when queries omitted, hierarchical tree structure, and the meaning of 'full_name'. It does not mention pagination limits or read-only nature explicitly, but those are implied and partially covered by schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the main action. Every sentence is informative: purpose, structure/path, and use case. No redundant words or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter tool with full schema coverage and an output schema, the description fully covers the operational context: what it does, when to use it, and the tree semantics. No gaps for an AI agent to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with both parameters described. The description adds value by illustrating the tree structure and how 'full_name' reflects the path, which is not in the schema. It also reinforces the 'queries' null behavior by restating that omitting queries lists all, though schema already says this.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: 'Look up keywords (tags) by name, or list them all.' It clearly differentiates from sibling tools like create_keyword and other search tools by focusing on read/lookup behavior. The tree/path explanation adds precise scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context: use without queries to list existing tags and align vocabulary. However, it does not explicitly state when NOT to use this tool or mention alternatives like search_foods/search_units, so it lacks explicit exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_recipesA
Search recipes. Returns compact hits (id, name, description, keywords).
Use get_recipe with an id from the result to read steps and ingredients.
| Name | Required | Description | Default |
|---|---|---|---|
| new | No | Only recently added recipes | |
| page | No | 1-based page number | |
| foods | No | Food ids; a recipe must contain all of them | |
| query | No | Free-text search over recipe names and content | |
| random | No | Randomise the result order | |
| keywords | No | Keyword ids; a recipe must carry all of them | |
| page_size | No | Hits per page; keep small | |
| rating_gte | No | Minimum rating, 1-5 |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the transparency burden. It discloses that results are compact hits with only id, name, description, and keywords, so the agent knows not to expect full recipe details. This is a meaningful behavioral trait. It doesn't mention pagination or random ordering, but those are visible in the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short sentences, front-loaded with the primary action and immediately providing the key return shape and a navigational pointer to get_recipe. Every word earns its place with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists and schema descriptions cover all parameters, the description is sufficiently complete. It adds the essential context that results are compact and that full details require a follow-up get_recipe call. It would benefit from noting the pagination behavior, but the schema already specifies page/page_size.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the description does not need to explain each parameter. The description adds no additional parameter-specific semantics beyond noting the returned id, which can be used with get_recipe. This meets the baseline but does not add extra value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource combination ('Search recipes') and clearly distinguishes the tool from get_recipe by noting it returns only compact hits and that full details require get_recipe. It is immediately clear this is the recipe search entry point.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent to use get_recipe with an id from the result to read steps and ingredients, which outlines the intended workflow. It does not explicitly mention when not to use this tool or compare with other search siblings, but the search-vs-get distinction is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_unitsA
Look up units of measure, or list them all.
Called without queries this returns the complete vocabulary, which is short. Do that when unsure rather than guessing a name: Tandoor keeps 'g' and 'Gramm' as separate units, and picking the wrong one is invisible afterwards.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Hits per query | |
| queries | No | One term per entity you are looking for; all are searched at once. Omit to list everything. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses that omitting queries returns the complete (short) vocabulary, and that unit names are case/locale-sensitive ('g' and 'Gramm' are separate), with invisible consequences for choosing the wrong one. This is key behavioral context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler. The first states the purpose, the second gives a usage recommendation and a concrete warning. Every sentence contributes.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The schema covers parameter descriptions and the output schema exists, so no return format explanation is needed. The description covers the edge case (no queries) and the ambiguity pitfall. For a simple lookup tool, this is fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already documents both parameters (limit and queries). The description adds meaningful guidance: omitting queries returns the full vocabulary and is recommended when unsure. It reinforces and extends the schema's 'Omit to list everything' with practical advice.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Look up units of measure, or list them all,' which clearly identifies the resource (units of measure) and the two supported actions (lookup and list). This distinguishes it from sibling tools like create_unit and search_foods.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly advises to call without queries to list the entire vocabulary when unsure, rather than guessing a name, and warns about Tandoor's 'g' vs 'Gramm' separation. This provides a clear decision rule and alternative, though it doesn't name specific sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_recipeA
Update an existing recipe. Only the fields you set are changed; the rest of the recipe is read and written back unchanged.
Passing 'steps' replaces the entire step list. Call get_recipe first and pass back every step you want to keep, including its id — a step without an id is created as new, and an omitted step is deleted. The same holds for the ingredients inside a step.
STEP TEMPLATING Instructions are Jinja templates. Placeholders are replaced when the recipe is viewed, and amounts inside them rescale automatically when the user changes the serving count. Literal numbers you type do not rescale.
{{ ingredients[0] }} amount + unit + food, plural-aware ("2 Chilischoten", "30 Gramm Ingwerwurzel", "2 Zehen Knoblauch") {{ ingredients[0].food }} just the food name, plural-aware {{ ingredients[0].amount }} just the number, scaling {{ ingredients[0].unit }} just the unit {{ ingredients[0].note }} the note; notes are NOT part of {{ ingredients[0] }} {{ scale(200) }} any other number that should scale with servings
The index is ZERO-BASED and refers to the ingredient list of the SAME step, in the order you supply it. Indexes are not shared across steps.
CRITICAL: an out-of-range index renders as an empty string, silently — the sentence simply loses a word and nothing reports an error. Count the step's own ingredients before writing an index. This server rejects out-of-range indexes, so a rejection means your index was wrong, not that the syntax was.
Convention worth following: annotate each reference with a Jinja comment so the template stays readable, e.g. {{ ingredients[1] }}{# Ingwerwurzel #} schälen und grob würfeln. Comments render to nothing.
Write prose and put a placeholder wherever an amount or an ingredient name occurs, instead of repeating the numbers literally.
| Name | Required | Description | Default |
|---|---|---|---|
| update | Yes | ||
| recipe_id | Yes | Recipe id |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully carries the burden of behavioral disclosure. It reveals the partial-update behavior, the destructive consequence of omitted steps ('an omitted step is deleted'), the zero-based indexing with silent empty-string rendering for out-of-range indexes, and the server's rejection of such indexes. It also explains Jinja templating, scaling behavior, and the convention for Jinja comments. This is exceptionally rich and transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place. It is front-loaded with the most important behavior (partial update, step replacement) then proceeds to templating details, with clear headers like 'STEP TEMPLATING' and 'CRITICAL'. The examples are concrete and useful for an AI agent. No filler or repetition of the schema is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's high complexity (nested update object, step/ingredient lifecycle, templating), the description covers all major hazards: partial updates, step replacement, id handling, index counting, silent errors, and scaling. The presence of an output schema means return values need not be explained. No critical behavioral gaps remain that would cause an agent to misuse the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds significant meaning beyond the schema, especially for the 'update' object and its nested steps. It explains that a step without an id is created as new, an omitted step is deleted, and the same holds for ingredients within a step. It details how template placeholders work ({{ ingredients[0] }} etc.), indexing semantics, and the critical silent-failure mode. Even with 50% schema coverage, the description compensates where it matters most, making the tool safe to invoke correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Update an existing recipe. Only the fields you set are changed; the rest of the recipe is read and written back unchanged.' This clearly states the verb (update), resource (recipe), and a critical scope nuance (partial update) that distinguishes it from create/delete/get siblings. It also explicitly contrasts with get_recipe and the replacement semantics for steps, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for when to use this tool, such as 'Call get_recipe first and pass back every step you want to keep' and explains that passing 'steps' replaces the entire step list. It does not explicitly name alternatives or state when not to use it, but the guidance is concrete and actionable, meeting the 'clear context, no exclusions' level.
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.
11 tool updates
v0.1.0- First observed
create_food - First observed
create_keyword - First observed
create_recipe - First observed
create_unit - First observed
delete_recipe - First observed
get_recipe - First observed
search_foods - First observed
search_keywords - First observed
search_recipes - First observed
search_units - First observed
update_recipe
TDQS
Scored across 11 tools
Each tool targets a distinct resource and action: recipe CRUD is split into search/get/create/update/delete, and the supporting vocabularies (foods, units, keywords) each have separate search and create tools. There is no overlap or ambiguity between tool purposes.
Tool names follow a consistent verb_noun pattern, with search verbs using plural entities (search_recipes, search_foods) and other verbs using singular (get_recipe, create_food). This is a predictable and readable convention throughout the set.
11 tools is well-scoped for a recipe management server: full CRUD for recipes plus search/create for the three reference vocabularies. Each tool earns its place without redundancy or bloat.
Recipe lifecycle is complete (search, get, create, update, delete). Supporting entities have search and create but no update or delete, which is a minor gap since erroneous food/unit/keyword entries cannot be corrected via the API. Core recipe workflows are fully covered.
Maintenance
Related MCP Connectors
MCP server for progressive tool usage at any scale (see https://klavis.ai)
MCP server for AI dialogue using various LLM models via AceDataCloud
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Related MCP Servers
- AlicenseBqualityBmaintenanceMCP server for Mealie that exposes its REST API to manage recipes, meal plans, shopping lists, cookbooks, and taxonomy through natural language.75MIT
- FlicenseBqualityDmaintenanceMCP server for managing Tandoor recipes, meal plans, and shopping lists. Enables creation, retrieval, and management of recipes, meal plans, and shopping list items via natural language.1315-
- AlicenseNot gradedqualityFmaintenanceMCP server for MealMastery AI meal planning that enables users to manage meal plans, recipes, and grocery lists through natural language conversation with AI agents like Claude.51 npmMIT
- AlicenseNot gradedqualityAmaintenanceAn MCP server for Mealie that provides curated tools for recipes, meal plans, and cookbooks with compact responses, enabling natural language management of your Mealie instance.1MIT