Skip to content

phpClaw for Magento

The Magento adapter is the PhpClaw_Magento module. It adds admin pages under phpClaw, two bin/magento commands, two REST routes, and 15 tools.


RequirementVersion
PHP8.1 or later
Magento2.4
Terminal window
composer require phpclaw/phpclaw-magento
bin/magento module:enable PhpClaw_Magento
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

setup:upgrade creates phpclaw_conversations, phpclaw_messages and phpclaw_memory, and grants the phpClaw ACL resources to every role that already has full admin access.

Set a provider and key on phpClaw → Settings. bin/magento config:set refuses phpclaw/... paths (“path doesn’t exist”) because the module declares no system.xml. Then run:

Terminal window
bin/magento phpclaw:run "check order queue status"

phpClaw in a Magento store

To update the module later:

Terminal window
composer update phpclaw/phpclaw-magento --with-dependencies
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Settings are stored in core_config_data:

PathDefaultNotes
phpclaw/general/provideremptythe provider to use
phpclaw/general/modelemptythe PHPCLAW_MODEL environment variable, then the provider’s default model
phpclaw/general/api_keyemptystored encrypted when saved from Settings, as are cloud_key, cloud_signing_secret and fallback_api_key
phpclaw/general/base_urlemptyfor the Custom provider; must be https:// and not an internal address
phpclaw/general/system_promptemptyadded to every request
phpclaw/general/store_messages1
phpclaw/general/max_iterationsempty, meaning 20
phpclaw/general/remote_skill_urlsemptyHTTPS URLs of skill files
phpclaw/general/cloud_key, cloud_signing_secret, cloud_disableemptyconfigure phpClaw Cloud; the key is used only while store_messages is on
phpclaw/general/fallback_providerempty, meaning offSettings lists only providers with the same tool format as the provider, never Custom
phpclaw/general/fallback_modelemptythe fallback provider’s default model
phpclaw/general/fallback_api_keyemptyhidden in Settings when the fallback provider is off or Ollama
phpclaw/general/rate_limit_rpm0, meaning off0 to 600
phpclaw/general/response_cache01 turns the response cache on
phpclaw/general/response_cache_ttl360060 to 86400; shown in Settings when the cache is on
phpclaw/general/max_token_budget0, meaning off0 to 10,000,000
phpclaw/general/max_tools_per_turn0, the automatic per-turn limit-1 sends every tool; below -1 is stored as -1
phpclaw/general/always_include_toolsemptycomma-separated tool names offered on every turn, outside the limit; a name that is not a tool is skipped, and a denied tool stays denied
phpclaw/security/shell_allowlistemptycomma separated; empty uses core’s default allowlist: ls, pwd, df, wc, date, uptime, hostname, whoami
phpclaw/security/tool_denyemptycomma-separated tool names or groups

With store_messages off, the agent stores no message content. See Memory.

The seven fallback_*, rate_limit_rpm, response_cache* and max_token_budget paths switch on the building blocks. Values are clamped to their ranges when read. The rate limit and the response cache are kept in Magento’s application cache, tagged so a clean of phpClaw’s entries leaves the rest of the cache alone.

max_tools_per_turn and always_include_tools are fields on the Settings page and apply to the CLI commands, the Chat page and the REST API; the MCP server’s tool list does not use them.

If provider is empty, phpClaw core detects one from the server’s environment variables, such as PHPCLAW_PROVIDER or OPENAI_API_KEY, and falls back to anthropic, even when api_key is set. Set the provider explicitly.


etc/acl.xml declares a PhpClaw_Magento::phpclaw parent with one resource per page:

ResourceAllows
PhpClaw_Magento::phpclaw_settingsthe Settings page and Test Connection, and seeing every admin’s conversations
PhpClaw_Magento::phpclaw_chatthe Chat page, both REST routes, and every Magento tool
PhpClaw_Magento::phpclaw_analyticsthe Analytics page
PhpClaw_Magento::phpclaw_guidethe Guide page
PhpClaw_Magento::phpclaw_aboutthe About page

