@mcp-utils/timeout
v1.0.0
Published
Timeout wrapper for MCP tool handlers with AbortSignal propagation — powered by vurb.
Maintainers
Readme
@mcp-utils/timeout
Your MCP tool calls an external API. The API stops responding. No error. No response. Just silence.
The LLM waits. The user waits. The connection stays open, burning resources, until something upstream finally gives up — if it ever does.
MCP has no built-in timeout. Every tool you build is vulnerable unless you add one yourself.
import { withTimeout } from '@mcp-utils/timeout';
// Before: one hung API = your agent stalls indefinitely
handler: async (ctx, args) => success(await slowApi.call(args))
// After: fails fast — clear error, clean cancellation, agent moves on
handler: withTimeout(
async (ctx, args, _p, signal) => success(await slowApi.call(args, { signal })),
10_000
)Install
npm install @mcp-utils/timeoutUsage
Basic timeout
import { withTimeout } from '@mcp-utils/timeout';
const generateReport = withTimeout(
async (ctx, args) => success(await reports.generate(args)),
15_000, // fail after 15 seconds
);Propagate the signal (important)
Pass the signal to downstream calls so they cancel cleanly when the timeout fires — no orphaned DB queries, no leaking HTTP connections:
const fetchData = withTimeout(
async (ctx, args, _progress, signal) => {
const rows = await db.query({ where: args, signal }); // cancels on timeout
const meta = await fetch(apiUrl, { signal }); // cancels on timeout
return success({ rows, meta });
},
8_000,
);Apply a consistent policy across tools
import { createTimeout } from '@mcp-utils/timeout';
const bounded = createTimeout(5_000);
const getUser = bounded(async (ctx, args, _p, signal) => success(await db.users.get(args['id'], signal)));
const getProduct = bounded(async (ctx, args, _p, signal) => success(await db.products.get(args['id'], signal)));Stack with retry
import { withRetry } from '@mcp-utils/retry';
import { withTimeout } from '@mcp-utils/timeout';
// Each attempt gets 5 seconds. If it fails, retry up to 3 times.
const handler = withRetry(
withTimeout(
async (ctx, args, _p, signal) => success(await api.call(args, signal)),
5_000,
),
{ attempts: 3, backoff: 'exponential' },
);API
withTimeout(handler, ms)
- Injects an
AbortSignalinto the handler for clean downstream cancellation - Chains with existing upstream signals from the MCP request — if the client cancels, the tool cancels too
- Returns
toolError('TIMEOUT')on expiry with a clear message - Returns an error immediately if the upstream signal was already aborted before the tool was called
createTimeout(ms)
Returns a decorator: (handler) => wrappedHandler.
Part of @mcp-utils — production-grade utilities for MCP server development.
License
Apache-2.0
