stimulus_plumbers_mcp 0.4.5 → 0.4.9

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +46 -25
  3. data/lib/stimulus_plumbers/mcp/loaders/aria_loader.rb +31 -0
  4. data/lib/stimulus_plumbers/mcp/loaders/component_docs_loader.rb +38 -0
  5. data/lib/stimulus_plumbers/mcp/loaders/component_manifest_loader.rb +46 -0
  6. data/lib/stimulus_plumbers/mcp/loaders/component_requirements.rb +41 -0
  7. data/lib/stimulus_plumbers/mcp/loaders/component_schema_loader.rb +49 -0
  8. data/lib/stimulus_plumbers/mcp/loaders/component_theme_loader.rb +41 -0
  9. data/lib/stimulus_plumbers/mcp/loaders/controller_docs_loader.rb +31 -0
  10. data/lib/stimulus_plumbers/mcp/loaders/controller_schema_loader.rb +53 -0
  11. data/lib/stimulus_plumbers/mcp/loaders/guide.md +48 -0
  12. data/lib/stimulus_plumbers/mcp/loaders/guide_loader.rb +40 -4
  13. data/lib/stimulus_plumbers/mcp/loaders/icons_loader.rb +34 -0
  14. data/lib/stimulus_plumbers/mcp/loaders/support/docs_table_parser.rb +112 -0
  15. data/lib/stimulus_plumbers/mcp/loaders/support/gem_vendor_path.rb +18 -0
  16. data/lib/stimulus_plumbers/mcp/loaders/tailwind_loader.rb +35 -0
  17. data/lib/stimulus_plumbers/mcp/loaders/versions_loader.rb +110 -0
  18. data/lib/stimulus_plumbers/mcp/plugins/aria.rb +34 -0
  19. data/lib/stimulus_plumbers/mcp/plugins/base.rb +40 -31
  20. data/lib/stimulus_plumbers/mcp/plugins/component_docs.rb +96 -0
  21. data/lib/stimulus_plumbers/mcp/plugins/component_schema.rb +133 -0
  22. data/lib/stimulus_plumbers/mcp/plugins/component_theme.rb +90 -0
  23. data/lib/stimulus_plumbers/mcp/plugins/controller_docs.rb +91 -0
  24. data/lib/stimulus_plumbers/mcp/plugins/controller_schema.rb +81 -0
  25. data/lib/stimulus_plumbers/mcp/plugins/guide.rb +70 -16
  26. data/lib/stimulus_plumbers/mcp/plugins/icons.rb +42 -0
  27. data/lib/stimulus_plumbers/mcp/plugins/tailwind.rb +69 -45
  28. data/lib/stimulus_plumbers/mcp/plugins/versions.rb +44 -0
  29. data/lib/stimulus_plumbers/mcp/server.rb +26 -9
  30. data/lib/stimulus_plumbers/mcp/version.rb +1 -1
  31. data/lib/stimulus_plumbers_mcp.rb +32 -11
  32. metadata +23 -12
  33. data/lib/stimulus_plumbers/mcp/loaders/component_controller_map.rb +0 -38
  34. data/lib/stimulus_plumbers/mcp/loaders/docs_loader.rb +0 -129
  35. data/lib/stimulus_plumbers/mcp/loaders/guide/overview.md +0 -48
  36. data/lib/stimulus_plumbers/mcp/loaders/schema_loader.rb +0 -45
  37. data/lib/stimulus_plumbers/mcp/loaders/stimulus_manifest.rb +0 -47
  38. data/lib/stimulus_plumbers/mcp/loaders/tailwind_theme_loader.rb +0 -29
  39. data/lib/stimulus_plumbers/mcp/loaders/theme_loader.rb +0 -97
  40. data/lib/stimulus_plumbers/mcp/plugins/docs.rb +0 -82
  41. data/lib/stimulus_plumbers/mcp/plugins/schema.rb +0 -110
  42. data/lib/stimulus_plumbers/mcp/plugins/stimulus.rb +0 -66
  43. data/lib/stimulus_plumbers/mcp/plugins/theme.rb +0 -66
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ class ComponentTheme < Base
7
+ class << self
8
+ def loader_key = :component_theme
9
+
10
+ def loader = ComponentThemeLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "component://theme/base",
16
+ name: "theme-base",
17
+ description: "Guide for implementing a custom stimulus-plumbers theme: method convention, return format",
18
+ mime_type: "text/markdown"
19
+ ),
20
+ ::MCP::Resource.new(
21
+ uri: "component://theme",
22
+ name: "component-theme-index",
23
+ description: "Index of all component keys that can be implemented in a custom theme",
24
+ mime_type: "application/json"
25
+ )
26
+ ].freeze
27
+ end
28
+
29
+ def dynamic_resource_templates
30
+ [
31
+ ::MCP::ResourceTemplate.new(
32
+ uri_template: "component://{name}/theme",
33
+ name: "component-theme-interface",
34
+ description: "Method name, param signature, and return contract for implementing a component in a custom theme",
35
+ mime_type: "application/json"
36
+ )
37
+ ].freeze
38
+ end
39
+
40
+ def read(uri, store)
41
+ theme = store[:component_theme]
42
+
43
+ case uri
44
+ when "component://theme/base"
45
+ text_resource(uri, "text/markdown", theme[:base_doc])
46
+ when "component://theme"
47
+ json_resource(uri, theme[:components].keys)
48
+ when %r{\Acomponent://([^/]+)/theme\z}
49
+ key = Regexp.last_match(1).to_sym
50
+ json_resource(uri, theme[:components][key] || { error: "unknown component: #{key}" })
51
+ end
52
+ end
53
+
54
+ def register_tools(server, store)
55
+ theme = store[:component_theme]
56
+
57
+ register_list_component_themes(server, theme)
58
+ register_get_component_theme(server, theme)
59
+ end
60
+
61
+ private
62
+
63
+ def register_list_component_themes(server, theme)
64
+ text_tool(
65
+ server,
66
+ name: "list_component_themes",
67
+ description: "Lists all component keys that can be implemented in a custom theme"
68
+ ) do
69
+ JSON.generate(theme[:components].keys)
70
+ end
71
+ end
72
+
73
+ def register_get_component_theme(server, theme)
74
+ text_tool(
75
+ server,
76
+ name: "get_component_theme",
77
+ description: "Returns the method name, param signature, and return contract for implementing " \
78
+ "a component in a custom theme. For the built-in Tailwind theme's output use " \
79
+ "get_component_tailwind; for themed params use get_component_schema",
80
+ input_schema: { properties: { name: { type: "string" } }, required: ["name"] }
81
+ ) do |name:|
82
+ data = theme[:components][name.to_sym]
83
+ data ? JSON.generate(data) : not_found("unknown component: #{name}")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ # Shares the controller:// scheme with Plugins::ControllerSchema, distinguished by the
7
+ # docs/ path segment (ControllerSchema is per-identifier schema data; docs/ is
8
+ # narrative markdown, grouped by controller family, not individual identifier).
9
+ class ControllerDocs < Base
10
+ class << self
11
+ def loader_key = :controller_docs
12
+
13
+ def loader = ControllerDocsLoader
14
+
15
+ def static_resources
16
+ [
17
+ ::MCP::Resource.new(
18
+ uri: "controller://docs",
19
+ name: "controller-docs-index",
20
+ description: "Index of JS controller narrative docs, grouped by controller family " \
21
+ "(e.g. :calendar covers calendar-month/-year/-decade)",
22
+ mime_type: "application/json"
23
+ )
24
+ ].freeze
25
+ end
26
+
27
+ def dynamic_resource_templates
28
+ [
29
+ ::MCP::ResourceTemplate.new(
30
+ uri_template: "controller://docs/{name}",
31
+ name: "controller-doc",
32
+ description: "Narrative usage doc for a controller family, for plain-JS/Hotwire consumers " \
33
+ "without Rails helpers",
34
+ mime_type: "text/markdown"
35
+ )
36
+ ].freeze
37
+ end
38
+
39
+ def read(uri, store)
40
+ docs = store[:controller_docs]
41
+
42
+ case uri
43
+ when "controller://docs"
44
+ json_resource(uri, docs.keys)
45
+ when %r{\Acontroller://docs/(.+)\z}
46
+ key = Regexp.last_match(1).to_sym
47
+ docs[key] ? text_resource(uri, "text/markdown", docs[key]) : missing(uri, key)
48
+ end
49
+ end
50
+
51
+ def register_tools(server, store)
52
+ docs = store[:controller_docs]
53
+
54
+ register_list_controller_docs(server, docs)
55
+ register_get_controller_docs(server, docs)
56
+ end
57
+
58
+ private
59
+
60
+ def missing(uri, key)
61
+ json_resource(uri, { error: "no controller docs for: #{key}" })
62
+ end
63
+
64
+ def register_list_controller_docs(server, docs)
65
+ text_tool(
66
+ server,
67
+ name: "list_controller_docs",
68
+ description: "Lists controller doc families available at controller://docs/{name} — " \
69
+ "grouped by family, not individual controller identifier"
70
+ ) do
71
+ JSON.generate(docs.keys)
72
+ end
73
+ end
74
+
75
+ def register_get_controller_docs(server, docs)
76
+ text_tool(
77
+ server,
78
+ name: "get_controller_docs",
79
+ description: "Returns the narrative usage doc for a controller family (e.g. \"calendar\", " \
80
+ "\"combobox\") — for individual controller targets/values/outlets use get_controller_schema",
81
+ input_schema: { properties: { name: { type: "string" } }, required: ["name"] }
82
+ ) do |name:|
83
+ doc = docs[name.to_sym]
84
+ doc || not_found("no controller docs for: #{name}")
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ class ControllerSchema < Base
7
+ class << self
8
+ def loader_key = :controller_schema
9
+
10
+ def loader = ControllerSchemaLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "controller://index",
16
+ name: "controllers-index",
17
+ description: "Index of all Stimulus controller identifiers in @stimulus-plumbers/controllers",
18
+ mime_type: "application/json"
19
+ )
20
+ ].freeze
21
+ end
22
+
23
+ def dynamic_resource_templates
24
+ [
25
+ ::MCP::ResourceTemplate.new(
26
+ uri_template: "controller://{name}/schema",
27
+ name: "controller-schema",
28
+ description: "Targets, values, outlets, and classes for a Stimulus controller",
29
+ mime_type: "application/json"
30
+ )
31
+ ].freeze
32
+ end
33
+
34
+ def read(uri, store)
35
+ controllers = store[:controller_schema]
36
+
37
+ case uri
38
+ when "controller://index"
39
+ json_resource(uri, controllers.keys)
40
+ when %r{\Acontroller://([^/]+)/schema\z}
41
+ identifier = Regexp.last_match(1)
42
+ json_resource(uri, controllers[identifier] || { error: "unknown controller: #{identifier}" })
43
+ end
44
+ end
45
+
46
+ def register_tools(server, store)
47
+ controllers = store[:controller_schema]
48
+
49
+ register_list_controllers(server, controllers)
50
+ register_get_controller_schema(server, controllers)
51
+ end
52
+
53
+ private
54
+
55
+ def register_list_controllers(server, controllers)
56
+ text_tool(
57
+ server,
58
+ name: "list_controllers",
59
+ description: "Lists all Stimulus controller identifiers provided by @stimulus-plumbers/controllers"
60
+ ) do
61
+ JSON.generate(controllers.keys)
62
+ end
63
+ end
64
+
65
+ def register_get_controller_schema(server, controllers)
66
+ text_tool(
67
+ server,
68
+ name: "get_controller_schema",
69
+ description: "Returns targets, values (with types and defaults), outlets, and classes for a " \
70
+ "Stimulus controller. For narrative usage docs use get_controller_docs",
71
+ input_schema: { properties: { name: { type: "string" } }, required: ["name"] }
72
+ ) do |name:|
73
+ data = controllers[name]
74
+ data ? JSON.generate(data) : not_found("unknown controller: #{name}")
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -3,27 +3,81 @@
3
3
  module StimulusPlumbers
4
4
  module MCP
5
5
  module Plugins
6
- module Guide
7
- extend Base
6
+ class Guide < Base
7
+ GUIDE_NAMES = %w[component controller tailwind theme].freeze
8
8
 
9
- LOADER_KEY = :guide
10
- LOADER = GuideLoader
9
+ class << self
10
+ def loader_key = :guide
11
11
 
12
- STATIC_RESOURCES = [
13
- ::MCP::Resource.new(
14
- uri: "guide://overview",
15
- name: "overview",
16
- description: "Start here — how to build views and forms with stimulus-plumbers, with pointers to every tool/resource",
17
- mime_type: "text/markdown"
18
- )
19
- ].freeze
12
+ def loader = GuideLoader
20
13
 
21
- DYNAMIC_RESOURCE_TEMPLATES = [].freeze
14
+ def static_resources
15
+ [
16
+ ::MCP::Resource.new(
17
+ uri: "guide://overview",
18
+ name: "overview",
19
+ description: "Start here — how to build views and forms with stimulus-plumbers, with pointers " \
20
+ "to every tool/resource",
21
+ mime_type: "text/markdown"
22
+ )
23
+ ].freeze
24
+ end
22
25
 
23
- def self.read(uri, store)
24
- return unless uri == "guide://overview"
26
+ def dynamic_resource_templates
27
+ [
28
+ ::MCP::ResourceTemplate.new(
29
+ uri_template: "guide://{name}",
30
+ name: "guide",
31
+ description: "Per-package usage guide: component (Rails forms/views), controller " \
32
+ "(plain-JS/non-Rails setup), tailwind (Tailwind install/theming), theme " \
33
+ "(custom theme integration contract)",
34
+ mime_type: "text/markdown"
35
+ )
36
+ ].freeze
37
+ end
25
38
 
26
- text_resource(uri, "text/markdown", store[:guide])
39
+ def read(uri, store)
40
+ guide = store[:guide]
41
+
42
+ case uri
43
+ when "guide://overview"
44
+ text_resource(uri, "text/markdown", guide[:overview])
45
+ when %r{\Aguide://(#{GUIDE_NAMES.join("|")})\z}
46
+ text_resource(uri, "text/markdown", guide[Regexp.last_match(1).to_sym])
47
+ end
48
+ end
49
+
50
+ def register_tools(server, store)
51
+ guide = store[:guide]
52
+
53
+ register_list_guides(server)
54
+ register_get_guide(server, guide)
55
+ end
56
+
57
+ private
58
+
59
+ def register_list_guides(server)
60
+ text_tool(
61
+ server,
62
+ name: "list_guides",
63
+ description: "Lists available per-package usage guides — see guide://overview for the full map"
64
+ ) do
65
+ JSON.generate(GUIDE_NAMES)
66
+ end
67
+ end
68
+
69
+ def register_get_guide(server, guide)
70
+ text_tool(
71
+ server,
72
+ name: "get_guide",
73
+ description: "Returns a per-package usage guide: component (Rails forms/views), controller " \
74
+ "(plain-JS/non-Rails setup), tailwind (Tailwind install/theming), theme (custom " \
75
+ "theme integration contract)",
76
+ input_schema: { properties: { name: { type: "string", enum: GUIDE_NAMES } }, required: ["name"] }
77
+ ) do |name:|
78
+ guide[name.to_sym] || not_found("unknown guide: #{name}")
79
+ end
80
+ end
27
81
  end
28
82
  end
29
83
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ class Icons < Base
7
+ class << self
8
+ def loader_key = :icons
9
+
10
+ def loader = IconsLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "component://icons",
16
+ name: "icons",
17
+ description: "All available icon names bundled with the Tailwind theme",
18
+ mime_type: "application/json"
19
+ )
20
+ ].freeze
21
+ end
22
+
23
+ def read(uri, store)
24
+ return unless uri == "component://icons"
25
+
26
+ json_resource(uri, store[:icons])
27
+ end
28
+
29
+ def register_tools(server, store)
30
+ text_tool(
31
+ server,
32
+ name: "list_icons",
33
+ description: "Lists all available icon names bundled with the Tailwind theme"
34
+ ) do
35
+ JSON.generate(store[:icons])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,53 +3,77 @@
3
3
  module StimulusPlumbers
