spree_mcp 6.0.1 → 6.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26677662fa547b049bd36309d482d988803944c56da1dc2a1a1999cdb399d904
4
- data.tar.gz: 11c8339cc0cdc1fc35dabb8843e146aa10877a670e1d31c4bbd487072a40f9a9
3
+ metadata.gz: 38b6ba2f07815f146329db7be4d30c9f9c29190842c14370f014a17de3ea848d
4
+ data.tar.gz: 03747b1df8ed398e5270fecf40cc95299c36ce4dc871858518a9d32619e6c09c
5
5
  SHA512:
6
- metadata.gz: da6e4010a1a9c0a53860329624cd9b50e8774b2db79d7d7f1b3f185a701b2aeee097c724b25f3a862fe960fbf2216a0cc07c951da79711ad2bf05a6c66b0575a
7
- data.tar.gz: 8a55dce5a6154f315fe5a752d03d810da98e81b748391a0e0adbb367d1bb93f4679176079df85359becd036c2d66794379e0310e2216b6e0851459d226241d3a
6
+ metadata.gz: 33197fbccf535cf54d96da786657eccfe56b7bfde60d1d85b49817f320726b4c7462aceb4d798638c50e4f751da7978648356c5ffe4b1ef6522cd8981bac65a7
7
+ data.tar.gz: ab0d70fff092c0fa0fa3129e6af285d53e623e81681d38553b1cca0db8be5f07ae556c94dfea2abe576dc6a883bd457b5cf19dc76cc6ef9b225dbcc6b38c853a
@@ -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 = ::MCP::Server.new(
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 declared params as JSON Schema.
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 [Hash]
35
+ # @return [MCP::Tool::InputSchema]
31
36
  def input_schema(tool)
32
- properties = tool.params.to_h do |name, options|
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 both hints set explicitly: the gem defaults
51
- # `destructiveHint` to true, so a read tool that says nothing would be
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
- { read_only_hint: !tool.mutating?, destructive_hint: tool.mutating?, idempotent_hint: !tool.mutating? }
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
- # A tool declares Ruby-ish types; JSON Schema wants its own names, and
92
- # anything unrecognised is a string, which every client can render.
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.1
4
+ version: 6.0.3
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-10-29 00:00:00.000000000 Z
12
+ date: 2026-11-05 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.1
20
+ version: 6.0.3
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.1
27
+ version: 6.0.3
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.1
34
+ version: 6.0.3
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.1
41
+ version: 6.0.3
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.1
80
- documentation_uri: https://docs.spreecommerce.org/
81
- source_code_uri: https://github.com/spree/spree/tree/v6.0.1
79
+ changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.3
80
+ documentation_uri: https://spreecommerce.org/docs/
81
+ source_code_uri: https://github.com/spree/spree/tree/v6.0.3
82
82
  post_install_message:
83
83
  rdoc_options: []
84
84
  require_paths: