Class: Otto::MCP::RouteParser

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/mcp/route_parser.rb

Overview

Parser for MCP route definitions and resource URIs

Class Method Summary collapse

Class Method Details

.extract_options_from_handler(handler_definition) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/otto/mcp/route_parser.rb', line 65

def self.extract_options_from_handler(handler_definition)
  parts   = handler_definition.split(/\s+/)
  options = {}

  # First part is the handler class.method. Delegate token parsing to
  # Otto::RouteDefinition so a bare/empty auth|role|csrf token here
  # fails fast exactly like it does for normal routes (issue #191
  # MCP/TOOL follow-up), instead of silently registering the route
  # without its intended protection.
  parts[1..]&.each do |part|
    pair = Otto::RouteDefinition.parse_option_token(part, "handler #{handler_definition.inspect}")
    if pair
      options[pair[0]] = pair[1]
    else
      Otto.structured_log(:warn, 'Malformed MCP/tool route option ignored',
        { option: part, handler: handler_definition })
    end
  end

  options
end

.is_mcp_route?(definition) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/otto/mcp/route_parser.rb', line 57

def self.is_mcp_route?(definition)
  definition.start_with?('MCP ')
end

.is_tool_route?(definition) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/otto/mcp/route_parser.rb', line 61

def self.is_tool_route?(definition)
  definition.start_with?('TOOL ')
end

.parse_mcp_route(_verb, _path, definition) ⇒ Object

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/otto/mcp/route_parser.rb', line 11

def self.parse_mcp_route(_verb, _path, definition)
  # MCP route format: MCP resource_uri HandlerClass.method_name
  # Note: The path parameter is ignored for MCP routes - resource_uri comes from definition
  parts = definition.split(/\s+/, 3)

  raise ArgumentError, "Expected MCP keyword, got: #{parts[0]}" if parts[0] != 'MCP'

  resource_uri       = parts[1]
  handler_definition = parts[2]

  raise ArgumentError, "Invalid MCP route format: #{definition}" unless resource_uri && handler_definition

  # Clean up URI - remove leading slash if present since MCP URIs are relative
  resource_uri = resource_uri.sub(%r{^/}, '')

  {
    type: :mcp_resource,
    resource_uri: resource_uri,
    handler: handler_definition,
    options: extract_options_from_handler(handler_definition),
  }
end

.parse_tool_route(_verb, _path, definition) ⇒ Object

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/otto/mcp/route_parser.rb', line 34

def self.parse_tool_route(_verb, _path, definition)
  # TOOL route format: TOOL tool_name HandlerClass.method_name
  # Note: The path parameter is ignored for TOOL routes - tool_name comes from definition
  parts = definition.split(/\s+/, 3)

  raise ArgumentError, "Expected TOOL keyword, got: #{parts[0]}" if parts[0] != 'TOOL'

  tool_name          = parts[1]
  handler_definition = parts[2]

  raise ArgumentError, "Invalid TOOL route format: #{definition}" unless tool_name && handler_definition

  # Clean up tool name - remove leading slash if present
  tool_name = tool_name.sub(%r{^/}, '')

  {
    type: :mcp_tool,
    tool_name: tool_name,
    handler: handler_definition,
    options: extract_options_from_handler(handler_definition),
  }
end