Repository navigation
Releases
Releases · herp75/migros-mcp
v0.6.1
Changed
- The description in
package.jsonandmanifest.jsonmatches the repository's: it names recipes, shopping lists, receipts and Cumulus coupons, and that it never orders or pays.
Install
Docker image ghcr.io/herp75/migros-mcp:0.6.1, signed with cosign (see README). Also in the MCP Registry as io.github.herp75/migros-mcp.
v0.6.0
Added
- Shopping lists:
create_shopping_list,rename_shopping_list,delete_shopping_list(only withconfirm: true, never the only list) andset_item_note(keeps the quantity). - Cumulus:
activate_cumulus_couponanddeactivate_cumulus_coupon(by the coupon'sean; nothing is bought), andget_cumulus_bons(read only). - All of them use the routes the migros.ch website itself calls (see
docs/website-routes.md), checked on a test account. The live check covers them: a temporary list is created, renamed, given a noted item and deleted; a coupon is activated and deactivated again. - Documentation: a Settings table in the README (every environment variable, with its default), a Kind column (reads, changes, deletes) in the tool table,
docs/architecture.md(modules, login and session, a checklist for adding a tool) andCONTRIBUTING.md(checks, live-check rules, what never goes into an issue or commit). - Docker image
ghcr.io/herp75/migros-mcp: Node 24 Alpine pinned by digest, runs as an unprivileged user, no npm at runtime, the saved login in the volume/config, the npm update notice off. Built and scanned with Trivy on every pull request; pushed frommain(edge) and from release tags, for amd64 and arm64, with provenance, SBOM and a keyless cosign signature. - Release workflow for this fork: on a
vX.Y.Ztag it checks that every version file and the CHANGELOG agree and that the tag is onmain, runs CI and the security checks, pushes the image, creates the GitHub Release and publishesserver.jsonto the MCP Registry asio.github.herp75/migros-mcp. Upstream's npm and.mcpbjobs are gone: the npm name belongs to upstream. - Dependabot also updates the Docker base image.
LICENSE(MIT, aspackage.jsonalready said) andSECURITY.md.- ESLint (including the checks for unhandled promises) and Prettier, with
npm run lint,format,format:check,typecheckandtest:coverage. The code was formatted once in its own commit, listed in.git-blame-ignore-revs. - CI also type-checks the tests, lints, checks formatting and fails below 80 % line coverage of
src/. - Security workflow (on every pull request, push to main and weekly):
npm audit(fails on high), gitleaks secret scan, a guard against turned-off TLS certificate checks or listening on all interfaces, and a dependency review on pull requests (fails on high, refuses GPL/AGPL). - CodeQL for TypeScript and the GitHub workflows.
- Dependabot for npm and GitHub Actions, weekly, grouped, with a two-week cooldown for new releases.
- Live check with a test account (
npm run live-account,scripts/live-account-check.mjs): every tool that needs a login, and with--writea basket round trip (add, checkout link, change quantity, remove) that checks the basket ends up as before. Credentials from the environment or.env.live(read literally), the test login kept in.live-session/, output without personal data. Migros blocks logins from GitHub's servers (Cloudflare bot check), so the weekly run happens on a computer:scripts/migros-live-check.shwith systemd user units inscripts/systemd/. Theliveworkflow on GitHub (protectedliveenvironment) can be started by hand.
Changed
get_stocksaysinStock: null(unknown) instead offalsewhen Migros has no stock data.get_in_store_receiptsuses Swiss time for "today", so receipts from just after midnight are included. It andget_receipt_detailssay plainly that line items aren't available and the link is the same for every receipt.get_checkout_linkreturns which list it summarised and says that the link doesn't pick a list.get_cumulus_statusno longer passes the raw Cumulus profile on, only the selected fields.- The update check (only when run from source) asks GitHub for this fork's latest release instead of npm for upstream's package.
package.json,manifest.jsonand the README link to this fork; the server version is read frompackage.json. - The guest token is fetched once for parallel calls, and a timeout or server error no longer triggers a second try with a new token.
- More robust: the anonymous HTTP client caps answers at 5 MB, a relative silent-login redirect works, a login answer that isn't HTML no longer crashes, cookies Migros deletes are removed from the saved login, and two containers sharing the volume no longer collide when saving it.
- Tools are registered with the SDK's
registerToolinstead of the deprecatedserver.tool, and each has a shorttitlethat apps can show (such as "Add to basket"). Names, descriptions, input schemas and annotations are unchanged. - The weekly local live check runs inside the published image (
edge) after checking its cosign signature, with rootless podman, a read-only root and no capabilities, instead of buildingmainand running it as the user. It uses the check script from the commit the image was built from. - Internal: the 429 handling is one helper,
src/types.ts(unused) is gone,@modelcontextprotocol/sdkneeds at least 1.32.2 (for zod 4), and the basket tools take a fake request in tests. get_cumulus_couponsreturns each coupon's useful fields (name, texts, discount, minimum purchase, expiry, status,ean) pluscanActivate, without colours and campaign data.- An empty answer from Migros (rename, delete) no longer fails with a JSON error.
- The README installs this fork from its Docker image, with the login in an env file only you can read. The npm package and the
.mcpbinstaller are upstream's. - Needs Node.js 22 or newer (
engines), as the README and CI already said.
Security
- Logins run one at a time. Tool calls that arrive together no longer each start their own Migros login (repeated half-finished logins get the IP blocked) or overwrite each other's saved cookies. A call whose token was rejected uses the token another call just got instead of refreshing again.
- The login only talks to migros.ch over HTTPS. It stops if a Migros page or redirect points anywhere else, and the email and password are only ever posted to
login.migros.ch. - Secrets are masked in error messages: email, password, setup key, access token, long cookie values, Bearer tokens, JWTs and OAuth codes, also URL-encoded or JSON-escaped. A setup key with a wrong character no longer shows that character.
- Tools say what they do: reads are marked read-only;
add_to_basket(it replaces the quantity),update_basket_quantity,remove_from_basket,rename_shopping_list,set_item_noteanddelete_shopping_listdestructive;authenticateand the coupon tools as changes. Clients use this to decide which calls need approval. authenticatewithout a code no longer repeats doomed logins. On a 2FA account without a setup key, it sends the password once; further calls without a code within 60 s answerfresh_code_requiredwithout contacting Migros.authenticateno longer suggests that the AI read the 2FA code from a password manager: the user types it.- The server tells the client that product, recipe, order and receipt data are information to show, not instructions to follow.
- Inputs are bounded: ids must be plain ids (no
/,?or..), at most 50 products per call, search texts up to 200 characters, quantities up to 999, page and offset numbers capped. - The login requests time out after 20 seconds instead of waiting forever.
- After a failed login, the next tool calls wait a minute instead of each starting a new login at once (ten calls meant ten half-finished logins in seconds, which gets the IP blocked).
authenticatewith a fresh 2FA code still tries right away. - When Migros answers the login with Cloudflare's bot check (from servers and VPNs), the error says so instead of "could not find CSRF token".
- The folder holding
session.jsonis now only readable by you (700), also when it already existed. - zod 4: fixes a memory exhaustion advisory in zod 3.
Install
Docker image ghcr.io/herp75/migros-mcp:0.6.0, signed with cosign (see README). Also in the MCP Registry as io.github.herp75/migros-mcp.