Tandoor Recipe Mcp

io.github.Cliftonz由 GitHub 账号 Cliftonz 发布v3.4.0更新于 Nov 8, 2026

Recipes, meal plans, shopping lists and more for your local or self-hosted Tandoor instance.

已验证STDIO仅桌面位置与生活效率与工作流

概览

AI 生成的概览

将助手连接到自建的 Tandoor 实例,用于浏览食谱、安排餐食计划、管理购物清单、库存与食材。

可以做什么
  • 浏览、搜索、读取、创建、更新和导入食谱,完整档位还含步骤、配料、图片与关键词
  • 创建、读取、更新和删除餐食计划条目与餐食类型,并支持自动排餐与 iCal 导出
  • 管理购物清单与条目、勾选已购项目,并把食谱配料或单个食材加入清单
  • 管理库存位置与条目:新增、消耗、移动、更新和删除
  • 列出、创建、更新、合并和删除食材与单位,并解析配料文本
  • 可选开启烹饪记录、食谱书、AI 导入、导入导出、家庭空间与管理员等分组
适用场景
当你自建 Tandoor 服务器,并希望助手在不切换应用的情况下回答食谱问题、调整餐食计划、生成或勾选购物清单、维护储藏库存时使用。
运行要求
需要可访问的 Tandoor 实例,并在 TANDOOR_URL 中填写其基础地址;还需在 Tandoor 的 Settings、API Tokens 中创建 API 令牌并填入 TANDOOR_TOKEN。本地运行 npm 包需 Node 24 或更高版本,也可改用 Claude Desktop 扩展或 Docker 镜像。远程 HTTP 模式需要 HTTPS 公网地址和 64 位十六进制的 TANDOOR_MCP_SECRET_KEY。
安装前请注意
TANDOOR_TOKEN 是可写凭证,拥有该令牌在 Tandoor 中的全部权限;只读令牌则仅能读取。许多工具会创建、更新、合并或删除食谱、餐食计划、购物清单、库存和食材,删除位置或条目还会一并删除其日志记录。远程模式用 TANDOOR_MCP_SECRET_KEY 加密存储令牌,并把 oauth.json 保存在数据目录中。Tandoor 中的内容对助手而言是不可信输入。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Tandoor Recipe Mcp,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

Tandoor Recipes MCP

[npm version] [CI] [OpenSSF Scorecard] [License: MIT]

A Model Context Protocol (MCP) server that gives LLM agents full read/write access to a Tandoor Recipes instance: recipes, meal plans, ingredients, shopping lists, cook logs, nutrition, AI-powered URL/image imports, invite/access tokens, storages, sync, spaces, exports, imports, and more.

Turn "plan the week and make me a grocery list" into one prompt.

