Class: Textus::Surfaces::MCP::Server
- Inherits:
-
Object
- Object
- Textus::Surfaces::MCP::Server
- Defined in:
- lib/textus/surfaces/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
-
#dispatch(verb_name, args, _server_context) ⇒ Object
Called from every MCP::Tool handler block in Catalog.
-
#initialize(store:, role: Textus::Role::DEFAULT, stdin: $stdin, stdout: $stdout) ⇒ Server
constructor
A new instance of Server.
-
#run ⇒ Object
Runs the stdio line loop; delegates each JSON line to the SDK.
Constructor Details
#initialize(store:, role: Textus::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 27 28 29 30 31 32 33 34 35 |
# File 'lib/textus/surfaces/mcp/server.rb', line 13 def initialize(store:, role: Textus::Role::DEFAULT, stdin: $stdin, stdout: $stdout) @store = store @role = role @stdin = stdin @stdout = stdout # Session built eagerly so the contract_etag is captured at server start. # Changes to manifest/hooks/schemas after this point are detected as drift. @session = Textus::Session.new( role: @role, cursor: @store.audit_log.latest_seq, propose_lane: @store.manifest.policy.propose_lane_for(@role), contract_etag: contract_etag_now, ) @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
Called from every MCP::Tool handler block in Catalog. The SDK parses JSON with symbolize_names: true — all nested keys are symbols. Deep-stringify so Catalog.call receives the string-key format it expects.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/textus/surfaces/mcp/server.rb', line 54 def dispatch(verb_name, args, _server_context) str_args = deep_stringify_keys(args) @session.check_etag!(contract_etag_now) unless Catalog.read_verbs.include?(verb_name.to_s) result = Catalog.call(verb_name.to_s, session: @session, store: @store, args: str_args) update_session_for(verb_name.to_s) ::MCP::Tool::Response.new([{ type: "text", text: JSON.dump(result) }]) rescue Textus::ContractDrift => e raise_handler_error(e., Textus::ContractDrift::JSONRPC_CODE) rescue CursorExpired => e raise_handler_error(e., CursorExpired::JSONRPC_CODE) rescue Textus::Surfaces::MCP::ToolError => e raise_handler_error(e., ToolError::JSONRPC_CODE) rescue StandardError => e raise_handler_error("internal: #{e.class}: #{e.}", -32_603) end |
#run ⇒ Object
Runs the stdio line loop; delegates each JSON line to the SDK.
38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/textus/surfaces/mcp/server.rb', line 38 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 |