magebitcom / magento2-mcp-catalog-tools
Catalog-domain MCP tools for Magebit_Mcp (read + write over products and categories)
Package info
github.com/magebitcom/magento2-mcp-catalog-tools
Type:magento2-module
pkg:composer/magebitcom/magento2-mcp-catalog-tools
Requires
- php: >=8.1
- magebitcom/magento2-mcp-module: ^1.1
- magento/framework: ^103.0
This package is auto-updated.
Last update: 2026-08-19 10:16:05 UTC
README
This is a sub-module for the Magento2 MCP module
Catalog-domain MCP tools for Magebit_Mcp. Exposes catalog products and
categories — paginated reads, single-entity reads with field-resolver-driven
shape, and writes (create / update / delete) wired through Magento service
contracts.
Each tool is a thin wrapper over the corresponding Magento service contract
(ProductRepositoryInterface, CategoryRepositoryInterface,
CategoryListInterface, StockRegistryInterface,
CategoryManagementInterface). Read responses are composed from field
resolvers that 3rd-party modules can extend; writes go through the same
repositories the admin UI uses, so server-side validation and reindex hooks
fire identically.
Install
composer require magebitcom/magento2-mcp-catalog-tools bin/magento module:enable Magebit_McpCatalogTools bin/magento setup:upgrade bin/magento setup:di:compile bin/magento cache:flush
Tool catalog
Products (read)
| Tool | What it does |
|---|---|
catalog.product.list |
Paginated product search; filter by sku (exact / *glob* / array), name substring, status, visibility, type_id, attribute_set_id, price range, qty range, category_id, website_id, created_at range, updated_at range, has_special_price (boolean; presence-only, ignores the special-price date window). |
catalog.product.get |
Single product by numeric id or SKU. Default response includes identity, state, pricing, tier prices, stock, categories (ids + names), websites, media gallery, links, configurable / bundle option metadata, configurable child variants (variants — id, sku, name, price, special_price, status per child), custom attributes, and timestamps; narrow with fields / exclude. |
Categories (read)
| Tool | What it does |
|---|---|
catalog.category.list |
Paginated category search; filter by name substring, is_active, include_in_menu, parent_id, level range. |
catalog.category.get |
Single category by numeric id; tree metadata, content, meta, state, plus the product ids assigned to the category (drop with exclude: ["products"]). Reads at global/default scope unless store_id is given. |
Products (write)
Write tools require the global magebit_mcp/general/allow_writes flag and
the token's own allow_writes flag to be 1. All writes require explicit
confirmation so MCP clients prompt before firing.
| Tool | Confirm? | What it does |
|---|---|---|
catalog.product.create |
yes | Create a product. Required: sku, name, price, attribute_set_id, type_id, status, visibility (plus weight for physical types). Accepts top-level scalars (description, url_key, tax_class_id, meta_*) plus custom_attributes, website_ids, category_ids. Values are saved at global/default scope. |
catalog.product.update |
yes | PATCH-style update by id or sku; only fields you provide are touched. Use new_sku to rename. Saves at global/default scope; pass store_code for a deliberate store-view override. |
catalog.product.delete |
yes | Permanently delete a product. |
Stock (write)
| Tool | Confirm? | What it does |
|---|---|---|
catalog.product.stock.set |
yes | Set stock levels and settings for up to 200 SKUs per call: qty, is_in_stock, manage_stock, backorders, min_qty, notify_stock_qty, min_sale_qty, max_sale_qty. Only the fields you pass per item are changed, and setting one clears its use_config_* flag so the value actually takes effect. Reports per-item success or failure, so one bad SKU doesn't fail the batch. |
catalog.product.create also accepts qty / is_in_stock so a new product
lands sellable in one call.
These stock tools target stores with Multi-Source Inventory (MSI) disabled.
They write Magento's legacy single-stock tables via StockRegistryInterface,
which is the whole inventory model on an MSI-free store.
If MSI is enabled, use
Magebit_McpInventoryTools
instead — inventory.source_item.set addresses a specific source, whereas a
legacy write resolves to the default source only. With that module installed,
every result row from catalog.product.stock.set on a multi-source store
carries a warning saying exactly that. Without it the check assumes
single-source and stays quiet; the seam is
Magebit\McpCatalogTools\Api\SingleSourceModeCheckerInterface.
One MSI quirk worth knowing: while MSI is installed it forces min_qty to 0
on read whenever backorders are enabled (AdaptMinQtyToBackordersPlugin), so a
stored threshold reads back as zero. Without MSI the stored value is returned
as written.
Media (write)
| Tool | Confirm? | What it does |
|---|---|---|
catalog.product.media.add |
yes | Upload an image to a product gallery. content_base64 takes the raw bytes base64-encoded, or an RFC 2397 data: URI. JPEG / PNG / GIF, 8 MB max. |
catalog.product.media.update |
yes | PATCH-style metadata update by entry_id — label, position, disabled, types. The file itself is not replaceable; remove and re-add. |
catalog.product.media.remove |
yes | Permanently remove a gallery entry. The file is deleted, not just unlinked. |
The image type is detected from the decoded bytes, never from the supplied filename — a payload that doesn't decode as a real image is rejected, and the stored extension is derived from the sniffed type. Filenames are stripped of path components before use.
Which types are accepted is decided by Magento's own
Magento\Framework\Api\ImageContentValidator, the same service the gallery
save uses, so a store that widens that allowlist through di.xml widens this
tool with it. Out of the box that means JPEG, PNG and GIF — not WebP.
Known Magento behaviour: catalog.product.media.update goes through
ProductAttributeMediaGalleryManagementInterface::update(), which re-copies the
image file on every call (photo.jpg becomes photo_1.jpg, then
photo_1_1.jpg). The gallery entry keeps its id and points at the newest file;
the previous file is left on disk. This is Magento's behaviour, not this
module's — budget for it if you script bulk metadata updates.
types assigns image roles (image, small_image, thumbnail,
swatch_image). Each role belongs to one image at a time, so assigning it here
removes it from whichever image held it before.
Request size. Base64 inflates a file by roughly a third, and the MCP
endpoint caps request bodies at 256 KB by default. For real product photos raise
Stores → Configuration → Magebit → MCP Server → Max Request Body (KB) (an
8 MB image needs about 11000) and raise the matching web-server limit —
nginx client_max_body_size or Apache LimitRequestBody — otherwise the upload
is rejected before Magento sees it.
Categories (write)
| Tool | Confirm? | What it does |
|---|---|---|
catalog.category.create |
yes | Create a category under an existing parent. Values are saved at global/default scope. |
catalog.category.update |
yes | PATCH-style update by id. Changing parent_id triggers a tree move via CategoryManagementInterface::move() (path / level rebuild); use after_id to control sibling ordering at the destination. Saves at global/default scope; pass store_id for a deliberate store-view override. |
catalog.category.delete |
yes | Permanently delete a category and its descendants. Cascades. |
Every write tool also implements Magebit\Mcp\Api\UnderlyingAclAwareInterface
with Magento_Catalog::products / Magento_Catalog::categories as the
underlying Magento admin resource, so they block calls from admins who
wouldn't be allowed to perform the same action in the admin UI.
Upgrade notes
- Global-scope writes by default.
catalog.product.create/catalog.product.updateandcatalog.category.create/catalog.category.updatenow save attribute values at global/default scope, matching REST/all/V1/products. Previously, calling these tools from the frontend area silently wrote store-view override rows instead of updating the default value.catalog.product.updategained an optionalstore_codeargument (a store view code, or"all"/omitted for global) for intentional store-view overrides;catalog.category.updatekeeps its existing integerstore_idargument for the same purpose (categories are always created at global scope — there is no store-scopedcatalog.category.create). Override rows created by earlier versions of these tools are not repaired automatically — reapply the desired value at global scope (or the correct store view) if you need to clean one up.catalog.product.getreads the resolved store view, so a pre-existing store-view override row will still mask a newly written global value when reading back — such overrides are not repaired automatically. catalog.category.getreads true admin scope by default. Without astore_id, the tool now reads at admin/default scope as documented. One consequence: theproducts.product_idsslice always lists every product assigned to the category, not a store-view-filtered subset — passexclude: ["products"]if you don't need it.catalog.product.listhas a newhas_special_pricefilter — boolean, presence-only (it does not evaluatespecial_from_date/special_to_date).catalog.product.gethas a newvariantsslice for configurable products: id, sku, name, price, special_price, and status for each child.
Extending
See docs/EXTENDING.md for:
- adding a new field to any tool response via
ProductFieldResolverInterface/CategoryFieldResolverInterface; - adding a new filter to
catalog.product.list/catalog.category.listviaProductFilterTranslatorInterface/CategoryFilterTranslatorInterface; - the ACL layering rules for custom write tools.
License
Released under the MIT License.
Magebit - Full-service e-commerce agency