New in 3.1.0

  • File downloads (#10): download_recipe_file and download_file return a Tandoor file inline, or save it to TANDOOR_MCP_DOWNLOAD_ROOT with save: true. See Downloading files.
  • get_external_recipe_file_link works again. get_recipe_file_metadata is removed; use download_recipe_file instead.

New in 3.0.0

  • Per-section tool settings replace TANDOOR_MCP_PROFILE. One env var per section: tiered sections take off, simple, or full, and toggle sections take off or on. See Tool sections and Migration from 2.x.
  • Pantry and freezer inventory tools in the inventory section, plus a combined tandoor://pantry/on-hand resource.
  • Households: list, get, create, update, and delete households, and assign_household to move user-spaces between them.
  • Named shopping lists, such as one per store. Foods and shopping entries take shopping_lists.
  • Tandoor 2.6 API alignment. Requires Tandoor 2.6.0 or later.
  • HTTP transport fix (#12): the HTTP transport now answers every request, not only the first.
  • oxlint for linting.

307 tools in total; 46 are visible at startup with the default settings. The CHANGELOG has the full list of changes and breaking changes.

Why this server

  • Full Tandoor 2.6 API coverage. 307 typed tools, spanning every domain the Tandoor OpenAPI exposes.
  • Resources + prompts, not just tools. Read-only resources like tandoor://meal-plan/this-week and tandoor://pantry/on-hand, plus ready-made prompts (plan_week, grocery_list_for_plan, what_can_i_make_tonight). Most Tandoor MCP forks ship tools only.
  • Slim-by-default responses. A full recipe GET shrinks from ~20k characters to ~2k by stripping substitute trees, readonly timestamps, image URLs, etc. Pass format: "full" anywhere for the raw Tandoor response.
  • Per-section tool settings. One env var per section (recipes, meal plan, shopping, and so on) sets it. Tiered sections take off, simple, or full. Toggle sections take off or on. In stdio mode the LLM can turn a hidden section on mid-session with enable_tool_group. See Tool sections.
  • jq stash for large payloads. Responses larger than 5KB return a stash handle plus a schema summary; the LLM follows up with jq_query to project just the fields it needs.
  • 3-stage URL import with a real fallback. Tries Tandoor's native scraper first, then extracts schema.org JSON-LD from the page directly (works when Tandoor can't reach the URL or doesn't support the site), with an optional stub-on-failure escape hatch.
  • AI import from images / PDFs. Multipart upload to Tandoor's ai-import/ endpoint, saves the parsed recipe.
  • Local or remote. Stdio (default, for Claude Desktop / Claude Code), stateless HTTP (for out-of-Claude callers), or remote mode: a self-hosted server with OAuth sign-in that Claude and ChatGPT connect to as a custom connector.
  • Destructive-action safety layer. Every tree-shaped delete has a preview_* sibling that projects the cascade before it runs; every token-mint tool leads with an ESCALATION SURFACE warning.

Install

bash
npm install -g @cliftonz/tandoor-recipes-mcp

Or npx without installing:

bash
npx -y @cliftonz/tandoor-recipes-mcp

Node 24 or later is required (see engines.node in package.json).

Other ways to get it:

  • Claude Desktop extension: tandoor-recipes-mcp.mcpb from the latest GitHub release. It needs no Node install. See Claude Desktop, desktop extension.
  • Docker image: ghcr.io/cliftonz/tandoor-recipes-mcp, for linux/amd64 and linux/arm64, tagged with each version and latest. The image starts in remote mode by default.
  • MCP Registry: listed as io.github.Cliftonz/tandoor-recipe-mcp in the MCP Registry, with the npm package, the .mcpb file and the Docker image.
  • Claude Code plugin and Codex plugin: see Configure.

Configure

  1. In Tandoor: Settings, API Tokens, Create new token.
  2. Point your MCP client at the server.

Claude Code, one-shot plugin install

/plugin marketplace add Cliftonz/tandoor-recipe-mcp/plugin install tandoor-recipes-mcp@cliftonz-tandoor

From a terminal, the same two steps are:

bash
claude plugin marketplace add Cliftonz/tandoor-recipe-mcpclaude plugin install tandoor-recipes-mcp@cliftonz-tandoor

Claude Code prompts you for TANDOOR_URL and TANDOOR_TOKEN on install and stores the token securely. No mcpServers JSON editing needed.

Claude Desktop, desktop extension

Use the desktop extension for Claude Desktop Chat and Cowork. It bundles the server and runs on Claude Desktop's built-in Node, so you do not need to install Node.

  1. Download tandoor-recipes-mcp.mcpb from the latest GitHub release.
  2. Double-click the file. Or, in Claude Desktop, open Settings > Extensions > Advanced settings > Install Extension and select the file.
  3. Enter your Tandoor URL and API token. Claude Desktop stores the token securely.
  4. Optional: set the section settings you want.

Claude Desktop, plugin marketplace

Claude Desktop can also add the plugin:

  1. Open Customize > Plugins > Add > Add marketplace > Add from a repository.
  2. Enter Cliftonz/tandoor-recipe-mcp.
  3. Open Customize > Plugins > Discover, select the plugin, and select Add.

This path does not give you a working server in Chat or Cowork. Chat and Cowork do not prompt for plugin settings, so the plugin's Tandoor URL and API token settings stay empty. Chat ignores local MCP servers from plugins. Cowork loads them only when the session runs on your computer. Use the desktop extension or the manual config instead. Use the plugin from the Claude Code CLI.

Claude Desktop / Claude Code, manual config

Edit the Claude Desktop config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

For Claude Code, put the same mcpServers block in a project .mcp.json, or use claude mcp add.

json
{  "mcpServers": {    "tandoor": {      "command": "npx",      "args": ["-y", "@cliftonz/tandoor-recipes-mcp"],      "env": {        "TANDOOR_URL": "https://your-tandoor-instance.com",        "TANDOOR_TOKEN": "your-api-token"      }    }  }}

Quit and reopen Claude Desktop to load the change. This config runs the server with npx, so Node 24 or later must be on your PATH.

Codex

The ChatGPT desktop app, the Codex CLI, and the Codex IDE extension share one config file, ~/.codex/config.toml. Every Codex path below runs the server with npx, so Node 24 or later must be on your PATH.

Codex, settings
  1. In the app, open Settings > MCP servers > Add server.
  2. Choose STDIO.
  3. Set the command to npx.
  4. Add the arguments -y and @cliftonz/tandoor-recipes-mcp.
  5. Add the environment variables TANDOOR_URL and TANDOOR_TOKEN. Add any TANDOOR_MCP_* section settings you want.
  6. Select Save, then restart the app.

From a terminal, the same setup is:

bash
codex mcp add tandoor \  --env TANDOOR_URL=https://your-tandoor-instance.com \  --env TANDOOR_TOKEN=your-api-token \  -- npx -y @cliftonz/tandoor-recipes-mcp

codex mcp add writes this block to ~/.codex/config.toml. You can also add it to config.toml by hand:

toml
[mcp_servers.tandoor]command = "npx"args = ["-y", "@cliftonz/tandoor-recipes-mcp"]
[mcp_servers.tandoor.env]TANDOOR_URL = "https://your-tandoor-instance.com"TANDOOR_TOKEN = "your-api-token"
Codex, plugin
  1. Open the Plugins page and select Add > Add a marketplace.
  2. Enter the source Cliftonz/tandoor-recipe-mcp.
  3. Open the Tandoor plugin's details and select the plus button.
  4. Restart the app.

From a terminal:

bash
codex plugin marketplace add Cliftonz/tandoor-recipe-mcpcodex plugin add tandoor-recipes-mcp@cliftonz-tandoor

Codex does not prompt for settings on install, and you cannot override a plugin's environment. The plugin passes through only TANDOOR_URL and TANDOOR_TOKEN from the app's environment. An app you start from the Dock usually does not see exports in ~/.zshrc. On macOS, set the variables for your login session and then restart the app:

bash
launchctl setenv TANDOOR_URL https://your-tandoor-instance.comlaunchctl setenv TANDOOR_TOKEN your-api-token

launchctl setenv does not survive a reboot. It also exposes the token to every app you start in that session.

The Codex plugin does not pass through section settings. To change sections, use saved sections or the settings path.

Recommended Claude Code permissions

The repo ships a .claude/settings.json that auto-allows read tools and prompts for everything else. Copy these into your own project's .claude/settings.json to reduce permission friction:

json
{  "permissions": {    "allow": [      "mcp__tandoor__list_*",      "mcp__tandoor__get_*",      "mcp__tandoor__preview_*"    ],    "ask": [      "mcp__tandoor__create_*",      "mcp__tandoor__update_*",      "mcp__tandoor__delete_*",      "mcp__tandoor__merge_*",      "mcp__tandoor__switch_active_space",      "mcp__tandoor__create_access_token",      "mcp__tandoor__create_invite_link",      "mcp__tandoor__get_external_recipe_file_link"    ]  }}

Transport modes

The server ships two transports. Default is stdio; opt in to HTTP with an env var. HTTP also has a remote mode for Claude and ChatGPT custom connectors.

ModeWhen to useAuth
stdio (default)Claude Desktop, Claude Code, any MCP client that spawns the server as a subprocess.Inherited from parent process.
http (stateless)Out-of-Claude callers, local scripts, other agents on the same host.Optional bearer via TANDOOR_MCP_HTTP_TOKEN.
http with TANDOOR_MCP_PUBLIC_URL (remote mode)Claude (web, mobile, Desktop, Cowork, Code) and ChatGPT, through your own HTTPS URL.OAuth sign-in. Each user pastes their own Tandoor API token.

HTTP mode

bash
TANDOOR_URL=https://tandoor.example.com \TANDOOR_TOKEN=... \TANDOOR_MCP_TRANSPORT=http \TANDOOR_MCP_HTTP_PORT=3737 \TANDOOR_MCP_HTTP_TOKEN=some-long-random-string \npx -y @cliftonz/tandoor-recipes-mcp

The server listens on http://127.0.0.1:${TANDOOR_MCP_HTTP_PORT:-3737}/mcp (POST only for JSON-RPC requests). Any other method on that path gets a 405 with Allow: POST and a JSON-RPC error body. Auth is checked first, so a request without a valid token gets a 401 instead. The server binds to 127.0.0.1 unless you set TANDOOR_MCP_HTTP_HOST. To reach it from another host, put a reverse proxy in front of it, or use remote mode.

If TANDOOR_MCP_HTTP_TOKEN is set, every request must include Authorization: Bearer <token>. Missing or wrong tokens get a 401 with WWW-Authenticate: Bearer.

HTTP-mode constraints: the jq stash and dynamic tool gating both assume a single long-lived session. In HTTP mode both are auto-disabled: enable_tool_group and disable_tool_group throw, and every tool response is inlined rather than stashed. The section settings fix the tool list at startup, so set every section you need before you start the server.

Remote mode

Remote mode runs the server on your own host, next to your Tandoor, with a built-in OAuth sign-in. You add it to Claude or ChatGPT as a custom connector, by its HTTPS URL. It works in Claude on the web, mobile, Desktop chat, Cowork and Claude Code, and in ChatGPT. Each person signs in by pasting their own Tandoor API token, so each person acts in Tandoor as themselves.

Why you host it yourself, and why it is not in a connector directory: every Tandoor is a separate, self-hosted server, so there is no single URL a directory listing could point to. A shared hosted copy would also have to hold everyone's Tandoor tokens. In remote mode the tokens stay on your server, encrypted with your key.

How a connection works:

  1. The app (Claude or ChatGPT) calls your public URL and gets a 401 that points to the server's OAuth metadata.
  2. The app identifies itself with its published client document, or registers itself automatically.
  3. The app opens the sign-in page in your browser. You paste a Tandoor API token and select Approve.
  4. The server checks the token against Tandoor, stores it encrypted, and gives the app its own tokens. The app never sees your Tandoor token.

Remote mode settings

Remote mode turns on when TANDOOR_MCP_PUBLIC_URL is set. These settings apply to remote mode, and the HTTP ones also apply to plain HTTP mode.

VariableDefaultRules
TANDOOR_URLnone (required)Your Tandoor. It only has to be reachable from the server, so an internal address such as http://web_recipes:8080 works. The server uses only the scheme, host and port.
TANDOOR_MCP_TANDOOR_PUBLIC_URLTANDOOR_URLThe address users open Tandoor at, such as https://recipes.example.com. The sign-in page shows it and links to its /settings/api page for creating a token. Set it when TANDOOR_URL is an internal address. The server still calls Tandoor only at TANDOOR_URL. It uses only the scheme, host and port, and adds https:// when the scheme is missing. Ignored outside remote mode.
TANDOOR_MCP_TRANSPORTstdio (http in the Docker image)Must be http. The server refuses to start in remote mode otherwise.
TANDOOR_MCP_PUBLIC_URLnoneThe HTTPS URL clients connect to, such as https://mcp.example.com/mcp. Must use https. Its path must equal TANDOOR_MCP_HTTP_PATH. No query string, fragment, username or password.
TANDOOR_MCP_SECRET_KEYnone (required in remote mode)Exactly 64 hex characters (32 bytes). Generate one with openssl rand -hex 32. It encrypts the stored Tandoor tokens and signs the sign-in form.
TANDOOR_MCP_DATA_DIR$XDG_DATA_HOME/tandoor-mcp, else ~/.local/share/tandoor-mcp (/data in the Docker image)The folder for oauth.json. Must be an absolute path; a relative path is ignored.
TANDOOR_MCP_TRUST_PROXYnone (no proxy trusted)Set it when a reverse proxy or tunnel sits in front. A hop count such as 1, or a comma-separated list of IP addresses and CIDR ranges. A /0 range is refused. The server uses it to find each visitor's IP address for rate limits.
TANDOOR_MCP_HTTP_HOST127.0.0.1 (0.0.0.0 in the Docker image)The address the server listens on.
TANDOOR_MCP_HTTP_PORT3737The port the server listens on.
TANDOOR_MCP_HTTP_PATH/mcpThe MCP endpoint path. It cannot be, or start with, /authorize, /token, /register, /revoke, /consent or /.well-known.
TANDOOR_MCP_HTTP_DRAIN_MS10000On SIGTERM or SIGINT, how long the server waits for requests in progress. New requests get a 503 meanwhile.
TANDOOR_MCP_HTTP_MAX_BODY_BYTES5242880Larger request bodies get a 413.
TANDOOR_MCP_HTTP_MAX_CONNS128The most open connections the server accepts.

Header and request timeouts are fixed at 10 and 30 seconds.

Rules and behavior in remote mode:

  • TANDOOR_TOKEN is refused. The server will not start while it is set. Each user brings their own token.
  • TANDOOR_MCP_HTTP_TOKEN is refused. It belongs to the shared-password HTTP mode and cannot be combined with remote mode.
  • Tool sections apply to every connected user. The section settings are read once at startup, and enable_tool_group is off in HTTP mode, so everyone sees the same tools.
  • file_path uploads are refused. The server's disk is not the user's disk. Use file_url instead. Saving downloads with save: true is also stdio only.
  • A read-only Tandoor token works for reads only. Write tools return Tandoor's error. The connection stays.
  • Host and Origin are checked. A request must carry the public URL's host in its Host header, or localhost, 127.0.0.1 or [::1]. Anything else gets a 403, logged as host_rejected. A browser Origin header on the MCP path must equal the public URL's origin, or the request gets a 403, logged as origin_rejected.
  • Responses are plain JSON. The server does not hold event streams open, so proxies need no streaming or buffering settings. GET on the MCP path returns 405.

Run with Docker

The image is ghcr.io/cliftonz/tandoor-recipes-mcp. It defaults to TANDOOR_MCP_TRANSPORT=http, TANDOOR_MCP_HTTP_HOST=0.0.0.0 and TANDOOR_MCP_DATA_DIR=/data, and runs as the node user.

  1. Generate a secret key:

    bash
    openssl rand -hex 32
  2. Copy the tandoor-mcp service and the tandoor-mcp-data volume from docker-compose.example.yml into your Tandoor compose file, so both services share a network.

  3. Set the environment:

    • TANDOOR_URL: your Tandoor service on the internal network. The example uses http://web_recipes:8080; replace web_recipes with the name of your Tandoor service.
    • TANDOOR_MCP_TANDOOR_PUBLIC_URL: the address users open Tandoor at, such as https://recipes.example.com.
    • TANDOOR_MCP_PUBLIC_URL: your public HTTPS URL, ending in /mcp.
    • TANDOOR_MCP_SECRET_KEY: the key from step 1.
    • TANDOOR_MCP_TRUST_PROXY: "1" when one proxy or tunnel sits in front, as in the example.
  4. Start it:

    bash
    docker compose up -d tandoor-mcp

Notes on the example file:

  • The /data volume holds oauth.json. Keep the volume across upgrades, or everyone has to reconnect.
  • The port is published on 127.0.0.1 only (127.0.0.1:3737:3737), so only a proxy or tunnel on the same host can reach it. Remove the ports: entry if your proxy reaches the container over the Docker network.
  • Keep TANDOOR_MCP_HTTP_HOST=0.0.0.0 inside the container. Binding a specific address inside the container is not supported: the image's health check calls 127.0.0.1, so the container reports unhealthy. Limit exposure with the port mapping instead.
  • stop_grace_period: 15s is longer than the 10-second drain, so requests in progress can finish on docker compose down.

The sign-in page shows your Tandoor address and links to its /settings/api page for creating a token. With an internal TANDOOR_URL such as http://web_recipes:8080, set TANDOOR_MCP_TANDOOR_PUBLIC_URL to the address users open Tandoor at, so the link opens from a browser.

Without Docker, run the npm package with the same settings:

bash
TANDOOR_URL=https://tandoor.example.com \TANDOOR_MCP_TRANSPORT=http \TANDOOR_MCP_PUBLIC_URL=https://mcp.example.com/mcp \TANDOOR_MCP_SECRET_KEY=<64 hex characters> \TANDOOR_MCP_TRUST_PROXY=1 \npx -y @cliftonz/tandoor-recipes-mcp

Expose it over HTTPS

Claude and ChatGPT need a public HTTPS URL. Put a reverse proxy or a tunnel in front of the server. Whatever you use must:

  • Forward the whole host, not only /mcp. The OAuth routes live at the root.
  • Pass the original Host header through.
  • Set X-Forwarded-For, and you set TANDOOR_MCP_TRUST_PROXY to match.
Caddy
caddyfile
mcp.example.com {	reverse_proxy 127.0.0.1:3737}

Caddy passes Host through unchanged and sets X-Forwarded-For. Set TANDOOR_MCP_TRUST_PROXY=1. If Caddy runs in the same compose network, use reverse_proxy tandoor-mcp:3737.

nginx
nginx
server {    listen 443 ssl;    server_name mcp.example.com;    # ssl_certificate and ssl_certificate_key go here
    location / {        proxy_pass http://127.0.0.1:3737;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    }}

proxy_set_header Host $host; is required. Without it, nginx sends the upstream name as Host, and the server answers 403 unless that name is localhost or a loopback address. The log line host_rejected includes a hint about this. Responses are plain JSON, so no SSE or buffering settings are needed. Set TANDOOR_MCP_TRUST_PROXY=1.

Cloudflare Tunnel

A tunnel needs no open port. The example compose file has an optional cloudflared service.

  1. In the Cloudflare dashboard, go to Networking > Tunnels and select Create a tunnel. Copy the tunnel token.
  2. Uncomment the cloudflared service and set TUNNEL_TOKEN to the token.
  3. In the tunnel's Routes tab, select Add route > Published application. Choose your subdomain and domain, and set Service URL to http://tandoor-mcp:3737.
  4. Leave HTTP Host Header empty, so the server sees your public hostname.

Set TANDOOR_MCP_TRUST_PROXY=1. You can remove the ports: entry from tandoor-mcp.

Tailscale Funnel

On the host where port 3737 is published on 127.0.0.1:

bash
tailscale funnel --bg 3737

The public URL is https://<device-name>.<tailnet-name>.ts.net, so set TANDOOR_MCP_PUBLIC_URL to https://<device-name>.<tailnet-name>.ts.net/mcp. Funnel keeps the Host header and sets X-Forwarded-For. Set TANDOOR_MCP_TRUST_PROXY=1. tailscale funnel reset turns it off.

Access gateways

Warning: If you put an access gateway in front, such as Cloudflare Access, Authelia or another forward-auth proxy, it must let these routes through without its own login. Otherwise Claude and ChatGPT cannot connect.

  • POST /mcp (your TANDOOR_MCP_HTTP_PATH)
  • GET /.well-known/oauth-protected-resource/mcp
  • GET /.well-known/oauth-authorization-server
  • POST /register
  • POST /token
  • POST /revoke
  • GET and POST /authorize
  • POST /consent

/authorize and /consent open in the user's own browser. The others are called by the app's servers. The simplest rule is to bypass the gateway for /mcp, /register, /token, /revoke, /authorize, /consent and /.well-known/*.

Connect a client

Use your public URL, for example https://mcp.example.com/mcp.

Claude

On a Free, Pro, Max or Ultra plan:

  1. Go to Customize > Connectors.
  2. Click Add custom connector.
  3. Enter the URL.
  4. If the dialog asks for the OAuth client, choose Use Claude's published identity or Register automatically. The server supports both. Do not enter a client ID or secret.
  5. Click Add. When you connect, the sign-in page opens in your browser.

On a Team or Enterprise plan, an Owner adds it under Organization settings > Connectors: select Add, then Custom, enter the URL, and click Add. Each member then finds it under Customize > Connectors and clicks Connect.

You can also share a link that opens the dialog with the name and URL filled in. Percent-encode the URL:

text
https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Tandoor&connectorUrl=https%3A%2F%2Fmcp.example.com%2Fmcp

Owners can use the same parameters on https://claude.ai/admin-settings/connectors. The user still reviews and confirms before anything is added.

Claude Code
bash
claude mcp add --transport http tandoor https://mcp.example.com/mcp

Then run /mcp in Claude Code and follow the sign-in steps in your browser.

ChatGPT
  1. Go to chatgpt.com/plugins.
  2. Select the plus button, then Add custom MCP server.
  3. Enter a name and description.
  4. Under Connection, enter the URL, including /mcp.
  5. Configure authentication. The server uses OAuth and supports both ways ChatGPT registers itself: a client ID metadata document and dynamic client registration. Review the risk warning and select I understand and want to continue.
  6. Select Create as a plugin.

Your ChatGPT account or workspace policies decide whether you can add custom MCP servers.

The sign-in page

The page is titled Connect to Tandoor. It names the app that wants access and the Tandoor URL, and shows where you return after you approve. Paste a Tandoor API token into Tandoor API token and select Approve, or Deny.

Create the token on your Tandoor's API settings page, /settings/api. The token decides what the app can do: a read-write token allows changes, a read-only token allows reads only.

If Tandoor rejects the token, the page says so and you can try again. The form expires after 10 minutes, and each IP address gets 10 attempts a minute.

Operate it

  • Back up oauth.json and the secret key separately. oauth.json in the data folder holds the registered apps, the connections, the encrypted Tandoor tokens and hashes of the tokens the server issued. Anyone with both the file and the key can read the stored Tandoor tokens.
  • Revoke everyone: stop the server, delete oauth.json, and start it again. Every user has to reconnect.
  • Revoke one user's access: delete their API token in Tandoor. Their tool calls then fail in Tandoor.
  • Rotate the secret key: the server refuses to start when the key cannot decrypt oauth.json (cannot read OAuth store). Rotation means stopping the server, deleting oauth.json, setting the new key and starting again. Every user has to reconnect.
  • Connections end on their own after 30 days without use, or when Tandoor rejects the stored token with a 401. In the second case the server ends every connection that uses that token, and the tool call returns "Tandoor rejected your token. Reconnect this connector to sign in again." along with a sign-in challenge for the app. App registrations that never finish signing in are removed after 24 hours.
  • Run one server process per data folder. Each process keeps oauth.json in memory and rewrites it on every change, so two processes overwrite each other.
  • Health check: GET /healthz returns {"status":"ok"} without auth. The Docker image checks it every 30 seconds.
  • Logs go to standard error (docker compose logs tandoor-mcp). The startup line shows the public URL, auth=oauth, the Tandoor URL and version, and the sections. Request events look like [tandoor-mcp] http reqId=... level=warn event=host_rejected .... Other events include origin_rejected, forwarded_header_untrusted (a proxy sent X-Forwarded-For but TANDOOR_MCP_TRUST_PROXY is not set), tandoor_check_failed, cimd_fetch_failed, payload_too_large and shutdown. PRIVACY.md lists what the logs contain.
  • Rate limits are per IP address: failed access tokens, sign-in form submissions, and the OAuth routes. Behind a proxy, set TANDOOR_MCP_TRUST_PROXY, or every visitor shares one limit.

Firewall allowlisting

You can limit who reaches the server-to-server routes (/mcp, /.well-known/*, /register, /token, /revoke):

Do not limit /authorize and /consent this way. They come from each user's own browser. Claude Code and the MCP Inspector also run on the user's computer, so their requests come from the user's address.

The server itself makes outbound requests to your Tandoor, to the app's client document (such as one on claude.ai or chatgpt.com) when an app uses its published identity, and to web addresses users import from.

Test it

Check the health endpoint and the metadata:

bash
curl https://mcp.example.com/healthzcurl https://mcp.example.com/.well-known/oauth-protected-resource/mcp

Then connect with the MCP Inspector. It runs the sign-in flow in your browser:

bash
npx @modelcontextprotocol/inspector --server-url https://mcp.example.com/mcp --transport http

Remote mode security

  • The sign-in routes are reachable from the internet. Anyone who can reach the URL can start signing in, but only a valid Tandoor token completes it.
  • The Tandoor token is the real credential. Whoever signs in with a token gets that token's full Tandoor access.
  • The secret key protects the stored tokens. The server stores only hashes of the tokens it issues.
  • Removing a connection in Claude or ChatGPT does not delete the Tandoor token. Delete it in Tandoor too.
  • Tandoor content is untrusted input to the assistant.

SECURITY.md has the full threat model and how to report a problem.

Environment variables

VarRequiredDefaultPurpose
TANDOOR_URLyesnoneBase URL of your Tandoor instance.
TANDOOR_TOKENyes, except in remote modenoneAPI token with write access. Remote mode refuses to start when it is set.
TANDOOR_MCP_TRANSPORTnostdiostdio or http.
TANDOOR_MCP_HTTP_PORTno3737HTTP-mode port.
TANDOOR_MCP_HTTP_TOKENnononeHTTP-mode bearer token. If unset, HTTP transport runs without auth. Not allowed in remote mode.
TANDOOR_MCP_<SECTION>noper sectionOne variable per tool section, such as TANDOOR_MCP_RECIPES. See Tool sections for every variable, its values, and its default.
TANDOOR_MCP_INCLUDE_ONLYnononeComma-separated allowlist (glob * supported). E.g. list_*,get_*,create_meal_plan.
TANDOOR_MCP_EXCLUDEnononeComma-separated denylist. E.g. merge_*,delete_* to hide destructive tools.
TANDOOR_MCP_LOGnononeStderr trace mode. request, response, error, all, or comma list. Bearer token redacted.
TANDOOR_MCP_STASH_ENABLEDno1Park large tool results in an in-memory cache and return a {stashed:true, handle, shape} summary; the LLM calls jq_query to extract subsets. Set 0 to disable. Auto-disabled in HTTP mode.
TANDOOR_MCP_STASH_THRESHOLDno25000Stash payloads strictly larger than this many bytes.
TANDOOR_MCP_STASH_MAX_ENTRIESno32LRU cap on stashed handles.
TANDOOR_MCP_STASH_MAX_BYTESno32000000Total-bytes cap on the stash; LRU eviction kicks in past this budget.
TANDOOR_MCP_STASH_TTL_MSno600000Per-entry TTL in milliseconds (default 10 minutes).
TANDOOR_MCP_DOWNLOAD_ROOTnononeExisting folder that save: true writes downloads into. Stdio mode only. See Downloading files.

The other HTTP settings and the remote-mode settings are in Remote mode settings.

INCLUDE_ONLY and EXCLUDE run before the section settings and give finer control ("give me only read tools" or "hide destructive ops from this agent").

Section values can also come from saved sections.

The Claude plugin and the desktop extension pass their settings as CLAUDE_PLUGIN_OPTION_<KEY>, such as CLAUDE_PLUGIN_OPTION_TANDOOR_URL or CLAUDE_PLUGIN_OPTION_RECIPES. A non-empty plugin setting wins over the variable it replaces. An empty one falls back to that variable.

Downloading files

download_recipe_file({recipe_id}) returns the file Tandoor stored for a recipe synced from a Storage backend (Dropbox, Nextcloud, or Local). It is in the recipes section's full tier. download_file({file_id}) returns a user file uploaded to Tandoor. It is in the admin section.

Files up to 5 MiB come back inline, after a text block with {filename, mime, size}. Images (PNG, JPEG, GIF, and WebP) come back as an MCP image block. Other files come back as an embedded resource block. A larger file returns an error that tells the model to retry with save: true.

To save a file to disk instead, set TANDOOR_MCP_DOWNLOAD_ROOT to an existing folder and call the tool with save: true. The tool streams the file into that folder and returns {path, size, mime}. Saving works over stdio only, because over HTTP the folder is on the server machine. The tool removes unsafe characters from the filename. It never overwrites a file: if the name is taken, it adds (2), (3), and so on.

Tandoor compatibility

Minimum: Tandoor server 2.6.0.

The 3.0.0 tools target the endpoints in the vendored Tandoor 2.6.15 OpenAPI spec (see tandoor api specification.yaml, info.version header). Older 2.x instances return 404 on the tools for newer endpoints, such as named shopping lists.

To check your instance version from an LLM session:

> Ask: what version of Tandoor am I connected to?> Claude calls: get_version> Returns: { mcp: "3.0.0", protocol: "...", tandoor: "2.6.15" }

The server also probes /api/server-settings/current/ at boot and writes a WARNING to stderr if the connected instance is older than 2.6.0.

Migration from 2.x

3.0.0 removes TANDOOR_MCP_PROFILE and the old tool-group names. The per-section settings in Tool sections replace both.

  • TANDOOR_MCP_PROFILE is ignored. When it has a value, the server prints one stderr warning.
  • TANDOOR_MCP_PROFILE=full: set every tiered section to full and every toggle section to on.
  • Claude Code plugin users: the plugin used to default to full (every tool). It now starts with the section defaults. To get more tools back, set recipes, meal_plan, shopping, inventory, foods, cooklog, recipe_books, ai, import_export, household_space, and admin in the plugin settings.
  • Old group names are gone, with no aliases. Use the section names in enable_tool_group. The always-on group is now always (was core). The CHANGELOG lists every removed name.

Migration from 1.4.0

  • Stdio users: nothing required. No breaking changes, no config edits.
  • HTTP users: opt in with TANDOOR_MCP_TRANSPORT=http. Set TANDOOR_MCP_HTTP_TOKEN and expect the jq stash + dynamic gating to be off (both are stdio-session assumptions).

Tool sections

Each section has one env var. The server trims each value and ignores case. An unset, empty, or whitespace-only variable means the saved setting for this TANDOOR_URL if there is one, otherwise the default. If any section variable is invalid, the server stops at startup with one error that names every invalid variable and its valid values.

Tiered sections take off, simple, or full and default to simple. full includes everything in simple.

SectionEnv varsimplefull adds
recipesTANDOOR_MCP_RECIPESBrowse, search, get, related recipes, create, update, import from URL, read keywords.Delete, steps, ingredients, images, batch update, keyword writes, custom filters, file links, share links.
meal-planTANDOOR_MCP_MEAL_PLANMeal plan create, read, update, and delete. Read meal types.Auto-plan, bulk create, meal type writes, iCal export.
shoppingTANDOOR_MCP_SHOPPINGList the named shopping lists. Shopping entry create, read, update, delete, and check. bulk_update_shopping_entries checks entries and adds, removes, sets, or clears their lists in one call. Add a recipe's ingredients or a single food as shopping entries.Named shopping list get, create, update, and delete, and their delete previews. Deleting a list removes only its links; the foods, entries, and supermarkets stay. Shopping-list-recipe create, read, update, and delete. Supermarkets, categories, and category relations.
inventoryTANDOOR_MCP_INVENTORYList locations, list entries, add, consume, and move. add_inventory_item always creates a new entry. consume_inventory_item stops at 0 and warns when you consume more than the entry holds. move_inventory_item moves a whole entry.Location create, get, update, and delete. Entry get, update, and delete. The read-only inventory log. Delete previews. Deleting a location also deletes its entries and the log rows of every entry that was ever in it, including entries moved elsewhere since. Deleting an entry deletes its log rows.
foodsTANDOOR_MCP_FOODSList, get, create, and update foods, including their shopping lists. Parse an ingredient. List units.Delete, merge, move, FDC lookup and search, batch update (including shopping list changes), unit CRUD, unit merge, food inherit fields, unit conversions, properties, and property types.

Toggle sections take off or on and default to off.

SectionEnv varContents
cooklogTANDOOR_MCP_COOKLOGCook-log entries: when and how often a recipe was made.
recipe-booksTANDOOR_MCP_RECIPE_BOOKSRecipe books (collections) and their entries.
aiTANDOOR_MCP_AIAI providers, AI recipe import from images, AI step sort and property estimation, AI logs.
import-exportTANDOOR_MCP_IMPORT_EXPORTRecipe import and export, import queues, bookmarklet and open-data imports, their logs.
household-spaceTANDOOR_MCP_HOUSEHOLD_SPACESpaces, memberships, households and household assignment, users, groups, invite links, user preferences. Only the space owner can use the household tools and update_user_space; Tandoor answers space admins with a 403.
adminTANDOOR_MCP_ADMINServer settings, access tokens, storages, syncs, connectors, automations, user files, view logs, search settings, localization.

A delete preview (preview_*_delete_*) belongs to the same section and tier as its delete tool.

These tools are always on, whatever the section settings: list_tool_groups, enable_tool_group, disable_tool_group, get_version, jq_query, jq_stash_stats, get_guard_stats, and get_current_space. Every tandoor:// resource and every prompt is also always available. list_tool_groups reports them as the always group.

The server applies the settings in this order:

  1. TANDOOR_MCP_INCLUDE_ONLY and TANDOOR_MCP_EXCLUDE filter the tool list. EXCLUDE is a hard block: nothing can enable an excluded tool.
  2. A section env var that is set and not empty or whitespace-only decides that section.
  3. For a section whose env var is unset, empty, or whitespace-only, the saved section for this TANDOOR_URL decides.
  4. Every other section takes its default.

The result decides which of the remaining tools are visible at startup. An off section is hidden.

In stdio mode the LLM can change sections during a session:

  • enable_tool_group({group, tier?}) widens a section: off to simple or full, or simple to full. It never narrows a section. For a tiered section, tier is simple or full and defaults to full. Passing tier for an on/off section is an error.
  • disable_tool_group({group}) sets a section to off. It cannot disable always.
  • enable_tool_group({group: "always"}) succeeds and changes nothing.

If you see only some tools in Claude Desktop or Codex, ask the model to enable a section, for example "enable the cooklog section". If the new tools do not appear, restart the app. If the change was saved, it applies on the next start.

A section with no registered tools does not count as a section at runtime. That covers any section that INCLUDE_ONLY or EXCLUDE empties. enable_tool_group and disable_tool_group reject it as unknown, and list_tool_groups and the server instructions leave it out.

From an active session, call list_tool_groups for a live listing of sections and their tools.

Saved sections

When enable_tool_group or disable_tool_group changes a section, it saves the change, so the change survives a restart. disable_tool_group saves an explicit "off". The server saves sections per Tandoor instance, keyed by TANDOOR_URL. The key is the URL origin only: the scheme, host, and port, lowercased, with a default port dropped. The server ignores any path, query, or trailing slash, so https://host/tandoor and https://host share one entry. These are the same rules the server applies to TANDOOR_URL, including adding https:// to a URL without a scheme.

The server matches keys in the file by the same rules, so a key you write by hand, such as "https://Tandoor.example.com/tandoor", still matches. A save updates that entry under its key as written.

The file is $XDG_CONFIG_HOME/tandoor-mcp/sections.json. When XDG_CONFIG_HOME is unset or is not an absolute path, the file is ~/.config/tandoor-mcp/sections.json. The path is the same on every OS.

json
{"https://tandoor.example.com": {"recipes": "full", "meal-plan": "full", "cooklog": "on"}}

Inside each entry, the keys are the section names from the tables above: recipes, meal-plan, shopping, inventory, foods, cooklog, recipe-books, ai, import-export, household-space, and admin. Like the env vars, saved values ignore case and surrounding spaces, so "FULL" works.

  • HTTP mode reads saved sections at startup but never changes them.
  • A missing file is normal and gives no warning.
  • If the file exists but cannot be read, is not valid JSON, has any invalid section value, or has two or more keys that match the same instance, the server ignores the whole file and starts from the env settings. It prints a warning to stderr that names the file.
  • If you save a change while the file is invalid, the server replaces the file with a new one that holds only that change.
  • If a save succeeds and the section's env var is set, the tool result notes that the env var will override the saved value on restart.
  • If saving fails, the change still applies for the current session. The tool result says that the change was not saved.
  • The server ignores saved entries for sections that are filtered out or no longer exist, and keeps them in the file.
  • The server ignores a key it cannot parse as a URL, and keeps it in the file.

Example conversations

Plan the week + generate a grocery list:

You: "Plan dinners for the week biased toward vegetarian recipes I've rated 4+, using what's on-hand where possible. Then give me the shopping list grouped by aisle."

Claude:

  1. Reads tandoor://pantry/on-hand and tandoor://meal-types
  2. Calls list_recipes({keywords_or: [<vegetarian>], rating_gte: 4, sort_order: '-rating'})
  3. Calls create_meal_plan for each day of the week
  4. Calls add_recipe_to_shopping_list for each plan
  5. Reads tandoor://shopping-list/active and groups by supermarket category

Import a recipe from a URL and schedule it:

You: "Import https://example.com/lasagna and schedule it for Friday dinner, 4 servings."

Claude:

  1. import_recipe_from_url({url: "..."}), JSON-LD extraction handles sites Tandoor doesn't know
  2. create_meal_plan({recipe_id, meal_type_id: <Dinner>, from_date: "2026-04-24", servings: 4})

Preview a destructive delete before running it:

You: "Delete the 'Test Keyword' keyword."

Claude:

  1. preview_keyword_delete_protecting({id: <k>}) to see what recipes still reference it
  2. If safe: delete_keyword({id: <k>})
  3. If not safe: reports the reference list back to you before proceeding

Destructive tools

Destructive tools (delete_*, merge_*, switch_active_space, create_access_token, create_invite_link) are irreversible or expose escalation surface. Their descriptions instruct the model to:

  • For deletes on tree-shaped resources, call the matching preview_*_delete_{cascading,nulling,protecting} first.
  • For merges, verify numrecipe counts and base-unit compatibility first.
  • For token / invite creation, warn the user before generating the call. Every such description leads with an explicit ESCALATION SURFACE line.

Operators who want them gone entirely can set TANDOOR_MCP_EXCLUDE="delete_*,merge_*,switch_active_space,create_access_token,create_invite_link". The permission config in Recommended Claude Code permissions makes Claude Code ask before any destructive call.

Gotchas

FDC lookups rate-limit after 30 requests/hour

food_fdc_lookup delegates to Tandoor, which calls the USDA FoodData Central API. Out of the box Tandoor uses USDA's public DEMO_KEY, capped at 30 requests/hour per IP. Bulk-enriching a grocery list hits that ceiling fast and you'll see 429s on Tandoor's side.

Fix (takes 2 minutes):

  1. Register a free personal key at api.data.gov/signup; no approval needed, instant.

  2. Set it on the Tandoor container, not the MCP server:

    yaml
    # docker-compose.yml, under the tandoor serviceenvironment:  - FDC_API_KEY=<your-key>

    Kubernetes / Helm: add FDC_API_KEY to the Tandoor deployment's env or a secret. Restart the Tandoor pod.

  3. The limit jumps to 1,000 requests/hour; plenty for bulk enrichment.

If FDC returns 404 for a food, the fdc_id you passed doesn't exist in USDA's database; try food_ai_properties as a fallback (uses Tandoor's configured AI provider instead of USDA).

Resources

URIPurpose
tandoor://meal-plan/this-weekMonday to Sunday meal plan entries, slimmed to {id, date, meal_type, recipe_name, servings, note}
tandoor://pantry/on-handInventory entries by location, plus on-hand foods with no entry; at most 500 of each
tandoor://shopping-list/activeUnchecked, recent shopping entries
tandoor://meal-typesBreakfast/Lunch/Dinner/etc. with IDs

Prompts

NamePurpose
plan_weekPlan 7 days of dinners, optionally pantry-biased and keyword-filtered
grocery_list_for_planTurn this week's meal plan into a shopping list grouped by aisle
what_can_i_make_tonightFind makeable recipes at most N minutes from on-hand foods
import_and_planImport a URL recipe and schedule it in one shot

Development

bash
git clone https://github.com/Cliftonz/tandoor-recipe-mcp.gitcd tandoor-recipe-mcpnpm installnpm run buildnpm test

Tests use Vitest and mock fetch; no live Tandoor instance needed for the default run.

End-to-end tests

An opt-in E2E suite exercises the full write path against a real Tandoor instance. Run it only against a throwaway/isolated Tandoor space; the suite creates and deletes real rows.

bash
TANDOOR_URL=https://your-tandoor.example \TANDOOR_TOKEN=... \npm run test:e2e
VarEffect
TANDOOR_E2E_KEEP=1Skip cleanup; leave created resources for inspection
TANDOOR_E2E_SKIP_IMPORT=1Skip the URL-import step (offline runners)
TANDOOR_E2E_IMPORT_URL=<url>Override the default recipe URL
TANDOOR_E2E_AI_PROVIDER=<id>Run the AI-import step against this provider

Contributing

See CONTRIBUTING.md for how pull requests are reviewed, credited, and merged.

Support, security and privacy

  • SUPPORT.md: where to ask questions and what to include in a bug report.
  • SECURITY.md: supported versions, how to report a vulnerability, and the remote-mode threat model.
  • PRIVACY.md: what the server stores, where data goes, and what it logs.

Security posture

This server treats the configured Tandoor instance as trusted enough to read+write on your behalf; you provide the bearer token. It does NOT treat the instance as fully trusted for content:

  • Tool responses from create_meal_plan / update_meal_plan are slimmed. Only ids and structural fields flow back to the LLM. User-visible names (recipe.name, meal_type.name, keyword names) are omitted so a hostile or compromised Tandoor instance can't inject model-steering text into Claude's context via these paths. Call get_recipe or list_meal_types if you need the human-readable form.
  • Storage token / password and AI-provider api_key are redacted in slim reads. format: "full" still surfaces them for operator inspection. Default (LLM-facing) responses never leak credentials into the model's context.
  • Keyword payloads are capped at 50 entries and projected to {id, name} before being forwarded into writes; stops amplification and mass-assignment exposure.
  • Every mint-a-token / grant-access tool description leads with an ESCALATION SURFACE warning. create_access_token, update_access_token, create_invite_link, update_invite_link, and authenticate all warn in-context that the returned token bypasses this MCP's own bearer redaction.
  • Bearer tokens are redacted from every error message and log line.
  • HTTP transport binds to 127.0.0.1 by default. TANDOOR_MCP_HTTP_HOST changes the address. Each request gets a fresh MCP session, so no session state builds up across requests.
  • Remote mode uses OAuth with per-user Tandoor tokens. Stored Tandoor tokens are encrypted with TANDOOR_MCP_SECRET_KEY, issued tokens are stored only as hashes, and Host and Origin headers are checked. See Remote mode security and SECURITY.md.

If your Tandoor deployment is single-tenant and you own every recipe, the posture is conservative for your case. If you share a Tandoor instance with untrusted users, or run one exposed to the public internet with anonymous sharing, the hardening above matters.

License

MIT

来源:README.md,提交 67f43b9

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v3.4.0最新Nov 8, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分