Module: RosettAi::Mcp::Governance

Defined in:
lib/rosett_ai/mcp/governance.rb

Overview

Centralized MCP tool, resource, and prompt registration.

Always loaded before plugins. Registers all built-in tools, resources, and prompts with the MCP server instance.

Author:

  • hugo

  • claude

Constant Summary collapse

TOOL_CLASSES =

Returns Registered MCP tool implementation classes.

Returns:

  • (Array)

    Registered MCP tool implementation classes.

[
  Tools::ValidateTool,
  Tools::CompileTool,
  Tools::BehaviourListTool,
  Tools::BehaviourShowTool,
  Tools::BehaviourDisplayTool,
  Tools::BehaviourManageTool,
  Tools::DesignListTool,
  Tools::DesignShowTool,
  Tools::ConfigStatusTool,
  Tools::ConfigCompileTool,
  Tools::AdoptTool,
  Tools::ComplyTool,
  Tools::DoctorTool,
  Tools::EnginesTool,
  Tools::HooksStatusTool,
  Tools::LicenseStatusTool,
  Tools::ProjectTool,
  Tools::ProvenanceTool,
  Tools::ProvenanceWriteTool,
  Tools::ToolingTool,
  Tools::WorkflowTool,
  Tools::WorkflowExecuteTool,
  Tools::DocumentationStatusTool,
  Tools::InitTool,
  Tools::BackupTool,
  Tools::ContentTool,
  Tools::RetrofitTool,
  Tools::RuleSearchTool,
  Tools::CompileStatusTool,
  Tools::HookPreviewTool,
  Tools::HookInstallTool,
  Tools::ContextQueryTool,
  Tools::SchemaGetTool
].freeze
RESOURCE_CLASSES =

Returns Registered MCP resource implementation classes.

Returns:

  • (Array)

    Registered MCP resource implementation classes.

[
  Resources::BehaviourResource,
  Resources::DesignResource,
  Resources::ProvenanceResource,
  Resources::ConfigResource,
  Resources::SchemaResource,
  Resources::RulesResource,
  Resources::HooksResource
].freeze
PROMPT_CLASSES =

Returns Registered MCP prompt implementation classes.

Returns:

  • (Array)

    Registered MCP prompt implementation classes.

[
  Prompts::ValidationPrompt,
  Prompts::CompilationPrompt,
  Prompts::CompliancePrompt,
  Prompts::DiagnosticsPrompt
].freeze
KEYWORD_PARAM_TYPES =

Returns Parameter types passed as keyword arguments.

Returns:

  • (Array)

    Parameter types passed as keyword arguments.

[:keyreq, :key].freeze

Class Method Summary collapse

Class Method Details

.build_kwargs(tool, args) ⇒ Object

Parameters:

  • tool (Object)

    tool instance with #call

  • args (Hash, nil)

    MCP arguments



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/rosett_ai/mcp/governance.rb', line 156

def build_kwargs(tool, args)
  return {} unless args.is_a?(Hash)

  method_params = tool.method(:call).parameters
  param_names = method_params.filter_map { |type, name| name if KEYWORD_PARAM_TYPES.include?(type) }

  args.each_with_object({}) do |(key, value), kwargs|
    sym = key.to_sym
    kwargs[sym] = value if param_names.include?(sym)
  end
end

.find_resource_for_uri(resources, uri) ⇒ Object?

Look up the MCP resource handler for a URI.

Parameters:

  • resources (Array)

    registered resource instances

  • uri (String)

    the resource URI to match

Returns:

  • (Object, nil)

    the matching resource, or nil



137
138
139
# File 'lib/rosett_ai/mcp/governance.rb', line 137

def find_resource_for_uri(resources, uri)
  resources.find { |r| resource_matches_uri?(r, uri) }
end

.register(server) ⇒ Object

Parameters:

  • server (MCP::Server)


78
79
80
81
82
# File 'lib/rosett_ai/mcp/governance.rb', line 78

def register(server)
  register_tools(server)
  register_resources(server)
  register_prompts(server)
end

.register_prompts(server) ⇒ Object

Parameters:

  • server (MCP::Server)


102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rosett_ai/mcp/governance.rb', line 102

def register_prompts(server)
  PROMPT_CLASSES.each do |klass|
    prompt = klass.new
    server.define_prompt(
      name: klass::PROMPT_NAME,
      description: klass::DESCRIPTION
    ) do |args|
      kwargs = (args || {}).transform_keys(&:to_sym)
      prompt.call(**kwargs)
    end
  end
end

.register_resources(server) ⇒ Object

Parameters:

  • server (MCP::Server)


90
91
92
93
94
95
96
97
98
99
# File 'lib/rosett_ai/mcp/governance.rb', line 90

def register_resources(server)
  resources = RESOURCE_CLASSES.map(&:new)
  server.resources_list_handler { resources.flat_map(&:list) }
  server.resources_read_handler do |uri:|
    resource_name = uri.split('/').last
    resource_class = find_resource_for_uri(resources, uri)
    result = resource_class&.read(resource_name)
    result&.fetch(:content, '')
  end
end

.register_tool(server, klass) ⇒ Object

Parameters:

  • server (MCP::Server)
  • klass (Class)

    tool class with TOOL_NAME, DESCRIPTION, ANNOTATIONS



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/rosett_ai/mcp/governance.rb', line 117

def register_tool(server, klass)
  schema = klass.const_defined?(:INPUT_SCHEMA) ? klass::INPUT_SCHEMA : nil
  server.define_tool(
    name: klass::TOOL_NAME,
    description: klass::DESCRIPTION,
    annotations: snake_case_annotations(klass::ANNOTATIONS),
    input_schema: schema
  ) do |**args|
    tool = klass.new
    kwargs = Governance.build_kwargs(tool, args)
    result = kwargs.empty? ? tool.call : tool.call(**kwargs)
    MCP::Tool::Response.new([{ type: 'text', text: JSON.generate(result) }])
  end
end

.register_tools(server) ⇒ Object

Parameters:

  • server (MCP::Server)


85
86
87
# File 'lib/rosett_ai/mcp/governance.rb', line 85

def register_tools(server)
  TOOL_CLASSES.each { |klass| register_tool(server, klass) }
end

.resource_matches_uri?(resource, uri) ⇒ Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/rosett_ai/mcp/governance.rb', line 141

def resource_matches_uri?(resource, uri)
  resource.list.any? { |entry| entry[:uri] == uri }
end

.snake_case_annotations(hash) ⇒ Object

Convert camelCase annotation keys to snake_case symbols for the mcp gem's Annotations initializer.



147
148
149
150
151
152
# File 'lib/rosett_ai/mcp/governance.rb', line 147

def snake_case_annotations(hash)
  hash.each_with_object({}) do |(key, value), result|
    snake = key.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
    result[snake] = value
  end
end