Runtime Guards
Introduction
Even with Presenter .limit() guardrails, handlers can still produce unbounded execution times or get called in aggressive loops. MCP Fusion provides built-in .timeout() and rate limiting to protect the server process.
Timeout
Set a per-tool timeout with .timeout(). If the handler exceeds the duration, the AbortSignal fires and the handler is cancelled:
const f = initFusion<AppContext>();
export const heavyQuery = f.query('analytics.heavy')
.describe('Run a heavy analytics query')
.timeout(60_000) // 60 seconds
.withString('range', 'Date range')
.handle(async (input, ctx) => {
return ctx.db.analytics.complexAggregation({
range: input.range,
signal: ctx.signal, // pass the signal to the DB driver
});
});When the timeout fires, the AI receives a structured error indicating the operation was cancelled due to timeout.
TIP
Always forward ctx.signal to async I/O (fetch, database drivers) so timeouts propagate through the entire call chain, not just the handler.
Rate Limiting
Configure rate limiting at the server level to protect backend services:
registry.attachToServer(server, {
contextFactory: createContext,
rateLimit: {
maxCallsPerMinute: 60,
perTool: {
'billing.charge': 5, // max 5 charges per minute
'data.export': 2, // max 2 exports per minute
},
},
});When the rate limit is exceeded, the tool returns a structured error that tells the AI to wait before retrying.
Manual Guards in Middleware
For custom protection logic, create guard middleware:
const withConcurrencyLimit = f.middleware(async (ctx) => {
const active = activeRequests.get(ctx.tenantId) ?? 0;
if (active >= 10) {
throw new Error('Too many concurrent requests. Wait for current operations to complete.');
}
activeRequests.set(ctx.tenantId, active + 1);
return {
_cleanup: () => {
activeRequests.set(ctx.tenantId, (activeRequests.get(ctx.tenantId) ?? 1) - 1);
},
};
});
export const heavyExport = f.action('data.export')
.describe('Export data in bulk')
.use(withAuth)
.use(withConcurrencyLimit)
.withEnum('format', ['csv', 'json'] as const, 'Export format')
.handle(async (input, ctx) => {
try {
const result = await ctx.db.export(input.format);
return result;
} finally {
ctx._cleanup();
}
});WARNING
Guards protect the server from resource exhaustion but should not replace business logic validation. Use guards for infrastructure safety and toolError() for business rules.