Fix MCP bridge dropping tool results from servers that send notifications

Read response lines until finding a JSON-RPC response matching the
request ID. Previously, the bridge read one line and assumed it was
the response, causing "No result from MCP tools/call" when MCP
servers send notifications or log lines before the actual result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Philippe Branchu
2026-03-22 16:49:28 +00:00
co-authored by Claude Opus 4.6
parent db86ff4ce3
commit bf9066a602
+44 -17
View File
@@ -328,24 +328,51 @@ impl McpConnection {
.await
.map_err(|e| format!("Failed to flush stdin: {e}"))?;
// Read response line
let mut line = String::new();
let timeout = tokio::time::Duration::from_secs(self.config.timeout_secs);
match tokio::time::timeout(timeout, stdout.read_line(&mut line)).await {
Ok(Ok(0)) => return Err("MCP server closed connection".to_string()),
Ok(Ok(_)) => {}
Ok(Err(e)) => return Err(format!("Failed to read MCP response: {e}")),
Err(_) => return Err("MCP request timed out".to_string()),
// Read response lines until we find one matching our request ID.
// MCP servers may send notifications or log lines before the
// actual response.
let timeout_dur = tokio::time::Duration::from_secs(self.config.timeout_secs);
let deadline = tokio::time::Instant::now() + timeout_dur;
loop {
let mut line = String::new();
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err("MCP request timed out".to_string());
}
match tokio::time::timeout(remaining, stdout.read_line(&mut line)).await {
Ok(Ok(0)) => return Err("MCP server closed connection".to_string()),
Ok(Ok(_)) => {}
Ok(Err(e)) => return Err(format!("Failed to read MCP response: {e}")),
Err(_) => return Err("MCP request timed out".to_string()),
}
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
// Try to parse as JSON-RPC response
let parsed: Result<JsonRpcResponse, _> = serde_json::from_str(trimmed);
match parsed {
Ok(response) if response.id == Some(id) => {
if let Some(err) = response.error {
return Err(format!("{err}"));
}
return Ok(response.result);
}
Ok(_other) => {
// Response for a different ID or notification — skip
debug!("MCP: skipping non-matching response line");
continue;
}
Err(_) => {
// Not valid JSON-RPC — skip (could be a log line)
debug!("MCP: skipping non-JSON line from server");
continue;
}
}
}
let response: JsonRpcResponse = serde_json::from_str(line.trim())
.map_err(|e| format!("Invalid MCP JSON-RPC response: {e}"))?;
if let Some(err) = response.error {
return Err(format!("{err}"));
}
Ok(response.result)
}
McpTransportHandle::Sse { client, url } => {
let response = client