Fix MCP request hangs caused by agent interruption

This fixes two root causes of MCP tool calls hanging indefinitely:

1. Transform ensureValidJson from validator to transformer
   - Previously threw errors on undefined values, causing handlers to crash
   - Now converts undefined→null, Date→ISO string, bigint→string, etc.
   - Ensures all MCP handlers always return valid JSON responses

2. Cancel waitTracker when interrupting agents
   - Added waitTracker.cancel() in cancel_agent handler
   - Added waitTracker.cancel() in kill_agent handler
   - Resolves waiting wait_for_agent promises when agents are interrupted
   - Prevents indefinite hangs requiring server restart

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-11-29 11:41:11 +00:00
co-authored by Claude
parent 7c1517d830
commit 6b9fbb83c0
2 changed files with 38 additions and 33 deletions
@@ -429,6 +429,9 @@ export async function createAgentMcpServer(
}, },
async ({ agentId }) => { async ({ agentId }) => {
const success = await agentManager.cancelAgentRun(agentId); const success = await agentManager.cancelAgentRun(agentId);
if (success) {
waitTracker.cancel(agentId, "Agent run cancelled");
}
return { return {
content: [], content: [],
structuredContent: ensureValidJson({ success }), structuredContent: ensureValidJson({ success }),
@@ -450,6 +453,7 @@ export async function createAgentMcpServer(
}, },
async ({ agentId }) => { async ({ agentId }) => {
await agentManager.closeAgent(agentId); await agentManager.closeAgent(agentId);
waitTracker.cancel(agentId, "Agent terminated");
return { return {
content: [], content: [],
structuredContent: ensureValidJson({ success: true }), structuredContent: ensureValidJson({ success: true }),
+34 -33
View File
@@ -7,54 +7,55 @@ export type JsonValue =
| { [key: string]: JsonValue }; | { [key: string]: JsonValue };
/** /**
* Ensure the provided value only contains JSON-safe primitives. * Coerce arbitrary values into JSON-safe structures.
* Throws when undefined, functions, symbols, BigInts, etc. are found. * Unlike the previous implementation, this never throws on undefined—
* it replaces unsupported values (undefined, functions, symbols) with null
* so responses always make it back to the client.
*/ */
export function ensureValidJson<T>(value: T, rootLabel: string = "root"): T { export function ensureValidJson<T>(value: T): T {
const seen = new Set<object>(); const seen = new WeakSet<object>();
const validate = (current: unknown, path: string): void => { const sanitize = (current: unknown): JsonValue => {
if (current === undefined) { if (current === null || current === undefined) {
throw new Error(`Invalid JSON value at ${path}: undefined`); return null;
} }
if (current === null) { if (typeof current === "string" || typeof current === "number") {
return; return current;
} }
const type = typeof current; if (typeof current === "boolean") {
return current;
}
if (type === "string" || type === "number" || type === "boolean") { if (typeof current === "bigint") {
return; return current.toString();
}
if (current instanceof Date) {
return current.toISOString();
} }
if (Array.isArray(current)) { if (Array.isArray(current)) {
current.forEach((item, index) => return current.map((item) => sanitize(item));
validate(item, `${path}[${index}]`)
);
return;
} }
if (type === "object") { if (typeof current === "object") {
const asObject = current as Record<string, unknown>; if (seen.has(current as object)) {
if (!seen.has(asObject)) { throw new Error("Cannot serialize circular structure to JSON");
seen.add(asObject);
for (const [key, val] of Object.entries(asObject)) {
const nextPath = path === rootLabel ? key : `${path}.${key}`;
validate(val, nextPath);
}
seen.delete(asObject);
} }
return; seen.add(current as object);
const obj: Record<string, JsonValue> = {};
for (const [key, val] of Object.entries(current as Record<string, unknown>)) {
obj[key] = sanitize(val);
}
seen.delete(current as object);
return obj;
} }
throw new Error( // functions, symbols, undefined, etc.
`Invalid JSON value at ${path}: ${ return null;
type === "symbol" ? "symbol" : String(current)
}`
);
}; };
validate(value, rootLabel); return sanitize(value) as T;
return value;
} }