2
2
# frozen_string_literal: true
3
3
4
4
require "ruby_lsp/mcp/tool"
5
+ require "socket"
5
6
6
7
module RubyLsp
7
8
class MCPServer
@@ -35,21 +36,12 @@ def initialize(global_state)
35
36
FileUtils . mkdir_p ( lsp_dir )
36
37
37
38
# Write port to file
38
- port_file = File . join ( lsp_dir , "mcp-port" )
39
- File . write ( port_file , @port . to_s )
40
-
41
- # Create WEBrick server
42
- @server = WEBrick ::HTTPServer . new (
43
- Port : @port ,
44
- BindAddress : "127.0.0.1" ,
45
- Logger : WEBrick ::Log . new ( File . join ( lsp_dir , "mcp-webrick.log" ) ) ,
46
- AccessLog : [ ] ,
47
- ) #: WEBrick::HTTPServer
48
-
49
- # Mount the MCP handler
50
- @server . mount_proc ( "/mcp" ) do |req , res |
51
- handle_mcp_request ( req , res )
52
- end
39
+ @port_file = File . join ( lsp_dir , "mcp-port" ) #: String
40
+ File . write ( @port_file , @port . to_s )
41
+
42
+ # Create TCP server
43
+ @server = TCPServer . new ( "127.0.0.1" , @port ) #: TCPServer
44
+ @server_thread = nil #: Thread?
53
45
54
46
@running = false #: T::Boolean
55
47
@global_state = global_state #: GlobalState
@@ -58,75 +50,64 @@ def initialize(global_state)
58
50
59
51
#: -> void
60
52
def start
61
- puts "[MCP] Server started on TCP port #{ @port } "
62
- Thread . new do
63
- @server . start
53
+ puts "[MCP] Server started on port #{ @port } "
54
+ @running = true
55
+
56
+ @server_thread = Thread . new do
57
+ while @running
58
+ begin
59
+ # Accept incoming connections
60
+ client = @server . accept
61
+
62
+ # Handle each client in a separate thread
63
+ Thread . new ( client ) do |client_socket |
64
+ handle_client ( client_socket )
65
+ end
66
+ rescue => e
67
+ puts "[MCP] Error accepting connection: #{ e . message } " if @running
68
+ end
69
+ end
64
70
end
65
71
end
66
72
67
73
#: -> void
68
74
def stop
69
- puts "[MCP] Stopping server"
70
- @server . shutdown
75
+ puts "[MCP] Server stopping"
76
+ @running = false
77
+ @server . close
78
+ @server_thread &.join
71
79
ensure
72
- # Clean up port file
73
- lsp_dir = File . join ( @workspace_path , ".ruby-lsp" )
74
- port_file = File . join ( lsp_dir , "mcp-port" )
75
- File . delete ( port_file ) if File . exist? ( port_file )
76
-
77
- # Clean up log file
78
- log_file = File . join ( lsp_dir , "mcp-webrick.log" )
79
- File . delete ( log_file ) if File . exist? ( log_file )
80
+ File . delete ( @port_file ) if File . exist? ( @port_file )
80
81
end
81
82
82
83
private
83
84
84
- #: (WEBrick::HTTPRequest, WEBrick::HTTPResponse) -> void
85
- def handle_mcp_request ( request , response )
86
- body = request . body || ""
85
+ #: (TCPSocket) -> void
86
+ def handle_client ( client_socket )
87
+ # Read JSON-RPC request from client
88
+ request_line = client_socket . gets
89
+ return unless request_line
87
90
88
- puts "[MCP] Received request: #{ body } "
91
+ request_line = request_line . strip
89
92
90
- result = process_jsonrpc_request ( body )
93
+ # Process the JSON-RPC request
94
+ response = process_jsonrpc_request ( request_line )
91
95
92
- if result . nil?
93
- response . status = 500
94
- response . body = {
95
- jsonrpc : "2.0" ,
96
- id : nil ,
97
- error : {
98
- code : ErrorCode ::INTERNAL_ERROR ,
99
- message : "Internal error" ,
100
- data : "No response from the server" ,
101
- } ,
102
- } . to_json
103
- else
104
- response . status = 200
105
- response . content_type = "application/json"
106
- response . body = result
96
+ if response
97
+ client_socket . puts ( response )
107
98
end
108
-
109
- puts "[MCP] Sent response: #{ response . body } "
110
99
rescue => e
111
- puts "[MCP] Error processing request: #{ e . message } "
112
- puts e . backtrace &.join ( "\n " )
100
+ puts "[MCP] Client error: #{ e . message } "
113
101
114
- response . status = 500
115
- response . body = {
116
- jsonrpc : "2.0" ,
117
- id : nil ,
118
- error : {
119
- code : ErrorCode ::INTERNAL_ERROR ,
120
- message : "Internal error" ,
121
- data : e . message ,
122
- } ,
123
- } . to_json
102
+ # Send error response
103
+ error_response = generate_error_response ( nil , ErrorCode ::INTERNAL_ERROR , "Internal error" , e . message )
104
+ client_socket . puts ( error_response )
105
+ ensure
106
+ client_socket . close
124
107
end
125
108
126
109
#: (String) -> String?
127
110
def process_jsonrpc_request ( json )
128
- puts "[MCP] Processing request: #{ json . inspect } "
129
-
130
111
# Parse JSON
131
112
begin
132
113
request = JSON . parse ( json , symbolize_names : true )
@@ -193,7 +174,6 @@ def process_request(method_name, params)
193
174
end ,
194
175
}
195
176
when "tools/call"
196
- puts "[MCP] Received tools/call request: #{ params . inspect } "
197
177
tool_name = params [ :name ]
198
178
tool_class = RubyLsp ::MCP ::Tool . get ( tool_name )
199
179
0 commit comments