Class: Silas::Mcp::Server

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

Overview

A minimal HTTP/1.1 server hosting the MCP Handler in-process. Deliberately NOT Puma/Rack: the transport MCP clients need (verified in the spike) is plain request/response JSON — one request per connection, application/json, no SSE — so a raw threaded TCPServer is fewer moving parts than embedding an app server in a worker.

In-process: the Handler has direct access to the Ledger/models, so no cross-service call is needed and it works on any worker box. This is the seam for the "mount your agent's tools as an MCP server" feature.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handler:, turn:, token:, host:) ⇒ Server

Returns a new instance of Server.



23
24
25
26
27
28
# File 'lib/silas/mcp/server.rb', line 23

def initialize(handler:, turn:, token:, host:)
  @handler = handler
  @turn = turn
  @token = token
  @host = host
end

Instance Attribute Details

#portObject (readonly)

Returns the value of attribute port.



15
16
17
# File 'lib/silas/mcp/server.rb', line 15

def port
  @port
end

Class Method Details

.start(turn:, step:, tools:, resolver:, host: Silas.config.mcp_server_host) ⇒ Object



17
18
19
20
21
# File 'lib/silas/mcp/server.rb', line 17

def self.start(turn:, step:, tools:, resolver:, host: Silas.config.mcp_server_host)
  token = SecureRandom.hex(16) # minted and compared in memory only
  handler = Handler.new(turn: turn, step: step, tools: tools, resolver: resolver, token: token)
  new(handler: handler, turn: turn, token: token, host: host).tap(&:boot)
end

Instance Method Details

#await_ready!(timeout: 5.0) ⇒ Object

Health-check before spawning claude: a booting server that refuses the first connection gets marked "pending" by the CLI with no retry (spike trap).



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/silas/mcp/server.rb', line 41

def await_ready!(timeout: 5.0)
  deadline = clock + timeout
  loop do
    TCPSocket.new(@host, @port).close
    return true
  rescue SystemCallError
    raise Silas::Error, "MCP server did not become ready" if clock > deadline

    sleep 0.02
  end
end

#bootObject



30
31
32
33
34
35
# File 'lib/silas/mcp/server.rb', line 30

def boot
  @socket = TCPServer.new(@host, 0)
  @port = @socket.addr[1]
  @thread = Thread.new { accept_loop }
  @thread.report_on_exception = false
end

#mcp_urlObject



37
# File 'lib/silas/mcp/server.rb', line 37

def mcp_url = "http://#{@host}:#{@port}/mcp/#{@turn.id}?t=#{@token}"

#stopObject



53
54
55
56
57
58
59
# File 'lib/silas/mcp/server.rb', line 53

def stop
  @stopping = true
  @socket&.close
  @thread&.kill
rescue IOError
  # already closed
end