Class: McpProcessor

Inherits:
Object
  • Object
show all
Includes:
RpcErrorHelpers
Defined in:
lib/stack-service-base/mcp/mcp_processor.rb

Constant Summary collapse

PROTOCOL_VERSION =
'2025-06-18'
DEFAULT_SERVER_INFO =
{
  name: 'mcp-server',
  title: 'MCP Server',
  version: '1.0.0'
}.freeze
ParseError =
Class.new(StandardError) do
  attr_reader :body, :status

  def initialize(body:, status:)
    @body = body
    @status = status
    super("MCP parse error")
  end
end

Instance Method Summary collapse

Methods included from RpcErrorHelpers

#rpc_error!

Constructor Details

#initialize(registry: nil, server_info: DEFAULT_SERVER_INFO, logger: (LOGGER) ? LOGGER : nil)) ⇒ McpProcessor

Returns a new instance of McpProcessor.



37
38
39
40
41
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 37

def initialize(registry: nil, server_info: DEFAULT_SERVER_INFO, logger: (defined?(LOGGER) ? LOGGER : nil))
  @registry = registry
  @server_info = server_info
  @logger = logger
end

Instance Method Details

#error_response(id:, code:, message:) ⇒ Object



71
72
73
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 71

def error_response(id:, code:, message:)
  json_rpc_response(id: id) { rpc_error!(code, message) }
end

#handle(method:, params:) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 87

def handle(method:, params:)
  case method
  when "tools/list"  then list_tools
  # when "resources/list"  then {}
  # when "prompts/list"  then {}
  when "tools/call"  then call_tool(params || {})
  when "initialize"  then initialize_response
  when "notifications/initialized" then @logger&.debug(params); {}
  when "logging/setLevel" then @logger&.debug(params); {}
  else
    rpc_error!(-32601, "Unknown method #{method}")
  end
end

#handle_notification(method:, params:) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 101

def handle_notification(method:, params:)
  case method
  when "notifications/initialized", "notifications/cancelled"
    @logger&.debug("MCP notification accepted: #{method}")
  else
    @logger&.debug("MCP notification ignored: #{method}")
  end
end

#initialize_(_body = nil) ⇒ Object



110
111
112
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 110

def initialize_(_body = nil)
  initialize_response
end

#initialize_responseObject



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 114

def initialize_response
  {
    serverInfo: @server_info,
    protocolVersion: PROTOCOL_VERSION,
    capabilities: {
      logging: {},
      prompts: { listChanged: false },
      resources: { listChanged: false },
      tools: { listChanged: false }
    }
  }
end

#list_toolsObject



63
64
65
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 63

def list_tools
  { tools: registry.list, nextCursor: 'no-more' }
end

#notification_response(method:, params:) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 79

def notification_response(method:, params:)
  handle_notification(method: method, params: params)
  nil
rescue => e
  @logger&.error("Unhandled MCP notification error: #{e.class}: #{e.message}")
  nil
end

#root_endpointObject



43
44
45
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 43

def root_endpoint
  root_response
end

#root_responseObject



67
68
69
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 67

def root_response
  json_rpc_response(id: nil) { list_tools }
end

#rpc_endpoint(raw_body) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 47

def rpc_endpoint(raw_body)
  req = JSON.parse(raw_body.to_s)
  method = req["method"]
  params = req["params"]

  if req.key?("id")
    rpc_response(id: req["id"], method: method, params: params)
  else
    notification_response(method: method, params: params)
  end
rescue JSON::ParserError => e
  @logger&.warn("MCP JSON parse failed: #{e.message}")
  body = error_response(id: nil, code: -32700, message: "Parse error")
  raise ParseError.new(body: body, status: 400)
end

#rpc_response(id:, method:, params:) ⇒ Object



75
76
77
# File 'lib/stack-service-base/mcp/mcp_processor.rb', line 75

def rpc_response(id:, method:, params:)
  json_rpc_response(id: id) { handle(method: method, params: params) }
end