Class: Silas::Mcp::Handler

Inherits:
Object
  • Object
show all
Defined in:
lib/silas/mcp/handler.rb

Overview

JSON-RPC handler for the hosted MCP endpoint. tools/call runs the tool THROUGH the Ledger, so the :agent_sdk path gets the same exactly-once and effect-mode semantics as :ruby_llm. Closes over one turn + its anchor step; authenticated by a per-turn bearer token in the URL query.

Constant Summary collapse

TOOL_PREFIX =
"mcp__silas__".freeze

Instance Method Summary collapse

Constructor Details

#initialize(turn:, step:, tools:, resolver:, token:) ⇒ Handler

Returns a new instance of Handler.



13
14
15
16
17
18
19
# File 'lib/silas/mcp/handler.rb', line 13

def initialize(turn:, step:, tools:, resolver:, token:)
  @turn = turn
  @step = step
  @tools = tools
  @resolver = resolver
  @token = token
end

Instance Method Details

#call(path:, query_token:, body:) ⇒ Object

Returns [status, json_string_or_nil]. path/query already parsed by the server.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/silas/mcp/handler.rb', line 22

def call(path:, query_token:, body:)
  return [ 404, error_body("not found") ] unless path == "/mcp/#{@turn.id}"
  return [ 403, error_body("forbidden") ] unless ActiveSupport::SecurityUtils.secure_compare(query_token.to_s, @token)

  msg = JSON.parse(body)
  case msg["method"]
  when "initialize"                then ok(msg["id"], initialize_result(msg))
  when "notifications/initialized" then [ 202, nil ]
  when "tools/list"                then ok(msg["id"], { "tools" => @tools.map { |d| mcp_tool(d) } })
  when "tools/call"                then ok(msg["id"], call_tool(msg))
  else rpc_error(msg["id"], -32601, "method not found: #{msg['method']}")
  end
rescue JSON::ParserError
  [ 400, error_body("bad json") ]
end