spree_core, so an extension or a host application adds to it without depending on the MCP gem. Anything in it is offered to the admin MCP server and to any other adapter reading the same registry.
Your admin endpoints are already tools
Every documented Admin API action is an agent tool: cancel, approve, send, capture, anything that is not a plain create, update or destroy (those go throughcreate_resource, update_resource and delete_resource). The exceptions are an action its spec opts out, a custom read its spec does not opt in, and the actions of a controller whose records carry credentials (API keys, webhooks, gift cards). Nothing registers a tool. Each is built from the endpoint’s OpenAPI operation:
So adding an endpoint and documenting it is the whole change. A call runs through the endpoint, with its authentication, scope check, record-level permissions and workflow, so an agent gets exactly what an API client gets. Write the summary and description for someone choosing the operation: they are what the model reads.
Core reads its own
admin.yaml. Your application, and each extension, generates the same document from its own rswag specs at openapi/admin.yaml, and Spree merges every one into the document the tools read. Spree’s own API reference and SDKs are built from core’s document only; an app or extension that publishes docs or a typed client builds them from its own file.
Documenting your application’s endpoints
A new project fromcreate-spree-app is set up already: server/spec/swagger_helper.rb and the contract spec come with it. In an older project, the generator adds them:
Documenting your extension’s endpoints
An extension runs the same generator and task from its own directory, against its dummy app:spree/testing_support/extension_rake, which your Rakefile already requires. A gemspec that ships git ls-files includes the committed openapi/admin.yaml already. One that lists its files needs openapi/*.yaml added:
my_extension.gemspec
openapi/store.yaml: pass store_title: to Spree::Api::TestingSupport::Openapi.configure in its spec/swagger_helper.rb, and name the document in those specs with openapi_spec: 'openapi/store.yaml'. A package that ships a client for the extension generates its client and types from these documents with spree sdk generate.
Writing the specs
Document each admin endpoint the way core does:spec/integration/admin/supplier_syncs_spec.rb
admin_operation sets what every admin operation repeats: the content types, the API key and bearer token headers, the scope note in the description, and a signed-in admin and secret key to call it with. A response block overrides either credential with its own let.
request_body_for documents the body from the controller’s request_params declaration, so the tool’s arguments, the API reference and the SDK types all come from the one declaration that also permits the request.
Keeping an action out
An action that should not be offered to agents says so in its spec, with the reason. Irreversible erasure, platform jobs and wizard steps qualify; “not done yet” does not.describe_resource already covers, or file downloads:
The contract spec
spec/agent_tools_spec.rb, which the generator writes, fails when a new admin action lands undocumented, an opt-out lands without its reason, or two actions claim one tool name. Keep it in your suite.
Something that is not an endpoint
A lookup against another system, a report of your own: subclassSpree::AgentTool and register the class by name.
app/tools/my_shop/agent_tools/supplier_stock.rb
server/config/initializers/spree.rb
- Registration is by name, not by class, so your edits to a description or a parameter take effect on the next request rather than needing a restart.
contextis who is asking:context.storeis always the caller’s store, andcontext.principalis the admin or the API key — pass it to anything that records who acted.- A refusal is
{ error: '…' }, not an exception. The model reads that text and corrects itself, so write it as an instruction rather than a stack trace. - Keep the result small. It lands in the model’s context. Return a
summaryline plus the few fields that answer the question, and let the caller ask for more.
permission, so a key that cannot read purchasing never learns supplier_stock exists.
Spree.admin_agent_tools.expose_workflows is deprecated and has no effect: an exposed workflow behind a documented endpoint is already that endpoint’s tool, and one whose endpoint is not in any OpenAPI document is not offered. It is removed in 7.0. Tool names and parameters from 6.0.0 that changed keep answering through 6.0.x, and a call that uses one says what to send instead.Which registry
There is one registry per surface, named the way the authentication strategies are::admin, so registering one in the wrong place raises rather than being discovered when a storefront visitor is offered the back office:
See also
Tool reference
The tools that ship, and the shape yours should follow.
Admin API
The operations the action tools are built from.

