Class: Hitch::MCP::Internal::SDKAdapter

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

Constant Summary collapse

STRUCTURAL_PARAMS =
{
  "server/discover" => [],
  "tools/list" => %w[cursor],
  "tools/call" => %w[name arguments]
}.freeze
SDK_REQUEST_ID =
"hitch_request"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(verified_request:, tools:, context:, server_info:) ⇒ SDKAdapter

Returns a new instance of SDKAdapter.



50
51
52
53
54
55
56
# File 'app/models/hitch/mcp/internal/sdk_adapter.rb', line 50

def initialize(verified_request:, tools:, context:, server_info:)
  @request = frozen_hash!(verified_request)
  @tools = tools.dup.freeze
  @context = context
  @server_info = frozen_hash!(server_info)
  @tool_responses = []
end

Class Method Details

.build_sdk_tool(name:, description:, input_schema:, output_schema: nil, annotations: nil) ⇒ Object

Builds the SDK-facing tool wrapper once per registry snapshot — ::MCP::Tool.define compiles the JSON schemas eagerly, which is the cost this memoization exists to stop paying per tool per request. The block closes over nothing request-scoped: each request routes through the dispatcher its own ::MCP::Server carries in server_context, so a shared wrapper can never see another request's principal.



28
29
30
31
32
33
34
35
36
37
38
39
# File 'app/models/hitch/mcp/internal/sdk_adapter.rb', line 28

def build_sdk_tool(name:, description:, input_schema:, output_schema: nil, annotations: nil)
  name = validate_tool_name!(name)
  ::MCP::Tool.define(
    name: name,
    description: description,
    input_schema: input_schema,
    output_schema: output_schema,
    annotations: annotations
  ) do |server_context:, **arguments|
    server_context.fetch(:hitch_dispatch).call(name, server_context, arguments)
  end
end

.call(**arguments) ⇒ Object



17
18
19
# File 'app/models/hitch/mcp/internal/sdk_adapter.rb', line 17

def call(**arguments)
  new(**arguments).call
end

.validate_tool_name!(name) ⇒ Object

Raises:

  • (ArgumentError)


41
42
43
44
45
46
47
# File 'app/models/hitch/mcp/internal/sdk_adapter.rb', line 41

def validate_tool_name!(name)
  return name if Protocol.tool_name?(name)

  raise ArgumentError,
    "tool name must be 1-#{Protocol::MAX_TOOL_NAME_LENGTH} ASCII letters, digits, " \
      "underscore, dot, or dash"
end

Instance Method Details

#callObject



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/models/hitch/mcp/internal/sdk_adapter.rb', line 58

def call
  return method_not_found unless Protocol::METHODS.include?(request_method)
  return invalid_params if reserved_server_context?

  response = build_server.handle(structural_request)
  ResponseNormalizer.call(
    response: response,
    method: request_method,
    server_info: server_info,
    request_id: read(request, "id"),
    tool_response: @tool_responses.last
  )
end