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.
- checksums.yaml +4 -4
- data/README.md +46 -25
- 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_manifest_loader.rb +46 -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 +53 -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 +23 -12
- data/lib/stimulus_plumbers/mcp/loaders/component_controller_map.rb +0 -38
- data/lib/stimulus_plumbers/mcp/loaders/docs_loader.rb +0 -129
- data/lib/stimulus_plumbers/mcp/loaders/guide/overview.md +0 -48
- data/lib/stimulus_plumbers/mcp/loaders/schema_loader.rb +0 -45
- data/lib/stimulus_plumbers/mcp/loaders/stimulus_manifest.rb +0 -47
- 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,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
|
|
@@ -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::
|
|
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 =
|
|
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
|
|
37
|
-
resource_templates: PLUGINS.flat_map
|
|
53
|
+
resources: PLUGINS.flat_map(&:static_resources),
|
|
54
|
+
resource_templates: PLUGINS.flat_map(&:dynamic_resource_templates)
|
|
38
55
|
)
|
|
39
56
|
end
|
|
40
57
|
|