Module: StimulusPlumbers::MCP::Server

Defined in:
lib/stimulus_plumbers/mcp/server.rb

Constant Summary collapse

PLUGINS =
[
  Plugins::ComponentDocs,
  Plugins::ComponentSchema,
  Plugins::ComponentTheme,

  Plugins::ControllerDocs,
  Plugins::ControllerSchema,

  Plugins::Icons,
  Plugins::Tailwind,

  Plugins::Aria,
  Plugins::Guide,
  Plugins::Versions
].freeze
INSTRUCTIONS =
"Use these resources and tools for accurate API references when " \
"generating Rails/ERB view code with the stimulus-plumbers UI library. " \
"Read guide://overview first for a map of the form/view/stimulus API."

Class Method Summary collapse

Class Method Details

.buildObject



26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/stimulus_plumbers/mcp/server.rb', line 26

def self.build
  store = build_store
  report_sources(store)

  server = new_server
  server.resources_read_handler do |params|
    PLUGINS.lazy.filter_map { |plugin| plugin.read(params[:uri], store) }.first ||
      unknown_resource(params[:uri])
  end
  PLUGINS.each { |plugin| plugin.register_tools(server, store) }
  server
end

.build_storeObject



39
40
41
# File 'lib/stimulus_plumbers/mcp/server.rb', line 39

def self.build_store
  PLUGINS.to_h { |plugin| [plugin.loader_key, plugin.loader.call] }
end

.empty_source?(value) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
# File 'lib/stimulus_plumbers/mcp/server.rb', line 77

def self.empty_source?(value)
  content = value.is_a?(Hash) && value.key?(:components) ? value[:components] : value
  content.respond_to?(:empty?) && content.empty?
end

.new_serverObject



48
49
50
51
52
53
54
55
56
# File 'lib/stimulus_plumbers/mcp/server.rb', line 48

def self.new_server
  ::MCP::Server.new(
    name:               "stimulus-plumbers",
    version:            StimulusPlumbers::MCP::VERSION,
    instructions:       INSTRUCTIONS,
    resources:          PLUGINS.flat_map(&:static_resources),
    resource_templates: PLUGINS.flat_map(&:dynamic_resource_templates)
  )
end

.report_sources(store) ⇒ Object

Loaders fail soft (sibling paths may be absent), so report what resolved and warn loudly on any empty source instead of starting silently wrong.



60
61
62
63
64
65
66
# File 'lib/stimulus_plumbers/mcp/server.rb', line 60

def self.report_sources(store)
  summary = store.map { |key, value| "#{key}=#{source_size(value)}" }.join(" ")
  StimulusPlumbers::Logger.info("sources: #{summary}")
  store.each_key do |key|
    StimulusPlumbers::Logger.warn("source '#{key}' is empty") if empty_source?(store[key])
  end
end

.source_size(value) ⇒ Object



68
69
70
71
72
73
74
75
# File 'lib/stimulus_plumbers/mcp/server.rb', line 68

def self.source_size(value)
  case value
  when String     then value.empty? ? 0 : "ok"
  when Hash       then value.key?(:components) ? value[:components].size : value.size
  when Enumerable then value.size
  else value.nil? ? 0 : 1
  end
end

.unknown_resource(uri) ⇒ Object



43
44
45
46
# File 'lib/stimulus_plumbers/mcp/server.rb', line 43

def self.unknown_resource(uri)
  message = "unknown resource: #{uri} — read guide://overview for a map of available resources"
  [{ uri: uri, mimeType: "application/json", text: JSON.generate(error: message) }]
end