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.
- checksums.yaml +4 -4
- data/README.md +120 -0
- data/bin/bump-version +26 -0
- data/bin/mcp-query +107 -0
- data/bin/server +8 -0
- data/bin/stimulus-plumbers-mcp +7 -0
- data/lib/stimulus_plumbers/mcp/cli.rb +13 -0
- data/lib/stimulus_plumbers/mcp/loaders/aria_loader.rb +31 -0
- data/lib/stimulus_plumbers/mcp/loaders/component_docs_loader.rb +38 -0
- data/lib/stimulus_plumbers/mcp/loaders/component_requirements.rb +41 -0
- data/lib/stimulus_plumbers/mcp/loaders/component_schema_loader.rb +49 -0
- data/lib/stimulus_plumbers/mcp/loaders/component_theme_loader.rb +41 -0
- data/lib/stimulus_plumbers/mcp/loaders/controller_docs_loader.rb +31 -0
- data/lib/stimulus_plumbers/mcp/loaders/controller_schema_loader.rb +49 -0
- data/lib/stimulus_plumbers/mcp/loaders/guide.md +48 -0
- data/lib/stimulus_plumbers/mcp/loaders/guide_loader.rb +40 -4
- data/lib/stimulus_plumbers/mcp/loaders/icons_loader.rb +34 -0
- data/lib/stimulus_plumbers/mcp/loaders/support/docs_table_parser.rb +112 -0
- data/lib/stimulus_plumbers/mcp/loaders/support/gem_vendor_path.rb +18 -0
- data/lib/stimulus_plumbers/mcp/loaders/tailwind_loader.rb +35 -0
- data/lib/stimulus_plumbers/mcp/loaders/versions_loader.rb +110 -0
- data/lib/stimulus_plumbers/mcp/plugins/aria.rb +34 -0
- data/lib/stimulus_plumbers/mcp/plugins/base.rb +40 -31
- data/lib/stimulus_plumbers/mcp/plugins/component_docs.rb +96 -0
- data/lib/stimulus_plumbers/mcp/plugins/component_schema.rb +133 -0
- data/lib/stimulus_plumbers/mcp/plugins/component_theme.rb +90 -0
- data/lib/stimulus_plumbers/mcp/plugins/controller_docs.rb +91 -0
- data/lib/stimulus_plumbers/mcp/plugins/controller_schema.rb +81 -0
- data/lib/stimulus_plumbers/mcp/plugins/guide.rb +70 -16
- data/lib/stimulus_plumbers/mcp/plugins/icons.rb +42 -0
- data/lib/stimulus_plumbers/mcp/plugins/tailwind.rb +69 -45
- data/lib/stimulus_plumbers/mcp/plugins/versions.rb +44 -0
- data/lib/stimulus_plumbers/mcp/server.rb +26 -9
- data/lib/stimulus_plumbers/mcp/version.rb +1 -1
- data/lib/stimulus_plumbers_mcp.rb +32 -11
- metadata +36 -13
- data/lib/stimulus_plumbers/mcp/loaders/component_controller_map.rb +0 -38
- data/lib/stimulus_plumbers/mcp/loaders/docs_loader.rb +0 -122
- data/lib/stimulus_plumbers/mcp/loaders/schema_loader.rb +0 -45
- data/lib/stimulus_plumbers/mcp/loaders/stimulus_manifest.rb +0 -23
- data/lib/stimulus_plumbers/mcp/loaders/tailwind_theme_loader.rb +0 -29
- data/lib/stimulus_plumbers/mcp/loaders/theme_loader.rb +0 -97
- data/lib/stimulus_plumbers/mcp/plugins/docs.rb +0 -82
- data/lib/stimulus_plumbers/mcp/plugins/schema.rb +0 -110
- data/lib/stimulus_plumbers/mcp/plugins/stimulus.rb +0 -66
- 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
|
-
|
|
7
|
-
|
|
6
|
+
class Guide < Base
|
|
7
|
+
GUIDE_NAMES = %w[component controller tailwind theme].freeze
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
class << self
|
|
10
|
+
def loader_key = :guide
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|