In short: The official PHP SDK for MCP, still experimental, can serve a Magento 2.4.9 catalogue to Claude Code or Codex as read-only tools. Magento's product permission also allows writes, so read-only must come from the server: a separate PHP project that sends signed GET requests to a fixed list of routes.
An MCP server lets an AI client such as Claude Code, Codex or Claude Desktop ask your systems questions through tools you define. For a Magento store, the obvious first job is the catalogue: find a product, read its details, check how many can be sold. Since late 2025, PHP has had an official software development kit (SDK) for building such servers, so the work can stay in the language the store is already written in.
This guide explains how to build a read-only catalogue and stock server for Magento Open Source or Adobe Commerce 2.4.9 with that SDK: where the server should run, which Magento credentials and permissions it needs, how the SDK turns PHP methods into tools, which transport to choose and how to test the result with MCP Inspector. It is written as an explanation rather than a code listing. Each step is described closely enough to build from, or to hand to a coding agent as a specification.
One thing it is not: a way to sell through AI assistants. An MCP server that answers catalogue questions is not a checkout, and it does not implement a commerce protocol. The iLogr guide Agentic Commerce Protocols Explained: ACP vs UCP vs MCP for WooCommerce, Shopify and Magento covers that difference.
Field note (test): A small catalogue server built with SDK v0.8.1 on PHP 8.4, using canned product data in place of Magento, was checked with raw JSON-RPC calls and MCP Inspector 2.9.0. The SDK rejected an out-of-range limit before the method ran, returned a disabled product as an error result the model could read, and over HTTP refused an unknown host name with a 403 while accepting a request with no credential at all. A Composer dry run against a stock Magento 2.4.9 project added 14 packages and changed none. No Magento API call was made, so the Magento findings below come from its source code.
What is the official PHP MCP SDK, and is it ready?
The PHP Foundation announced the SDK in September 2025 as a collaboration with Anthropic's MCP team and the Symfony project, with the Symfony team as lead maintainers and Kyrian Obikwelu, author of the earlier PHP-MCP library, among the maintainers.[1] It lives at modelcontextprotocol/php-sdk on GitHub and installs from Packagist as mcp/sdk. Its README credits PHP-MCP and the Symfony AI initiative as its starting point.[2]
Four facts matter before you depend on it.
- It is experimental. The README says that until the first major release the SDK "is considered experimental".[2] This guide was checked against v0.8.1, published on 29 August 2026. Version 0.9.0 followed on 18 October 2026, with more breaking changes, including to the HTTP host check and to authorisation.[3] Before 1.0, the project's own tier document says any version digit can carry a breaking change, and each one is marked "[BC Break]" in the changelog.[4] The 0.8.0 entry alone lists several.[5]
- The server side is the mature half. An official audit on 19 August 2026 placed the SDK in Tier 3, the experimental tier of the MCP project's SDK classification. The same document reports server conformance at 100% (67 of 67 scenarios) and client conformance at 20%, almost entirely because of OAuth client scenarios.[4] A catalogue server uses the server half.
- It needs PHP 8.1 or later and the
fileinfoextension.[6] Its CI runs on PHP 8.1 to 8.5, so any PHP version Magento 2.4.9 supports is covered.[7] - It speaks both protocol generations. MCP's current revision, 2026-07-28, removed the opening handshake and protocol-level sessions, so each request carries its own version and capabilities.[8][9] Older clients still open with the handshake used up to revision 2025-11-25. In v0.8.1, one HTTP endpoint can serve both generations, but STDIO serves only the older handshake era; v0.9.0 adds both eras over STDIO.[3] The tools you register are the same for either.[10]
In practice: pin an exact SDK version in Composer, read the changelog before every upgrade, and expect to change a few lines when you do.
Should you build a Magento MCP server or buy one?
Adobe documents a Commerce MCP for Adobe Commerce, aimed at agentic experiences "from discovery to checkout", but Experience League still badges it "Coming Soon".[11] Third-party MCP modules for Magento already exist, for example on Packagist.[12] This guide does not review them.
Building your own makes sense when you want a small, fixed set of read-only questions that you understand completely, and when you would rather own a few hundred lines of PHP than audit someone else's module with admin access to your store. The rest of this guide assumes that choice. If you buy instead, use the later sections as a checklist for the module you are evaluating, starting with these questions:
- Which permissions does its integration ask for, and is that more than product and stock reads?
- Does it register any tool that can create, change or delete data, and can you switch those tools off?
- How does its HTTP endpoint authenticate callers, and does it rely on a bare bearer token?
- Which product fields does it send to the model, and does it leave out disabled products?
- Which MCP SDK version does it pin, and how quickly does it follow a new release?
Where should a PHP MCP server for Magento run?
There are two places to put the server, and the choice decides how much of the store it can reach.
A separate PHP project that calls Magento's web API. The server is its own small Composer project, started by the AI client or run as its own web endpoint. It reaches Magento only through REST (or GraphQL) with a credential you issue for it. It never sees app/etc/env.php, the database password or the encryption key. Upgrading Magento does not touch the SDK, and upgrading the SDK does not touch Magento.
A Magento module. The server runs inside Magento and calls its service contracts directly: the product repository for products, and the Inventory modules' salable quantity service for stock. There is no HTTP hop and no web API credential. The SDK supports this style: handlers can receive their dependencies through a container, either its own basic one or any PSR-11 container.[13] The price is that the MCP server runs with the whole application's privileges, so a mistake in a tool is a mistake inside Magento.
Dependency conflicts are not the deciding factor. In a test, a Composer dry run that added the SDK to a stock Magento 2.4.9 project locked 14 new packages and changed none of the existing ones. That was one project, and the SDK's churn is the bigger issue: every SDK upgrade would become a change to your Magento deployment.
For a first catalogue server, the separate project is the better default. The rest of this guide describes that design and notes where a module would differ.

