Class: Textus::Surface::MCP::Server

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

Overview

MCP stdio server backed by the official mcp gem. The SDK owns protocol negotiation, tool dispatch, and JSON-RPC framing. This class owns the textus Session lifecycle (built lazily on first tool call) and delegates execution to Catalog.

Instance Method Summary collapse

Constructor Details

#initialize(store:, role: Textus::Value::Role::DEFAULT, stdin: $stdin, stdout: $stdout) ⇒ Server

Returns a new instance of Server.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/textus/surface/mcp/server.rb', line 13

def initialize(store:, role: Textus::Value::Role::DEFAULT, stdin: $stdin, stdout: $stdout)
  @store  = store.with_role(role)
  @stdin  = stdin
  @stdout = stdout

  @sdk = ::MCP::Server.new(
    name: "textus",
    version: Textus::VERSION,
    tools: Catalog.build_tools(self),
    resources: build_resources,
    server_context: { mcp_server: self },
  )
  @sdk.resources_read_handler { |params, server_context:| handle_resource_read(params[:uri].to_s, server_context) }
end

Instance Method Details

#dispatch(verb_name, args, _server_context) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/textus/surface/mcp/server.rb', line 41

def dispatch(verb_name, args, _server_context)
  str_args = deep_stringify_keys(args)
  @store.check_etag!(contract_etag_now) unless Catalog.read_verbs.include?(verb_name.to_s)
  result = Catalog.call(verb_name.to_s, store: @store, args: str_args)
  @store = @store.advance_cursor(@store.audit_log.latest_seq) if verb_name == :pulse
  @store = @store.with_role(@store.role) if verb_name == :boot
  ::MCP::Tool::Response.new([{ type: "text", text: JSON.dump(result) }])
rescue Textus::ContractDrift => e
  raise_handler_error(e.message, Textus::ContractDrift::JSONRPC_CODE)
rescue Textus::CursorExpired => e
  raise_handler_error(e.message, Textus::CursorExpired::JSONRPC_CODE)
rescue Textus::Surface::MCP::ToolError => e
  raise_handler_error(e.message, ToolError::JSONRPC_CODE)
rescue StandardError => e
  raise_handler_error("internal: #{e.message}", -32_603)
end

#runObject



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/textus/surface/mcp/server.rb', line 28

def run
  @stdin.each_line do |line|
    line = line.strip
    next if line.empty?

    response = @sdk.handle_json(line)
    next unless response

    @stdout.puts(response)
    @stdout.flush
  end
end