Skip to content

Changelog ​

0.5.1 - 2026-09-29 ​

Fixed ​

  • set_recipe_image and the imageUrl of every recipe work again against Mealie v3.26 and later. Mealie replaced the numeric image counter with a short alphanumeric cache key, which the server refused as a version: an upload that had been stored was reported as failed, and recipes came back without an image URL. The integration suite now runs against Mealie v3.28.0.

0.5.0 - 2026-09-22 ​

Added ​

  • A preparation step can carry its own heading. Mealie's recipeInstructions entries hold a title beside the text, and create_recipe and update_recipe wrote that title as an empty string whatever was asked of them — a recipe divided into "Prep", "Bake" and "Serve" had no way through this server. instructions now takes either a plain string per step, as before, or {title, text}. The bound on text is the same in both forms and a bare string still means a step with no heading, so nothing an existing caller sends behaves differently. Contributed by @titusjaka.

  • set_recipe_image sets a recipe's cover picture, the fifty-third tool. Mealie's image route was not reachable through this server at all — neither by URL nor by upload — so a recipe created here had whatever picture the scraper found, or none. The tool takes the image base64-encoded with its format named from a closed set (jpeg, jpg, png, webp), which is what keeps the upload filename and content type out of a caller's hands; the bytes are capped at 8 MB, as import_recipe_from_image already caps them. It answers with the image version Mealie assigned rather than a constant, so a 200 from something that never forwarded the write — a reverse proxy, an SSO portal — is an error instead of a confident claim. Not guarded by a dialog: a cover image is usually the scraper's rather than a person's writing, and re-importing brings it back. Contributed by @titusjaka.

    Note for anyone who read the old text: image uploads used to be listed among the routes this server deliberately does not reach. That sentence was true until this release and is now corrected in the README and the security guide.

Fixed ​

  • The approval guide said eleven tools ask a person; sixteen do. Its table stopped after eleven and closed with "everything else — never", so the page that explains the guard denied it for update_recipe, update_organizer, update_mealplan_entry, update_shopping_list_items and create_cookbook — the five added in 0.2.1. Its worked example was wrong in both halves as well: it named update_recipe as a tool marked destructive without being guarded, which it has not been since 0.2.1, and no tool in the catalogue is in that position — every one of the fourteen destructive tools takes a confirmation token. The gap runs the other way, and the page now names the case that exists. SECURITY.md and the tool reference were right all along; a test holds the guide to the built server from here on, the way one already held the reference page.
  • The read-tool count was stated as seventeen in server.json and the getting started guide, where the catalogue has eighteen. server.json is what the MCP registry shows.
  • The README pointed at scripts/verify-live.mjs for a run against a live instance. There is no scripts/ directory — it became test/integration/ — so it names npm run test:integration now.

0.4.0 - 2026-09-07 ​