Grant them to a role under System → Permissions → User Roles → Role Resources.

A Magento tool call without the resource returns a FORBIDDEN error. The five core tools, shell_exec, http_request, file_read, code_search and project_info, check no Magento resource and run for any caller that reaches the agent.

The adapter treats a run as the console only when PHP runs from the command line and the area is none of adminhtml, webapi_rest, webapi_soap, graphql, frontend and crontab. That is bin/magento. In the console the tool check is skipped and conversations store no owner. Cron is not the console: its Magento tools run the ACL check, and with no admin user they refuse; the core tools run without a check.


All 15 are registered on every run. Agent runs (admin chat, REST, phpclaw:run) also get core’s load_skill, because core’s discovered skills, at least php_best_practices, are always registered; phpclaw:mcp-server serves the 15 only:

GroupTools
Magentomagento_products, magento_inventory, magento_categories, magento_stores, magento_report, magento_cache, read_log, magento_orders, magento_customer, db_query
Coreshell_exec, http_request, file_read, code_search, project_info
ToolDoes
magento_productsfetches a product by SKU or searches products
magento_inventoryshows stock for a SKU, or lists products at or below a stock threshold
magento_categorieswalks the category tree: top level, children of a category, or products in one
magento_storeslists websites, store groups and store views as a hierarchy
magento_reportsales report for a date range: revenue, orders, average order value or top products
magento_cacheshows each cache type’s state; it never flushes
read_logreads the last lines of var/log/system.log, or var/log/exception.log when system.log does not exist
magento_ordersfetches an order by increment id, or lists orders by status and date
magento_customerlooks up a customer by email or id, or counts customers
db_queryruns one read-only SELECT

See Tools for the core tools. The adapter builds shell_exec (with phpclaw/security/shell_allowlist) and http_request itself, since core marks both opt-in. The allowlist matches the first word of the command exactly, case included. file_write and file_edit are not registered: core marks them opt-in, and the adapter asks core only for its default tools. A module can add them through the phpclaw_extra_tools event, as a tool object it builds or as a class name, which is built with no constructor arguments.

db_query runs one read-only SELECT, adds LIMIT 500 when the query has no limit, and refuses the core_config_data, phpclaw_conversations and phpclaw_messages tables. Modules keep credentials such as API keys in core_config_data, and no phpClaw tool reads that table.

GroupTools
group:commercemagento_products, magento_orders, magento_customer, magento_inventory
group:catalogmagento_categories, magento_stores, magento_report
group:systemmagento_cache, db_query, read_log

phpclaw/security/tool_deny has no Settings field, and bin/magento config:set refuses the path for the same reason.

Every agent is built with CliApprovalGate. In a terminal you are asked before a tool changes something; in the admin and the REST API there is nobody to ask, so the change is refused. See Security: approval. In the admin and the REST API that refusal covers any shell_exec command on the allowlist but outside core’s read-only command list, and every call to a tool that changes data, such as file_write or file_edit, when a module has added one. The MCP server does not use the approval gate; its tools/list marks tools that can change data with readOnlyHint: false, destructiveHint: true.


Terminal window
bin/magento phpclaw:run "check order queue status"
bin/magento phpclaw:run "summarise today's orders" --stream
bin/magento phpclaw:run "and yesterday?" --conv-id=01JQABCDE...
bin/magento phpclaw:run "and the day before?" --continue --trace
bin/magento phpclaw:chat
bin/magento phpclaw:mcp-server

phpclaw:run takes --stream, --conv-id, --continue and --trace. phpclaw:chat opens an interactive session and takes --conv-id and --continue. phpclaw:mcp-server serves the tools over stdio; see MCP.

After each reply phpclaw:run prints the provider, model, tokens, the tools that ran and the conversation id, then conv-id=<id>. --conv-id=<id> continues that stored conversation; an id with no stored conversation prints a warning and starts a new one. --continue picks the last conversation, whose id is kept in var/phpclaw/.last-conversation. That is one file per install, so every console user on the server continues the same conversation. --trace prints one line per tool call, the tool name plus a short path or command, never the result.

