Class: Hitch::MCP::Internal::RegistryRuntime

Inherits:
Object
  • Object
show all
Defined in:
app/models/hitch/mcp/internal/registry_runtime.rb

Overview

Validates Registry declarations into the persistent snapshot at prepare time and resolves listings and calls against it per request. The snapshot retains only class names and frozen data.

Defined Under Namespace

Classes: CallResolution, Entry, RuntimeTool, Snapshot

Constant Summary collapse

CONSTANT_NAME_PATTERN =
/\A[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*\z/
OAUTH_SCOPE_PATTERN =
/\A[\x21\x23-\x5B\x5D-\x7E]+\z/
MAX_SCOPE_BYTES =
64
ANNOTATION_KEYS =
{
  "title" => :title,
  "readOnlyHint" => :read_only_hint,
  "read_only_hint" => :read_only_hint,
  "destructiveHint" => :destructive_hint,
  "destructive_hint" => :destructive_hint,
  "idempotentHint" => :idempotent_hint,
  "idempotent_hint" => :idempotent_hint,
  "openWorldHint" => :open_world_hint,
  "open_world_hint" => :open_world_hint
}.freeze

Class Method Summary collapse

Class Method Details

.build_snapshot(registry_name:, supported_scopes:) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/models/hitch/mcp/internal/registry_runtime.rb', line 54

def build_snapshot(registry_name:, supported_scopes:)
  registry_class = resolve_named_constant(registry_name, "registry")
  unless registry_class.is_a?(Class) && registry_class < Registry
    raise ArgumentError, "mcp.registry must resolve to a Hitch::MCP::Registry subclass"
  end

  supported = validate_supported_scopes(supported_scopes)
  entries = registry_class.declarations.each_with_index.map do |declaration, index|
    build_entry(declaration, index:, supported_scopes: supported)
  end
  duplicate_names = entries.map(&:name).tally.select { |_name, count| count > 1 }.keys
  unless duplicate_names.empty?
    raise ArgumentError, "mcp.registry contains duplicate tool names: #{duplicate_names.sort.join(', ')}"
  end

  Snapshot.new(
    registry_name: registry_name.dup.freeze,
    entries: entries.sort_by(&:name).freeze
  )
# Names what failed to resolve. A host reading this at boot has a
# typo in one line of one file, and the line is the message.
rescue NameError => error
  raise ArgumentError, "mcp.registry could not be built: #{error.message}"
end

.runtime_call(snapshot:, name:, context:) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'app/models/hitch/mcp/internal/registry_runtime.rb', line 88

def runtime_call(snapshot:, name:, context:)
  validate_snapshot!(snapshot)
  entry = snapshot.entries.find { |candidate| candidate.name == name }
  return hidden_call_resolution unless entry

  runtime_tool = available_runtime_tool(entry, context)
  return hidden_call_resolution unless runtime_tool

  unless scopes_granted?(entry.scopes, context)
    return CallResolution.new(
      status: :insufficient_scope,
      tool: nil,
      required_scopes: entry.scopes
    )
  end

  CallResolution.new(
    status: :available,
    tool: runtime_tool,
    required_scopes: [].freeze
  )
end

.runtime_listing(snapshot:, context:) ⇒ Object



79
80
81
82
83
84
85
86
# File 'app/models/hitch/mcp/internal/registry_runtime.rb', line 79

def runtime_listing(snapshot:, context:)
  validate_snapshot!(snapshot)

  snapshot.entries.filter_map do |entry|
    runtime_tool = available_runtime_tool(entry, context)
    runtime_tool if runtime_tool && scopes_granted?(entry.scopes, context)
  end.freeze
end