Class: RosettAi::Thor::Tasks::Mcp

Inherits:
Thor
  • Object
show all
Defined in:
lib/rosett_ai/thor/tasks/mcp.rb

Overview

Thor CLI for MCP server and administration.

Provides serve, list, validate, status, audit, key management, and trust source subcommands for MCP server management.

Author:

  • hugo

  • claude

Instance Method Summary collapse

Instance Method Details

#add(name, uri) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 195

def add(name, uri)
  installer = build_installer
  result = installer.install(name: name, uri: uri, transport: options[:transport])

  if result[:success]
    say(Rainbow(result[:message]).green)
  else
    warn(Rainbow(result[:message]).red)
    exit(result[:trusted] == false ? 1 : 2)
  end
end

#auditObject



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 169

def audit
  registry = build_registry
  checker = RosettAi::Mcp::Admin::HealthChecker.new
  validator = RosettAi::Mcp::Admin::SchemaValidator.new
  auditor = RosettAi::Mcp::Admin::Auditor.new(
    registry: registry, health_checker: checker, schema_validator: validator
  )

  report = auditor.audit
  render_audit_report(report)
end

#generate_keyObject



337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 337

def generate_key
  path = RosettAi::Mcp::Keyfile.create_if_missing(options[:keyfile])
  plaintext = RosettAi::Mcp::KeyHasher.generate_key
  key_hash = RosettAi::Mcp::KeyHasher.hash_key(plaintext)

  keyfile = RosettAi::Mcp::Keyfile.new(path)
  keyfile.load!
  keyfile.add_key(name: options[:name], key_hash: key_hash)

  say(Rainbow("API key generated for '#{options[:name]}':").green)
  say(plaintext)
  say(Rainbow('Store this key securely — it cannot be recovered.').yellow)
end

#listObject



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 88

def list
  registry = build_registry
  servers = registry.list

  if servers.empty?
    say(t('no_servers'))
    return
  end

  render_server_list(servers)
end

#list_keysObject



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 365

def list_keys
  path = File.expand_path(options[:keyfile])
  unless File.exist?(path)
    say(Rainbow('No keyfile found.').yellow)
    return
  end

  keyfile = RosettAi::Mcp::Keyfile.new(path)
  keyfile.load!
  keys = keyfile.enabled_keys

  if keys.empty?
    say('No keys configured.')
    return
  end

  table = ::Terminal::Table.new(
    headings: ['Name', 'Created', 'Enabled', 'Expires'],
    rows: keys.map { |k| [k[:name], k[:created_at], k[:enabled], k[:expires_at] || 'never'] }
  )
  say(table)
end

#remove(name) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 222

def remove(name)
  installer = build_installer
  result = installer.remove(name: name, force: options[:force])

  if result[:success]
    say(Rainbow(result[:message]).green)
  else
    warn(Rainbow(result[:message]).red)
    exit(1)
  end
end

#revoke_keyObject



402
403
404
405
406
407
408
409
410
411
412
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 402

def revoke_key
  keyfile = RosettAi::Mcp::Keyfile.new(File.expand_path(options[:keyfile]))
  keyfile.load!

  if keyfile.revoke_key(options[:name])
    say(Rainbow("Key '#{options[:name]}' revoked.").green)
  else
    warn(Rainbow("Key '#{options[:name]}' not found.").red)
    exit(1)
  end
end

#rotate_keyObject



428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 428

def rotate_key
  keyfile = RosettAi::Mcp::Keyfile.new(File.expand_path(options[:keyfile]))
  keyfile.load!

  result = keyfile.rotate_key(options[:name])
  if result
    say(Rainbow("Key '#{options[:name]}' rotated. New key:").green)
    say(result[:plaintext])
    say(Rainbow('Store this key securely — it cannot be recovered.').yellow)
  else
    warn(Rainbow("Key '#{options[:name]}' not found.").red)
    exit(1)
  end
end

#serveObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 47

def serve
  server = RosettAi::Mcp::Server.new(
    config_path: options[:config],
    plugins: options[:plugin]
  )

  if options[:http]
    unless ENV.fetch('RAI_MCP_API_KEY', nil)
      warn Rainbow('[rosett-ai-mcp] WARNING: Starting HTTP server with no API key configured. ' \
                   'Set RAI_MCP_API_KEY or use a keyfile for authentication.').yellow
    end
    server.serve_http(
      port: options[:port],
      host: options[:host],
      tls_cert: options[:tls_cert],
      tls_key: options[:tls_key],
      allow_remote: options[:allow_remote]
    )
  else
    server.serve
  end
rescue RosettAi::McpError => e
  warn Rainbow(e.message).red
  exit(5)
end

#statusObject



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 140

def status
  registry = build_registry
  checker = RosettAi::Mcp::Admin::HealthChecker.new
  results = checker.check_all(registry)

  if results.empty?
    say(t('no_servers'))
    return
  end

  render_health_results(results)
end

#toolsObject



311
312
313
314
315
316
317
318
319
320
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 311

def tools
  server = RosettAi::Mcp::Server.new
  tool_list = server.tools

  table = ::Terminal::Table.new(
    headings: ['Tool', 'Description', 'Read-Only'],
    rows: tool_list.map { |t| [t[:name], t[:description], t[:annotations]['readOnlyHint']] }
  )
  say(table)
end

#trust_add(domain) ⇒ Object



270
271
272
273
274
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 270

def trust_add(domain)
  manager = build_trust_manager
  manager.add_trust(domain, description: options[:description])
  say(Rainbow("Trust added for: #{domain}").green)
end

#trust_remove(domain) ⇒ Object



290
291
292
293
294
295
296
297
298
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 290

def trust_remove(domain)
  manager = build_trust_manager
  if manager.remove_trust(domain)
    say(Rainbow("Trust removed for: #{domain}").green)
  else
    warn(Rainbow("Domain not found in user trust sources: #{domain}").red)
    exit(1)
  end
end

#trust_sourcesObject



250
251
252
253
254
255
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 250

def trust_sources
  manager = build_trust_manager
  sources = manager.list

  render_trust_sources(sources)
end

#validateObject



115
116
117
118
119
120
121
122
123
# File 'lib/rosett_ai/thor/tasks/mcp.rb', line 115

def validate
  registry = build_registry
  validator = RosettAi::Mcp::Admin::SchemaValidator.new
  results = validator.validate_all(registry)

  render_validation_results(results)
  all_valid = results.all? { |r| r[:valid] }
  exit(all_valid ? 0 : 2)
end