How do you keep a Magento MCP server read-only?
The word "read-only" can sit in three places in this design. Only one of them is a security control.
In the tool metadata. MCP tools can carry annotations, including readOnlyHint, which means the tool "does not modify its environment". The specification calls every annotation a hint, says it is not guaranteed to describe the tool faithfully, and says clients should not make tool-use decisions on annotations from untrusted servers.[14][15] Clients do use them, though. Codex has an approval mode, writes, that prompts only for tools not marked read-only.[16] So set readOnlyHint to true only on tools that really cannot change anything. A false hint does not just mislead the model; it can remove a person's chance to say no.
In the Magento permission. This is where Magento surprises people. Its web API permissions are ACL resources, and for products one resource covers reading and writing alike. In Magento 2.4.9, Magento_ guards GET, POST, PUT and DELETE on the products routes.[17] The legacy stock item routes work the same way: Magento_ guards both reading a stock item and updating it.[18] A credential that can list products can also delete them.
Magento's Inventory modules (the multi-source inventory system) are split more usefully. In Magento Inventory 1.2.9, the release tagged on the same day as Magento 2.4.9, the salable quantity routes use Magento_ and have no write methods at all, and reading sources needs Magento_ while changing them needs Magento_.[19][20] Adobe's REST documentation describes the salable quantity call, which takes a SKU and a stock ID.[21]
In your server code. Because the product permission cannot be made read-only, the guarantee has to come from the server. Its Magento client should be able to send GET requests to a short, fixed list of routes and nothing else: the product search, the single product read, the salable quantity check and, if the server has to work out which stock serves a website, the stock resolver, which sits under the same read-only inventory resource.[19] It should not accept a route, method or request body from the model. A generic "call any Magento endpoint" tool turns your careful permission choices into whatever the model decides to send.
Put together, read-only here means: the narrowest ACL resources that make the reads work, a client that can only issue those reads, and annotations that tell the truth about both.
A storefront alternative
If the questions are ones a shopper could answer from the website, there is a route with no admin credential at all. Magento's GraphQL products query is the storefront's own catalogue search, with full-text search, filters and paging.[22] Products carry a stock_status of in stock or out of stock.[23] The server then sees only what the storefront shows, which is usually the right boundary for an assistant.
Check one field before relying on that boundary. Magento 2.4.9 also has a quantity field on products, described as "Amount of available stock". Its resolver returns nothing while the Not Available Message setting (Stores, Configuration, Catalog, Inventory, Stock Options) has its default value, "Not enough items for sale". Switch that setting to "Only X of Y available" and the field returns the salable quantity to anyone who queries the storefront.[23][24][25][26][27] Check that setting before assuming that stock levels stay private.
Which Magento credential should the server use?
For the separate-project design, create an integration under System, Extensions, Integrations, and grant it only the resources the reads need. Adobe's integration guide points to each module's etc/acl.xml file for the list of resources.[28] For the three tools in this guide that means Magento_, shown in the resource tree as Products under Catalog and Inventory, and Magento_, shown as Sales Stock under Stores and Inventory.[29][30]
Activating an integration produces four values: a consumer key and secret, and an access token and secret. Integration tokens last until they are revoked. Since Magento 2.4.4 the access token cannot be used on its own as a bearer token by default. Adobe explains that a never-expiring token gives "undetected persistent access" if it leaks, and that switching the old behaviour back on is possible but not recommended.[31][32] Sign requests with all four values instead. Magento's scheme is OAuth 1.0a with the HMAC-SHA256 signature method, so check that any OAuth library you use supports that method.[33] Adobe's documentation shows the signing done without the full OAuth handshake.[31] Admin user tokens are not a good fit either: they expire after four hours by default.[31]
Keep the four values in the server's environment, never in a tool result or a log line the model can read. And do not take a token from the AI client and forward it to Magento. The MCP security guidance calls this token passthrough, an anti-pattern that the authorisation specification explicitly forbids, because the downstream API ends up trusting a token that was never issued for your server.[34] The client authenticates to your MCP server, if it authenticates at all. Your server authenticates to Magento with its own credential.
Which tools should a Magento catalogue server expose?
Three tools cover most catalogue questions, and a small surface is easier to reason about than a large one.
- search_products takes a search phrase and a result limit, and returns SKU, name, price and status for each match.
- get_product takes one SKU and returns the fields an assistant needs to describe the product: name, short description, price, status, visibility and the attributes you choose.
- get_salable_quantity takes a SKU and returns the salable quantity for the stock that serves the relevant website.
How the SDK turns a method into a tool
With the SDK, a tool is an ordinary public method on a PHP class, marked with the #[McpTool] attribute. The tool name defaults to the method name, the description comes from the method's docblock, and the input schema is generated from the parameter types.[35][36] The attribute can also set the name, a title and the annotations, including readOnlyHint.[36] To have the server find attributed classes itself, also install symfony/finder; without it the SDK logs a warning and skips discovery, so attributed tools are never registered. Point setDiscovery() at the project directory and the directory holding your handler classes, and exclude vendor. Discovery is lazy by default: it runs on the first request that needs the list of tools, not at start-up, and setLazyLoading(false) moves it to start-up. Alternatively, register each handler explicitly with addTool().[35]
The server script that clients start ties this together. It loads Composer's vendor/autoload.php, creates a builder with Server::builder(), sets the server name and version with setServerInfo(), configures discovery or the explicit handlers, calls build(), and then calls run() with a StdioTransport, passing the exit code that run() returns to PHP's exit().[35]
Three details from the test are worth knowing, because they change what the model sees.
- The docblock is the prompt. Its summary line became the tool description word for word, and
@paramdescriptions appeared as parameter descriptions in the schema. Write them for the model: say what the tool returns, what it will not return, and what a SKU looks like in your store. - Limits belong in the schema. A plain integer parameter produced a schema with a type and a default but no range. With the SDK's
#[Schema]attribute on the parameter, the minimum and maximum appeared in the schema, and a call asking for 500 results was rejected with a JSON-RPC invalid-parameters error before the method ran.[37] Cap the limit there, and cap it again in the method, because the method is the last line of defence. - Errors for the model are a separate exception. Throwing the SDK's
ToolCallExceptionproduced a normal result flagged as an error, carrying your message, so the model can correct itself; any other exception becomes a JSON-RPC error with a generic message.[36] Use the first for "no enabled product with that SKU", and let real faults stay generic.
What to return
Return associative arrays. The SDK sends an associative array as structured content as well as text. A bare list (an array of products with no key around it) counts as structured content only on revision 2026-07-28, so wrap lists in a key such as "items" if older clients will connect.[36]
Choose the fields deliberately. Magento's product API returns everything the credential can see, which can include cost, supplier attributes and disabled products. Map each product to the handful of fields the assistant needs, and drop disabled products unless the tool exists to find them. Set a page size on every search, and consider Magento's own input limits as a backstop: when enabled, they cap paginated REST and GraphQL results at 300 items per page by default, but they are off unless you turn them on.[38]
Treat what comes back as data the model will read, not as instructions. A product description is text written by whoever edits the catalogue, and it reaches the model unfiltered. The earlier iLogr article Prompt Injection in CMS and RAG Systems explains why that matters even for read-only tools.
The SDK also supports resources, which are read by the application rather than called by the model, and resource templates with a variable in the URI, such as a product addressed by SKU.[35][39] They are a reasonable addition once the tools work. Start with tools, because tools are what coding clients such as Claude Code and Codex call.

