solidus_mcp 0.2.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +52 -0
  3. data/CONTRIBUTING.md +31 -0
  4. data/LICENSE +27 -0
  5. data/README.md +355 -0
  6. data/app/assets/stylesheets/solidus_mcp/admin.css +284 -0
  7. data/app/controllers/concerns/solidus_mcp/http_access.rb +33 -0
  8. data/app/controllers/solidus_admin/mcp_administrators_controller.rb +91 -0
  9. data/app/controllers/solidus_admin/mcp_connections_controller.rb +132 -0
  10. data/app/controllers/solidus_mcp/authorizations_controller.rb +40 -0
  11. data/app/controllers/solidus_mcp/endpoint_controller.rb +61 -0
  12. data/app/controllers/solidus_mcp/metadata_controller.rb +23 -0
  13. data/app/controllers/solidus_mcp/registrations_controller.rb +48 -0
  14. data/app/controllers/solidus_mcp/tokens_controller.rb +23 -0
  15. data/app/models/solidus_mcp/administrator.rb +35 -0
  16. data/app/models/solidus_mcp/administrator_change.rb +8 -0
  17. data/app/models/solidus_mcp/connection_policy.rb +53 -0
  18. data/app/models/solidus_mcp/credential.rb +36 -0
  19. data/app/models/solidus_mcp/policy_change.rb +9 -0
  20. data/app/services/solidus_mcp/client_registration.rb +94 -0
  21. data/app/services/solidus_mcp/price_update.rb +42 -0
  22. data/app/services/solidus_mcp/serializers.rb +49 -0
  23. data/app/tools/solidus_mcp/tools/base.rb +37 -0
  24. data/app/tools/solidus_mcp/tools/bulk_update_variant_prices.rb +48 -0
  25. data/app/tools/solidus_mcp/tools/create_product.rb +42 -0
  26. data/app/tools/solidus_mcp/tools/get_inventory_levels.rb +26 -0
  27. data/app/tools/solidus_mcp/tools/get_order.rb +29 -0
  28. data/app/tools/solidus_mcp/tools/get_order_status.rb +21 -0
  29. data/app/tools/solidus_mcp/tools/get_product.rb +21 -0
  30. data/app/tools/solidus_mcp/tools/get_product_variants.rb +24 -0
  31. data/app/tools/solidus_mcp/tools/get_variant_prices.rb +25 -0
  32. data/app/tools/solidus_mcp/tools/search_inventory.rb +27 -0
  33. data/app/tools/solidus_mcp/tools/search_orders.rb +28 -0
  34. data/app/tools/solidus_mcp/tools/search_products.rb +27 -0
  35. data/app/tools/solidus_mcp/tools/update_product.rb +35 -0
  36. data/app/tools/solidus_mcp/tools/update_variant_price.rb +27 -0
  37. data/app/views/solidus_admin/mcp_administrators/_account_search.html.erb +161 -0
  38. data/app/views/solidus_admin/mcp_administrators/index.html.erb +67 -0
  39. data/app/views/solidus_admin/mcp_connections/_permission_selection.html.erb +51 -0
  40. data/app/views/solidus_admin/mcp_connections/_permissions.html.erb +54 -0
  41. data/app/views/solidus_admin/mcp_connections/edit.html.erb +59 -0
  42. data/app/views/solidus_admin/mcp_connections/index.html.erb +48 -0
  43. data/app/views/solidus_mcp/authorizations/new.html.erb +43 -0
  44. data/config/locales/en.yml +4 -0
  45. data/config/routes.rb +34 -0
  46. data/db/migrate/20261023000000_create_solidus_mcp_connection_policies.rb +26 -0
  47. data/db/migrate/20261024000000_add_solidus_mcp_oauth_attributes.rb +17 -0
  48. data/db/migrate/20261024010000_add_deleted_at_to_solidus_mcp_connection_policies.rb +7 -0
  49. data/db/migrate/20261108000000_create_solidus_mcp_administrators.rb +21 -0
  50. data/docs/administration.md +121 -0
  51. data/lib/solidus_mcp/administration.rb +90 -0
  52. data/lib/solidus_mcp/configuration.rb +36 -0
  53. data/lib/solidus_mcp/connection_scopes.rb +25 -0
  54. data/lib/solidus_mcp/engine.rb +27 -0
  55. data/lib/solidus_mcp/oauth.rb +44 -0
  56. data/lib/solidus_mcp/policy.rb +48 -0
  57. data/lib/solidus_mcp/registry.rb +69 -0
  58. data/lib/solidus_mcp/server.rb +28 -0
  59. data/lib/solidus_mcp/tool.rb +63 -0
  60. data/lib/solidus_mcp/version.rb +5 -0
  61. data/lib/solidus_mcp.rb +57 -0
  62. data/lib/tasks/solidus_mcp.rake +9 -0
  63. metadata +189 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9309d2faa1dc3fb43625241d6d56734bdf7ad99605a910714cb1241e6da551b7
