stimulus_plumbers_mcp 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00ce340e2b2d68fcfef0406efd76e88e91c5214c653bc44cbed78d6ebf23bd4e
4
- data.tar.gz: c4ec9ad1abdabfb7ab464b685e604d78feec3a67b10944339885adbd5f322668
3
+ metadata.gz: c888ecb5ce465abc47875df8b2924ed833930400af77b9ebc546207173adf4d3
4
+ data.tar.gz: 9f2278aa3c144f18e82a3070894bbfd4aeb1a30870b0ca1611c137252aedcaac
5
5
  SHA512:
6
- metadata.gz: 752269bd37dc2716c2fdabf2aab3bb6cecb9bd6aaae82ff245c95414ef98c9a670ac1da896a32dafa00b9d47b8231214b5f150f3ca1efd7705faafe978eabccb
7
- data.tar.gz: 8540984025d8870982f1d75d8a33a51dfcb3096084eb8c8d0ef0dc5cc39f62b02310f787568a0d34a8cb49ac055273b6f2fa7254b398a67703879d750dceb9f5
6
+ metadata.gz: e62c6f7a43d006f158e2bf309f7a044ddff0bc74534667d2c736ac9f7c8dd5f977f0daf1b00ba063b4a5d6dc7c0d7043949cc89ca40e0a203847ba0e25cb87ad
7
+ data.tar.gz: 8d7dafd931f3db501d06b48436ab5d028d5e4fb57d49b1a8ed4cc20c5ffad7ccdb6bebf45cc166438cafb55cf637ced44bb48e78339998189f2ebd7629c13878
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # stimulus-plumbers-mcp
2
+
3
+ MCP server for [`stimulus_plumbers`](../stimulus-plumbers-rails). Exposes the component API schema, documentation, and Stimulus controller metadata to LLM-powered IDEs.
4
+
5
+ ## Requirements
6
+
7
+ - Ruby >= 3.0
8
+ - Node (contributors only — to build the Stimulus controller manifest)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ gem install stimulus_plumbers_mcp
14
+ ```
15
+
16
+ ## Resources
17
+
18
+ | URI | Content |
19
+ |-----|---------|
20
+ | `guide://overview` | **Start here** — map of the form/view/stimulus API with pointers to every tool/resource |
21
+ | `schema://components` | Index of all component theme keys |
22
+ | `schema://components/{name}` | Params, valid values, defaults + required controllers |
23
+ | `schema://icons` | All available icon names |
24
+ | `schema://stimulus` | Map from component key → required Stimulus controller identifiers |
25
+ | `docs://components/{name}` | Full markdown doc with ERB examples |
26
+ | `helper://components/{name}` | Full `sp_` helper surface: keyword options + defaults (grouped by helper signature) + slot methods (with block-required flag) |
27
+ | `stimulus://controllers` | Index of Stimulus controller identifiers |
28
+ | `stimulus://controllers/{identifier}` | Targets, values, outlets, classes for a controller |
29
+ | `theme://base` | Custom theme authoring guide |
30
+ | `theme://components` | Index of theme-implementable component keys |
31
+ | `theme://components/{name}` | Method name, param signature, return contract |
32
+ | `tailwind://components` | Index of Tailwind theme component keys |
33
+ | `tailwind://components/{name}` | Tailwind CSS classes per variant |
34
+
35
+ ## Tools
36
+
37
+ | Tool | Input | Output |
38
+ |------|-------|--------|
39
+ | `list_components` | — | All component theme keys |
40
+ | `list_docs` | — | Components with markdown docs + helper signatures (the `docs://`/`helper://` `{name}` set) |
41
+ | `get_component_schema` | `component` | Themed params (type/variant/size) + defaults + required controllers |
42
+ | `get_helper_signature` | `component` | Full `sp_` helper surface — keyword options grouped by helper signature (incl. `icon_leading`) + slot methods |
43
+ | `get_erb_examples` | `component` | ERB code fences from docs |
44
+ | `get_field_types` | `builder_method` (`field`/`collection_field`/`choice`) | Valid `as:` values |
45
+ | `list_controllers` | — | All Stimulus controller identifiers |
46
+ | `get_controller_schema` | `controller` | Targets, values, outlets, classes |
47
+ | `get_theme_interface` | `component` | Method signature + return contract for custom theme |
48
+ | `get_tailwind_classes` | `component` | Tailwind CSS utility classes per variant |
49
+
50
+ ## IDE Setup
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "stimulus-plumbers": {
56
+ "command": "gem",
57
+ "args": ["exec", "stimulus-plumbers-mcp"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Requires Ruby >= 3.3. No `cwd` required. Pin a specific version by adding it to your `Gemfile` and running `bundle exec gem exec stimulus-plumbers-mcp`.
64
+
65
+ ## Stimulus Manifest
66
+
67
+ `stimulus://` resources require the controller manifest. The server resolves it in order:
68
+
69
+ 1. `node_modules/@stimulus-plumbers/controllers/dist/controllers.manifest.json` — npm/yarn/bun projects
70
+ 2. `vendor/controllers.manifest.json` inside the installed `stimulus_plumbers` gem — importmaps fallback
71
+ 3. `../stimulus-plumbers/dist/controllers.manifest.json` — monorepo dev fallback
72
+
73
+ No extra steps needed for setups 1 or 2.
74
+
75
+ ## Development
76
+
77
+ ```bash
78
+ git clone https://github.com/ryancyq/stimulus-plumbers
79
+ cd stimulus-plumbers/stimulus-plumbers-mcp
80
+ bundle install
81
+
82
+ # Build the Stimulus controller manifest (pure Node, no toolchain install needed)
83
+ cd ../stimulus-plumbers && node --run build:manifest && cd -
84
+
85
+ # Start the server
86
+ bundle exec ruby bin/server
87
+ ```
88
+
89
+ ### Manual querying
90
+
91
+ `bin/mcp-query` wraps the JSON-RPC stdio protocol for quick manual inspection:
92
+
93
+ ```bash
94
+ bundle exec ruby bin/mcp-query tools # list tools
95
+ bundle exec ruby bin/mcp-query resources # list resources
96
+ bundle exec ruby bin/mcp-query tool list_components # call a tool (no args)
97
+ bundle exec ruby bin/mcp-query tool get_component_schema '{"component":"button"}'
98
+ bundle exec ruby bin/mcp-query read guide://overview
99
+ bundle exec ruby bin/mcp-query read docs://components/form
100
+ ```
101
+
102
+ ```bash
103
+ bundle exec rake test:unit # unit tests
104
+ bundle exec rake rubocop # lint
105
+ bundle exec rake coverage # run tests with coverage
106
+ ```
107
+
108
+ ## License
109
+
110
+ [MIT](https://opensource.org/licenses/MIT)
data/bin/bump-version ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ VERSION = ARGV[0]
5
+
6
+ if VERSION.nil? || VERSION.empty?
7
+ warn "Usage: #{$PROGRAM_NAME} <version>"
8
+ exit 1
9
+ end
10
+
11
+ OUTPUT_FILE = File.expand_path("../lib/stimulus_plumbers/mcp/version.rb", __dir__)
12
+
13
+ output = <<~RUBY
14
+ # frozen_string_literal: true
15
+
16
+ module StimulusPlumbers
17
+ module MCP
18
+ VERSION = "#{VERSION}"
19
+ end
20
+ end
21
+ RUBY
22
+
23
+ File.write(OUTPUT_FILE, output)
24
+ system("bundle > /dev/null")
25
+
26
+ puts VERSION
data/bin/mcp-query ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Query the stimulus-plumbers MCP server over stdio.
5
+ #
6
+ # Usage:
7
+ # bin/mcp-query tools # list available tools
8
+ # bin/mcp-query resources # list available resources
9
+ # bin/mcp-query tool <name> [args_json] # call a tool (args_json defaults to {})
10
+ # bin/mcp-query read <uri> # read a resource
11
+
12
+ require "bundler/setup"
13
+ require "json"
14
+ require "open3"
15
+ require "rbconfig"
16
+
17
+ SERVER = File.join(__dir__, "server")
18
+
19
+ def usage
20
+ warn <<~USAGE
21
+ Usage:
22
+ bin/mcp-query tools
23
+ bin/mcp-query resources
24
+ bin/mcp-query tool <name> [args_json]
25
+ bin/mcp-query read <uri>
26
+ USAGE
27
+ exit 1
28
+ end
29
+
30
+ def die(message)
31
+ warn "error: #{message}"
32
+ exit 1
33
+ end
34
+
35
+ def build_requests(*requests)
36
+ client_info = { name: "mcp-query", version: "1.0" }
37
+ init_params = { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: client_info }
38
+ handshake = [
39
+ { jsonrpc: "2.0", id: 1, method: "initialize", params: init_params },
40
+ { jsonrpc: "2.0", method: "notifications/initialized" }
41
+ ]
42
+ "#{(handshake + requests).map { JSON.generate(_1) }.join("\n")}\n"
43
+ end
44
+
45
+ def query_server(requests)
46
+ stdout, _stderr, = Open3.capture3(RbConfig.ruby, SERVER, stdin_data: requests)
47
+ parsed = stdout.each_line.filter_map do |l|
48
+ JSON.parse(l.strip)
49
+ rescue StandardError
50
+ nil
51
+ end
52
+ response = parsed.find { |r| r["id"] == 2 }
53
+ raise "No response from server" unless response
54
+
55
+ response
56
+ end
57
+
58
+ def print_error_and_exit(response)
59
+ warn "error: #{JSON.pretty_generate(response["error"])}"
60
+ exit 1
61
+ end
62
+
63
+ subcommand = ARGV.shift || usage
64
+
65
+ case subcommand
66
+ when "tools"
67
+ response = query_server(build_requests({ jsonrpc: "2.0", id: 2, method: "tools/list" }))
68
+ print_error_and_exit(response) if response["error"]
69
+
70
+ response.dig("result", "tools").each do |tool|
71
+ puts " #{tool["name"]}"
72
+ puts " #{tool["description"]}"
73
+ puts
74
+ end
75
+
76
+ when "resources"
77
+ response = query_server(build_requests({ jsonrpc: "2.0", id: 2, method: "resources/list" }))
78
+ print_error_and_exit(response) if response["error"]
79
+
80
+ response.dig("result", "resources").each do |res|
81
+ puts " #{res["uri"]}"
82
+ puts " #{res["description"] || res["name"]}"
83
+ puts
84
+ end
85
+
86
+ when "tool"
87
+ name = ARGV.shift || die("tool requires a name")
88
+ args = JSON.parse(ARGV.shift || "{}")
89
+
90
+ request = { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: name, arguments: args } }
91
+ response = query_server(build_requests(request))
92
+ print_error_and_exit(response) if response["error"]
93
+
94
+ response.dig("result", "content").each { puts _1["text"] }
95
+
96
+ when "read"
97
+ uri = ARGV.shift || die("read requires a URI")
98
+
99
+ request = { jsonrpc: "2.0", id: 2, method: "resources/read", params: { uri: uri } }
100
+ response = query_server(build_requests(request))
101
+ print_error_and_exit(response) if response["error"]
102
+
103
+ response.dig("result", "contents").each { puts _1["text"] }
104
+
105
+ else
106
+ usage
107
+ end
data/bin/server ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Monorepo dev entry point — loads lib via require_relative (no gem install needed).
5
+ require "bundler/setup"
6
+ require_relative "../lib/stimulus_plumbers_mcp"
7
+
8
+ StimulusPlumbers::MCP::CLI.run
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # gem exec entry point — loads via installed gem load path (no clone needed).
5
+ require "stimulus_plumbers_mcp"
6
+
7
+ StimulusPlumbers::MCP::CLI.run
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module MCP
5
+ module CLI
6
+ def self.run
7
+ server = Server.build
8
+ warn "[stimulus-plumbers-mcp] server started (version #{VERSION})"
9
+ ::MCP::Server::Transports::StdioTransport.new(server).open
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,13 +3,20 @@
3
3
  module StimulusPlumbers
4
4
  module MCP
5
5
  class DocsLoader
6
- DOCS_DIR = File.expand_path(
7
- "../../../../../stimulus-plumbers-rails/docs/component",
8
- __dir__
9
- )
6
+ def self.docs_dir
7
+ @docs_dir ||= resolve_docs_dir
8
+ end
9
+
10
+ def self.resolve_docs_dir
11
+ gem_dir = Gem::Specification.find_by_name("stimulus_plumbers").gem_dir
12
+ File.join(gem_dir, "docs/component")
13
+ rescue Gem::MissingSpecError
14
+ File.expand_path("../../../../../stimulus-plumbers-rails/docs/component", __dir__)
15
+ end
16
+ private_class_method :resolve_docs_dir
10
17
 
11
18
  def self.call
12
- Dir[File.join(DOCS_DIR, "*.md")].each_with_object({}) do |path, result|
19
+ Dir[File.join(docs_dir, "*.md")].each_with_object({}) do |path, result|
13
20
  name = File.basename(path, ".md").to_sym
14
21
  content = File.read(path)
15
22
  result[name] = {
@@ -0,0 +1,48 @@
1
+ # stimulus-plumbers — overview
2
+
3
+ Accessible Rails view components plus a themed form builder, backed by Stimulus controllers.
4
+ Start here, then drill into the per-component resources/tools below.
5
+
6
+ ## Tailwind theme setup
7
+ The `stimulus_plumbers_tailwind` gem provides themed CSS classes for all components. After adding it to
8
+ your Gemfile, run the install generator once:
9
+ bin/rails generate stimulus_plumbers_tailwind:install
10
+ This injects an `@source` directive into your Tailwind CSS entry file so component classes are included
11
+ in the compiled output. The generator checks `app/assets/stylesheets/application.tailwind.css`,
12
+ `app/assets/stylesheets/application.css`, and `app/javascript/entrypoints/application.css` in that
13
+ order. Override with `TAILWIND_CSS_FILE=/path/to/entry.css`. The path updates automatically on
14
+ `assets:precompile` and `tailwindcss:build` — no re-run needed after `bundle update`.
15
+
16
+ ## Building forms
17
+ Use `StimulusPlumbers::Form::Builder` (set `config.action_view.default_form_builder`, or pass
18
+ `builder:` to `form_with`). Two levels:
19
+ - **Level 2 — recommended.** Full accessible field (label + input + hint + error):
20
+ `f.field(attr, as:)`, `f.collection_field(attr, as:, collection:, ...)`, `f.choice(attr, as:)`.
21
+ Valid `as:` values come from `get_field_types` (pass `builder_method: "field"`, `"collection_field"`, or `"choice"`).
22
+ - **Level 1.** Native helper overrides (`f.text_field`, `f.select`, `f.check_box`, ...) render
23
+ only the themed input element — use when you control the surrounding markup.
24
+ Submit with `f.submit` (themed button). Full form reference + examples: read `docs://components/form`.
25
+
26
+ ## Building views
27
+ Render components with `sp_*` helpers (`sp_button`, `sp_button_group`, `sp_card`, `sp_list`,
28
+ `sp_link`, `sp_avatar`, `sp_divider`, `sp_icon`, `sp_popover`). For any component:
29
+ - `get_helper_signature(name)` — full helper surface: keyword options (incl. `icon_leading`) + slot methods
30
+ - `get_component_schema(name)` — themed params (type/variant/size) with valid values + defaults
31
+ - `get_erb_examples(name)` — runnable ERB snippets
32
+ List everything with `list_components`; `list_docs` shows which components have full docs
33
+ (`docs://components/{name}`) and helper signatures (`helper://components/{name}`).
34
+ Icon options take a name from `schema://icons`.
35
+ All 77 schema components are queryable via `get_component_schema`; only a subset have full docs and ERB examples — use `list_docs` to see what's covered.
36
+
37
+ ## Stimulus wiring
38
+ Most display components are pure markup; interactive ones (combobox, popover, calendar) emit their
39
+ `data-controller` wiring automatically. Component → required controllers: `schema://stimulus`.
40
+ Controller details (targets/values/outlets/classes): `get_controller_schema(id)` / `list_controllers`.
41
+
42
+ ## How this server is organized
43
+ - **Resources** (`schema://`, `docs://`, `helper://`, `theme://`, `tailwind://`, `guide://`) are for
44
+ browsing/pulling context; **tools** (`get_*`, `list_*`) are for targeted lookups. They expose the
45
+ same data two ways — use whichever your client prefers.
46
+ - **View generation** uses the form builder + `sp_` helper tools/resources above. The `theme://` and
47
+ `tailwind://` surfaces are for *theme authors* implementing a custom theme — skip them when generating views.
48
+ - **Errors are uniform:** a not-found tool/resource returns `{ "error": "..." }` (tools also set `isError`).
@@ -2,22 +2,46 @@
2
2
 
3
3
  module StimulusPlumbers
4
4
  module MCP
5
- class StimulusManifest
6
- MANIFEST_PATH = File.expand_path(
7
- File.join(__dir__, "../../../../..", "stimulus-plumbers", "dist", "controllers.manifest.json")
8
- ).freeze
5
+ class StimulusManifestLoader
6
+ MANIFEST_FILENAME = "controllers.manifest.json"
9
7
 
10
8
  def self.call
11
- unless File.exist?(MANIFEST_PATH)
9
+ paths = manifest_paths
10
+ path = paths.find { |p| File.exist?(p) }
11
+
12
+ unless path
12
13
  StimulusPlumbers::Logger.warn(
13
- "controllers.manifest.json not found at #{MANIFEST_PATH}. " \
14
+ "#{MANIFEST_FILENAME} not found. Tried:\n" \
15
+ "#{paths.map { |p| " - #{p}" }.join("\n")}\n" \
14
16
  "Run `node --run build:manifest` in stimulus-plumbers/ to generate it."
15
17
  )
16
18
  return {}
17
19
  end
18
20
 
19
- JSON.parse(File.read(MANIFEST_PATH))
21
+ JSON.parse(File.read(path))
22
+ end
23
+
24
+ def self.manifest_paths
25
+ [
26
+ # 1. npm/yarn/bun project — version matches project lockfile
27
+ File.join(Dir.pwd, "node_modules/@stimulus-plumbers/controllers/dist", MANIFEST_FILENAME),
28
+ # 2. importmaps users — vendored in Rails gem at release time
29
+ gem_vendor_path,
30
+ # 3. Monorepo dev fallback
31
+ File.expand_path(
32
+ File.join(__dir__, "../../../../..", "stimulus-plumbers", "dist", MANIFEST_FILENAME)
33
+ )
34
+ ].compact
20
35
  end
36
+
37
+ def self.gem_vendor_path
38
+ gem_dir = Gem::Specification.find_by_name("stimulus_plumbers").gem_dir
39
+ File.join(gem_dir, "vendor", MANIFEST_FILENAME)
40
+ rescue Gem::MissingSpecError
41
+ nil
42
+ end
43
+
44
+ private_class_method :manifest_paths, :gem_vendor_path
21
45
  end
22
46
  end
23
47
  end
@@ -7,7 +7,7 @@ module StimulusPlumbers
7
7
  extend Base
8
8
 
9
9
  LOADER_KEY = :stimulus
10
- LOADER = StimulusManifest
10
+ LOADER = StimulusManifestLoader
11
11
 
12
12
  STATIC_RESOURCES = [
13
13
  ::MCP::Resource.new(
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StimulusPlumbers
4
4
  module MCP
5
- VERSION = "0.4.4"
5
+ VERSION = "0.4.5"
6
6
  end
7
7
  end
@@ -24,3 +24,4 @@ require_relative "stimulus_plumbers/mcp/plugins/stimulus"
24
24
  require_relative "stimulus_plumbers/mcp/plugins/theme"
25
25
  require_relative "stimulus_plumbers/mcp/plugins/tailwind"
26
26
  require_relative "stimulus_plumbers/mcp/server"
27
+ require_relative "stimulus_plumbers/mcp/cli"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimulus_plumbers_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Chang
@@ -69,12 +69,20 @@ description: A local MCP server that exposes the stimulus-plumbers API schema an
69
69
  documentation to LLM-powered IDEs
70
70
  email:
71
71
  - ryancyq@gmail.com
72
- executables: []
72
+ executables:
73
+ - stimulus-plumbers-mcp
73
74
  extensions: []
74
75
  extra_rdoc_files: []
75
76
  files:
77
+ - README.md
78
+ - bin/bump-version
79
+ - bin/mcp-query
80
+ - bin/server
81
+ - bin/stimulus-plumbers-mcp
82
+ - lib/stimulus_plumbers/mcp/cli.rb
76
83
  - lib/stimulus_plumbers/mcp/loaders/component_controller_map.rb
77
84
  - lib/stimulus_plumbers/mcp/loaders/docs_loader.rb
85
+ - lib/stimulus_plumbers/mcp/loaders/guide/overview.md
78
86
  - lib/stimulus_plumbers/mcp/loaders/guide_loader.rb
79
87
  - lib/stimulus_plumbers/mcp/loaders/schema_loader.rb
80
88
  - lib/stimulus_plumbers/mcp/loaders/stimulus_manifest.rb
@@ -93,7 +101,12 @@ files:
93
101
  homepage: https://github.com/ryancyq/stimulus-plumbers
94
102
  licenses:
95
103
  - MIT
96
- metadata: {}
104
+ metadata:
105
+ rubygems_mfa_required: 'true'
106
+ allowed_push_host: https://rubygems.org
107
+ changelog_uri: https://github.com/ryancyq/stimulus-plumbers/blob/main/CHANGELOG.md
108
+ homepage_uri: https://github.com/ryancyq/stimulus-plumbers
109
+ source_code_uri: https://github.com/ryancyq/stimulus-plumbers/tree/main/stimulus-plumbers-mcp
97
110
  rdoc_options: []
98
111
  require_paths:
99
112
  - lib