4
4
  module MCP
5
5
  module Plugins
6
- module Tailwind
7
- extend Base
8
-
9
- LOADER_KEY = :tailwind
10
- LOADER = TailwindThemeLoader
11
-
12
- STATIC_RESOURCES = [
13
- ::MCP::Resource.new(
14
- uri: "tailwind://components",
15
- name: "tailwind-components-index",
16
- description: "Index of component keys implemented by the stimulus-plumbers Tailwind theme",
17
- mime_type: "application/json"
18
- )
19
- ].freeze
20
-
21
- DYNAMIC_RESOURCE_TEMPLATES = [
22
- ::MCP::ResourceTemplate.new(
23
- uri_template: "tailwind://components/{name}",
24
- name: "tailwind-component-classes",
25
- description: "Tailwind CSS utility classes emitted per variant for a component",
26
- mime_type: "application/json"
27
- )
28
- ].freeze
29
-
30
- def self.read(uri, store)
31
- tailwind = store[:tailwind]
32
-
33
- case uri
34
- when "tailwind://components"
35
- json_resource(uri, tailwind.keys)
36
- when %r{\Atailwind://components/(.+)\z}
37
- key = Regexp.last_match(1).to_sym
38
- json_resource(uri, tailwind[key] || { error: "unknown component: #{key}" })
6
+ class Tailwind < Base
7
+ class << self
8
+ def loader_key = :tailwind
9
+
10
+ def loader = TailwindLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "component://tailwind",
16
+ name: "component-tailwind-index",
17
+ description: "Index of component keys implemented by the stimulus-plumbers Tailwind theme",
18
+ mime_type: "application/json"
19
+ )
20
+ ].freeze
21
+ end
22
+
23
+ def dynamic_resource_templates
24
+ [
25
+ ::MCP::ResourceTemplate.new(
26
+ uri_template: "component://{name}/tailwind",
27
+ name: "component-tailwind-classes",
28
+ description: "Tailwind CSS utility classes emitted per variant for a component",
29
+ mime_type: "application/json"
30
+ )
31
+ ].freeze
32
+ end
33
+
34
+ def read(uri, store)
35
+ tailwind = store[:tailwind]
36
+
37
+ case uri
38
+ when "component://tailwind"
39
+ json_resource(uri, tailwind.keys)
40
+ when %r{\Acomponent://([^/]+)/tailwind\z}
41
+ key = Regexp.last_match(1).to_sym
42
+ json_resource(uri, tailwind[key] || { error: "unknown component: #{key}" })
43
+ end
44
+ end
45
+
46
+ def register_tools(server, store)
47
+ tailwind = store[:tailwind]
48
+
49
+ register_list_component_tailwind(server, tailwind)
50
+ register_get_component_tailwind(server, tailwind)
51
+ end
52
+
53
+ private
54
+
55
+ def register_list_component_tailwind(server, tailwind)
56
+ text_tool(
57
+ server,
58
+ name: "list_component_tailwind",
59
+ description: "Lists component keys implemented by the stimulus-plumbers Tailwind theme"
60
+ ) do
61
+ JSON.generate(tailwind.keys)
62
+ end
39
63
  end
40
- end
41
64
 
42
- def self.register_tools(server, store)
43
- tailwind = store[:tailwind]
44
-
45
- text_tool(
46
- server,
47
- name: "get_tailwind_classes",
48
- description: "Returns Tailwind CSS utility classes emitted per variant for a component",
49
- input_schema: { properties: { component: { type: "string" } }, required: ["component"] }
50
- ) do |component:|
51
- data = tailwind[component.to_sym]
52
- data ? JSON.generate(data) : not_found("unknown component: #{component}")
65
+ def register_get_component_tailwind(server, tailwind)
66
+ text_tool(
67
+ server,
68
+ name: "get_component_tailwind",
69
+ description: "Returns Tailwind CSS utility classes emitted per variant for a component. " \
70
+ "For themed params/controllers use get_component_schema; for the Rails helper " \
71
+ "surface use get_component_helper",
72
+ input_schema: { properties: { name: { type: "string" } }, required: ["name"] }
73
+ ) do |name:|
74
+ data = tailwind[name.to_sym]
75
+ data ? JSON.generate(data) : not_found("unknown component: #{name}")
76
+ end
53
77
  end
54
78
  end
55
79
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ class Versions < Base
7
+ class << self
8
+ def loader_key = :versions
9
+
10
+ def loader = VersionsLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "versions://sources",
16
+ name: "source-versions",
17
+ description: "Resolved version (and resolution path, for stimulus) of each gem/package " \
18
+ "backing this server's data — use to spot version drift between sources",
19
+ mime_type: "application/json"
20
+ )
21
+ ].freeze
22
+ end
23
+
24
+ def read(uri, store)
25
+ return unless uri == "versions://sources"
26
+
27
+ json_resource(uri, store[:versions])
28
+ end
29
+
30
+ def register_tools(server, store)
31
+ text_tool(
32
+ server,
33
+ name: "get_source_versions",
34
+ description: "Returns the resolved version (and resolution path, for stimulus) of each " \
35
+ "gem/package backing this server's data — use to spot version drift between sources"
36
+ ) do
37
+ JSON.generate(store[:versions])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -4,12 +4,19 @@ module StimulusPlumbers
4
4
  module MCP
