cgoodmaker Claude Opus 4.6 commited on
Commit
c376e14
·
1 Parent(s): 672ed11

Add timeout and stderr logging to MCP subprocess to debug tool hangs

Browse files

- _recv() now has 300s timeout instead of blocking forever
- _drain_stderr() captures MCP subprocess error output for debugging
- Checks if subprocess died and surfaces exit code
- Log MCP server start confirmation
- Fix max_length: only clear it, don't double-set max_new_tokens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. models/medgemma_agent.py +35 -4
models/medgemma_agent.py CHANGED
@@ -38,10 +38,41 @@ class MCPClient:
38
  self._process.stdin.write(line)
39
  self._process.stdin.flush()
40
 
41
- def _recv(self) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  while True:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  line = self._process.stdout.readline()
44
  if not line:
 
45
  raise RuntimeError("MCP server closed connection unexpectedly")
46
  line = line.strip()
47
  if not line:
@@ -254,9 +285,7 @@ class MedGemmaAgent:
254
  # Clear default max_length (20) from generation_config to avoid conflict
255
  # with max_new_tokens passed at inference time
256
  if hasattr(self.pipe.model, "generation_config"):
257
- gc = self.pipe.model.generation_config
258
- gc.max_length = None
259
- gc.max_new_tokens = 400
260
 
261
  self._print(f"Model loaded in {time.time() - start:.1f}s")
262
  self.loaded = True
@@ -291,6 +320,8 @@ class MedGemmaAgent:
291
  return
292
  self.mcp_client = MCPClient()
293
  self.mcp_client.start()
 
 
294
  self.tools_loaded = True
295
 
296
  def _multi_pass_visual_exam(self, image, question: Optional[str] = None) -> Generator[str, None, Dict[str, str]]:
 
38
  self._process.stdin.write(line)
39
  self._process.stdin.flush()
40
 
41
+ def _drain_stderr(self):
42
+ """Read any available stderr from the subprocess and print it."""
43
+ if self._process and self._process.stderr:
44
+ import select
45
+ while select.select([self._process.stderr], [], [], 0)[0]:
46
+ line = self._process.stderr.readline()
47
+ if line:
48
+ print(f"[MCP stderr] {line.strip()}", flush=True)
49
+ else:
50
+ break
51
+
52
+ def _recv(self, timeout: int = 300) -> dict:
53
+ import select
54
+ deadline = time.time() + timeout
55
  while True:
56
+ remaining = deadline - time.time()
57
+ if remaining <= 0:
58
+ self._drain_stderr()
59
+ raise RuntimeError(
60
+ f"MCP server did not respond within {timeout}s"
61
+ )
62
+ ready, _, _ = select.select(
63
+ [self._process.stdout], [], [], min(remaining, 5)
64
+ )
65
+ if not ready:
66
+ # Check if subprocess died
67
+ if self._process.poll() is not None:
68
+ self._drain_stderr()
69
+ raise RuntimeError(
70
+ f"MCP server exited with code {self._process.returncode}"
71
+ )
72
+ continue
73
  line = self._process.stdout.readline()
74
  if not line:
75
+ self._drain_stderr()
76
  raise RuntimeError("MCP server closed connection unexpectedly")
77
  line = line.strip()
78
  if not line:
 
285
  # Clear default max_length (20) from generation_config to avoid conflict
286
  # with max_new_tokens passed at inference time
287
  if hasattr(self.pipe.model, "generation_config"):
288
+ self.pipe.model.generation_config.max_length = None
 
 
289
 
290
  self._print(f"Model loaded in {time.time() - start:.1f}s")
291
  self.loaded = True
 
320
  return
321
  self.mcp_client = MCPClient()
322
  self.mcp_client.start()
323
+ self._print("MCP server started successfully")
324
+ self.mcp_client._drain_stderr()
325
  self.tools_loaded = True
326
 
327
  def _multi_pass_visual_exam(self, image, question: Optional[str] = None) -> Generator[str, None, Dict[str, str]]: