Timeout & Cancellation
AI agents frequently trigger network requests, database transactions, or external operations that might stall or take too long. Without robust timeout and cancellation systems:
- Server worker threads become blocked indefinitely.
- Users cancelling a query in Claude or Cursor leave dangling zombie processes behind.
mcponce provides a dual-layer deadline management system combining cooperative cancellation via standard web AbortSignal with uncooperative hard deadlines.
Configuring Deadlines #
Timeouts can be configured globally, per-tool, or dynamically on individual invocations:
app.tool({
name: 'scrape_webpage',
description: 'Fetches HTML with 5s timeout',
timeoutMs: 5000, // 5 seconds deadline for this tool
handler: async ({ url }, context) => {
// Pass context.signal to cooperative APIs
const response = await fetch(url, { signal: context.signal });
return response.text();
}
});
Set timeoutMs: 0 on a tool to allow it to run indefinitely (useful for long-running backup jobs, ML inference, or daemon tasks).
Cooperative Cancellation (AbortSignal)
#
Every tool handler receives context.signal (and extra.signal), an instance of the standard web AbortSignal .
Using context.signal with Native APIs
#
Pass context.signal directly to standard libraries such as fetch, node:fs, node:child_process, or database drivers:
app.tool({
name: 'query_remote_database',
handler: async ({ query }, context) => {
// 1. Pass signal directly to fetch or database queries
const res = await fetch('https://api.db.com/query', {
method: 'POST',
body: JSON.stringify({ query }),
signal: context.signal
});
return res.json();
}
});
Checking signal.aborted in Loops
#
For CPU-bound loops or iterative jobs, periodically check signal.aborted:
app.tool({
name: 'train_iteration',
handler: async ({ epochs }, context) => {
for (let i = 0; i < epochs; i++) {
// Check if client cancelled or timeout fired
if (context.signal?.aborted) {
throw new Error('Operation aborted by client or deadline');
}
await computeStep(i);
}
}
});
Uncooperative Hard Deadlines #
What if a third-party library hangs and completely ignores context.signal?
mcponce ensures your server never blocks. When a deadline expires:
context.signalis immediately aborted (signal.aborted === true).- The invocation promise is rejected with a descriptive timeout error.
- The server slot is freed in the concurrency queue, allowing subsequent tool calls to proceed without starvation.
Client Cancellation (notifications/cancelled)
#
When an end-user presses Stop Generation in Claude Desktop or Cursor:
- The MCP client sends a
notifications/cancellednotification containing therequestId. mcponceinstantly matches the active request and triggers the correspondingAbortController.- Running HTTP requests, child processes, or database queries listening to
context.signalare terminated immediately, saving compute and bandwidth.