willys-mcp
Plans meals, prices baskets across stores, and fills a Willys cart from recipes — without ever checking out.
Auth (Willys):
willys_login(reuses 24h session,forceto refresh),willys_check_auth,willys_logout.Search & price:
willys_search,willys_search_suggestions,willys_get_product_detail(nutrition, ingredients, origin),willys_find_cheapest— server-side sorting on compare-price with filters (category,mustContain,mustNotContain,excludeBrands,onlySwedish,requireSwedishMeat,needfor total cost).Recipe-driven planning:
plan_add_recipe,plan_set_ingredient,plan_remove_ingredient,plan_remove_recipe,plan_status(proposed basket + cost + pending surplus questions),plan_apply(syncs cart by diff,dryRundefault),plan_clear. Needs are aggregated per ingredient across recipes, with provenance.Cart (Willys):
willys_get_cart,willys_add_to_cart,willys_remove_from_cart.Orders & account (Willys):
willys_get_orders,willys_get_order_details,willys_get_frequent_products(from real order history),willys_get_customer_info.Stores:
willys_find_stores(city/name/postcode),willys_nearest_stores(coordinates),willys_get_store(hours, fees, offers leaflet),willys_refresh_stores.Campaigns:
willys_get_campaigns(store-specific).Setup & helpers:
willys_setup,willys_setup_init(creates .env, mode 600, no password argument),willys_open_file,willys_read_doc(bundled docs only),willys_fetch_url(fetches pages via user's IP, extracts JSON-LD/recipes, renders JS).Safety limits: no checkout tool — orders cannot be placed; cart-mutating plan actions default to dry-run; uncertain matches and unaccepted-surplus rows are held back.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@willys-mcp Plan my cart from these recipes: pancakes, omelette, scrambled eggs."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Ge Claude dina recept. Få rätt varor, i rätt mängd, i din kundvagn — och veta var det är billigast.
En MCP-server för Willys, Hemköp, Coop, City Gross och Mathem som gör svensk matplanering till något du delegerar i stället för något du gör.
Vad det här är
Ingen av de svenska matkedjorna har ett publikt API. Den här servern kartlägger deras faktiska endpoints och exponerar dem som MCP-verktyg — sökning, jämförprisrankning, prisjämförelse mellan kedjor, butiksregister, och för Willys även inloggning, kundvagn och orderhistorik.
Men det intressanta ligger ovanpå: kundvagnen behandlas som en projektion av vad dina recept kräver. Inte som en hög varor någon lagt i en gång. Och samma plan kan prissättas i alla fem kedjor.
Kedja | Sök & pris | Butik | Kundvagn & ordrar |
Willys | ✅ | valfri, samma pris i hela landet | ✅ |
Hemköp | ✅ | valfri, priser per butik | — |
Coop | ✅ | krävs, välj via postnummer | — |
City Gross | ✅ | valfri, smalnar av sortimentet | — |
Mathem | ✅ | finns inte, en nationell prislista | — |
ICA stöds inte: deras API ligger bakom ett botskydd som tillåter ungefär fem anrop per fem minuter, för lite för att planera en korg. Detaljer per kedja: docs/kedjor.md.
Related MCP server: Willys MCP Server
Se den göra något
Du: Här är tre recept för veckan — pannkakor, omelett och äggröra.
Claude: (lägger till recepten, räknar om korgen) Du behöver 13 ägg totalt. Billigast blir ett 15-pack för 36,81 kr — 2 ägg över. Jag har också hittat vispgrädde: 4× 2,5 dl för 43,52 kr täcker dina 8 dl.
Summa 80,33 kr. Vill du att jag lägger det i vagnen?
Du: Ja. Och lägg till en gratäng som behöver 5 dl grädde.
Claude: (behovet ändras, korgen räknas om) Grädden går från 8 till 13 dl. Samma produkt är fortfarande billigast, så jag höjer antalet från 4 till 6 förpackningar — 65,28 kr. Äggen är oförändrade och rörs inte.
Servern har inget kassaverktyg. Den kan fylla kundvagnen men aldrig lägga en order. Det sista klicket är alltid ditt.
Du: Vad kostar pastan i de andra kedjorna?
Claude: (samma plan prissatt i fem kedjor, inget ändras) Willys 61,30 kr · Coop Bäckebol 64,40 kr · Hemköp 69,35 kr · Mathem 70,10 kr · City Gross 89,85 kr. Willys är billigast med 3,10 kr till Coop.
Riktiga priser från 2026-10-14: 500 g köttfärs, 400 g krossade tomater och 500 g spaghetti.
Det svåra problemet: du kan inte köpa 13 ägg
Bara förpackningar av dem. Det låter trivialt och är det inte.
En vanlig prissökning rankar på jämförpris — kr per kilo, kr per liter, kr per styck. Men när mängden är känd är det fel fråga. Det som avgör är vad just ditt behov kostar när du bara kan köpa hela förpackningar.
Strategi | Antal | Får | Över | Kostnad |
6-pack | 3× | 18 st | 5 | 74,70 kr |
10-pack | 2× | 20 st | 7 | 69,80 kr |
24-pack | 1× | 24 st | 11 | 62,00 kr ✅ |
Konstruerat exempel med satta priser, hämtat ur testsviten. I Willys faktiska sortiment löses 13 ägg av ett 15-pack med bara 2 över — men principen är densamma.
24-packet vinner på riktiga pengar trots sämst antal spillda ägg. Servern räknar igenom varje täckningsstrategi och rangordnar på total kostnad.
Men den väljer inte åt dig när avvägningen är verklig. Överstiger billigaste vägen behovet med mer än 50 %, och finns ett märkbart tätare alternativ, returneras en fråga i stället för ett beslut:
Ägg 24-pack är 7,80 kr billigare men ger 11 st över behovet. Ägg 10-pack ger bara 7 st över. Vill du ha det billigare med överskott?
Det är hushållets avvägning, inte serverns. Rader med obesvarad fråga hamnar aldrig i vagnen av misstag.
Hur behoven hålls ihop
flowchart LR
R1["Recept: Pannkakor<br/>6 ägg"] --> L
R2["Recept: Omelett<br/>4 ägg"] --> L
R3["Recept: Äggröra<br/>3 ägg"] --> L
L["Behovsregister<br/><b>13 ägg</b>"] --> C
C["Täckningsmatte<br/>1× 15-pack"] --> D
D{"Diff mot<br/>kundvagnen"} --> A["Byt 6-pack<br/>→ 15-pack"]Varje recepts bidrag lagras separat. Det ger tre egenskaper som en löpande summa inte kan ge:
Härkomst gratis. 13 ägg är inte ett tal utan
Pannkakor:6 + Omelett:4 + Äggröra:3. Claude kan förklara en ändring, inte bara meddela den.Borttagning är en radering, inte ett försök att subtrahera mängder som redan rundats upp till förpackningar.
Omräkning, inte lappning. Hela korgen härleds ur registret varje gång. Det är därför 6-packet byts ut när behovet växer, i stället för att ett till staplas på.
plan_apply skickar bara differensen. Rätt vara i rätt antal ger inget anrop
alls, så det är gratis att köra om efter varje nytt recept.
Verktyg
Nästan alla verktyg tar chain. Claude frågar vilken kedja du handlar i om du
inte sagt det — servern väljer aldrig åt dig.
Installation
npm install
npm run buildKoppla in i Claude Code:
claude mcp add willys --env WILLYS_HOME=$HOME/.willys-mcp -- node /absolut/sökväg/willys-mcp/dist/index.js…eller i Claude Desktop:
{
"mcpServers": {
"willys": {
"command": "node",
"args": ["/absolut/sökväg/till/willys-mcp/dist/index.js"],
"env": { "WILLYS_HOME": "/Users/dittnamn/.willys-mcp" }
}
}
}Sök, priser och jämförelser fungerar direkt, utan konto. För kundvagn och orderhistorik hos Willys skriver du "hjälp mig komma igång med Willys" i en chatt. Claude frågar vilken ort du handlar i, slår upp din butik, skapar konfigurationsfilen och öppnar den åt dig. Det enda du gör själv är att fylla i två rader.
WILLYS_HOME styr var .env och sessionsdatabasen hamnar. Utan den ligger de i
projektmappen, vilket är bekvämt i en checkout men gör att de följer med om
mappen någon gång byts ut.
Fullständig guide: INSTALL.md
För kundvagnen måste ditt Willys-konto ha ettlösenord. BankID går inte att automatisera — servern loggar in genom att fylla i ett formulär. Har du bara använt BankID måste du skapa ett lösenord först, och Claude förklarar hur.
Arkitektur
src/
├── index.ts MCP-entrypoint: transport + wiring (tunn)
├── setup.ts förstagångsdiagnostik
├── tools/ ett verktyg = ett objekt, grupperat per domän
│ ├── registry.ts samlar grupperna, äger dispatch (validering, auth, felgräns)
│ ├── kit.ts Tool-kontrakt, Ctx, defineTool, svarshjälpare
│ ├── session.ts · search-tools.ts · cart-tools.ts · orders-tools.ts
│ ├── stores-tools.ts · setup-tools.ts · planner-tools.ts
│ ├── schema-parts.ts delade Zod-fragment
│ └── json-schema.ts Zod → JSON Schema för manifestet
├── planner/
│ ├── ledger.ts behovsregister med härkomst per recept
│ └── reconcile.ts bygger korgen, diffar mot vagnen
├── chains/ ChainAdapter-kontraktet, registret, delad rangordning och butiksregister
├── domain/ ren matte: volym, täckning, produktmodell, namnmatchning, jämförpris
├── upstream/ HTTP-klienter, en mapp per plattform, en fil per resurs
│ ├── axfood/ Willys + Hemköp (samma API, två värdar)
│ ├── coop/ API-nycklarna läses ur coop.se vid första anropet
│ ├── citygross/
│ └── mathem/
├── auth/ Puppeteer-inloggning, sessionslagring
├── net/ generisk URL-hämtare (fetch_url)
├── core/ http, result, logger, env
└── constants/ endpoints, gränsvärden, instructionsEn adapter per plattform, inte per butik: Willys och Hemköp är samma
Axfood-API, och skillnaden mellan dem är en bas-URL. Rangordning och filter
ligger i chains/ och gäller alla kedjor lika.
domain/ och planner/ledger.ts rör aldrig nätverket. Täckningsmatten och
behovsregistret är rena funktioner mot SQLite — vilket är varför de har riktiga
tester som kör på under en sekund utan att logga in någonstans.
Designprinciper
Fem beslut som formar resten av kodbasen.
Servern gissar aldrig tyst. Filtreras varor bort står det hur många och varför — djurmat, fel namn, ej köpbara. Matchar en kategorifacett ingenting görs omförsök utan den och svaret säger vilka facetter som faktiskt finns. Ett tyst nollresultat är värre än ett fel — det ser ut som att hyllan är tom.
Beslut med verklig avvägning returneras som frågor. Överskott mot pris är
inte serverns val. Samma sak när två recept vill ha 2 st tomat och 400 g tomat: det blir en konflikt att lösa, inte en påhittad omräkning.
Farliga tillstånd görs omöjliga i protokollet. Det finns inget kassaverktyg,
så servern kan inte beställa. plan_apply har dryRun: true som default.
setup_init tar inte emot lösenord som argument — inte som en regel
modellen ska följa, utan för att schemat vägrar. Det är starkare än att skriva
"gör inte så".
Zod validerar på riktigt. MCP-SDK:n validerar inte argument mot schemat en
server annonserar — det är dokumentation, inte kontroll. Varje anrop parsas före
dispatch. Utan det går en påhittad quantity rakt in i kundvagnen, och för
add_to_cart betyder det riktiga varor.
Fel bär en klass hela vägen ut. AUTH_EXPIRED, SHAPE, NOT_FOUND,
SETUP_REQUIRED, UPSTREAM. Modellen kan skilja "sessionen dog" från "kedjan
ligger nere" från "en människa måste redigera en fil", och agera olika på varje.
Servern rangordnar själv. Bara Axfood sorterar rätt på jämförpris. City Gross ignorerar kampanjpris, Mathem kan inte sortera alls, och Coop har jämförpriser som är fel med en faktor tusen. Servern hämtar hela träffmängden, kontrollerar jämförpriset mot pris ÷ förpackning och sorterar.
Fällor som redan kostat tid
Dokumenterade i koden, med datum, så ingen återinför dem.
sort=price:ascrankar en portionsrätt på 16 kr över ett kilo köttfärs på 69 kr. Sortera påcompareprice:asc.Facettfilter ligger i
q, inte i en egen parameter:q=köttfärs:category:Kött. Utan kategorin är billigaste träffen på "köttfärs" en Findus köttfärssås.Fritextsök utan
mustContainger självsäkert nonsens. "torkad dragon" matchade ett torkat grisöra sålt som hundtugg — det var billigast med ordet "torkad".Näringsvärden ligger i
nutritionsFactList, intenutritionFacts(tom sträng på varje produkt som inspekterats).Willys svarar 400, inte 404, för en produktkod som inte finns.
Kundvagnen har ingen delete-endpoint. Borttagning är en add med
quantity: 0.Varje endpoint som innehåller ett Next.js build-id är en tidsinställd bomb. Tre är redan döda och ligger kvar i
DEAD_ENDPOINTSsom varning."köttfärs" hos Coop rankade kattmat först. Den var billigast av allt med kött.
City Gross
totalCounträknar recept som träffar: "köttfärs" ger 101 träffar och 9 produkter.Coop-sök mot en butik utan e-handel ger noll träffar, inte ett fel.
City Gross produktkoder är Axfoods: samma kod är samma vara hos Willys, Hemköp och City Gross.
Vad den inte gör
Beställer inte. Inget kassaverktyg finns.
Fungerar inte i claude.ai i webbläsaren. En webbsida kan inte starta program på din dator. Claude Desktop eller Claude Code.
Stödjer inte BankID.
Stödjer inte ICA.
Har ingen kundvagn för Hemköp, Coop, City Gross eller Mathem än.
Är inte officiell. Endpoints är kartlagda mot produktion och kan sluta fungera utan förvarning.
Utveckling
npm run typecheck # enda verkliga porten före commit
npm run test:coverage # täckningsmatte, inget nätverk
npm run test:planner # behovsregister, inget nätverk
npm run test:volume # 44 verkliga förpackningsformat
npm run test:chains # mappning per kedja, rangordning, djurmat, jämförpriskontroll
npm run test:coop # Coops nyckelhämtning och rotation, med stubbad fetch
npm run test:releases # release notes och tombstones för gamla verktygsnamn
npm run smoke # end-to-end över stdio, kräver sessionKonventioner och fallgropar för framtida ändringar: CLAUDE.md
Endpoint-kartläggning: docs/kedjor.md · Willys i detalj: docs/endpoints.md
Ändringar per version: docs/CHANGELOG.md — AI-klienter läser samma fil via release_notes.
Förbehåll
Ett privat verktyg som automatiserar ett konto du själv äger och läser publika priser. Respektera kedjornas användarvillkor, kör det inte mot konton du inte förfogar över, och undvik anropsvolymer som liknar skrapning.
MIT · LICENSE
Available Tools
31 toolswillys_add_to_cartC
Lägg en produkt i kundvagnen.
Inputs: sessionId (required), productCode (required), quantity.
willys_check_authB
Kontrollera om en session fortfarande är giltig.
Inputs: sessionId (required).
willys_fetch_urlA
Hämta en webbsida från ANVÄNDARENS dator och få tillbaka strukturerad data (JSON-LD, t.ex. schema.org Recipe) plus ren text. Kör detta FÖRST när du behöver läsa en URL — det går via användarens hem-IP och tar sig förbi block som din egen hämtning fastnar på, och det är billigare. Faller din egen webbläsning tillbaka bara om detta misslyckas. Renderar JS-sidor automatiskt vid behov.
Inputs: url (required), render.
willys_find_cheapestA
Sök och rangordna på jämförpris (kr/kg, kr/l, kr/st), sorterat av Willys över hela träffmängden. Returnerar tillgängliga kategorier så du kan smalna av — utan kategori rankas köttfärssås som köttfärs.
Inputs: sessionId (required), need, limit, query, +7 more.
willys_find_storesB
Slå upp butiker på ort, namn eller postnummer. Läser ur lokal cache (hela kedjan, 254 butiker).
Inputs: query (required), limit, onlyClickAndCollect.
willys_get_campaignsB
Butiksspecifika kampanjer och erbjudanden. Ersätter den döda get_offers.
Inputs: sessionId (required), page, size, storeId.
willys_get_cartC
Hämta kundvagnens innehåll.
Inputs: sessionId (required).
willys_get_customer_infoC
Hämta kundprofil.
Inputs: sessionId (required).
willys_get_frequent_productsB
Vanligast köpta varor, härledda ur din faktiska orderhistorik (inte CMS-sidan, som saknar produktdata).
Inputs: sessionId (required), limit, scanOrders.
willys_get_order_detailsC
Alla varurader i en tidigare order.
Inputs: orderId (required), sessionId (required).
willys_get_ordersC
Hämta orderhistorik, sidad.
Inputs: sessionId (required), limit, offset.
willys_get_product_detailC
Detaljerad produktinformation för en artikelkod.
Inputs: sessionId (required), productCode (required).
willys_get_storeB
Detaljer för en butik: öppettider, plockavgift, fri frakt-gräns och länk till butikens erbjudandeblad.
Inputs: storeId (required).
willys_loginA
Logga in på Willys och få ett sessionId tillbaka. Säkert att anropa: servern läser uppgifterna ur .env, de når aldrig din kontext (se read_doc med help/security.md). Återanvänder en giltig session (24h); force=true tvingar ny inloggning.
Inputs: force.
willys_logoutC
Avsluta sessionen.
Inputs: sessionId (required).
willys_nearest_storesB
Närmaste butiker till en koordinat, sorterade på avstånd.
Inputs: lat (required), lng (required), limit, onlyClickAndCollect.
willys_open_fileA
Öppna en lokal fil i användarens standardeditor. Använd för att öppna .env åt dem vid uppsättning — sökvägen står i svaret från willys_setup. En chattklient gör inte filsökvägar klickbara. Läser inte innehållet.
Inputs: path (required).
willys_plan_add_recipeA
Lägg ett recepts ingredienser i behovsregistret. Mängder slås ihop per vara över alla recept i planen, så tre recept som vill ha ägg blir ETT behov. Rör inte kundvagnen — kör willys_plan_apply för det.
Inputs: recipe (required), sessionId (required), ingredients (required), plan.
willys_plan_applyA
Synka kundvagnen mot planen. Skickar BARA differensen: rätt vara i rätt antal ger inget anrop, växande behov byter förpackning eller höjer antalet. dryRun=true (default) visar ändringarna utan att röra vagnen.
Inputs: sessionId (required), plan, dryRun, acceptMatch, +2 more.
willys_plan_clearA
Nollställ planen. Tömmer INTE kundvagnen — den behåller det som redan lagts i.
Inputs: confirm (required), plan.
willys_plan_remove_ingredientA
Ta bort EN ingrediens ur ett recept (t.ex. en skafferivara användaren redan har hemma), utan att röra receptets övriga rader.
Inputs: query (required), recipe (required), plan.
willys_plan_remove_recipeA
Ta bort ett recept ur planen. Behoven räknas om; varor som bara det receptet krävde försvinner ur den föreslagna korgen.
Inputs: recipe (required), plan.
willys_plan_set_ingredientA
Lägg till eller rätta EN ingrediens i ett recept utan att röra de andra. Använd detta för att fixa en felmatchad rad (t.ex. skärpa mustContain) i stället för att skicka om hela receptet med willys_plan_add_recipe.
Inputs: recipe (required), sessionId (required), ingredient (required), plan.
willys_plan_statusA
Visa vad planen kräver, vilken produkt och mängd som föreslås för varje vara, vad det kostar, och vilka val som väntar på svar om överskott. Läser bara — ändrar varken plan eller kundvagn. Kör detta innan willys_plan_apply.
Inputs: sessionId (required), plan, preferTight, acceptSurplus.
willys_read_docA
Läs ett dokument som följer med servern (t.ex. "help/setup.md"). Deterministiskt begränsad till serverns docs/-mapp — kan inte läsa .env, sessioner eller något annat på datorn, så den är säker att anropa fritt. De korta instruktionerna pekar hit för detaljer så de inte belastar varje session. Tillgängliga: help/setup.md, help/security.md, help/search.md, help/planning.md, help/errors.md.
Inputs: path (required).
willys_refresh_storesC
Hämta om butiksregistret från Willys.
Inputs: no named parameters.
willys_remove_from_cartB
Ta bort en produkt ur kundvagnen.
Inputs: sessionId (required), productCode (required).
willys_searchC
Sök produkter. onlySwedish filtrerar på verifierad svensk märkning (labels), inte på varumärke.
Inputs: query (required), sessionId (required), page, size, +2 more.
willys_search_suggestionsB
Sökförslag (autocomplete) för en term.
Inputs: term (required), sessionId (required).
willys_setupA
Kontrollera om servern är konfigurerad på den här datorn och få exakta steg om något saknas, med full sökväg till filen. Kräver ingen session. Kör detta först om willys_login svarar SETUP_REQUIRED. Se read_doc med help/setup.md.
Inputs: no named parameters.
willys_setup_initA
Skapa .env åt användaren och skriv in butiks-id, med rättigheter 600. Returnerar sökvägen till filen. Öppna den sedan med willys_open_file så användaren kan fylla i sina uppgifter.
Inputs: storeId.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 31 tools
The tools span distinct domains (stores, products, cart, orders, planning) with clear purposes. Some pairs like find_stores vs nearest_stores and search vs find_cheapest could be confused, but descriptions clarify the specific use cases. Overall, a capable agent can differentiate most tools without difficulty.
Most tools follow a willys_verb_noun pattern (e.g., willys_get_cart, willys_plan_apply) with snake_case throughout. Minor deviations like willys_search, willys_login, and willys_setup_init don't break the pattern significantly. The naming is predictable and readable.
With 31 tools, the server feels heavy, especially when including meta-tools like willys_fetch_url and willys_open_file that aren't grocery-specific. The broad scope (stores, products, cart, orders, planning, auth, setup) justifies many tools, but 31 is above the typical well-scoped range. It's borderline acceptable.
The tool surface covers the main workflows: store lookup, product search, cart management, order history, customer info, and meal planning. Missing explicit cart quantity updates or recipe listing can be worked around via add/remove and plan status. Overall, no critical dead ends for the core domain.
Maintenance
Related MCP Connectors
The Mercado Pago MCP Server implements the Model Context Protocol to provide AI agents and LLMs with access to Mercado Pago's APIs and tools within compatible development environments. It acts as an intermediary that translates Mercado Pago resources into executable functions (tools) that AI applications can invoke to perform actions and automate flows. The server simplifies integration, enables using documentation to implement or improve code, and optimizes operations through natural language interactions without manual implementations.
One MCP server for the services your AI needs: memory, files, Microsoft 365, kanban and more.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for generating rough-draft project plans from natural-language prompts.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceAn MCP server for programmatically interacting with the Oda grocery shopping platform. It enables users to search for products, browse results, and manage their shopping cart contents through natural language.22-
- AlicenseNot gradedqualityDmaintenanceAn MCP server for Sweden's largest grocery chain Willys. Enables controlling your shopping cart, browsing orders, searching products, and getting AI-powered recommendations from any MCP client.5MIT
- AlicenseAqualityBmaintenanceAn MCP server for Nordic grocery shopping that finds deals across supermarkets in Denmark, Norway, Sweden, and Finland, plans weekly dinners around cheap ingredients, and generates shopping lists grouped by store.1848MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server for grocery shopping at Kroger-owned stores, enabling product search, store finder, cart management, and more through AI assistants.4 npmMIT