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 =
[
  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 =
[
  Resources::BehaviourResource,
  Resources::DesignResource,
  Resources::ProvenanceResource,
  Resources::ConfigResource,
  Resources::SchemaResource,
  Resources::RulesResource,
  Resources::HooksResource
].freeze
PROMPT_CLASSES =
[
  Prompts::ValidationPrompt,
  Prompts::CompilationPrompt,
  Prompts::CompliancePrompt,
  Prompts::DiagnosticsPrompt
].freeze
KEYWORD_PARAM_TYPES =
[: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



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rosett_ai/mcp/governance.rb', line 145

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



126
127
128
# File 'lib/rosett_ai/mcp/governance.rb', line 126

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

.register(server) ⇒ Object

Parameters:

  • server (MCP::Server)


74
75
76
77
78
# File 'lib/rosett_ai/mcp/governance.rb', line 74

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

.register_prompts(server) ⇒ Object

Parameters:

  • server (MCP::Server)


98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rosett_ai/mcp/governance.rb', line 98

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)


86
87
88
89
90
91
92
93
94
95
# File 'lib/rosett_ai/mcp/governance.rb', line 86

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



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rosett_ai/mcp/governance.rb', line 113

def register_tool(server, klass)
  server.define_tool(
    name: klass::TOOL_NAME,
    description: klass::DESCRIPTION,
    annotations: snake_case_annotations(klass::ANNOTATIONS)
  ) do |args|
    tool = klass.new
    kwargs = build_kwargs(tool, args)
    result = kwargs.empty? ? tool.call : tool.call(**kwargs)
    JSON.generate(result)
  end
end

.register_tools(server) ⇒ Object

Parameters:

  • server (MCP::Server)


81
82
83
# File 'lib/rosett_ai/mcp/governance.rb', line 81

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

.resource_matches_uri?(resource, uri) ⇒ Boolean

Returns:

  • (Boolean)


130
131
132
# File 'lib/rosett_ai/mcp/governance.rb', line 130

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.



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

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