STDIO or HTTP: which transport should you use?
The SDK offers two ways to connect, and they lead to very different security work.
STDIO, for a client on the same machine
With the STDIO transport, the AI client starts the server as a child process and talks to it over standard input and output.[35] Claude Code adds such a server with claude mcp add, where everything after a double dash is the command that starts the server, and --env passes environment variables.[40] Codex does the same with codex mcp add, or with an [mcp_servers] table in its config.toml file.[16] Give the absolute path to the server script.
Two rules apply. Nothing may write to standard output except the protocol, so a stray echo or var_dump() in a handler corrupts the stream; log to standard error instead.[35] And the credential lives on the machine running the client, so this suits a developer's workstation or a support laptop, not a shared service.
HTTP, for a shared endpoint
The HTTP transport is a PSR-7 request handler rather than a web server. It needs a PSR-17 implementation, a way to emit the response, and a persistent session store for clients that use the older handshake, because PHP keeps no state between requests.[41][42][10] It can sit behind your normal web server or inside a framework route.
The test showed two things to plan for.
- It refuses unknown origins and host names by default. In v0.8.1, the transport's default middleware checks the Origin hostname when that header is present, and otherwise the Host header, against an allowlist containing only localhost variants.[41] It does not check both together: a request with an allowed Origin passes whatever its Host says, so configure your web server or reverse proxy to reject unexpected Host headers as well. Version 0.9.0 closes that gap: it always validates Host, then accepts a supplied Origin only if it matches the request's own origin or the separately configured allowed origins.[3] On the test server, a request with the host
mcp.shop.examplegot "403 Forbidden" with the message "Invalid Host header", and a request with a foreign Origin got the matching Origin message. Add your real host name to the allowlist when you deploy; do not remove the check. - It has no authentication by default. The test server answered an initialisation request with no credential at all. The SDK provides OAuth 2.1 middleware that validates bearer tokens as a resource server, but it is not an authorisation server and does not issue tokens; you need an identity provider such as Keycloak, Auth0, Microsoft Entra ID or Okta for that.[43]
For a first version, STDIO is the simpler and safer choice. Move to HTTP when several people need the same server, and budget for the identity provider when you do.
How do you test a PHP MCP server with MCP Inspector?
MCP Inspector is the MCP project's reference tool for testing servers. It ships as one package with three clients: a web interface, a command-line client for scripts and continuous integration, and a terminal interface. It needs Node 22.19.0 or later and runs through npx without installation.[44] Point it at the command that starts your server, or at the URL of an HTTP server.[45]
In the test, Inspector 2.9.0's command-line client listed the fixture server's tools, with their schemas and annotations, and called the salable quantity tool, receiving both the structured result and its text form. The fixture returned canned data in place of Magento, so the test covered the SDK side only.
Before connecting a real client, check these against a local Magento install with sample data, never against production:
- The tool list contains exactly the tools you meant to expose, with accurate descriptions and annotations.
- Each schema carries its limits, and out-of-range values are rejected before the handler runs.
- Disabled products, and products not assigned to the website, do not appear in search results.
- An unknown SKU produces an error result with a useful message, not a stack trace.
- With the integration's permissions reduced, the tools fail cleanly rather than returning partial data.
- No result contains a field you meant to drop, such as cost or a supplier attribute.
The command-line client makes these repeatable. Run them in continuous integration, and again after every SDK or Magento upgrade.
What not to claim
A server like this resembles parts of the commerce protocols: it has a catalogue lookup and a stock check. That resemblance is not conformance. Do not describe it as a UCP or ACP implementation, publish a UCP business profile for it, or suggest that it makes the store sellable in ChatGPT or Gemini. Conformance means implementing a specification's operations and rules as written, and being accepted by the agents that use it.
Equally, it is not a store management tool. If you later need writes, such as updating stock or changing prices, treat that as a new project with its own permissions, its own approval step and its own tests. The earlier iLogr articles WordPress MCP Server: Least-Privilege Abilities for Claude Code and Codex and WooCommerce MCP After 10.9: Moving AI Agents to the Built-in Abilities work through the same questions for those two platforms.
What should you check before connecting a real store?
- Pin the SDK to an exact version, and read the changelog before every upgrade.
- Run the server as a separate project unless you have a strong reason to run it inside Magento.
- Issue an integration with only
Magento_andCatalog:: products Magento_, remembering that the first also allows writes.InventorySalesApi:: stock - Sign requests with the four integration values; leave standalone bearer tokens switched off.
- Hard-code the GET routes the client may call. Accept no route, method or body from the model.
- Filter every product down to the fields the assistant needs, and drop disabled products.
- Put limits in the schemas and again in the methods.
- Mark tools
readOnlyHintonly because the points above make it true. - Start with STDIO. Before moving to HTTP, keep the host check, add your domain to it and put real authentication in front.
- Test with MCP Inspector against a local install, then keep those tests running.
References
Numbers in square brackets in the text link to these sources.
- The PHP Foundation: Announcing the Official PHP SDK for MCP (5 September 2025) (opens in a new tab).Used for: who is behind the SDK: the PHP Foundation, Anthropic’s MCP team and Symfony.
- MCP PHP SDK v0.8.1: README (opens in a new tab).Used for: package name, the experimental wording and credits for the SDK’s origins.
- MCP PHP SDK: releases (v0.8.1, 29 August 2026; v0.9.0, 18 October 2026) (opens in a new tab).Used for: release dates for v0.8.1 and v0.9.0 and the breaking changes in v0.9.0.
- MCP PHP SDK v0.8.1 documentation: SDK tier target (SEP-1730) (opens in a new tab).Used for: breaking changes before 1.0, the Tier 3 audit and server conformance figures.
- MCP PHP SDK v0.8.1: CHANGELOG (opens in a new tab).Used for: the breaking changes listed in the 0.8.0 changelog.
- MCP PHP SDK v0.8.1 documentation: Installation (opens in a new tab).Used for: SDK requirements: PHP 8.1 or newer and the fileinfo extension.
- MCP PHP SDK v0.8.1: CI pipeline (PHP version matrix) (opens in a new tab).Used for: the PHP versions the SDK’s own CI tests, 8.1 to 8.5.
- Model Context Protocol: Versioning (opens in a new tab).Used for: the current MCP revision and that each request declares its version.
- Model Context Protocol specification, version 2026-07-28: key changes (opens in a new tab).Used for: removal of protocol-level sessions and the initialize handshake.
- MCP PHP SDK v0.8.1 documentation: Protocol versions (opens in a new tab).Used for: the protocol eras the SDK serves and where sessions still apply.
- Adobe Experience League: Commerce MCP overview (opens in a new tab).Used for: Adobe’s Commerce MCP status and its Coming Soon badge.
- Packagist: angeo/module-mcp-server (opens in a new tab).Used for: an example showing that third-party Magento MCP modules exist (not reviewed).
- MCP PHP SDK v0.8.1 documentation: Server builder (opens in a new tab).Used for: how tool handlers receive dependencies from a PSR-11 container.
- Model Context Protocol specification, version 2026-07-28: schema reference (ToolAnnotations) (opens in a new tab).Used for: what readOnlyHint means and why annotations are only hints.
- Model Context Protocol specification, version 2026-07-28: Tools (opens in a new tab).Used for: the same warning about trusting tool annotations in the 2026-07-28 revision.
- OpenAI: Codex MCP documentation (opens in a new tab).Used for: Codex’s writes approval mode and how a server is added to Codex.
- Magento 2.4.9 source: Magento_Catalog etc/webapi.xml (opens in a new tab).Used for: one Magento permission covering GET, POST, PUT and DELETE on products.
- Magento 2.4.9 source: Magento_CatalogInventory etc/webapi.xml (opens in a new tab).Used for: stock item read and update sharing one permission.
- Magento Inventory 1.2.9 source: InventorySalesApi etc/webapi.xml (opens in a new tab).Used for: the salable quantity routes, their GET-only methods and the stock permission.
- Magento Inventory 1.2.9 source: InventoryApi etc/webapi.xml (opens in a new tab).Used for: the separate inventory source read and edit permissions.
- Adobe Commerce developer documentation: Check salable quantity (REST inventory) (opens in a new tab).Used for: the salable quantity call and its SKU and stock ID inputs.
- Adobe Commerce developer documentation: products query (GraphQL) (opens in a new tab).Used for: the storefront products query with search, filters and paging.
- Magento 2.4.9 source: Magento_
CatalogInventoryGraphQl etc/schema.graphqls (opens in a new tab) .Used for: the GraphQL stock_status and quantity fields in 2.4.9. - Magento 2.4.9 source: CatalogInventoryGraphQl QuantityResolver.php (opens in a new tab).Used for: how quantity returns salable stock or null depending on one setting.
- Magento 2.4.9 source: Magento_CatalogInventory etc/adminhtml/system.xml (opens in a new tab).Used for: where the Not Available Message setting lives and its label.
- Magento 2.4.9 source: Magento_CatalogInventory etc/config.xml (defaults) (opens in a new tab).Used for: the default value of that setting.
- Magento 2.4.9 source: CatalogInventory NotAvailableMessage.php (option labels) (opens in a new tab).Used for: the option labels of that setting.
- Adobe Commerce developer documentation: Create an integration (opens in a new tab).Used for: how to create an integration and where its permissions are listed.
- Magento 2.4.9 source: Magento_Catalog etc/acl.xml (opens in a new tab).Used for: where the Products permission sits in the permission tree.
- Magento Inventory 1.2.9 source: InventorySalesApi etc/acl.xml (opens in a new tab).Used for: where the Sales Stock permission sits in the tree.
- Adobe Commerce developer documentation: Token-based authentication (opens in a new tab).Used for: the four integration values, token lifetime and the bearer-token default.
- Adobe Commerce PHP documentation source: backward-incompatible changes, 2.4.3 to 2.4.4 (system configuration) (opens in a new tab).Used for: the Magento release that introduced the bearer-token setting, 2.4.4.
- Adobe Commerce developer documentation: OAuth-based authentication (opens in a new tab).Used for: Magento’s OAuth 1.0a signing scheme with HMAC-SHA256.
- Model Context Protocol specification, version 2026-07-28: Security best practices (token passthrough) (opens in a new tab).Used for: why passing a client’s token through to another API is forbidden.
- MCP PHP SDK v0.8.1 documentation: First server (opens in a new tab).Used for: how the SDK builds a tool from an attribute, a docblock and types.
- MCP PHP SDK v0.8.1 documentation: Tools (opens in a new tab).Used for: tool attributes, ToolCallException handling and structured content rules.
- MCP PHP SDK v0.8.1 documentation: Schema generation and validation (opens in a new tab).Used for: schema constraints on tool parameters.
- Adobe Commerce developer documentation: Input limiting (opens in a new tab).Used for: the page-size limit Magento applies when input limiting is on.
- MCP PHP SDK v0.8.1 documentation: Resource templates (opens in a new tab).Used for: resource templates with URI variables.
- Claude Code Docs: Connect Claude Code to tools via MCP (opens in a new tab).Used for: how Claude Code adds a STDIO server.
- MCP PHP SDK v0.8.1 documentation: HTTP transport (opens in a new tab).Used for: what the HTTP transport needs and its default host and origin check.
- MCP PHP SDK v0.8.1 documentation: Framework integration (opens in a new tab).Used for: why the HTTP transport needs a persistent session store in PHP.
- MCP PHP SDK v0.8.1 documentation: Authorization (opens in a new tab).Used for: the SDK’s OAuth 2.1 resource server role and what it leaves to an identity provider.
- Model Context Protocol: MCP Inspector (opens in a new tab).Used for: what MCP Inspector is and its Node requirement.
- MCP PHP SDK v0.8.1 documentation: Try it with the Inspector (opens in a new tab).Used for: pointing Inspector at a server command or an HTTP URL.