spree_mcp 6.0.2 → 6.0.4
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.
- checksums.yaml +4 -4
- data/README.md +9 -4
- data/app/controllers/spree/api/v3/admin/mcp_controller.rb +7 -0
- data/lib/spree/mcp/server.rb +21 -1
- data/lib/spree/mcp/tool_adapter.rb +22 -32
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea3e895b47908ef85cefc8dc52477c4f0626c303cd29bad2f951a0616de02153
|
|
4
|
+
data.tar.gz: e5e49db768e488711faf68b1a82e6c361023b75a35776842880f15fc55c2b09d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a503624ec3ab6e62768c948f4836a2e476777c55f623a012b61e7ce0c2170c35395186cbdcbf85878453479ff6ba592e53123734524c18a1885f2d5a9ca1c49
|
|
7
|
+
data.tar.gz: e641107a176fd70d965e40827df7c12884d8d036315868ae308df2f19bc58796318d705ffe983623b0bc4482d4728b222fd77b92ee5a8decee998cec0cc09005
|
data/README.md
CHANGED
|
@@ -10,10 +10,15 @@ API.
|
|
|
10
10
|
spree add mcp # or add gem 'spree_mcp' to the Gemfile
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The endpoint is `POST /api/v3/admin/mcp`, authenticated with
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
The endpoint is `POST /api/v3/admin/mcp`, authenticated with an OAuth token
|
|
14
|
+
issued to a person: the client sends them to the store to sign in and approve
|
|
15
|
+
what it may do. The token carries that admin's own permissions, narrowed to
|
|
16
|
+
what they approved, and those decide which tools the model is offered, so an
|
|
17
|
+
agent is never told about a capability its credential does not carry.
|
|
18
|
+
|
|
19
|
+
A Spree secret API key (`sk_…`) is deliberately refused here. A nightly job or
|
|
20
|
+
a server-side integration has no person behind it; it calls the Admin API with
|
|
21
|
+
a key, or uses the `spree api` CLI.
|
|
17
22
|
|
|
18
23
|
This gem is a protocol adapter and nothing more. The tools, their schemas and
|
|
19
24
|
the permissions gating them live in `spree_core` as `Spree.admin_agent_tools`, which
|
|
@@ -29,6 +29,13 @@ module Spree
|
|
|
29
29
|
# client is misconfigured.
|
|
30
30
|
prepend_before_action :require_oauth_token!
|
|
31
31
|
|
|
32
|
+
# The body is a JSON-RPC envelope, not an Admin API payload, so a
|
|
33
|
+
# key named `price` in it may be anything: the column an import
|
|
34
|
+
# mapping reads prices from, say. Amounts are still checked where
|
|
35
|
+
# they are written — by the Admin API a tool dispatches to, or by
|
|
36
|
+
# the model's own canonical decimal setters.
|
|
37
|
+
skip_before_action :refuse_noncanonical_decimals
|
|
38
|
+
|
|
32
39
|
def create
|
|
33
40
|
render json: server.handle_json(request.body.read), content_type: 'application/json'
|
|
34
41
|
end
|
data/lib/spree/mcp/server.rb
CHANGED
|
@@ -9,6 +9,22 @@ module Spree
|
|
|
9
9
|
# request) is not what mounts this. An ordinary admin API controller does,
|
|
10
10
|
# and hands the JSON-RPC message to {MCP::Server#handle_json}.
|
|
11
11
|
module Server
|
|
12
|
+
# The protocol server, plus the 6.0.0 tool names kept callable through
|
|
13
|
+
# 6.0.x. They answer `tools/call` but are not in `tools/list`: a client
|
|
14
|
+
# that learned one keeps working, and a new one is shown only the
|
|
15
|
+
# current names. The gem lists `@tools` and looks a call up through
|
|
16
|
+
# `#tools`, so an unknown name is resolved there, only when called.
|
|
17
|
+
class AdminServer < ::MCP::Server
|
|
18
|
+
# @param resolver [#call] name => MCP tool class, or nil
|
|
19
|
+
attr_writer :resolver
|
|
20
|
+
|
|
21
|
+
def tools
|
|
22
|
+
resolver = @resolver
|
|
23
|
+
lookup = Hash.new { |_tools, name| resolver&.call(name) }
|
|
24
|
+
lookup.merge!(super)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
# What the model is told about using this server at discovery, so it
|
|
13
29
|
# reaches for the discovery tools before guessing at names and filters.
|
|
14
30
|
INSTRUCTIONS = <<~TEXT.freeze
|
|
@@ -47,7 +63,7 @@ module Spree
|
|
|
47
63
|
# @param context [Spree::AgentTools::Context] who is asking, and where
|
|
48
64
|
# @return [MCP::Server]
|
|
49
65
|
def for(context)
|
|
50
|
-
server =
|
|
66
|
+
server = AdminServer.new(
|
|
51
67
|
name: 'spree-admin',
|
|
52
68
|
title: "#{context.store.name} (Spree admin)",
|
|
53
69
|
version: Spree.version,
|
|
@@ -56,6 +72,10 @@ module Spree
|
|
|
56
72
|
server_context: { store_id: context.store.id }
|
|
57
73
|
)
|
|
58
74
|
|
|
75
|
+
server.resolver = lambda do |name|
|
|
76
|
+
tool = Spree.admin_agent_tools.find_for(name, context)
|
|
77
|
+
ToolAdapter.build(tool) if tool
|
|
78
|
+
end
|
|
59
79
|
register_resources(server, context)
|
|
60
80
|
server
|
|
61
81
|
end
|
|
@@ -24,36 +24,35 @@ module Spree
|
|
|
24
24
|
) { |**arguments| adapter.respond(tool, arguments) }
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# The tool's
|
|
27
|
+
# The tool's arguments as the gem's schema object, built once per tool
|
|
28
|
+
# class. Building one deep-copies and validates the schema, and an
|
|
29
|
+
# action tool's schema is a whole request body: done on every request
|
|
30
|
+
# for every tool, it was most of what answering `tools/list` cost.
|
|
31
|
+
# Keyed weakly by the tool class, so a class replaced on a code reload
|
|
32
|
+
# is let go with its schema.
|
|
28
33
|
#
|
|
29
34
|
# @param tool [Spree::AgentTool]
|
|
30
|
-
# @return [
|
|
35
|
+
# @return [MCP::Tool::InputSchema]
|
|
31
36
|
def input_schema(tool)
|
|
32
|
-
|
|
33
|
-
property = { type: json_type(options[:type]) }
|
|
34
|
-
property[:description] = options[:description] if options[:description].present?
|
|
35
|
-
# An array must say what it holds: a client validating arguments
|
|
36
|
-
# against the schema refuses the call otherwise.
|
|
37
|
-
property[:items] = { type: json_type(options[:items] || :string) } if property[:type] == 'array'
|
|
38
|
-
|
|
39
|
-
[name.to_s, property]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: properties,
|
|
45
|
-
required: tool.params.select { |_name, options| options[:required] }.keys.map(&:to_s)
|
|
46
|
-
}
|
|
37
|
+
schemas[tool.class] ||= ::MCP::Tool::InputSchema.new(tool.class.input_schema)
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
# Clients that gate confirmation on annotations rather than on a
|
|
50
|
-
# server's word need
|
|
51
|
-
# `destructiveHint` to true, so a
|
|
52
|
-
# confirmed like a deletion.
|
|
41
|
+
# server's word need every hint set explicitly: the protocol defaults
|
|
42
|
+
# `destructiveHint` and `openWorldHint` to true, so a tool that says
|
|
43
|
+
# nothing would be confirmed like a deletion that leaves the store.
|
|
44
|
+
# Every write runs the same workflow the dashboard does, which can
|
|
45
|
+
# email, charge a gateway or fire webhooks, so writes are open-world
|
|
46
|
+
# and reads are not.
|
|
53
47
|
#
|
|
54
48
|
# @return [Hash]
|
|
55
49
|
def annotations(tool)
|
|
56
|
-
{
|
|
50
|
+
{
|
|
51
|
+
read_only_hint: !tool.mutating?,
|
|
52
|
+
destructive_hint: tool.destructive?,
|
|
53
|
+
idempotent_hint: !tool.mutating?,
|
|
54
|
+
open_world_hint: tool.mutating?
|
|
55
|
+
}
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
# A tool answers with a plain hash. A hash carrying `:error` is the
|
|
@@ -88,17 +87,8 @@ module Spree
|
|
|
88
87
|
|
|
89
88
|
private
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def json_type(type)
|
|
94
|
-
case type&.to_sym
|
|
95
|
-
when :boolean then 'boolean'
|
|
96
|
-
when :integer then 'integer'
|
|
97
|
-
when :number, :float, :decimal then 'number'
|
|
98
|
-
when :object, :hash then 'object'
|
|
99
|
-
when :array then 'array'
|
|
100
|
-
else 'string'
|
|
101
|
-
end
|
|
90
|
+
def schemas
|
|
91
|
+
@schemas ||= ObjectSpace::WeakKeyMap.new
|
|
102
92
|
end
|
|
103
93
|
|
|
104
94
|
# A line the client can show beside its confirmation prompt, and the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.0.
|
|
4
|
+
version: 6.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-11-
|
|
12
|
+
date: 2026-11-12 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: spree_core
|
|
@@ -17,28 +17,28 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - '='
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 6.0.
|
|
20
|
+
version: 6.0.4
|
|
21
21
|
type: :runtime
|
|
22
22
|
prerelease: false
|
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
24
|
requirements:
|
|
25
25
|
- - '='
|
|
26
26
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: 6.0.
|
|
27
|
+
version: 6.0.4
|
|
28
28
|
- !ruby/object:Gem::Dependency
|
|
29
29
|
name: spree_api
|
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
|
31
31
|
requirements:
|
|
32
32
|
- - '='
|
|
33
33
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: 6.0.
|
|
34
|
+
version: 6.0.4
|
|
35
35
|
type: :runtime
|
|
36
36
|
prerelease: false
|
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
38
|
requirements:
|
|
39
39
|
- - '='
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: 6.0.
|
|
41
|
+
version: 6.0.4
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: mcp
|
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -76,9 +76,9 @@ licenses:
|
|
|
76
76
|
- MIT
|
|
77
77
|
metadata:
|
|
78
78
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
79
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.
|
|
79
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.4
|
|
80
80
|
documentation_uri: https://spreecommerce.org/docs/
|
|
81
|
-
source_code_uri: https://github.com/spree/spree/tree/v6.0.
|
|
81
|
+
source_code_uri: https://github.com/spree/spree/tree/v6.0.4
|
|
82
82
|
post_install_message:
|
|
83
83
|
rdoc_options: []
|
|
84
84
|
require_paths:
|