Security ​

  • The image scan of import_recipe_from_html_or_json is one pass over the document, and it decodes what Mealie decodes. A document of "image":[ repeated to the 2 MiB limit cost 223 seconds on the thread that serves every request: a 4096-character window was sliced, searched and URL-parsed per key. The scan now reads the document as JSON when it parses (so "\u0069mage" is image, as it is to Mealie), as JSON text with string escapes decoded (so "http:\/\/…" — what PHP's json_encode writes by default — is the address it decodes to, where a reader that stopped at the backslash saw http: and dropped the candidate), and as HTML with character references decoded (so &#49;00.100.100.200 is 100.100.100.200, where the host used to be &). Both bypasses were reproduced against the metadata address Mealie's own guard does not stop. It also reads <meta itemprop="image"> and <link rel="image_src">, which extruct reads and the scan did not; refuses an absolute address it cannot parse rather than passing it on, since "cannot parse" is not "harmless" when the next parser is somebody else's; and refuses a document naming more than 25 hosts or carrying more than 500 image references, where the 26th host used to be skipped in silence. A timing table holds every form at the size limit under half a second.
  • MEALIE_API_TOKEN and MEALIE_ACCEPT_LANGUAGE are checked for shape at startup, and every header value before a request. undici refuses a header value with a control character in it by quoting the whole value in its error — for Authorization, the whole value is the token — and that error reached the model as the tool result. A token with a line break inside it (the way a wrapped paste arrives) now ends the process with a message that names the variable and its length, and never the value; a language that is not a language range is dropped with the same care. A property test drives random tokens with control characters through the whole path and asserts the result text never carries them.
  • Every string the instance wrote is cleaned before it is shown. C0 and C1 controls, DEL, the zero-width set, the BiDi overrides and the byte-order mark are removed from every field the projections carry and from the whole object in get_recipe's raw mode, preview_recipe_url, parse_ingredients and set_recipe_rating, which used to hand Mealie's object on untouched. Recipes are scraped from arbitrary websites; an escape sequence in a step arrived intact. Identifiers, dates and URLs are validated instead: an id that is not shaped like one is dropped, and the credentials in a stored source URL are redacted.
  • Fields named like a credential are redacted at any depth — password, secret, token, api_key, private_key, passphrase, matched on the suffix of the normalised name. Mealie's extras is arbitrary key/value data written by integrations, and it travelled through the raw passthroughs as written.
  • create_cookbook binds the confirmation to everything its dialog names. The consequence said "its name, description and saved filter become readable outside the instance" and the resource key held the name alone, so a token issued for one description executed with another. The key now covers all three, in order, and the dialog shows all three. create_share_token and merge_foods/merge_units build their ordered keys with the library's orderedResourceKey instead of a hand-written join, and update_shopping_list_items binds the list its sentence names.
  • The status is decided before the body is read. A 401 behind a reverse proxy that answers with a login page of megabytes surfaced as "more than the byte limit" — the size, not the status, and no hint about credentials. An error body is now read under its own 64 KiB ceiling that cuts instead of refusing; the 8 MiB cap applies to a success body only.
  • What resolveRecipe hands on is validated, not merely typed. The id and the slug are the instance's strings, and they went on into a request path, a query-filter literal (recipe_id="…") and the sentence a person is asked to approve; a " in the id would have broken out of the filter. The id must be a UUID and the slug a slug, or the recipe is reported as unrecognisable. The same for the user id set_recipe_rating puts in a path.
  • SECURITY.md described a transport this server no longer uses. The "binding is not freshness" section argued that protocol revision 2026-07-28 was unreachable and that no anti-replay mechanism was therefore needed — while serveStdio negotiates that revision and mcp-approval 0.8.1 made a sealed answer single-use. The section now describes what the code does.
  • Supply chain. The release job installs with --ignore-scripts, as the audit job and the Dockerfile already did — it is the job that holds the OIDC token; mcp-publisher is pinned to v1.8.1 and its published checksum instead of releases/latest; gh release create verifies the tag; pull requests get dependency-review-action at fail-on-severity: high; the runtime image no longer carries yarn, corepack or the lockfile; the integration job's checkout keeps no credentials.
  • mcp-approval 0.8.2. A sealed dialog answer is single-use since 0.8.1: the same requestState presented again within its lifetime used to be accepted again, and with a resource key that is the same every time — a whole stream, a fixed set of targets — every replay landed. npm users on ^0.8.0 already had the fix; the Docker image is built from the lockfile and carried 0.8.0 until this release.

Fixed ​

  • Organizer lookups have a budget per call. search_recipes resolves up to sixty names and update_recipe up to a hundred, one or two requests each in sequence, and the fifteen-second timeout bounded each request rather than the call. A wall clock of thirty seconds is checked before every request; past it the call says how far it got and what to narrow.
  • A single oversized field no longer makes a recipe unanswerable. A 300 kB calories — schema.org nutrition is text Mealie takes from the page — left nothing array-shaped for the budget to shrink, so get_recipe threw. Every projected field is bounded, raw strings are cut at twenty thousand characters, and the budget shrinks the array that is largest in bytes rather than in elements, which used to halve a list of three hundred tag names to nothing while forty long instructions stayed.
  • The text block of a marked result stays under the cap with the untrusted preamble included; the budget measured the JSON alone.
  • A truncated key the instance carries cannot replace the budget's own notice or fail the answer against its schema; it is dropped alongside untrusted and source.
  • A list of names — tags, categories, aliases, missing foods — no longer carries undefined for a reference without a name, which the text block wrote as null and the structured half did not. A property test now feeds every read tool arbitrary JSON, including 1e999, -0 and a __proto__ key, and asserts both channels of each answer agree and no answer reads "Output validation error" or "Cannot read properties".
  • Error bodies and runtime error messages quoted into a result are stripped of control characters, cut, and labelled as the instance's words.
  • confirm_token, search_recipes.cookbook and every ISO timestamp argument are bounded; the token past 512 characters was compared before it was refused.
  • MEALIE_URL is stored as it was checked — the parsed origin and path — rather than as the raw string, and the trailing-slash trim is a loop rather than an unanchored /\/+$/.
  • The ELICITATION diagnostic describes a long value by its length instead of quoting it, and the wrong-scheme diagnostic for MEALIE_URL no longer prints the scheme: a hexadecimal key with a colon after it is a valid URL whose scheme is the key.
  • get_shopping_list, update_shopping_list_items, update_mealplan_entry and the organizer lookups read a null or non-object body as an empty record instead of answering "Cannot read properties of null".
  • get_about answers with scalars only — the group and household as names, not the whole objects — and cleans the instance's strings before they travel unmarked.
  • publishConfig.access is now public. This package is scoped, and npm publishes a scoped package as restricted unless told otherwise — every other scoped server in the family carried the field and this one did not. The published versions are unaffected: npm keeps the visibility a package already has, so this closes a hole that would only have opened on a first publish under a new name.

Added ​

  • The server introduces itself in full. title, description, websiteUrl and icons now travel with name and version, so a client that shows a server to a person has something to show. All four were already in server.json for the registry and reached no client at all; a test compares the two so they cannot drift.
  • Server instructions. Results carry an untrusted marker, but that is read after the fact — this is the channel a model sees before it calls anything.
  • An OpenSSF Scorecard run, weekly and on every push to main, reporting into the Security tab next to CodeQL and Trivy. The badge is the second in the row.

Changed ​

  • The tool reference marks the essential preset and the tools that ask a person before they act, per tool rather than only in the introduction. A test keeps both sets in step with the code.
  • Source maps are no longer published in the npm tarball. Node reads them only under --enable-source-maps, which nothing here sets, and the maps pointed at a src/ this package does not ship — so a stack trace under that flag named a file nobody could open. dist/**/*.js is unchanged; the package is about a fifth smaller.

0.3.0 - 2026-09-03 ​

Added ​

  • Every tool declares an outputSchema and answers with structuredContent beside the text block. A client no longer has to parse prose to use a result — which nine of them made unavoidable, since they answered with a sentence. The sentence stays, in the text block.

    Most tools carry untrusted: true and source: "mealie" as fields, not only as a preamble in the text: recipes are routinely scraped from arbitrary websites and comments come from other users of the instance. The ten without the marker answer with an id this server was given, or — for get_about — a version string and the permission flags of the account it authenticates as.

    Mealie's records are described as open objects with the top-level keys this server builds. A self-hosted Mealie is any release, and a strict shape would turn a field one adds into a tool that fails outright.

  • Tools that need a confirmation now ask the user, on clients that can show a prompt. The two-call confirm_token remains for clients that cannot, so nothing that works today stops working — but where a person can be asked, one is, instead of a token that only proves the same call was made twice. This covers all guarded tools, create_share_token among them.

  • delete_share_token now asks too. Its description said in so many words that it needed "no confirmation — this narrows access rather than widening it", and the direction really is the safe one. What is not is that the link cannot be reissued: a new share token is a different URL, so whoever was sent the old one finds a dead link, and this server cannot tell whom that was.

  • ELICITATION switches the dialog off — false sends a client that could have been asked down the two-call-token path instead. For a scheduled job or a test harness, where a dialog is the wrong shape rather than an unwanted one.

    It does not remove the guard: there is no setting in which a guarded call goes unannounced. Two deliberate rough edges come with it. The variable is not prefixed, so one export ELICITATION=false reaches every MCP server in the environment — which is why a server started with it off prints a line saying so, and why the fallback text names the server instead of blaming a client that was working fine. And a value that is neither true nor falsestops the server, where the MEALIE_* booleans beside it fail off on a typo: this is the only variable here that defaults to on. It is read after MEALIE_API_TOKEN is wiped from the environment, so that exit cannot leave the token behind.

  • A docs/guide/approval.md page.

Changed ​

  • The advertised schemas avoid spellings that are legal JSON Schema and still get a tool refused, or its constraint silently dropped, by some MCP clients: an open object now writes "additionalProperties": true rather than the empty schema {} zod emits for it; and a nullable field is written as anyOf branches rather than "type": ["string", "null"], which several clients read as a single type and then drop. What the tools accept and return is unchanged; only the way the schema says so is.

  • A result too large to shrink is now an error rather than an envelope carrying the oversized document as a string. That envelope is valid JSON and no longer a valid answer: the SDK checks a result against the schema its tool declares.

  • update_recipe with no fields answers {recipe, changed: false, note} rather than the bare sentence. It is still not an error — a model that resolved every field to its current value should not be punished for asking.

  • The two-call confirm_token prompt is an error result. What was asked for did not happen, which is what isError says. The text is unchanged and still carries the token.

  • MEALIE_READ_ONLY now accepts 1, true and yes in any case and ignores surrounding whitespace, matching the rest of the family. It only ever takes capability away, so an operator who wrote MEALIE_READ_ONLY=True meant the safe thing and now gets it — where before that spelling silently left every write tool registered. MEALIE_INSECURE_TLS stays exactly true on purpose: it weakens the server, so only the one unambiguous spelling should do it.

  • The shared libraries move to mcp-approval 0.7.1, mcp-tool-allowlist 0.2.1, mcp-internal-hosts 0.2.1, mcp-integration-harness 0.2.0 and svg-asset-set 0.2.0.

  • Runs on MCP SDK 2.0. Existing clients see the same protocol revision they always did; the change is the package layout behind it, and it is what lets the dialog above work on both protocol eras from one code path — including behind a stateless gateway, where the older mechanism silently fell back to the weaker token for every client.

  • The linter is oxlint instead of eslint plus typescript-eslint, which lifts the TypeScript ceiling: typescript-eslint pins typescript below 6.1, so this repository was held on TypeScript 6 by its linter rather than by its code.

  • The tool filter, the confirmation store, the host classifier and the documentation-asset generator now come from mcp-tool-allowlist, mcp-approval, mcp-internal-hosts and svg-asset-set rather than from copies kept here — 802 fewer lines, and one place to fix each. None of them has a runtime dependency of its own.

  • A confirm_token that does not match its arguments is refused with the reason instead of being answered with a fresh prompt, in the same words as every other server in the family. The binding is unchanged: a confirmation issued for one recipe still cannot delete another. A wrong token also no longer voids the outstanding one — voiding it let anyone who could reach the tool cancel a pending confirmation by sending rubbish, which protected nothing.

  • stdio is served through serveStdio, so the connection's era is negotiated on the opening exchange rather than assumed. A client that pins the 2026-07-28 era is served it; until now its server/discover probe was answered with "Method not found" and only 2025-11-25 was on offer. A client that speaks the older era sees no change — it is still pinned to one instance for the life of the connection, exactly as a hand-wired StdioServerTransport served it.

Fixed ​

  • search_recipes answered a narrowed question with the whole collection. The tool described tags, categories and tools as taking "names, slugs or UUIDs". Mealie takes no names: _uuids_for_items looks a non-UUID up as a slug, returns an empty list when nothing matches, and _build_recipe_filter then tests if tags: — so the filter is not attached at all. Measured on v3.22.0 with three recipes, one tagged "Weeknight Dinner": ?tags=Weeknight%20Dinner returned all three, as did the mistyped slug weeknight-dinnerrr, with nothing in the answer saying it had not been filtered. Names and slugs are now resolved to ids before the search runs, and an entry that resolves to nothing is an error.

  • search_recipes promised AND and did not deliver it. cookbook and the organizer filters are mutually exclusive in Mealie — _build_recipe_filter returns the cookbook's own filter and returns early — so {cookbook: "desserts", tags: ["vegan"]} silently ignored the tags. Confirmed live. The combination is now refused.

  • search_recipes({foods: […]}) could only 500. Mealie resolves foods not at all and puts the value straight into RecipeIngredientModel.food_id == food, so a name or slug reaches the GUID type decorator and comes back as HTTP 500. foods now takes UUIDs only, as suggest_recipes always has.

  • order_by: "random" always failed. Mealie's pagination model validates paginationSeed is required when orderBy is random and answers HTTP 422; the tool took no seed, so the option could not be used. It now generates one per call.

  • Mealie's organizer slug routes answer "no such slug" two different ways — /categories/slug/nope is a clean 404 while /tags/slug/nope and /tools/slug/nope are HTTP 500. The lookup reads both as a miss and falls through to the name search; a Mealie that is genuinely failing still reports the failure, because that second request has to succeed for anything to resolve. Found by the integration suite, not by reading.

  • merge_foods and merge_units named the wrong tool. Both come out of one factory, and the factory passed toolName: 'create_unit' — a real, unrelated tool of this server. That name is printed in two places a caller acts on: the fallback instruction ("call create_unit again with the token") and the sentence after a decline. Callers were being pointed at something that creates rather than merges. Introduced on 2026-09-01 with the move to mcp-approval.

  • Four update_* tools were annotated destructiveHint: false: update_recipe, update_organizer, update_mealplan_entry and update_shopping_list_items. Mealie keeps no version history, so replacing an instruction list leaves nowhere to read the old one back from — that is the definition the whole family uses, and Wiki.js's update_page is genuinely on the other side of it because Wiki.js has page history. The difference is the backend, not the verb.

  • Confirmation tokens are compared with a constant-time comparison. The copy in this repository used !==, which leaks through timing how much of a guess was right. Reaching a token still requires having received it in a previous tool result, so this closes a margin rather than a hole.

  • An entry in MEALIE_ALLOW_TOOLS that is not tool-name-shaped is now redacted in the error rather than quoted back. MEALIE_TOKEN and MEALIE_ALLOW_TOOLS are adjacent lines in every compose file, and a paste into the wrong one used to print the credential into the client's log.

Security ​

  • The confirmation gate was drawn along the wrong line. It followed the tool name — everything called delete_* or merge_* asked — rather than "cannot be undone", and four tools fell in the gap. update_recipe with {ingredients: [], instructions: []} emptied a recipe in one call and answered with the now-empty recipe, where delete_recipe on the same recipe cost two calls and a token; Mealie keeps no version history, so both are equally final. update_organizer, update_mealplan_entry and update_shopping_list_items were the other three.

    The line is now the one annotations.ts always stated — content a person wrote, replaced with no way back — applied per call rather than per tool. update_recipe asks when it replaces name, description, ingredients, instructions, tags, categories or notes, and goes straight through for times, servings, yield and the source link. update_shopping_list_items asks for note and not for ticking off. update_mealplan_entry asks for title and text and not for a move. update_organizer always asks: a rename regenerates the slug. Each approval is bound to a fingerprint of the replacing values as well as to the target, so one shown for one new instruction list cannot be spent on a call that clears the list instead.

    The lasting part is test/gating.test.ts, which claims this over the whole catalogue: every tool annotated destructiveHint: true has to accept a confirm_token, has to write nothing on its first call, and has to appear in that file's table. A per-tool test would not have found the gap — every per-tool test that existed passed.

  • create_cookbook(is_public: true) published without asking. The one other tool that widens who can see something; create_share_token has been guarded since it existed. It exposes less than a share link — the recipes themselves need settings.public of their own — but the name, description and saved filter go out, and there is no update_cookbook to take it back with. A private cookbook is still created without a prompt.

  • import_recipe_from_html_or_json claimed openWorldHint: false while Mealie fetched an address out of the document it was handed. Verified against v3.22.0: a pasted {"image": "http://…/latest/meta-data/"} puts Image URL: … in Mealie's log and goes through recipe_data_service.scrape_image. Mealie's own guard refuses on is_private, which is False for 100.100.100.200 (Alibaba Cloud metadata) and for all of 100.64.0.0/10. The tool now carries openWorldHint: true, and the addresses this server can find in the document — schema.org image/thumbnailUrl/contentUrl in all three shapes, <img src>, og:image — go through the same assertFetchableUrl as a URL argument.

  • source_url was the only URL argument with no scheme check. httpUrl exists because zod's .url() accepts javascript:, file: and data:; source_url was a plain string, Mealie does not validate org_url either, and the value comes back to every reader through recipeDetail. It is now httpUrl.

0.2.0 - 2026-08-27 ​

Added ​

  • MEALIE_ALLOW_TOOLS and MEALIE_DENY_TOOLS choose which of the 52 tools are registered. Both take comma-separated tool names or a prefix with a trailing *, the allow list decides what is in and the deny list is subtracted from it, and MEALIE_ALLOW_TOOLS=essential selects a curated eight — search_recipes, get_recipe, import_recipe_from_url, create_recipe, get_todays_meals, create_mealplan_entry, list_shopping_lists, add_recipe_to_shopping_list. A model picks the right tool far more reliably from eight than from fifty-two, and every visible tool costs context on every request. Nothing changes for an installation that sets neither.

    A filtered tool is not registered at all, so it is absent from tools/list and answers tools/call with "tool not found" — the same cut MEALIE_READ_ONLY already makes, not a second, weaker one.

    An entry that matches no tool stops the server at startup, naming the entry and listing the real names, rather than being ignored: an ignored typo leaves a tool missing from tools/list with nothing pointing at the cause.

Changed ​

  • The README now carries the same eight badges, in the same order, as every other MCP server in this family, all of them reading from npm rather than hard-coded; the opening follows one shape; and the standalone "Full documentation" line is gone, because the docs badge three lines above it points at the same page.

Fixed ​

  • The container image no longer ships OpenSSL 3.5.7-r0, which carries CVE-2026-14456 (denial of service via unbounded memory growth). The pinned node:24-alpine digest is already the newest one; Alpine's fixed 3.5.8-r0 has simply not been rebuilt into it yet, so the runtime stage now upgrades libcrypto3 and libssl3 by name. Upgrading those two rather than running a blanket apk upgrade keeps the rest of the image exactly as the digest pins it. The step can go once the base image ships the fix.

0.1.2 - 2026-08-26 ​

Security ​

  • The host of an import URL is now classified numerically instead of by comparing strings, and a hostname is resolved before it is accepted. http://localhost./recipe — the same name with its root label — walked past the old host === 'localhost' and endsWith('.local') checks, as did http://nas.local./. A DNS name pointing at 127.0.0.1 walked past all of it, because a Zod refinement is synchronous and cannot resolve anything. The check therefore moved out of the schema and into the tool handlers, where it can.
  • What is sent to Mealie is the parsed URL rather than the string that came in, so the address that was checked is the one Mealie fetches. http://ok.example.com\@127.0.0.1/recipe has the host ok.example.com for a URL parser and 127.0.0.1 for a fetcher that splits at the @.
  • The hostnames of the cloud metadata service — metadata.google.internal, instance-data and their siblings — are refused by name. They resolve to 169.254.169.254 on the instance and to nothing anywhere else, so resolving them is exactly what cannot catch them. So are the endpoints that sit outside 169.254/16: 100.100.100.200 (Alibaba Cloud) and 192.0.0.192 (Oracle).
  • An IPv6 scope id is stripped before the address is read. net.isIP accepts ::ffff:127.0.0.1%eth0, which made the dotted-quad fold miss its anchor and the address come out as routable. A URL cannot carry one, but a resolver answer can.

Changed ​

  • Private addresses are no longer refused here. Loopback, link-local and the metadata endpoints are still refused, but 10/8, 172.16/12, 192.168/16, fc00::/7, the rest of carrier-grade NAT and the .lan/.local/.internal/.home suffixes are not. That is a consistency change, not a new capability: Mealie has refused private addresses in its own HTTP transport since v1.4.0, so an import that names one still fails — further downstream and with a less helpful message. What this server now guards is the set of addresses Mealie does not guard for itself, which is why the two metadata endpoints outside 169.254/16 were added. If you were relying on this check to keep Mealie off your LAN, that job belongs to Mealie's own egress rules; SECURITY.md says what is and is not covered.
  • Bracketed IPv6 literals are no longer refused as a class. They were, because classifying them piecemeal was thought to be a losing game — the IPv4-mapped forms in particular. The classifier now unwraps those forms instead, so a public IPv6 address works while [::ffff:169.254.169.254] is still caught.
  • A name that a resolver sinkholes — answering 0.0.0.0 or ::, which is what every ad blocker and DNS filter does — is no longer refused as a loopback address. That is the resolver declining to answer rather than the name addressing the machine, and describing it as loopback made every blocklisted domain unusable with a message that was simply wrong.
  • The description of import_recipe_from_html_or_json no longer claims the tool works "without Mealie fetching anything". Mealie does not fetch the page, but it does read the image address out of the supplied document and retrieve that — an address this server never sees.

0.1.1 - 2026-08-18 ​

Fixed ​

  • First release published through the automated pipeline, with npm provenance and the MCP registry entry. 0.1.0 had been published manually while setting up Trusted Publishing and therefore carries no provenance attestation.

0.1.0 - 2026-08-18 ​

Added ​

  • Initial implementation: MCP server for Mealie, the self-hosted recipe manager and meal planner.
  • 52 tools — 17 read tools that are always registered, 35 write and import tools that are omitted when MEALIE_READ_ONLY=true: recipe search and detail, recipe CRUD, import from URL / HTML / schema.org JSON / photo, tags, categories and tools, foods, units and the ingredient parser, meal plans, shopping lists including recipe-to-list, cookbooks, ratings, comments, timeline entries and public share links.
  • Confirmation tokens on the nine operations that delete, merge or widen access. Tokens are single-use, bound to the target, and for set operations bound to a fingerprint of the whole sorted id set.
  • URL validation that restricts the import tools to public http/https addresses, because Mealie performs those fetches from inside its own network.
  • All instance content is returned behind an explicit untrusted-content marker.

Released under the MIT License.