grocy-mcp
Provides tools for managing a Grocy instance: checking stock levels, tracking expiring products, managing the product catalog, and handling shopping lists, with safeguards against invalid operations.
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., "@grocy-mcp check my pantry for items expiring this week"
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.
grocy-mcp
An MCP server for Grocy, so an AI assistant can read and manage your pantry: what's in stock, what's going off, what needs buying, and what you just used.
27 tools covering stock, the product catalog and the shopping list. Every call reads live from Grocy — there is no cache to go stale.
You: what's going off this week, and can I make something with it?
...
You: right, I used the last of the coconut milk and two of the tomatoes
...
You: add coconut milk to the shopping listInstall
Not on PyPI yet — install from the repo:
pip install git+https://github.com/anishanilkumar/grocy-mcp # stdio only
pip install 'grocy-mcp[http] @ git+https://github.com/anishanilkumar/grocy-mcp'The http extra adds PyJWT and cryptography, needed only to verify OAuth
bearer tokens when serving over HTTP. A stdio server needs neither.
You need a Grocy API key: in Grocy, wrench icon → Manage API keys → add.
export GROCY_API_URL=https://grocy.example.com/api
export GROCY_API_KEY=...Related MCP server: Unofficial AnyList MCP Server
Use it from a local client
Most MCP clients launch the server themselves over stdio. For Claude Desktop,
add this to claude_desktop_config.json:
{
"mcpServers": {
"grocy": {
"command": "grocy-mcp",
"env": {
"GROCY_API_URL": "https://grocy.example.com/api",
"GROCY_API_KEY": "your-api-key",
"GROCY_MCP_CONFIG": "/path/to/pantry.toml"
}
}
}
}GROCY_MCP_CONFIG is optional — see Configuration.
Tools
Stock
Tool | |
| Everything on the shelf, filterable by location or category |
| Expired or due within N days, worst first |
| Products at zero. Needs no minimum levels |
| Products under a configured minimum |
| The individual batches making up a total, with their own dates |
| Last bought, last used, average shelf life, spoil rate, minimum |
| The journal: what was bought, used, opened or corrected |
| Record a purchase |
| Record use, or something thrown away |
| Mark a pack opened without consuming it |
| Set the amount to what you actually counted, either direction |
| Move stock between locations |
| Fix one batch's date, shelf or amount |
| Reverse a stock transaction |
Catalog — search_products, get_conventions, create_product,
update_product, delete_product, add_barcode, remove_barcode
Shopping list — list_shopping_list, add_to_shopping_list,
remove_from_shopping_list, check_off_shopping_item,
add_missing_to_shopping_list, clear_shopping_list
It refuses rather than guessing
Most of the value over raw API calls is in what these tools won't do. Grocy will happily take stock negative or move a batch out of a shelf it isn't on; an agent that does so is very hard to notice afterwards.
An ambiguous product name raises with the candidates listed, instead of picking one. A wrong guess silently moves the wrong product's stock.
Consuming, opening or transferring more than is on hand is refused.
A transfer with stock split across shelves refuses until you say which shelf.
Changing a product's unit while it holds stock is refused — Grocy would reinterpret the existing amount in the new unit.
A barcode already belonging to another product is refused.
Adding a misspelled product to the shopping list is refused rather than quietly written as a free-text note that can never be matched back to stock.
Deleting a product that still has stock is refused.
Every stock write returns a transaction_id, so mistakes get undone properly
rather than cancelled out with an opposite booking that leaves both rows in the
journal and invents a best-before date.
Configuration
Optional, and only for things Grocy has no field for. Locations, categories and units are always read live from your instance, so they are never configured here and cannot drift.
What you can configure is the advice: what each location is for, how long things keep when the package has no date, how you name products. That is what makes an agent's guesses good, and it is different in every kitchen.
[pantry]
summary = "Household inventory for a two-person kitchen."
soon_days = 7
expiry_guidance = """
Best-before estimates when the package date is unknown:
fresh veg ~1 week frozen ~2 months whole spices ~3 years
"""
[pantry.location_notes]
"Fridge" = "Perishables: dairy, eggs, opened jars"
"Freezer" = "Frozen items, meat, fish"location_notes is a fallback: a location's own description in Grocy wins
where one is set, so the advice can be edited in the web UI and cannot be
orphaned by a rename.
If the instance also tracks durable possessions — tools, cables, documents —
alongside the food, list the consumable categories under food_categories.
Anything not listed counts as non-food, so a pantry view can leave the drill
out. Unset (the default) means everything is food.
See examples/pantry.toml for every option. Point at
it with GROCY_MCP_CONFIG=/path/to/pantry.toml or --config.
With no config file the server still works — it just describes your instance without opinions about it.
Serving over HTTP
For a remote client (e.g. a Claude custom connector) rather than a local one. Bind to loopback and put a reverse proxy in front to terminate TLS.
grocy-mcp --transport http --public-host grocy-mcp.example.comAuthentication is required by default over HTTP, because a write-capable server without it is the kind of default nobody notices until it is reachable from somewhere it shouldn't be. Tokens are validated locally against the issuer's published keys — no introspection call, so a public PKCE client needs no secret here.
export GROCY_MCP_OIDC_ISSUER=https://auth.example.com/realms/home
export GROCY_MCP_OIDC_AUDIENCE=grocy-mcp # usually the client id
export GROCY_MCP_OIDC_SCOPES=mcp # optional, space separatedThe JWKS endpoint is discovered from the issuer. Set
GROCY_MCP_OIDC_JWKS_URI if your provider doesn't publish standard discovery
metadata — Kanidm, for instance, serves per-client keys
at <issuer>/public_key.jwk, which this tries as a fallback.
--no-auth exists for a server on an interface nothing untrusted can reach.
Be sure that's true before using it.
The SSE response must not be buffered, and the timeouts need raising:
location /mcp {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
}
# RFC 9728 protected-resource metadata, served at the path-suffixed location.
location /.well-known/oauth-protected-resource/mcp {
proxy_pass http://127.0.0.1:8765;
}Two things are easy to get wrong here. The metadata lives at the
path-suffixed location (…/oauth-protected-resource/mcp), not the bare one.
And --public-url must equal the URL exactly as the client has it configured,
path included, or the metadata is rejected as not describing this server.
Requirements
Python 3.11+, and Grocy 4.x. Developed against 4.6; every endpoint used is checked against the instance's own OpenAPI spec.
Development
pip install -e '.[http,dev]'
pytestThe tests run against an in-memory fake Grocy, so they need no instance and no network. They assert on the request bodies the tools send, not just their return values.
License
MIT
Available Tools
27 toolsadd_barcodeA
Attach a GTIN/EAN barcode to a product.
Once attached, that code resolves to this product everywhere a tool takes a product argument, so a scanned package can be consumed or restocked without typing a name.
Args: product: Product name or description fragment. barcode: The full GTIN/EAN as printed under the bars.
Inputs: barcode (required), product (required).
add_missing_to_shopping_listA
Add everything below its minimum stock level to the shopping list.
Grocy's own "add missing products". It only sees products that have a minimum set (update_product's min_stock), so in an instance where few do, read out_of_stock and add what's wanted by name instead — that needs no minimums.
Inputs: no named parameters.
add_stockA
Record newly bought stock, and report the new total.
Args: product: Product name, description fragment, or barcode. Must identify exactly one existing product — this does not create new products. amount: How much, in the product's own stock unit. best_before_date: YYYY-MM-DD. Use the package date when it is known; otherwise estimate it, and see get_conventions for whatever estimates this pantry uses. location_id: Where it is being put, if not the product's usual spot — either its id or its exact name.
Inputs: amount (required), product (required), best_before_date (required), location_id.
add_to_shopping_listA
Add something to the shopping list.
If item names a catalog product the row is linked to that product, which
is what lets a later add_stock match it up. If it names nothing the pantry
tracks, the row is added as a free-text note instead — right for kitchen
roll, wrong for a misspelled product name, so this refuses when the text
looks close to an existing product rather than quietly writing a note.
Args: item: Product name, description fragment, barcode, or free text. amount: How much to buy, in the product's stock unit. note: Optional extra text on the row, e.g. "the big tin".
Inputs: item (required), note, amount.
below_min_stockA
List products that have fallen below their minimum stock amount.
This is Grocy's own "missing products" calculation, so it only sees products that have a minimum set (update_product's min_stock). In an instance where few products have one, an empty result means "no minimums are set" at least as often as it means "nothing is low" — out_of_stock is the one that works regardless.
Inputs: no named parameters.
check_off_shopping_itemA
Mark a shopping list row as bought (or un-mark it).
Checking off does not add anything to stock — call add_stock for that, with the real best-before date from the package.
Args: item: Product name or note text, as shown by list_shopping_list. done: False to un-check a row checked off by mistake.
Inputs: item (required), done.
clear_shopping_listA
Clear the shopping list after a shop.
Args: done_only: True (the default) removes only the rows checked off, leaving anything still to buy. Pass False to wipe the list completely — that discards unbought items too.
Inputs: done_only.
consume_productA
Record that stock was used up, and report what is left.
Args: product: Product name, description fragment, or barcode. Must identify exactly one product — this raises rather than guessing between two. amount: How much, in the product's own stock unit. Items tracked by weight need the weight, so "a teaspoon of turmeric" is roughly amount=5, not 1. Check list_stock's qty first if unsure. spoiled: True when it was thrown away rather than eaten.
Inputs: product (required), amount, spoiled.
correct_stockA
Set stock to the amount actually counted on the shelf.
This is the "I looked, and there are three" fix, for when the tracked amount has drifted from reality — someone used something without recording it, or a purchase was entered twice. It books the difference either way, so it can correct upward as well as down. To record ordinary use, prefer consume_product; to record a purchase, prefer add_stock.
Args: product: Product name, description fragment, or barcode. actual_amount: The amount really there, in the product's stock unit. Zero is allowed and clears the product's stock. best_before_date: YYYY-MM-DD. Required when correcting upward, since the extra stock is a new entry and needs a date. location_id: Where the corrected stock is, if not the usual spot.
Inputs: product (required), actual_amount (required), location_id, best_before_date.
create_productA
Add a new product to the catalog, for something never stocked before.
This only creates the product record — call add_stock next to record an amount on hand. Refuses rather than guessing if the name already matches an existing product; use add_stock for those instead. A near-duplicate name (e.g. "Applesauce" vs an existing "Apple Sauce") does not block creation, but comes back in possible_duplicates — check that list before assuming the new product is really new.
Args: name: Product name. See get_conventions for any naming rule this pantry follows. category: Product group name, exact match. See get_conventions. location_id: Usual storage location — either its id or its exact name. unit: Stock/purchase/consume unit name, e.g. "Piece", "Pack", "Gram". Must already exist in Grocy; see get_conventions. description: Short description identifying the exact item — brand, size, variant. min_stock: Optional minimum to keep on hand, in the same unit. Setting it is what makes below_min_stock and add_missing_to_shopping_list work for this product.
Inputs: name (required), category (required), location_id (required), unit, +2 more.
delete_productA
Permanently remove a product with zero stock from the catalog.
Refuses if any stock is on hand — consume_product it down to zero first. This is for cleaning up mistakes (a test product, a typo'd duplicate caught by search_products or create_product's possible_duplicates), not for retiring a product still in use.
Args: product: Product name, description fragment, or barcode. Must identify exactly one product.
Inputs: product (required).
edit_stock_entryA
Fix one stock entry: its date, its location, or its amount.
For correcting a best-before date that was estimated wrong, or a batch filed on the wrong shelf. Get the entry_id from list_stock_entries. Only the arguments given are changed; the rest of the entry is preserved.
Args: entry_id: From list_stock_entries. best_before_date: YYYY-MM-DD. location_id: Either an id or an exact location name. amount: New amount for this entry, in the product's stock unit. Prefer correct_stock for "the total is wrong" — this is for when one specific batch is wrong.
Inputs: entry_id (required), amount, location_id, best_before_date.
expiring_soonA
List items already expired or due within N days, worst first.
Args: days: Look-ahead window in days. Defaults to the configured window.
Inputs: days.
get_conventionsA
This instance's locations (with ids), categories, quantity units, and whatever house rules are configured — the reference every other tool's docstring points at.
Call this once at the start of a session rather than guessing or trial-and-erroring an id. Locations, categories and units are read live, so this stays right even after something is renamed in the web UI.
Inputs: no named parameters. Returns: result
list_shopping_listA
Show the shopping list: what to buy, how much, and what's checked off.
Rows either point at a catalog product or are free-text notes for things the pantry doesn't track. Checked-off rows stay on the list until clear_shopping_list removes them.
Inputs: no named parameters.
list_stockA
List everything currently in the pantry, read live from Grocy.
This is always current — it queries Grocy on every call, so there is no need to check a timestamp or work around a cache.
Args: location: Optional case-insensitive substring, e.g. "fridge", "freezer". Omit for everything. category: Optional product-group substring, e.g. "spices", "dairy".
Inputs: category, location.
list_stock_entriesA
List a product's individual stock entries — one per purchase batch.
list_stock shows the total; this shows what that total is made of, since each batch carries its own best-before date and location. Call this to get an entry_id before edit_stock_entry, or to see which pack expires first.
Args: product: Product name, description fragment, or barcode.
Inputs: product (required).
open_productA
Mark stock as opened, without consuming it.
Opened items usually keep for less time than the printed best-before date, so flagging them helps expiring_soon stay useful.
Args: product: Product name, description fragment, or barcode. amount: How many units were opened, in the product's stock unit.
Inputs: product (required), amount.
out_of_stockA
List products with zero stock — candidates for the next shopping trip.
This is "there is none left at all", and it needs no minimum stock levels to be set. For products that still have some but have fallen under a configured minimum, use below_min_stock.
Inputs: no named parameters.
product_detailsB
Everything Grocy knows about one product: stock, dates, and history.
Includes when it was last bought and last used, its average shelf life and spoil rate, the amount currently open, and its minimum stock level — the context for deciding whether to restock something or stop buying it.
Args: product: Product name, description fragment, or barcode.
Inputs: product (required).
remove_barcodeA
Detach a barcode that was attached to the wrong product.
Args: barcode: The code to remove. It is removed from whichever product currently holds it.
Inputs: barcode (required).
remove_from_shopping_listA
Take a row off the shopping list entirely.
For something added by mistake or no longer needed. If it was bought, prefer check_off_shopping_item (and add_stock), which leaves a record that the trip covered it.
Args: item: Product name or note text, as shown by list_shopping_list.
Inputs: item (required).
search_productsA
Find products by name, description, or barcode — across the whole catalog, not just what's currently on the shelf.
A miss means the product genuinely doesn't exist in Grocy. A hit's in_stock flag says whether there's actually any on hand right now — a hit with in_stock: false is a known product sitting at zero, not a false positive. Check this before create_product to catch an existing or near-duplicate product, and before add_stock/consume_product when unsure how something is named.
Args: query: Case-insensitive substring, or a full GTIN/EAN barcode.
Inputs: query (required).
stock_historyA
What has been bought, used, opened or corrected recently.
Grocy's stock journal, newest first. Use it to answer "what did we get through this week", to check a suspicious amount, or to find the transaction_id of something that needs undoing.
Args: days: How far back to look. Defaults to a month. product: Optional — restrict to one product. limit: Maximum rows returned, newest first. Defaults to 100.
Inputs: days, limit, product.
transfer_stockA
Move stock from one location to another — freezer to fridge, and so on.
This changes where something is, not how much there is. Both locations take either an id or an exact name.
Args: product: Product name, description fragment, or barcode. to_location: Where it is going. amount: How much is moving, in the product's stock unit. from_location: Where it is coming from. Optional when the product is only stored in one place; required when its stock is split, since guessing would move the wrong batch.
Inputs: product (required), to_location (required), amount, from_location.
undo_transactionA
Reverse a stock transaction that should not have been recorded.
Every write tool returns a transaction_id; stock_history shows older ones. Undoing is the right way to fix a mistake — booking an opposite consume or purchase instead leaves both entries in the history and gets the best-before dates wrong.
Args: transaction_id: From a write tool's response, or from stock_history.
Inputs: transaction_id (required).
update_productA
Edit an existing product's catalog record.
For fixing a typo, recategorising something, moving its default shelf, or setting a minimum stock level. Only the arguments given are changed. This does not touch stock amounts — use add_stock, consume_product or correct_stock for those.
Args: product: Product name, description fragment, or barcode identifying the product to edit. name: New name. description: New description — brand, size, variant. category: New product group, exact name. See get_conventions. location_id: New default location, id or exact name. unit: New stock unit. Refused while any stock is on hand, since Grocy would reinterpret the existing amount in the new unit — 2 Packs silently becoming 2 Grams. Consume to zero first, or create a separate product. min_stock: Minimum to keep on hand, in the product's stock unit. Zero clears it. This is what below_min_stock and add_missing_to_shopping_list read.
Inputs: product (required), name, unit, category, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 27 tools
Every tool targets a distinct resource/action, and overlapping pairs (out_of_stock vs below_min_stock, remove_from_shopping_list vs check_off_shopping_item, correct_stock vs edit_stock_entry) are explicitly cross-referenced so an agent can choose correctly. The stock and shopping-list surfaces are dense, but the descriptions make boundaries unambiguous.
Most tools follow a readable verb_noun style (list_stock, add_stock, create_product, clear_shopping_list) with consistent snake_case. A few status queries are named as noun/adjective phrases rather than verbs (out_of_stock, below_min_stock, expiring_soon, product_details, stock_history), which is a minor deviation from the dominant pattern.
27 tools is on the heavy side and pushes past the comfortable 15-25 range, which can be a lot for an agent to navigate. The breadth is largely justified by Grocy's domain—catalog, stock batches, barcodes, and shopping list—but the set could have been tightened by merging some stock mutations or status queries.
The surface covers product CRUD, barcode management, stock lifecycle (add/consume/open/correct/transfer/edit/undo), stock queries, and shopping-list lifecycle with no dead ends. Two-step flows like create_product then add_stock, or check_off then add_stock, are explicitly documented.
Maintenance
Related MCP Connectors
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for Gainium — manage trading bots, deals, and balances via AI assistants
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
MCP server for AI dialogue using various LLM models via AceDataCloud
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables interaction with Grocy's API through MCP, allowing management of grocery inventory, shopping lists, and household tasks via natural language.121 npm28MIT
- AlicenseNot gradedqualityAmaintenanceMCP server that integrates with AnyList for managing shopping lists, recipes, and meal planning via natural language.35ISC
- AlicenseNot gradedqualityDmaintenanceMCP server for MealMastery AI meal planning that enables users to manage meal plans, recipes, and grocery lists through natural language conversation with AI agents like Claude.57 npmMIT
- AlicenseNot gradedqualityDmaintenanceMCP server for grocery-related web automation using Playwright, enabling AI assistants to interact with grocery websites.ISC