actionmcp 0.201.1 → 0.201.2

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: 23300ca3a38254cb24ded139a21cef8b5fc876936dfc24a47cfa3faa834aaee1
4
- data.tar.gz: 2ff17b07add99768508b6f9dbc5e3f0d57377c8c8c2dba597cdd2b794a2c8309
3
+ metadata.gz: 49402a02736231ce65b7851e94d98550eb0dac2b605d8e469b4907ae886677db
4
+ data.tar.gz: 69432a9e6a7ccfaf557a3eca2fc44136d3f2a658fa46bb30b9c0e119cf9cde2a
5
5
  SHA512:
6
- metadata.gz: f9ec21482ddb688a69188f9f30ed5299f43b7fc972fccaf5863706f6414574bbf50c67edf426149787bc857c9e123fa34f67db412520282522c6688a993db31a
7
- data.tar.gz: abe1eea4a7610d782f109006685edb7749377736c752b1bf5c6c398b16caa20d34cc153d17fd96370bd20ea9a021e9b39335feee7328f30ebae33555a57ed3f6
6
+ metadata.gz: 7687741dd8be7cdeac36a500154dfd2a9441c352ce0c2be1524f3dedbfe4c75ada593022e82f933b316070f73ffac112fa0c7f291ba67d24f335784cc187f1bd
7
+ data.tar.gz: 87a0ae3173f26f09a0e323e24649b691341683ec77dbd1ab1602dcca15eb7570bba043150c8c9764c21dad6a27ca9ee6178b73f622f2231eb424b31625191424
@@ -82,13 +82,13 @@ module ActionMCP
82
82
  end
83
83
 
84
84
  generated.concat(ResourceTemplate.registered_templates.select { |klass| generated_template?(klass) })
85
- generated.concat(ResourceTemplatesRegistry.items.values.select { |klass| generated_template?(klass) })
85
+ generated.concat(ResourceTemplatesRegistry.store.values.select { |klass| generated_template?(klass) })
86
86
  generated.uniq!
87
87
 
88
88
  ResourceTemplate.registered_templates.delete_if do |klass|
89
89
  generated.include?(klass) || generated_template?(klass)
90
90
  end
91
- ResourceTemplatesRegistry.items.delete_if do |_name, klass|
91
+ ResourceTemplatesRegistry.store.delete_if do |_name, klass|
92
92
  generated.include?(klass) || generated_template?(klass)
93
93
  end
94
94
 
@@ -139,7 +139,7 @@ module ActionMCP
139
139
  end
140
140
  end
141
141
 
142
- ResourceTemplatesRegistry.items.delete_if { |_key, registered| registered.equal?(klass) }
142
+ ResourceTemplatesRegistry.store.delete_if { |_key, registered| registered.equal?(klass) }
143
143
  ResourceTemplatesRegistry.register(klass)
144
144
 
145
145
  [ klass, logical_uri, uri ]
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "monitor"
3
4
  require_relative "gateway"
4
5
 
5
6
  module ActionMCP
@@ -107,6 +108,8 @@ module ActionMCP
107
108
  # Browser origins allowed to reach a local server. Remote deployments must
108
109
  # explicitly add their trusted origin hosts.
109
110
  @allowed_origins = %w[localhost 127.0.0.1 ::1].freeze
111
+
112
+ @mcp_components_monitor = Monitor.new
110
113
  end
111
114
 
112
115
  def name
@@ -340,21 +343,55 @@ module ActionMCP
340
343
  end
341
344
 
342
345
  def eager_load_if_needed
343
- profile = @profiles[active_profile]
344
- return unless profile
346
+ ensure_mcp_components_loaded if mcp_components_load_needed?
347
+ end
348
+
349
+ # Loading app/mcp inside initialize! drags every model the tools reference into boot.
350
+ def prepare_mcp_components
351
+ if defer_mcp_components_load?
352
+ @mcp_components_state = (:deferred if mcp_components_load_needed?)
353
+ else
354
+ @mcp_components_state = nil
355
+ eager_load_if_needed
356
+ end
357
+ end
358
+
359
+ def mcp_components_deferred?
360
+ @mcp_components_state == :deferred
361
+ end
345
362
 
346
- # Check if any component type includes "all"
347
- needs_eager_load = profile[:tools]&.include?("all") ||
348
- profile[:prompts]&.include?("all") ||
349
- profile[:resources]&.include?("all")
363
+ # Reentrant Monitor: the load registers (and may read) through the registries on this thread.
364
+ def load_deferred_mcp_components
365
+ return unless @mcp_components_state
350
366
 
351
- return unless needs_eager_load
367
+ @mcp_components_monitor.synchronize do
368
+ next unless @mcp_components_state == :deferred
352
369
 
353
- ensure_mcp_components_loaded
370
+ @mcp_components_state = :loading
371
+ begin
372
+ ensure_mcp_components_loaded
373
+ @mcp_components_state = nil
374
+ ensure
375
+ @mcp_components_state = :deferred if @mcp_components_state == :loading
376
+ end
377
+ end
354
378
  end
355
379
 
356
380
  private
357
381
 
382
+ def defer_mcp_components_load?
383
+ !Rails.configuration.eager_load && !Rails.application.initialized?
384
+ end
385
+
386
+ def mcp_components_load_needed?
387
+ profile = @profiles[active_profile]
388
+ return false unless profile
389
+
390
+ profile[:tools]&.include?("all") ||
391
+ profile[:prompts]&.include?("all") ||
392
+ profile[:resources]&.include?("all")
393
+ end
394
+
358
395
  def default_profiles
