Changelog
0.5.1 - 2026-09-29
Fixed
set_recipe_imageand theimageUrlof 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
recipeInstructionsentries hold atitlebeside thetext, andcreate_recipeandupdate_recipewrote 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.instructionsnow takes either a plain string per step, as before, or{title, text}. The bound ontextis 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_imagesets 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, asimport_recipe_from_imagealready caps them. It answers with the image version Mealie assigned rather than a constant, so a200from 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_itemsandcreate_cookbook— the five added in 0.2.1. Its worked example was wrong in both halves as well: it namedupdate_recipeas 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.mdand 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.jsonand the getting started guide, where the catalogue has eighteen.server.jsonis what the MCP registry shows. - The README pointed at
scripts/verify-live.mjsfor a run against a live instance. There is noscripts/directory — it becametest/integration/— so it namesnpm run test:integrationnow.
0.4.0 - 2026-09-07
Security
- The image scan of
import_recipe_from_html_or_jsonis 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"isimage, as it is to Mealie), as JSON text with string escapes decoded (so"http:\/\/…"— what PHP'sjson_encodewrites by default — is the address it decodes to, where a reader that stopped at the backslash sawhttp:and dropped the candidate), and as HTML with character references decoded (so100.100.100.200is100.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_TOKENandMEALIE_ACCEPT_LANGUAGEare 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 — forAuthorization, 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'srawmode,preview_recipe_url,parse_ingredientsandset_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'sextrasis arbitrary key/value data written by integrations, and it travelled through the raw passthroughs as written. create_cookbookbinds 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_tokenandmerge_foods/merge_unitsbuild their ordered keys with the library'sorderedResourceKeyinstead of a hand-written join, andupdate_shopping_list_itemsbinds the list its sentence names.- The status is decided before the body is read. A
401behind 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
resolveRecipehands 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 idset_recipe_ratingputs 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-28was unreachable and that no anti-replay mechanism was therefore needed — whileserveStdionegotiates that revision andmcp-approval0.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-publisheris pinned to v1.8.1 and its published checksum instead ofreleases/latest;gh release createverifies the tag; pull requests getdependency-review-actionatfail-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
requestStatepresented 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.0already 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_recipesresolves up to sixty names andupdate_recipeup 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, soget_recipethrew. 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
truncatedkey the instance carries cannot replace the budget's own notice or fail the answer against its schema; it is dropped alongsideuntrustedandsource. - A list of names — tags, categories, aliases, missing foods — no longer carries
undefinedfor a reference without a name, which the text block wrote asnulland the structured half did not. A property test now feeds every read tool arbitrary JSON, including1e999,-0and 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.cookbookand every ISO timestamp argument are bounded; the token past 512 characters was compared before it was refused.MEALIE_URLis 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
ELICITATIONdiagnostic describes a long value by its length instead of quoting it, and the wrong-scheme diagnostic forMEALIE_URLno 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_entryand the organizer lookups read anullor non-object body as an empty record instead of answering "Cannot read properties of null".get_aboutanswers with scalars only — the group and household as names, not the whole objects — and cleans the instance's strings before they travel unmarked.publishConfig.accessis nowpublic. This package is scoped, and npm publishes a scoped package asrestrictedunless 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,websiteUrlandiconsnow travel withnameandversion, so a client that shows a server to a person has something to show. All four were already inserver.jsonfor the registry and reached no client at all; a test compares the two so they cannot drift. - Server
instructions. Results carry anuntrustedmarker, 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
essentialpreset 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 asrc/this package does not ship — so a stack trace under that flag named a file nobody could open.dist/**/*.jsis unchanged; the package is about a fifth smaller.
0.3.0 - 2026-09-03
Added
Every tool declares an
outputSchemaand answers withstructuredContentbeside 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: trueandsource: "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 — forget_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_tokenremains 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_tokenamong them.delete_share_tokennow 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.ELICITATIONswitches the dialog off —falsesends 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=falsereaches 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 neithertruenorfalsestops the server, where theMEALIE_*booleans beside it fail off on a typo: this is the only variable here that defaults to on. It is read afterMEALIE_API_TOKENis wiped from the environment, so that exit cannot leave the token behind.A
docs/guide/approval.mdpage.
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": truerather than the empty schema{}zod emits for it; and a nullable field is written asanyOfbranches 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_recipewith 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_tokenprompt is an error result. What was asked for did not happen, which is whatisErrorsays. The text is unchanged and still carries the token.MEALIE_READ_ONLYnow accepts1,trueandyesin any case and ignores surrounding whitespace, matching the rest of the family. It only ever takes capability away, so an operator who wroteMEALIE_READ_ONLY=Truemeant the safe thing and now gets it — where before that spelling silently left every write tool registered.MEALIE_INSECURE_TLSstays exactlytrueon purpose: it weakens the server, so only the one unambiguous spelling should do it.The shared libraries move to
mcp-approval0.7.1,mcp-tool-allowlist0.2.1,mcp-internal-hosts0.2.1,mcp-integration-harness0.2.0 andsvg-asset-set0.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
typescriptbelow 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-hostsandsvg-asset-setrather 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_tokenthat 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 the2026-07-28era is served it; until now itsserver/discoverprobe was answered with "Method not found" and only2025-11-25was 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-wiredStdioServerTransportserved it.
Fixed
search_recipesanswered a narrowed question with the whole collection. The tool describedtags,categoriesandtoolsas taking "names, slugs or UUIDs". Mealie takes no names:_uuids_for_itemslooks a non-UUID up as a slug, returns an empty list when nothing matches, and_build_recipe_filterthen testsif tags:— so the filter is not attached at all. Measured on v3.22.0 with three recipes, one tagged "Weeknight Dinner":?tags=Weeknight%20Dinnerreturned all three, as did the mistyped slugweeknight-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_recipespromised AND and did not deliver it.cookbookand the organizer filters are mutually exclusive in Mealie —_build_recipe_filterreturns 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 intoRecipeIngredientModel.food_id == food, so a name or slug reaches theGUIDtype decorator and comes back as HTTP 500.foodsnow takes UUIDs only, assuggest_recipesalways has.order_by: "random"always failed. Mealie's pagination model validatespaginationSeed is required when orderBy is randomand 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/nopeis a clean 404 while/tags/slug/nopeand/tools/slug/nopeare 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_foodsandmerge_unitsnamed the wrong tool. Both come out of one factory, and the factory passedtoolName: 'create_unit'— a real, unrelated tool of this server. That name is printed in two places a caller acts on: the fallback instruction ("callcreate_unitagain 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 tomcp-approval.Four
update_*tools were annotateddestructiveHint: false:update_recipe,update_organizer,update_mealplan_entryandupdate_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'supdate_pageis 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_TOOLSthat is not tool-name-shaped is now redacted in the error rather than quoted back.MEALIE_TOKENandMEALIE_ALLOW_TOOLSare 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_*ormerge_*asked — rather than "cannot be undone", and four tools fell in the gap.update_recipewith{ingredients: [], instructions: []}emptied a recipe in one call and answered with the now-empty recipe, wheredelete_recipeon the same recipe cost two calls and a token; Mealie keeps no version history, so both are equally final.update_organizer,update_mealplan_entryandupdate_shopping_list_itemswere the other three.The line is now the one
annotations.tsalways stated — content a person wrote, replaced with no way back — applied per call rather than per tool.update_recipeasks 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_itemsasks fornoteand not for ticking off.update_mealplan_entryasks fortitleandtextand not for a move.update_organizeralways 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 annotateddestructiveHint: truehas to accept aconfirm_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_tokenhas been guarded since it existed. It exposes less than a share link — the recipes themselves needsettings.publicof their own — but the name, description and saved filter go out, and there is noupdate_cookbookto take it back with. A private cookbook is still created without a prompt.import_recipe_from_html_or_jsonclaimedopenWorldHint: falsewhile Mealie fetched an address out of the document it was handed. Verified against v3.22.0: a pasted{"image": "http://…/latest/meta-data/"}putsImage URL: …in Mealie's log and goes throughrecipe_data_service.scrape_image. Mealie's own guard refuses onis_private, which is False for100.100.100.200(Alibaba Cloud metadata) and for all of100.64.0.0/10. The tool now carriesopenWorldHint: true, and the addresses this server can find in the document — schema.orgimage/thumbnailUrl/contentUrlin all three shapes,<img src>,og:image— go through the sameassertFetchableUrlas a URL argument.source_urlwas the only URL argument with no scheme check.httpUrlexists because zod's.url()acceptsjavascript:,file:anddata:;source_urlwas a plain string, Mealie does not validateorg_urleither, and the value comes back to every reader throughrecipeDetail. It is nowhttpUrl.
0.2.0 - 2026-08-27
Added
MEALIE_ALLOW_TOOLSandMEALIE_DENY_TOOLSchoose 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, andMEALIE_ALLOW_TOOLS=essentialselects 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/listand answerstools/callwith "tool not found" — the same cutMEALIE_READ_ONLYalready 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/listwith 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-alpinedigest 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 upgradeslibcrypto3andlibssl3by name. Upgrading those two rather than running a blanketapk upgradekeeps 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 oldhost === 'localhost'andendsWith('.local')checks, as didhttp://nas.local./. A DNS name pointing at127.0.0.1walked 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/recipehas the hostok.example.comfor a URL parser and127.0.0.1for a fetcher that splits at the@. - The hostnames of the cloud metadata service —
metadata.google.internal,instance-dataand their siblings — are refused by name. They resolve to169.254.169.254on the instance and to nothing anywhere else, so resolving them is exactly what cannot catch them. So are the endpoints that sit outside169.254/16:100.100.100.200(Alibaba Cloud) and192.0.0.192(Oracle). - An IPv6 scope id is stripped before the address is read.
net.isIPaccepts::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/.homesuffixes 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 outside169.254/16were added. If you were relying on this check to keep Mealie off your LAN, that job belongs to Mealie's own egress rules;SECURITY.mdsays 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.0or::, 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_jsonno 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/httpsaddresses, because Mealie performs those fetches from inside its own network. - All instance content is returned behind an explicit untrusted-content marker.