Class: Rubydex::MCPServer::Server
- Inherits:
-
Object
- Object
- Rubydex::MCPServer::Server
- Defined in:
- lib/rubydex/mcp_server.rb
Constant Summary collapse
- WORKER_COUNT =
4
Instance Attribute Summary collapse
-
#root_path ⇒ Object
readonly
Returns the value of attribute root_path.
Instance Method Summary collapse
-
#graph_or_error ⇒ Object
: -> Graph | Error.
-
#handle(request) ⇒ Object
: (Hash | Array | untyped) -> Hash | Array?.
-
#initialize(root_path:, transport: nil) ⇒ Server
constructor
: (root_path: String, ?transport: StdioTransport) -> void.
-
#main_loop ⇒ Object
: -> void.
-
#spawn_indexer ⇒ Object
: -> Thread.
Constructor Details
#initialize(root_path:, transport: nil) ⇒ Server
: (root_path: String, ?transport: StdioTransport) -> void
28 29 30 31 32 33 34 35 36 37 |
# File 'lib/rubydex/mcp_server.rb', line 28 def initialize(root_path:, transport: nil) @root_path = root_path @transport = transport @graph = Graph.configure_for_workspace(@root_path) @index_finished = false @incoming_queue = Thread::Queue.new @outgoing_queue = Thread::Queue.new @workers = [] @outgoing_dispatcher = nil end |
Instance Attribute Details
#root_path ⇒ Object (readonly)
Returns the value of attribute root_path.
39 40 41 |
# File 'lib/rubydex/mcp_server.rb', line 39 def root_path @root_path end |
Instance Method Details
#graph_or_error ⇒ Object
: -> Graph | Error
138 139 140 141 142 143 144 145 146 |
# File 'lib/rubydex/mcp_server.rb', line 138 def graph_or_error return @graph if @index_finished Error.new( "indexing", "Rubydex is still indexing the codebase", "The server is starting up. Please retry in a few seconds.", ) end |
#handle(request) ⇒ Object
: (Hash | Array | untyped) -> Hash | Array?
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/rubydex/mcp_server.rb', line 72 def handle(request) if request.is_a?(Array) return JSONRPC.error_response(nil, JSONRPC::INVALID_REQUEST, "Invalid Request", data: "Request is an empty array") if request.empty? responses = request.filter_map { |entry| handle(entry) } return responses if responses.any? return end unless request.is_a?(Hash) return JSONRPC.error_response(nil, JSONRPC::INVALID_REQUEST, "Invalid Request", data: "Request must be a hash") end has_id = request.key?(:id) id = request[:id] method = request[:method] params = request[:params] unless request[:jsonrpc] == "2.0" return JSONRPC.error_response(nil, JSONRPC::INVALID_REQUEST, "Invalid Request", data: "JSON-RPC version must be 2.0") end unless !has_id || id.is_a?(Integer) || (id.is_a?(String) && id.match?(/\A[a-zA-Z0-9_-]+\z/)) return JSONRPC.error_response(nil, JSONRPC::INVALID_REQUEST, "Invalid Request", data: "Request ID must be a string or integer") end unless method.is_a?(String) && !method.start_with?("rpc.") return JSONRPC.error_response(nil, JSONRPC::INVALID_REQUEST, "Invalid Request", data: 'Method name must be a string and not start with "rpc."') end unless params.nil? || params.is_a?(Hash) return JSONRPC.error_response(id, JSONRPC::INVALID_PARAMS, "Invalid params", data: "Method parameters must be an object or null") end result = case method when "initialize" { protocolVersion: "2025-03-26", capabilities: { tools: {} }, serverInfo: { name: "rubydex_mcp", version: Rubydex::VERSION, }, instructions: SERVER_INSTRUCTIONS, } when "tools/list" { tools: Tool.tools.map(&:to_h) } when "tools/call" call_tool(params || {}) when "ping" {} when "notifications/initialized" return else return has_id ? JSONRPC.error_response(id, JSONRPC::METHOD_NOT_FOUND, "Method not found", data: method) : nil end has_id ? { jsonrpc: "2.0", id: id, result: result } : nil rescue KeyError => e has_id ? JSONRPC.error_response(id, JSONRPC::INVALID_PARAMS, "Invalid params", data: e.) : nil rescue StandardError => e has_id ? JSONRPC.error_response(id, JSONRPC::INTERNAL_ERROR, "Internal error", data: e.) : nil end |
#main_loop ⇒ Object
: -> void
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/rubydex/mcp_server.rb', line 51 def main_loop @workers = Array.new(WORKER_COUNT) { new_worker } @outgoing_dispatcher = Thread.new do while (response = @outgoing_queue.pop) @transport.write(response) end end @transport.open do |request, parse_error| if parse_error (parse_error) else @incoming_queue << request end end ensure run_shutdown @transport.close end |
#spawn_indexer ⇒ Object
: -> Thread
42 43 44 45 46 47 48 |
# File 'lib/rubydex/mcp_server.rb', line 42 def spawn_indexer Thread.new do @graph.index_workspace @graph.resolve @index_finished = true end end |