5
5
  module Server
6
6
  PLUGINS = [
7
+ Plugins::ComponentDocs,
8
+ Plugins::ComponentSchema,
9
+ Plugins::ComponentTheme,
10
+
11
+ Plugins::ControllerDocs,
12
+ Plugins::ControllerSchema,
13
+
14
+ Plugins::Icons,
15
+ Plugins::Tailwind,
16
+
17
+ Plugins::Aria,
7
18
  Plugins::Guide,
8
- Plugins::Schema,
9
- Plugins::Docs,
10
- Plugins::Stimulus,
11
- Plugins::Theme,
12
- Plugins::Tailwind
19
+ Plugins::Versions
13
20
  ].freeze
14
21
 
15
22
  INSTRUCTIONS = "Use these resources and tools for accurate API references when " \
@@ -17,24 +24,34 @@ module StimulusPlumbers
17
24
  "Read guide://overview first for a map of the form/view/stimulus API."
18
25
 
19
26
  def self.build
20
- store = PLUGINS.to_h { |plugin| [plugin::LOADER_KEY, plugin::LOADER.call] }
27
+ store = build_store
21
28
  report_sources(store)
22
29
 
23
30
  server = new_server
24
31
  server.resources_read_handler do |params|
25
- PLUGINS.lazy.filter_map { |plugin| plugin.read(params[:uri], store) }.first || []
32
+ PLUGINS.lazy.filter_map { |plugin| plugin.read(params[:uri], store) }.first ||
33
+ unknown_resource(params[:uri])
26
34
  end