4
+ data.tar.gz: a2e9c1d6ac448bb64d97134652c353cb85becdd602c39e2370b6fc70bcd2624e
5
+ SHA512:
6
+ metadata.gz: b6d6e4a456c1415d09edd064c6bb35c884d867d75fddb82aa58fb077d972b9d68d538bdddaef97e8b16f8d293d461e30f1fde75ec85519ba5f19d59d4b538329
7
+ data.tar.gz: 8f56da97fc37e7df7ac216bf9397a13df4219644759e37df50bccfab42b5c63110bd8173eb82f2f2adac964e8f7ccaec8c930206706040de8febe65af2c68214
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0.rc1 (2026-11-08)
4
+
5
+ First public release candidate. The earlier registry implementation was unpublished.
6
+
7
+ - Support Rails 7.2 and 8.0 on Ruby 3.2 or newer, with explicit Rails component
8
+ dependencies and CI coverage for both Rails versions, with and without
9
+ Solidus Admin. Use Rails 7.2-compatible migrations and registration rate limits.
10
+
11
+ - Add environment-designated MCP superadministrators and audited, gem-owned
12
+ MCP-admin assignments. MCP admins manage ordinary users' grants; only
13
+ superadministrators can assign admins or manage privileged connections.
14
+ Solidus role checkboxes do not grant MCP-admin authority. Run the new engine
15
+ migration and configure superadministrator login emails before deployment.
16
+ Add an accessible account-search dropdown for assigning and restoring admins.
17
+ See [MCP administration hierarchy](docs/administration.md).
18
+
19
+ - Add draft product creation with an overridable shipping-category default,
20
+ record-level authorization, and structured MCP results.
21
+ - Isolate test caches so registration throttles do not leak between suite runs.
22
+
23
+ - Make the self-contained engine the canonical implementation; retire the
24
+ unpublished host registry API and compatibility instructions.
25
+ - Offer explicit read/write access review on the consent screen for clients
26
+ without scope controls, preserving registration limits and PKCE.
27
+
28
+ - Add audited permission-set deletion, a deleted-sets view and explicit restoration.
29
+ - Bundle scoped admin styles and permission tables with category and global
30
+ selection controls, without requiring host Tailwind configuration. Preserve
31
+ connection owners during theme changes and highlight AI connections in navigation.
32
+
33
+ - Replace resource/operation maps with an automatically registered catalog of
34
+ thirteen built-in tools and additive custom tool classes.
35
+ - Add standard product, pricing, inventory-read and order-read implementations
36
+ with explicit serializers and record authorization.
37
+ - Add per-entry bulk pricing using normal Solidus validation and row locks.
38
+ - Own the authenticated Streamable HTTP endpoint, OAuth metadata, registration
39
+ and consent integration while retaining Doorkeeper token/PKCE handling.
40
+ - Use independent `mcp:read` / `mcp:write` scopes and unique default-deny tool
41
+ permissions; support standard OAuth insufficient-scope challenges.
42
+ - Retain transactional permission auditing, optimistic locking and SolidusAdmin
43
+ controls, grouped by tool category with scope diagnostics.
44
+ - Add direct gem migration task, real Solidus integration coverage and actual
45
+ development/production loading checks.
46
+ - Breaking: remove resource declarations, legacy handler registration, scope
47
+ implications, default grants and redundant capability-discovery tools. See
48
+ README for installation and upgrade instructions.
49
+
50
+ ## 0.1.0
51
+
52
+ Initial unpublished registry, connection permissions and admin integration.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,31 @@
1
+ # Contributing
2
+
3
+ Develop and test locally before pushing a cohesive change. `bin/ci` runs the
4
+ same checks used in CI; `docker compose run --rm test` provides Ruby/PostgreSQL.
5
+ Use only a disposable database. No store checkout or production credentials are
6
+ required.
7
+
8
+ CI checks Rails 7.2 and 8.0 on Ruby 3.2, with and without Solidus Admin. To
9
+ select a Rails version locally, set `RAILS_VERSION='~> 7.2.0'` or
10
+ `RAILS_VERSION='~> 8.0.0'` when resolving dependencies and running checks.
11
+ Set `WITHOUT_ADMIN=1` for the core-only suite. The default is Rails 8.0 with
12
+ Solidus Admin. Use separate disposable databases when testing fresh migrations.
13
+
14
+ Tests exercise real Solidus 4.7 models, engine-owned tools and transport,
15
+ Doorkeeper consent/PKCE/refresh, record authorization, explicit per-tool grants,
16
+ admin audits/locking/CSRF, mixed bulk results, custom tools, catalog upgrades,
17
+ and actual development reloading and production eager loading.
18
+
19
+ Add built-in classes to `SolidusMcp::BUILTIN_TOOLS` inside the gem. Never add host
20
+ registration requirements. Each tool needs a unique permission; changed behavior
21
+ that broadens an existing grant needs a new key. Do not reuse retired permission
22
+ keys. Include negative tests for changes to authorization, inputs or serializers.
23
+
24
+ The direct migration command is `bin/rails solidus_mcp:migrate` after host
25
+ migrations. Keep migrations immutable and safe alongside the previous release.
26
+ Do not run down migrations automatically during deployment rollback.
27
+
28
+ For release, run admin and core-only suites, both loading checks, style checks,
29
+ and gem packaging locally. Review the package and changelog. Preserve immutable
30
+ build inputs and verify the real host integration before production promotion.
31
+ Publishing, pushing, tagging and deployment remain deliberate separate actions.
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Michael Caviness
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+ 3. Neither the name of the copyright holder nor the names of its contributors
15
+ may be used to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # Solidus MCP
2
+
3
+ ## Overview
4
+
5
+ Solidus MCP provides an MCP server for managing and administering Solidus storefronts.
6
+
7
+ It comes with a set of built-in tools to get you started. Connected agents can look up products and orders, update pricing, and check inventory.
8
+
9
+ With Solidus, customization is king, so you aren't limited to the built-in tools. You can add your own MCP tools to support your specific store and workflows.
10
+
11
+ The default install runs inside your store. It provides the MCP server and OAuth integration with Doorkeeper.
12
+
13
+ A permission system allows for granular control. Any Solidus admin can connect to the MCP server, but has no access to tools by default. MCP admins can grant and revoke tool access for ordinary MCP users. Only superadministrators can manage privileged connections or elevate users to MCP administrators.
14
+
15
+ Superadministrators are designated through server-side configuration, outside the store’s user interface. This lets you delegate MCP permission management without giving administrators the ability to promote themselves or others.
16
+
17
+ ## Requirements
18
+
19
+ Solidus MCP requires Ruby 3.2 or newer and Rails 7.2 or 8.0. Both Rails versions are tested with Ruby 3.2, Solidus 4.7, PostgreSQL 17, Doorkeeper 5.9, and MCP Ruby SDK 1.8.1.
20
+
21
+ Solidus Admin 0.4 integration allows users to administer accounts and permissions in the default Solidus Admin interface. It is optional; the MCP server can run without it, but you'll need to manage connection grants through trusted application code.
22
+
23
+ ## Installation
24
+
25
+ These instructions assume you already have a working Solidus application. If your store already uses Doorkeeper, read [Integrating with existing Doorkeeper](#integrating-with-existing-doorkeeper) before configuring OAuth.
26
+
27
+ ### 1. Install the gem
28
+
29
+ Add the gem to your `Gemfile`:
30
+
31
+ ```ruby
32
+ gem 'solidus_mcp', '0.2.0.rc1'
33
+ ```
34
+
35
+ Then run:
36
+
37
+ ```sh
38
+ bundle install
39
+ ```
40
+
41
+ This is a release candidate. Keep `Gemfile.lock` under version control so deployments use the reviewed dependency versions.
42
+
43
+ ### 2. Generate the Doorkeeper migration
44
+
45
+ The gem installs Doorkeeper for OAuth, but it does not automatically install its database tables:
46
+
47
+ ```sh
48
+ bin/rails generate doorkeeper:migration
49
+ ```
50
+
51
+ Skip this command if your app already has the standard Doorkeeper tables or a pending migration to create them. You don't need `doorkeeper:install` for a fresh setup: the initializer below supplies the OAuth configuration, and the engine supplies the routes.
52
+
53
+ ### 3. Set environment variables
54
+
55
+ For local testing:
56
+
57
+ ```dotenv
58
+ SOLIDUS_MCP_ENDPOINT_URL=http://localhost:3000/mcp
59
+ SOLIDUS_MCP_ISSUER_URL=http://localhost:3000
60
+ SOLIDUS_MCP_SUPERADMIN_EMAILS=owner@example.com
61
+ ```
62
+
63
+ For a deployed store, use your public HTTPS URLs instead:
64
+
65
+ ```dotenv
66
+ SOLIDUS_MCP_ENDPOINT_URL=https://www.mystore.com/mcp
67
+ SOLIDUS_MCP_ISSUER_URL=https://www.mystore.com
68
+ SOLIDUS_MCP_SUPERADMIN_EMAILS=owner@example.com,security@example.com
69
+ ```
70
+
71
+ The endpoint URL must match the mount path you'll add to `config/routes.rb`. The issuer is the base URL of the app hosting the server.
72
+
73
+ Superadministrator emails must match existing Solidus admin accounts. The gem reads this variable automatically. The two URL variables are read by the initializer in the next step.
74
+
75
+ ### 4. Create the initializer
76
+
77
+ Create `config/initializers/solidus_mcp.rb`:
78
+
79
+ ```ruby
80
+ SolidusMcp.configure do |config|
81
+ config.endpoint_url = ENV.fetch('SOLIDUS_MCP_ENDPOINT_URL')
82
+ config.issuer_url = ENV.fetch('SOLIDUS_MCP_ISSUER_URL')
83
+ config.allowed_origins = []
84
+ end
85
+
86
+ SolidusMcp::Oauth.configure!
87
+ ```
88
+
89
+ An empty `allowed_origins` list works for clients that send no `Origin` header. For browser clients, add the exact origins they use, including the scheme and port. For example, a local MCP Inspector may need:
90
+
91
+ ```ruby
92
+ config.allowed_origins = ['http://localhost:6274', 'http://127.0.0.1:6274']
93
+ ```
94
+
95
+ Use the origin from the actual request, not the address of the MCP endpoint. Requests carrying an unlisted origin return 403.
96
+
97
+ The default OAuth configuration authenticates through Solidus's `:spree_user` Warden scope and sends unauthenticated users to `/admin/login`. Stores with custom authentication can supply a `config.resource_owner_authenticator` block returning the signed-in Solidus user.
98
+
99
+ MCP access still requires Solidus admin status.
100
+
101
+ ### 5. Add routes
102
+
103
+ Add these routes to `config/routes.rb`, before your storefront mount or any catch-all routes:
104
+
105
+ ```ruby
106
+ Rails.application.routes.draw do
107
+ mount SolidusMcp::Engine => '/mcp'
108
+ get '/.well-known/oauth-authorization-server',
109
+ to: 'solidus_mcp/metadata#authorization_server'
110
+ get '/.well-known/oauth-protected-resource/mcp',
111
+ to: 'solidus_mcp/metadata#resource'
112
+
113
+ # Your existing admin and storefront routes follow.
114
+ end
115
+ ```
116
+
117
+ Rails host authorization must also allow the hostname used to reach your app.
118
+
119
+ ### 6. Run migrations and restart
120
+
121
+ With your environment variables loaded, run:
122
+
123
+ ```sh
124
+ bin/rails db:migrate
125
+ bin/rails solidus_mcp:migrate
126
+ ```
127
+
128
+ The first command runs your app's migrations, including the generated Doorkeeper migration. The second runs the gem's migrations directly from the installed gem. These add connection permissions and audits, OAuth resource and PKCE attributes, and MCP administrator assignments.
129
+
130
+ ### 7. Verify the endpoint
131
+
132
+ For a local server on port 3000:
133
+
134
+ ```sh
135
+ curl -i http://localhost:3000/.well-known/oauth-authorization-server
136
+ curl -i http://localhost:3000/.well-known/oauth-protected-resource/mcp
137
+ curl -i http://localhost:3000/mcp
138
+ ```
139
+
140
+ The discovery requests should return 200 with JSON describing the issuer, OAuth endpoints, resource URL, and supported scopes. The unauthenticated MCP request should return 401 with a `WWW-Authenticate` header pointing to resource metadata. That 401 is expected: the endpoint requires an OAuth token.
141
+
142
+ ## Connect a client
143
+
144
+ Choose **Streamable HTTP** as the transport and enter your endpoint URL, such as `http://localhost:3000/mcp`.
145
+
146
+ Use a client that supports OAuth discovery, dynamic client registration, and S256 PKCE. Follow its authorization flow, sign in with a Solidus admin account, and approve the requested scopes. Being signed into the store in a browser does not authenticate the MCP client; it needs its own OAuth token.
147
+
148
+ The scopes are independent:
149
+
150
+ - `mcp:read` allows read tools when the connection has permission for them.
151
+ - `mcp:write` allows write tools when the connection has permission for them. It does not include read access.
152
+
153
+ If the client initially requests only read access, review and request write access before testing write tools. An administrator granting a write permission cannot expand an existing token's scopes. Refreshing a token also cannot add scopes beyond its original authorization.
154
+
155
+ ## Enable tools
156
+
157
+ New connections start with no tool permissions, including connections owned by superadministrators.
158
+
159
+ 1. Sign into Solidus Admin as a configured MCP superadministrator.
160
+ 2. Open **AI connections** and select the client's connection for the intended user.
161
+ 3. Enable a read tool, such as `search_products`, and save.
162
+ 4. Refresh the client's tool list or reconnect, then ask it to find a product in your store.
163
+
164
+ The permission table lets you select individual tools, a whole category, or all permissions. Start with the tools the connection needs. Test writes against disposable data before granting access to live store changes.
165
+
166
+ Permission changes apply on subsequent calls. Clients may cache their tool lists, so refresh discovery after changing grants. Disabling a connection blocks requests made with its existing tokens. Deleting a permission set disables it and retains its audit history; it does not delete the OAuth client or its tokens. To use it again, restore and enable it from **Deleted sets**.
167
+
168
+ ## Administration
169
+
170
+ All three levels require a Solidus admin account:
171
+
172
+ | Level | Can do |
173
+ | --- | --- |
174
+ | MCP user | Connect clients, use assigned tools, and view their own connections. |
175
+ | MCP administrator | Manage ordinary MCP users' connection grants. Their own connections are read-only; other privileged users' connections are hidden. |
176
+ | MCP superadministrator | Manage all connections, including their own, and assign or remove MCP administrators. |
177
+
178
+ To assign an MCP administrator, open **AI connections → Manage MCP administrators** as a superadministrator. Search by login email, choose an existing Solidus admin, and assign access. The same screen lets you remove or restore assignments.
179
+
180
+ These assignments belong to the gem. Adding a Solidus role named `mcp_admin` does not grant MCP administration rights. Removing an MCP administrator assignment removes management authority, but retains that user's existing connection tool grants.
181
+
182
+ Every tool call must pass the connection grant, OAuth scope, and Solidus ability checks. Tools must also respect record-level access. Administration roles do not bypass these checks.
183
+
184
+ The hierarchy depends on your store's account security. Restrict who can change privileged accounts' emails, reset their passwords, or impersonate them. Someone who can take over a superadministrator's account can act as that superadministrator.
185
+
186
+ See [MCP administration hierarchy](docs/administration.md) for configuration details, audit behavior, and management APIs for stores without Solidus Admin.
187
+
188
+ ## Integrating with existing Doorkeeper
189
+
190
+ Reuse your existing OAuth tables and initializer. Do not call `SolidusMcp::Oauth.configure!` on top of an existing configuration: Doorkeeper is application-wide, and that helper is intended for a fresh installation.
191
+
192
+ Merge the MCP requirements into your existing configuration instead. Use [the helper's implementation](lib/solidus_mcp/oauth.rb) as a reference:
193
+
194
+ - Authorize Solidus admin users through your existing login flow.
195
+ - Support authorization-code grants, S256 PKCE, and refresh tokens.
196
+ - Add `mcp:read` and `mcp:write` to your configured scopes and enforce configured scopes.
197
+ - Include `:resource` in `custom_access_token_attributes`, preserving any other attributes.
198
+ - Use token and application secret hashing, accounting for existing clients and stored credentials before changing those settings.
199
+ - Allow literal loopback HTTP callbacks for local clients while requiring HTTPS for other callbacks.
200
+
201
+ Run the engine migrations after your host migrations. They add the required resource and PKCE columns without removing existing columns. Mount the engine as shown above; keep routes for your other OAuth integrations in place.
202
+
203
+ Check your existing integrations before deploying changes to shared OAuth settings. If you ran `doorkeeper:install` solely for this setup, review its generated initializer and routes so you don't configure Doorkeeper twice.
204
+
205
+ ## Built-in tools
206
+
207
+ | Category | Tool | Purpose |
208
+ | --- | --- | --- |
209
+ | Products | `search_products` | Search by product name or variant SKU. |
210
+ | Products | `get_product` | Read product descriptions and supported attributes. |
211
+ | Products | `get_product_variants` | List variants, including the master variant. |
212
+ | Products | `create_product` | Create a draft product. |
213
+ | Products | `update_product` | Update the name, description, and metadata fields. |
214
+ | Pricing | `get_variant_prices` | Read prices with their currency and country context. |
215
+ | Pricing | `update_variant_price` | Update an existing price row. |
216
+ | Pricing | `bulk_update_variant_prices` | Update up to 100 prices by SKU. |
217
+ | Inventory | `get_inventory_levels` | Read stock counts and backorderability by location. |
218
+ | Inventory | `search_inventory` | Search stock levels by SKU or product name. |
219
+ | Orders | `search_orders` | Search by order number, state, or store ID. |
220
+ | Orders | `get_order` | Read totals, status, and up to 100 authorized line items. |
221
+ | Orders | `get_order_status` | Read order, payment, and shipment states. |
222
+
223
+ Your client discovers each tool's input schema. Paginated tools accept `after_id` and `limit`, with a default limit of 25 and maximum of 100. Pass `next_after_id` from a response as the next request's `after_id` until it is null. Order results exclude customer contact information by default.
224
+
225
+ Product creation requires a valid `shipping_category_id`, supplied with the call or through `config.default_shipping_category_id` in your initializer.
226
+
227
+ Price updates require an existing price row, an explicit currency, and `country_iso` (`null` for general-country pricing). Supply decimal amounts as strings, such as `"19.95"`. Bulk updates report success or failure for each entry and can partially succeed. Review the results and retry only failed entries as appropriate.
228
+
229
+ The built-in price update tools use Solidus's standard pricing options. Stores with a custom `pricing_options_class` need a store-specific pricing tool.
230
+
231
+ Product and price updates take effect immediately. There are currently no built-in order or inventory mutation tools. Add your own, as desired.
232
+
233
+ ## Custom tools
234
+
235
+ To create your own custom MCP tools, add a named subclass of `SolidusMcp::Tool` under `app/tools`. For example, `app/tools/count_products.rb`:
236
+
237
+ ```ruby
238
+ class CountProducts < SolidusMcp::Tool
239
+ tool_name :count_products
240
+ description 'Count products visible to the connected user.'
241
+ category 'Products'
242
+ permission 'count_products'
243
+ read_only true
244
+ ability :read, 'Spree::Product'
245
+
246
+ protected
247
+
248
+ def call
249
+ { count: policy.scope(Spree::Product).count }
250
+ end
251
+ end
252
+ ```
253
+
254
+ Register its class name in your initializer:
255
+
256
+ ```ruby
257
+ config.custom_tools = ['CountProducts']
258
+ ```
259
+
260
+ Built-in tools are registered automatically. Use class-name strings for custom tools so Rails can reload them in development. Each tool needs a unique tool name and permission key. The new tool appears in the permission table, but it still needs an explicit grant before a client can use it.
261
+
262
+ ### Tool declarations
263
+
264
+ These declarations form the gem's Ruby DSL for describing tools:
265
+
266
+ | Declaration | Required? | Purpose |
267
+ | --- | --- | --- |
268
+ | `tool_name :count_products` | Yes | Name exposed to MCP clients. Must be unique, start with a lowercase letter, and contain only lowercase letters, numbers, and underscores. |
269
+ | `description 'Count visible products.'` | Yes | Explains what the tool does to the connected agent. |
270
+ | `category 'Products'` | Yes | Groups the tool in the permissions interface. |
271
+ | `permission 'count_products'` | Yes | Unique key used for permission grants. Accepts the same characters as tool names, plus periods. |
272
+ | `read_only true` | Yes | Use `true` for read tools requiring `mcp:read`, or `false` for write tools requiring `mcp:write`. This declaration does not itself prevent database writes. |
273
+ | `ability :read, 'Spree::Product'` | At least one | Required Solidus ability. Repeat for additional abilities; all must pass. |
274
+ | `input :query, type: 'string', required: true` | No | Declares an argument and its JSON Schema constraints. Repeat for each argument the tool accepts. |
275
+
276
+ Declarations can be inherited from a parent tool class. Every tool also needs a protected `call` method containing its implementation.
277
+
278
+ Inputs are optional unless you specify `required: true`. Other options describe the accepted value, such as `type`, `enum`, `minLength`, or `minimum`:
279
+
280
+ ```ruby
281
+ input :query, type: 'string', minLength: 1, required: true
282
+ input :limit, type: 'integer', minimum: 1, maximum: 100
283
+ ```
284
+
285
+ Receive the arguments as keyword arguments in `call`, and put any defaults there:
286
+
287
+ ```ruby
288
+ protected
289
+
290
+ def call(query:, limit: 25)
291
+ # Your implementation here.
292
+ end
293
+ ```
294
+
295
+ Undeclared arguments are rejected. Use `policy.scope(Model, action)` for collections and `policy.authorize_record!(action, record)` for individual records. Serialize only the fields the client needs. For writes, preserve your store's validations and business rules and recheck authorization before committing changes. The [built-in tools](app/tools/solidus_mcp/tools) provide examples.
296
+
297
+ If you call a tool from elsewhere in your Ruby app, such as a service, job, or another tool, use `.execute` with an authenticated MCP credential:
298
+
299
+ ```ruby
300
+ CountProducts.execute(credential: credential)
301
+ ```
302
+
303
+ This runs the same connection permission, OAuth scope, Solidus ability, and input checks used for MCP requests. MCP clients already go through these checks automatically.
304
+
305
+ Inside the tool, your implementation is still responsible for record access and business rules. For example, `policy.scope(Spree::Product)` limits a query to products the connected user can access; `Spree::Product.all` does not. Declaring abilities does not automatically restrict queries or enforce your store's rules.
306
+
307
+ ### Changing existing tools
308
+
309
+ Connection grants are stored by permission key. If you expand what a tool can access or change—for example, adding inventory adjustments to a tool that only reads stock levels—give it a new permission key. This requires administrators to explicitly approve the expanded access.
310
+
311
+ For the same reason, don't assign a removed tool's permission key to a different tool. Existing connections may still hold that grant.
312
+
313
+ ## Troubleshooting
314
+
315
+ | Problem | Check |
316
+ | --- | --- |
317
+ | Missing environment variable at startup | Confirm your launcher loads the variables. A `.env` file alone is not enough. |
318
+ | Discovery returns 404 | Check that the routes are loaded and appear before storefront or catch-all routes. |
319
+ | `unknown attribute 'resource' for Doorkeeper::AccessGrant`, missing PKCE columns, or missing administrator tables | Run `bin/rails solidus_mcp:migrate`, then restart the app to reload its schema. |
320
+ | MCP endpoint returns 401 | Complete the client's OAuth flow. A browser login alone does not provide an MCP token. |
321
+ | MCP endpoint returns 403 | Check the request's `Origin` against `allowed_origins`, and check whether the connection is disabled or its permission set was deleted. |
322
+ | Connected, but no tools appear | Check tool grants, token scopes, and Solidus abilities, then refresh the client's tool list. |
323
+ | A write tool is denied | Confirm both the tool grant and `mcp:write` consent. Reauthorize for additional scopes. |
324
+ | No superadministrator access | Verify the configured login email and Solidus admin status. Restart the whole process after changing the environment. |
325
+ | Own connection is read-only or administrator management is missing | Check your MCP administration level. Ordinary Solidus admin status does not grant MCP management rights. |
326
+ | Port already in use | Check which process owns it. Rails and the local end of an SSH tunnel need separate listeners on their respective machines. |
327
+ | HTTP URL rejected | HTTP endpoints are limited to loopback development and test use. HTTP OAuth callbacks require a literal loopback address. |
328
+
329
+ ## Deployment and upgrades
330
+
331
+ Keep the gem revision and dependencies locked. During deployment, run host migrations followed by engine migrations, rebuild the app and its assets as needed, and restart the application processes. Use a shared Rails cache store so dynamic client registration rate limits apply across processes.
332
+
333
+ When adopting the administration hierarchy, configure at least one superadministrator and deliberately assign any MCP administrators. Existing Solidus roles do not automatically confer MCP administration rights. Existing tokens and connection grants are retained.
334
+
335
+ Review the [changelog](CHANGELOG.md) before upgrading and test the integration in your own store. Keep migrations compatible with the previous release where possible; application rollback should not automatically run down migrations.
336
+
337
+ ## Local development and contributing
338
+
339
+ Run the checks with a disposable PostgreSQL database:
340
+
341
+ ```sh
342
+ docker compose run --rm test
343
+ ```
344
+
345
+ Or, with Ruby and a disposable `DATABASE_URL` configured locally:
346
+
347
+ ```sh
348
+ RAILS_ENV=test bin/ci
349
+ ```
350
+
351
+ The suite uses a fixture app; you don't need a separate store checkout or production credentials. See [CONTRIBUTING.md](CONTRIBUTING.md) for checks and release procedures.
352
+
353
+ ## License
354
+
355
+ [BSD 3-Clause](LICENSE).