Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions adapters/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,30 @@ function httpRequest(
return;
}
let data = '';
let truncated = false;
res.on('data', (chunk: Buffer) => {
if (truncated) return;
data += chunk.toString();
// Truncate early to avoid memory issues
if (data.length > MAX_BODY_CHARS * 2) {
truncated = true;
res.destroy();
resolve({
statusCode: res.statusCode || 0,
headers: res.headers as Record<string, string | string[] | undefined>,
body: data.slice(0, MAX_BODY_CHARS * 2),
finalUrl: urlStr
});
}
});
res.on('end', () => resolve({
statusCode: res.statusCode || 0,
headers: res.headers as Record<string, string | string[] | undefined>,
body: data,
finalUrl: urlStr
}));
res.on('end', () => {
if (truncated) return;
resolve({
statusCode: res.statusCode || 0,
headers: res.headers as Record<string, string | string[] | undefined>,
body: data,
finalUrl: urlStr
});
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out (30s)')); });
Expand Down
30 changes: 29 additions & 1 deletion java/src/main/java/dev/sorted/mcphub/ProviderManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,14 @@ public synchronized JsonNode call(String groupId, String method, JsonNode params
try {
return futureResult.get(CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (java.util.concurrent.TimeoutException e) {
restartBrokenProvider(groupId);
throw new IOException("Provider '" + groupId + "' call timed out after " + CALL_TIMEOUT_MS + "ms");
} catch (java.util.concurrent.ExecutionException e) {
throw new IOException("Provider '" + groupId + "' call failed: " + e.getCause().getMessage());
String causeMsg = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
if (causeMsg != null && causeMsg.contains("closed stdout")) {
restartBrokenProvider(groupId);
}
throw new IOException("Provider '" + groupId + "' call failed: " + causeMsg);
}
}
}
Expand All @@ -289,6 +294,29 @@ public boolean isRunning(String groupId) {
// Internal
// -------------------------------------------------------------------------

/** Kill and restart a provider whose stdin/stdout sync may be broken
* (e.g., after a call timeout or stdout close). */
private void restartBrokenProvider(String groupId) {
ProviderProcess broken = processes.remove(groupId);
if (broken != null) {
broken.process.destroy();
log.warn("Provider group '{}' call failed — destroying and restarting", groupId);
for (GroupConfig g : groups) {
if (g.id.equals(groupId)) {
try {
start(g);
reportHealth(groupId, "running");
confirmWithRegistry(g);
} catch (Exception restartEx) {
log.error("Failed to restart provider group '{}' after failure: {}", groupId, restartEx.getMessage());
reportHealth(groupId, "unavailable");
}
break;
}
}
}
}

private void start(GroupConfig cfg) throws IOException {
// Prevent orphan processes: skip if group already has a running process
ProviderProcess existing = processes.get(cfg.id);
Expand Down
Loading