359
396
  {
360
397
  primary: {
@@ -50,9 +50,9 @@ module ActionMCP
50
50
  ActionMCP::PromptsRegistry.clear!
51
51
  end
52
52
 
53
- # Eager load MCP components if profile includes "all"
54
- # This runs after Zeitwerk is fully set up
55
- ActionMCP.configuration.eager_load_if_needed
53
+ # Eager load MCP components if profile includes "all". Booting without
54
+ # eager loading defers them to the first registry read instead.
55
+ ActionMCP.configuration.prepare_mcp_components
56
56
 
57
57
  # Register compiled MCP Apps views after registries
58
58
  # are (re)built. Runs on each dev reload so rebuilt view bundles/hashes
@@ -56,7 +56,7 @@ module ActionMCP
56
56
  end
57
57
 
58
58
  def unregister_from_registry
59
- ActionMCP::PromptsRegistry.unregister(self) if ActionMCP::PromptsRegistry.items.values.include?(self)
59
+ ActionMCP::PromptsRegistry.unregister(self) if ActionMCP::PromptsRegistry.store.values.include?(self)
60
60
  end
61
61
 
62
62
  # Hook called when a class inherits from Prompt
@@ -7,13 +7,21 @@ module ActionMCP
7
7
  class NotFound < StandardError; end
8
8
 
9
9
  class << self
10
- # Returns all registered items.
10
+ # Raw storage for writes: registering never triggers the deferred component load.
11
11
  #
12
12
  # @return [Hash] A hash of registered items.
13
- def items
13
+ def store
14
14
  @items ||= {}
15
15
  end
16
16
 
17
+ # Returns all registered items, running a deferred component load first.
18
+ #
19
+ # @return [Hash] A hash of registered items.
20
+ def items
21
+ ActionMCP.configuration.load_deferred_mcp_components
22
+ store
23
+ end
24
+
17
25
  # Register an item explicitly
18
26
  #
19
27
  # @param klass [Class] The class to register
@@ -22,7 +30,7 @@ module ActionMCP
22
30
  return if klass.abstract?
23
31
 
24
32
  name = klass.capability_name
25
- items[name] = klass
33
+ store[name] = klass
26
34
  klass._registered_name = name if klass.respond_to?(:_registered_name=)
27
35
  end
28
36
 
@@ -38,11 +46,11 @@ module ActionMCP
38
46
  new_name = klass.capability_name
39
47
 
40
48
  # Remove old entry if it exists and points to this class
41
- if old_name && items[old_name] == klass
42
- items.delete(old_name)
49
+ if old_name && store[old_name] == klass
50
+ store.delete(old_name)
43
51
  end
44
52
 
45
- items[new_name] = klass
53
+ store[new_name] = klass
46
54
  klass._registered_name = new_name if klass.respond_to?(:_registered_name=)
47
55
  end
48
56
 
@@ -51,7 +59,7 @@ module ActionMCP
51
59
  # @param klass [Class] The class to unregister
52
60
  # @return [void]
53
61
  def unregister(klass)
54
- items.delete(klass.capability_name)
62
+ store.delete(klass.capability_name)
55
63
  end
56
64
 
57
65
  # Retrieve an item by name.
@@ -31,7 +31,7 @@ module ActionMCP
31
31
  def abstract!
32
32
  @abstract = true
33
33
  # Unregister from the appropriate registry if already registered
34
- return unless ActionMCP::ResourceTemplatesRegistry.items.values.include?(self)
34
+ return unless ActionMCP::ResourceTemplatesRegistry.store.values.include?(self)
35
35
 
36
36
  ActionMCP::ResourceTemplatesRegistry.unregister(self)
37
37
  end
@@ -87,7 +87,7 @@ module ActionMCP
87
87
  end
88
88
 
89
89
  def unregister_from_registry
90
- ActionMCP::ToolsRegistry.unregister(self) if ActionMCP::ToolsRegistry.items.values.include?(self)
90
+ ActionMCP::ToolsRegistry.unregister(self) if ActionMCP::ToolsRegistry.store.values.include?(self)
91
91
  end
92
92
 
93
93
  # Hook called when a class inherits from Tool
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "gem_version"
4
4
  module ActionMCP
5
- VERSION = "0.201.1"
5
+ VERSION = "0.201.2"
6
6
 
7
7
  class << self
8
8
  alias version gem_version
data/lib/action_mcp.rb CHANGED
@@ -83,14 +83,14 @@ module ActionMCP
83
83
  #
84
84
  # @return [ActionMCP::RegistryBase::RegistryScope] the available tools
85
85
  def available_tools
86
- ToolsRegistry.available_tools
86
+ ToolsRegistry.non_abstract
87
87
  end
88
88
 
89
89
  # Returns the available prompts.
90
90
  #
91
91
  # @return [ActionMCP::RegistryBase::RegistryScope] the available prompts
92
92
  def available_prompts
93
- PromptsRegistry.available_prompts
93
+ PromptsRegistry.non_abstract
94
94
  end
95
95
 
96
96
  ActiveModel::Type.register(:string_array, StringArray)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionmcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.201.1
4
+ version: 0.201.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih