stimulus_plumbers_mcp 0.4.4 → 0.4.8

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +120 -0
  3. data/bin/bump-version +26 -0
  4. data/bin/mcp-query +107 -0
  5. data/bin/server +8 -0
  6. data/bin/stimulus-plumbers-mcp +7 -0
  7. data/lib/stimulus_plumbers/mcp/cli.rb +13 -0
  8. data/lib/stimulus_plumbers/mcp/loaders/aria_loader.rb +31 -0
  9. data/lib/stimulus_plumbers/mcp/loaders/component_docs_loader.rb +38 -0
  10. data/lib/stimulus_plumbers/mcp/loaders/component_requirements.rb +41 -0
  11. data/lib/stimulus_plumbers/mcp/loaders/component_schema_loader.rb +49 -0
  12. data/lib/stimulus_plumbers/mcp/loaders/component_theme_loader.rb +41 -0
  13. data/lib/stimulus_plumbers/mcp/loaders/controller_docs_loader.rb +31 -0
  14. data/lib/stimulus_plumbers/mcp/loaders/controller_schema_loader.rb +49 -0
  15. data/lib/stimulus_plumbers/mcp/loaders/guide.md +48 -0
  16. data/lib/stimulus_plumbers/mcp/loaders/guide_loader.rb +40 -4
  17. data/lib/stimulus_plumbers/mcp/loaders/icons_loader.rb +34 -0
  18. data/lib/stimulus_plumbers/mcp/loaders/support/docs_table_parser.rb +112 -0
  19. data/lib/stimulus_plumbers/mcp/loaders/support/gem_vendor_path.rb +18 -0
  20. data/lib/stimulus_plumbers/mcp/loaders/tailwind_loader.rb +35 -0
  21. data/lib/stimulus_plumbers/mcp/loaders/versions_loader.rb +110 -0
  22. data/lib/stimulus_plumbers/mcp/plugins/aria.rb +34 -0
  23. data/lib/stimulus_plumbers/mcp/plugins/base.rb +40 -31
  24. data/lib/stimulus_plumbers/mcp/plugins/component_docs.rb +96 -0
  25. data/lib/stimulus_plumbers/mcp/plugins/component_schema.rb +133 -0
  26. data/lib/stimulus_plumbers/mcp/plugins/component_theme.rb +90 -0
  27. data/lib/stimulus_plumbers/mcp/plugins/controller_docs.rb +91 -0
  28. data/lib/stimulus_plumbers/mcp/plugins/controller_schema.rb +81 -0
  29. data/lib/stimulus_plumbers/mcp/plugins/guide.rb +70 -16
  30. data/lib/stimulus_plumbers/mcp/plugins/icons.rb +42 -0
  31. data/lib/stimulus_plumbers/mcp/plugins/tailwind.rb +69 -45
  32. data/lib/stimulus_plumbers/mcp/plugins/versions.rb +44 -0
  33. data/lib/stimulus_plumbers/mcp/server.rb +26 -9
  34. data/lib/stimulus_plumbers/mcp/version.rb +1 -1
  35. data/lib/stimulus_plumbers_mcp.rb +32 -11
  36. metadata +36 -13
  37. data/lib/stimulus_plumbers/mcp/loaders/component_controller_map.rb +0 -38
  38. data/lib/stimulus_plumbers/mcp/loaders/docs_loader.rb +0 -122
  39. data/lib/stimulus_plumbers/mcp/loaders/schema_loader.rb +0 -45
  40. data/lib/stimulus_plumbers/mcp/loaders/stimulus_manifest.rb +0 -23
  41. data/lib/stimulus_plumbers/mcp/loaders/tailwind_theme_loader.rb +0 -29
  42. data/lib/stimulus_plumbers/mcp/loaders/theme_loader.rb +0 -97
  43. data/lib/stimulus_plumbers/mcp/plugins/docs.rb +0 -82
  44. data/lib/stimulus_plumbers/mcp/plugins/schema.rb +0 -110
  45. data/lib/stimulus_plumbers/mcp/plugins/stimulus.rb +0 -66
  46. data/lib/stimulus_plumbers/mcp/plugins/theme.rb +0 -66
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module Plugins
6
+ class ComponentSchema < Base
7
+ class << self
8
+ def loader_key = :component_schema
9
+
10
+ def loader = ComponentSchemaLoader
11
+
12
+ def static_resources
13
+ [
14
+ ::MCP::Resource.new(
15
+ uri: "component://index",
16
+ name: "components-index",
17
+ description: "Index of all stimulus-plumbers component theme keys",
18
+ mime_type: "application/json"
19
+ ),
20
+ ::MCP::Resource.new(
21
+ uri: "component://integration",
22
+ name: "component-integration",
23
+ description: "Mapping from Rails component name to the Stimulus controller identifiers it requires",
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}/schema",
33
+ name: "component-schema",
34
+ description: "Params, valid values, defaults, and required controllers for a component",
35
+ mime_type: "application/json"
36
+ )
37
+ ].freeze
38
+ end
39
+
40
+ def read(uri, store)
41
+ schema = store[:component_schema]
42
+
43
+ case uri
44
+ when "component://index"
45
+ json_resource(uri, schema[:components].keys)
46
+ when "component://integration"
47
+ json_resource(uri, schema[:controllers])
48
+ when %r{\Acomponent://([^/]+)/schema\z}
49
+ key = Regexp.last_match(1).to_sym
50
+ json_resource(uri, component_data(schema, key) || { error: "unknown component: #{key}" })
51
+ end
52
+ end
53
+
54
+ def register_tools(server, store)
55
+ schema = store[:component_schema]
56
+ data_by_component = component_data_map(schema)
57
+
58
+ register_list_components(server, schema)
59
+ register_get_component_schema(server, data_by_component)
60
+ register_get_field_as_values(server, schema)
61
+ register_get_field_as_controller(server, schema)
62
+ end
63
+
64
+ private
65
+
66
+ def register_list_components(server, schema)
67
+ text_tool(server, name: "list_components", description: "Lists all stimulus-plumbers component theme keys") do
68
+ JSON.generate(schema[:components].keys)
69
+ end
70
+ end
71
+
72
+ def register_get_component_schema(server, data_by_component)
73
+ text_tool(
74
+ server,
75
+ name: "get_component_schema",
76
+ description: "Returns themed params (e.g. type/variant/size) with valid values, defaults, and " \
77
+ "required Stimulus controllers. For the full helper surface (icon options, slots) " \
78
+ "use get_component_helper. Keys are renderer-level (e.g. combobox_listbox), not " \
79
+ "f.field(as:) values — for the as: value's backing controller use get_field_as_controller",
80
+ input_schema: { properties: { name: { type: "string" } }, required: ["name"] }
81
+ ) do |name:|
82
+ data = data_by_component[name.to_sym]
83
+ data ? JSON.generate(data) : not_found("unknown component: #{name}")
84
+ end
85
+ end
86
+
87
+ def register_get_field_as_values(server, schema)
88
+ text_tool(
89
+ server,
90
+ name: "get_field_as_values",
91
+ description: "Returns valid as: values for a form builder method",
92
+ input_schema: {
93
+ properties: { builder_method: { type: "string", enum: %w[field collection_field choice] } },
94
+ required: ["builder_method"]
95
+ }
96
+ ) do |builder_method:|
97
+ values = schema[:field_as][builder_method.to_sym]
98
+ values ? JSON.generate(values) : not_found("unknown builder_method: #{builder_method}")
99
+ end
100
+ end
101
+
102
+ def register_get_field_as_controller(server, schema)
103
+ text_tool(
104
+ server,
105
+ name: "get_field_as_controller",
106
+ description: "Returns the Stimulus controller identifier backing an f.field/f.collection_field " \
107
+ "as: value (e.g. \"select\" -> \"combobox-dropdown\"), for as: values whose picker " \
108
+ "is controller-backed. Plain input as: values (text, email, file, ...) return not-found",
109
+ input_schema: { properties: { as: { type: "string" } }, required: ["as"] }
110
+ ) do |as:|
111
+ controller = schema[:field_as_controllers][as.to_sym]
112
+ controller ? JSON.generate(controller: controller) : not_found("no dedicated controller for as: #{as}")
113
+ end
114
+ end
115
+
116
+ # Resolve component_data at module scope; define_tool blocks run in another context.
117
+ def component_data_map(schema)
118
+ keys = (schema[:components].keys + schema[:controllers].keys).uniq
119
+ keys.to_h { |key| [key, component_data(schema, key)] }
120
+ end
121
+
122
+ def component_data(schema, key)
123
+ params = schema[:components][key]
124
+ required = schema[:controllers]
125
+ return nil unless params || required.key?(key)
126
+
127
+ (params || {}).merge(controllers: required[key] || [])
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -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