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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
# File 'lib/debug_mcp/tools/connect.rb', line 77
def call(path: nil, host: nil, port: nil, session_id: nil, remote: nil,
restore_breakpoints: nil, auto_escape: nil, force_reset: nil, server_context:)
manager = server_context[:session_manager]
if force_reset
begin
existing_client = manager.client(session_id)
unless existing_client.paused
begin
existing_client.auto_repause!
rescue DebugMcp::Error
if existing_client.remote && existing_client.listen_ports&.any?
begin
existing_client.wake_io_blocked_process(existing_client.listen_ports.first)
sleep DebugClient::HTTP_WAKE_SETTLE_TIME
existing_client.check_paused(timeout: 5)
rescue DebugMcp::Error
end
end
end
end
existing_client.send_command_no_wait("c", force: true) rescue nil
manager.disconnect(session_id)
sleep 1
rescue DebugMcp::Error
end
end
manager.clear_breakpoint_specs unless restore_breakpoints
pre_target_pid = resolve_target_pid(path, port)
pre_listen_ports = pre_target_pid ? detect_listen_ports(pre_target_pid) : []
woke = false
connect_timeout = if force_reset
FORCE_RESET_CONNECT_TIMEOUT
elsif pre_listen_ports.any?
5
end
result = manager.connect(
session_id: session_id,
path: path,
host: host,
port: port,
remote: remote,
connect_timeout: connect_timeout,
pre_cleanup_pid: pre_target_pid,
pre_cleanup_port: port,
) {
if pre_listen_ports.any?
woke = true
wake_process_via_http(pre_listen_ports)
end
}
client = manager.client(result[:session_id])
if force_reset
begin
health = client.send_command("p :debug_mcp_health_check", timeout: 5)
unless health.include?("debug_mcp_health_check")
client.send_command_no_wait("c", force: true) rescue nil
manager.disconnect(result[:session_id])
raise ConnectionError, "Health check failed after force_reset. " \
"The process may need to be restarted."
end
rescue DebugMcp::TimeoutError
client.send_command_no_wait("c", force: true) rescue nil
manager.disconnect(result[:session_id])
raise ConnectionError, "Health check timed out after force_reset. " \
"The process may need to be restarted."
end
end
text = "Connected to debug session.\n" \
" Session ID: #{result[:session_id]}\n" \
" PID: #{result[:pid]}\n"
if woke
text += " Status: Woke IO-blocked process via HTTP (port #{pre_listen_ports.first})\n"
end
listen_ports = detect_listen_ports(result[:pid])
if listen_ports.empty? && client.remote && port
listen_ports = TcpSessionDiscovery.container_web_ports(port)
end
if listen_ports.any?
port_list = listen_ports.map { |p| "http://127.0.0.1:#{p}" }.join(", ")
text += " Listening on: #{port_list}\n"
end
is_rails = RailsHelper.rails?(client)
route_info = is_rails ? RailsHelper.route_summary(client, limit: 5) : nil
auto_escape_enabled = auto_escape != false
text += escape_trap_context(client,
listen_ports: listen_ports,
route_info: route_info,
auto_escape: auto_escape_enabled)
client.listen_ports = listen_ports
if is_rails && listen_ports.any?
url_path = (route_info)
client.escape_target = find_target_from_framework(client, url_path)
end
install_sigint_handler(client)
unless restore_breakpoints
clear_process_breakpoints(client)
end
escaped = text.include?("Auto-escaped signal trap context")
text += "\nIMPORTANT: The target process is now PAUSED. " \
"Use 'continue_execution' to resume it when done investigating, " \
"or 'disconnect' to detach (which also resumes the process).\n" \
"Note: stdout/stderr are not captured for 'connect' sessions " \
"(use 'run_script' for capture).\n\n" \
"Initial state:\n#{result[:output]}"
if is_rails
text += build_rails_summary(client, result[:output], listen_ports, route_info,
escaped: escaped)
end
restored = manager.restore_breakpoints(client)
if restored.any?
text += "\n\nRestored #{restored.size} breakpoint(s) from previous session:"
restored.each do |r|
text += if r[:error]
"\n #{r[:spec]} -> Error: #{r[:error]}"
else
"\n #{r[:spec]} -> #{r[:output]}"
end
end
end
MCP::Tool::Response.new([{ type: "text", text: text }])
rescue DebugMcp::Error => e
error_text = "Error: #{e.message}"
unless force_reset
if e.message.include?("timed out") || e.message.include?("stuck")
error_text += "\n\nTip: Try 'connect' with force_reset: true to force cleanup of any stuck session."
end
else
error_text += "\n\nThe process may need to be restarted (kill and re-launch with 'rdbg --open')."
end
MCP::Tool::Response.new([{ type: "text", text: error_text }])
end
|