bin/magento phpclaw:chat keeps one conversation open, streams each reply and prints the tool calls as they run. It needs a terminal: with piped input it refuses and exits 1. A line starting with / is a command and is never sent to the agent:

CommandWhat it does
/helpList these commands
/toolsList the active tools
/model <name>Switch the model for this session only
/provider <slug>Switch the provider for this session only
/newStart a new conversation
/convPrint the conversation id
/quietTurn the tool trace off or on
/clearClear the screen
/exitLeave; Ctrl+D also leaves

While the agent works, a Thinking... spinner shows until the first word of the reply arrives. It hides before each tool call is traced or approved and shows again while the model works after it. It is not shown when the output is not a terminal, and without the pcntl extension a still Thinking... line is printed instead of the animation.

On leaving it prints bin/magento phpclaw:chat --conv-id=<id> to resume. /model and /provider never write the admin settings.


Both routes need an admin bearer token whose user holds PhpClaw_Magento::phpclaw_chat:

Terminal window
curl -X POST "https://example.com/rest/V1/integration/admin/token" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"..."}'
MethodRouteBodyReturns
POST/rest/V1/phpclaw/sendmessagetext, provider, model, tokens, iterations
POST/rest/V1/phpclaw/chat/streammessage, optional conversationIdServer-Sent Events
Terminal window
curl -X POST "https://example.com/rest/V1/phpclaw/send" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"message":"how many orders came in today?"}'

send starts a new conversation on every call. An empty message is a 400, a guard block 422, and any other failure 500.

A spent token budget answers 422 with “Token budget reached for this run.” and a rate limit 429 with “Rate limit reached, try again shortly.” chat/stream sends tool_before, tool_after, chunk, then done or error; for those two limits the error event carries the message and a code of 422 or 429. Naming another admin’s conversation is refused before the stream starts.


Each conversation stores its creator’s admin user id in phpclaw_conversations.admin_user_id: from the admin session, or from the token for REST calls. Conversations created in a console area store 0. An admin sees and opens only their own conversations; opening another’s is refused with 403. Admins holding PhpClaw_Magento::phpclaw_settings see every conversation.

Model/IdentityResolver reads the backend session and, for REST and SOAP calls, Magento’s user context, which it accepts only for admin tokens. Both routes refuse any other caller, such as an integration token, with 403 before the agent runs. admin_user_id is written on insert and is not in the update columns, so a later save cannot change the owner. The index is PHPCLAW_CONV_USER_NS_UPDATED in etc/db_schema.xml. Analytics caches its counts under a key that includes the acting admin id, or all for the manage-all tier.

The agent always uses the database-backed router memory, RouterMemory: the conversations namespace goes to ConversationMemory, everything else to ResourceMemory and phpclaw_memory.

Inject PhpClaw\Magento\Factory\PhpClawFactoryInterface and call create(), which returns a fully configured PhpClaw\Contracts\ClawInterface. The module declares the preference in etc/di.xml. The module does not register PhpClaw\Claw itself, and building one by hand skips the settings, memory and Magento tools.

use PhpClaw\Magento\Factory\PhpClawFactoryInterface;
final class OrderSummary
{
public function __construct(private readonly PhpClawFactoryInterface $phpClawFactory) {}
public function summarise(): string
{
return $this->phpClawFactory->create()->send('Summarise today\'s orders.')->text;
}
}

A module registers additions by observing these events and adding entries to the transport object:

EventAdds
phpclaw_extra_toolstools; a tool with the same name as a built-in replaces it
phpclaw_extra_skillsskills
phpclaw_extra_guardsguards
phpclaw_extra_hookslifecycle listeners
phpclaw_extra_memorymemory drivers
phpclaw_extra_providersproviders

Every lifecycle event is also dispatched as a Magento event named phpclaw_ followed by the event name with dots replaced by underscores, such as phpclaw_agent_after, with the context under data. The event names are listed on the Hooks page.