cookidough-mcp
AGENTS.md9 starsChanged 5 months agoMITView on GitHub
Instructions for AI coding agents working in this repository. Agent-agnostic by design — Claude Code, Cursor, Aider, OpenAI Codex CLI, and any other tool that consumes AGENTS.md should follow what is described here. cookidough-mcp is a Model Context Protocol (MCP) server that exposes the Thermomix® Cookidoo® platform to LLM clients (Claude Desktop, Claude Code, any MCP-aware tool). It consolidates four predecessor projects into a single Python 3.12+ codebase (tested through 3.14) built on the MCP Python SDK (MCPServer, mcp 2.x)…
- Reads credentials
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- CLAUDE.mdCLAUDE.md
The file
We split this AGENTS.md into the rules it gives an agent
12 rules. About 0 are common, 9 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 53
Run ./check.sh before committing.
- Distinctive: line 58
Language: All code, identifiers, docstrings, commit messages, and documentation are in English.
- Distinctive: line 69
Comments: Default to none. Only add a comment when the why is non-obvious (a hidden constraint, a workaround for an upstream quirk).
- Distinctive: line 71
Never document what the code does — sprechende identifiers cover that.
- Distinctive: line 72
Docstrings: Only on public modules, classes, and tool functions.
- Distinctive: line 89
If you add a method to the protocol, the guard breaks at mypy time — update the fake in the same PR.
- Distinctive: line 91
Test the behaviour, not the implementation.
- Distinctive: line 94
If you need a tool function inside a test, route through there.
- Very specific: line 185
All upstream error bodies pass through _redact_error_body before being surfaced.
- Very specific: line 192
Every HTTP request enforces a per-request ClientTimeout (30 s by default, see constants.HTTP_TIMEOUT_SECONDS).
- Distinctive: line 230
Update README.md and this file if you change build commands, env vars, or registered tool names.
- Distinctive: line 232
Keep tool names stable — they are part of the public MCP contract.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Instructions for AI coding agents working in this repository. Agent-agnostic by design — Claude Code, Cursor, Aider, OpenAI Codex CLI, and any other tool that consumes AGENTS.md should follow what is described here.
Project overview
cookidough-mcp is a Model Context Protocol (MCP) server that exposes the
Thermomix® Cookidoo® platform to LLM clients (Claude
Desktop, Claude Code, any MCP-aware tool). It consolidates four predecessor
projects into a single Python 3.12+ codebase (tested through 3.14) built
on the MCP Python SDK (MCPServer, mcp 2.x) and the
miaucl/cookidoo-api library.
The full feature list and tool table is in README.md.
Setup commands
cp .env.example .env # fill in COOKIDOUGH_EMAIL / COOKIDOUGH_PASSWORD
./run.sh # idempotent: bootstrap venv + install + start serverrun.sh skips the install step when pyproject.toml has not changed since
the last successful install, so subsequent runs start immediately. It parses
no options of its own — configuration is entirely environment-driven.
Manual setup (without run.sh) is documented in README.md under the
"Development" section.
Build, lint, and test commands
The project supports four quality gates that must all stay green. The canonical way to run them is via the bundled script:
./check.sh # run all gates, stop on first failure
./check.sh --fix # auto-fix ruff lint + format, then run all gatesEquivalent manual invocation:
source .venv/bin/activate
ruff check . # lint
ruff format --check . # formatting
mypy # strict type checks over src/ and tests/
pytest # full test suite, coverage gate ≥ 80 %Run ./check.sh before committing. Coverage failures and any lint/type
errors block the build.
Code style
- Language: All code, identifiers, docstrings, commit messages, and documentation are in English.
- Linter / formatter:
ruff(seepyproject.tomlfor the active rules:E F W I B UP SIM RUF N ANN PT C4 PIE RET PTH ASYNC DTZ LOG G T20 TID). - Type checker:
mypy --strictoversrc/andtests/. Avoid# type: ignore— fix the type instead. The few existing ignores are documented at the call site. - Python idioms: Python 3.12+ syntax (PEP 695 generics like
async def _run[T](...),Selfreturn types,|unions, nofrom __future__ import annotationsin code that needs runtime introspection by Pydantic/MCPServer). - Comments: Default to none. Only add a comment when the why is non-obvious (a hidden constraint, a workaround for an upstream quirk). Never document what the code does — sprechende identifiers cover that.
- Docstrings: Only on public modules, classes, and tool functions. Tool docstrings are surfaced to the LLM client, so keep them short, precise, and outcome-focused.
- Imports: Inside
src/cookidough_mcp/tools/*relative imports from the parent package (from ..context import ...) are intentional and ignored by ruff's TID rule via per-file config.
Testing instructions
- Async tests are the default (
asyncio_mode = "auto"inpyproject.toml). tests/conftest.pyprovides aFakeSessionthat is statically guarded againstCookidoughSessionProtocol:_PROTOCOL_GUARD: CookidoughSessionProtocol = FakeSession()If you add a method to the protocol, the guard breaks at mypy time — update the fake in the same PR.
- Test the behaviour, not the implementation. Avoid tests that assert "mock was called" without verifying observable output.
- Private MCPServer API access is centralized in
tests/_mcp_internals.py. If you need a tool function inside a test, route through there. - New session methods need both a unit test (DTO mapping, error paths) and
an integration-style test via
tests/test_session_methods.py.
Architecture
src/cookidough_mcp/
├── config.py # Pydantic-settings, env-driven Settings
├── constants.py # Magic numbers / strings (timeouts, defaults)
├── context.py # AppContext dataclass + ToolContext type alias
├── errors.py # Domain exception hierarchy
├── models.py # Pydantic DTOs for every tool I/O
├── session.py # Repository facade over cookidoo-api + custom HTTP
├── china_client.py # Platform client for the mainland-China deployment
├── transport.py # Stdio / HTTP transport strategies
├── quality.py # Recipe quality rule strategy set
├── annotation_models.py # Guided-cooking annotation DTOs (discriminated union)
├── annotations.py # Annotation inferrer (text patterns → StepAnnotation)
├── step_text.py # formattedText → plain text (markup, appliance icons)
├── web_import.py # recipe-scrapers adapter → CustomRecipeDraft
├── resources.py # MCP resources + prompts (read-only context, workflows)
├── server.py # MCPServer instance + lifespan
└── tools/ # Thin tool adapters: one module per domainKey invariants — do not break these without discussion:
session.pyandchina_client.pyare the only modules that import fromcookidoo_api. Tools always go through theCookidoughSessionProtocolinterface.china_client.pyis exempt because it subclasses the upstream client; keep every other module free of that import.ChinaClientoverrides only the four CIAM-specific steps (discovery, origin guard, login form, credential POST) and reuses the base class for PKCE, the code exchange and token storage. It must end a login with real tokens:_ensure_tokengates all 42cookidoo-apimethods, so a cookie-only login would break every one of them.- Tool modules in
tools/are thin adapters. Business logic lives insession.py,quality.py, orweb_import.py. A tool function should read like: validate → call session → return DTO. - Pydantic DTOs validate at the system boundary. Domain code works with validated objects — no double-checks deeper in the stack.
session.pyuses one lock (_login_lock) plus a latched_closedflag. Login, re-login, and close all serialize on the same lock; the flag stays set afteracloseso a stale tool call fails fast instead of silently bootstrapping a fresh session._reloginreuses_login_lockinstead of having a dedicated refresh lock.- Auth flow:
login()(cookidoo-api ≥ 0.18) performs an OAuth2 authorization-code + PKCE exchange and authenticates every later call with aBearertoken. The library refreshes an expired access token on its own via the stored refresh token; on a 401 we re-runclient.login()through_reloginrather than drivingrefresh()ourselves. The login redirects still need the cookie jar, soCookieJar(unsafe=True)stays required. - One auth mechanism. Both the
cookidoo-apicalls and the undocumentedcookidoo.<tld>endpoints in_authed_httpcarry the sameBearertoken;_bearer_headerreads it fromclient.auth_dataper attempt so the retry after_reloginuses the fresh token. Cookie auth on those endpoints was retired upstream (verified 2026-09-18: cookie-only answers 401, or 302 to the login for/created-recipes) — do not go back to it. A token restored fromCOOKIDOUGH_TOKEN_FILEtherefore authenticates every path right away. - The session-generation counter (
_session_generation, exposed via thesession_generationproperty) is the single source of truth for re-login races. Snapshot it before the request, pass the snapshot to_reloginso parallel callers do not redundantly log in. - The HTTP session must be built with
aiohttp.CookieJar(unsafe=True)— the OAuth2 redirect chain crosses domains (cookidoo.<tld>→ CIAM → login-srv), and the default jar drops those cookies. - The interaction endpoints (rating, bookmark, recipe-notes,
cooking-history, recommender, customer-devices) are undocumented
platform APIs discovered via its
.well-known/homedocument. Methods and payload shapes were verified live on 2026-06-05 (seetests/smoke/smoke_test.py); parsers insession.pymust still tolerate missing fields and degrade toNone/empty instead of raising, and per-action failures inset_recipe_interactionsare reported in the result DTO rather than failing the call. - Naming (legal requirement): the project is "cookidough" /
"Cookidough" — never "cookidoo". This covers every project-owned
identifier: the server name, resource URIs, env prefix, config keys,
and class prefixes (
CookidoughSession,CookidoughSessionProtocol,CookidoughMcpError). "Cookidoo" may appear only when referring to the third-party platform itself (prose, platform URLs) or in identifiers imported from the upstreamcookidoo_apipackage, which we cannot rename.
Security considerations
- The account password is stored as
pydantic.SecretStrand never logged. - The login banner logs a redacted email (
a***@example.com), never the full address. - All upstream error bodies pass through
_redact_error_bodybefore being surfaced. The patterns scrubaccess_token/refresh_token/id_token/api_key/session_id/csrf/authorization/bearerkeys, nakedBearer xxxheaders, JWTs (eyJ…), and email addresses; the body is also truncated to 200 characters. _localization_originrejects any URL scheme other thanhttp/httpsto prevent reflectedjavascript:orfile:schemes from upstream.- Every HTTP request enforces a per-request
ClientTimeout(30 s by default, seeconstants.HTTP_TIMEOUT_SECONDS). Do not bypass this. aiohttp.ClientSessioncleanup is reentrant and lock-protected; do not nullself._httpoutside ofaclose().- The optional token file (
COOKIDOUGH_TOKEN_FILE) holds the OAuth2 access and refresh tokens — equivalent to a password, and longer-lived than a session cookie._persist_tokencreates it with0600before writing, sincesave_tokenwrites in place; never log its contents, never widen its permissions, and keep the.gitignorepatterns (token.json,*.token.json) intact. set_custom_recipe_imageuploads the image bytes directly to Vorwerk's Cloudinary tenant (third-party egress, same as the official web app). The upload runs on a dedicated plainaiohttp.ClientSession— the platform cookie jar must NEVER be sent to that host. URL sources are restricted to http/https and capped atMAX_RECIPE_IMAGE_BYTES.
Dev environment tips
./run.shis the canonical entry point. It is idempotent, detects Python 3.12+, creates.venv/, installs the package, sources.env, validates credentials, andexecs the server.- For HTTP transport:
COOKIDOUGH_MCP_MODE=http ./run.sh. - For local development without the script:
source .venv/bin/activate && cookidough-mcp. The MCP Inspector is the fastest way to smoke-test tool changes:
npx @modelcontextprotocol/inspector ./run.sh- Stdio MCP servers must keep
stdoutclean — only the MCP wire protocol goes there. All logs go tostderr(thelogging.basicConfigcall in__main__.pyenforces that, andaiohttp/cookidoo_apiloggers are pinned toWARNING).
Pull request expectations
- Open a PR only when
./check.shis fully green locally. - Update
README.mdand this file if you change build commands, env vars, or registered tool names. - Keep tool names stable — they are part of the public MCP contract.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