27
35
  PLUGINS.each { |plugin| plugin.register_tools(server, store) }
28
36
  server
29
37
  end
30
38
 
39
+ def self.build_store
40
+ PLUGINS.to_h { |plugin| [plugin.loader_key, plugin.loader.call] }
41
+ end
42
+
43
+ def self.unknown_resource(uri)
44
+ message = "unknown resource: #{uri} — read guide://overview for a map of available resources"
45
+ [{ uri: uri, mimeType: "application/json", text: JSON.generate(error: message) }]
46
+ end
47
+
31
48
  def self.new_server
32
49
  ::MCP::Server.new(
33
50
  name: "stimulus-plumbers",
34
51
  version: StimulusPlumbers::MCP::VERSION,
35
52
  instructions: INSTRUCTIONS,
36
- resources: PLUGINS.flat_map { |p| p::STATIC_RESOURCES },
37
- resource_templates: PLUGINS.flat_map { |p| p::DYNAMIC_RESOURCE_TEMPLATES }
53
+ resources: PLUGINS.flat_map(&:static_resources),
54
+ resource_templates: PLUGINS.flat_map(&:dynamic_resource_templates)
38
55
  )
39
56
  end
40
57
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StimulusPlumbers
4
4
  module MCP
5
- VERSION = "0.4.5"
5
+ VERSION = "0.4.9"
6
6
  end
7
7
  end