An MCP tool can stay responsive at its public endpoint while a downstream service keeps its requests open. Adding another container does not tell a waiting request when to stop. Give each downstream read an explicit deadline. Decide upfront how the tool reports the failure.
This article adapts AI Automation Blueprint's MCP-hosting topic into a small Node.js transport example. The helper performs one GET and parses its JSON response. It is not an MCP server or a deployment template. The checks use local fixtures, with no cloud service or customer data involved.
Keep the deadline around the response body
A request can receive headers quickly and then stall while reading its body. Keep the abort signal alive until JSON parsing finishes. Clearing the timer immediately after fetch returns leaves that body read outside the intended deadline. That's a mistake.
Node's fetch documentation describes its built-in fetch API. The AbortController reference covers aborting fetch requests and consumption of response bodies. The helper combines both mechanisms and clears its timer on either success or failure.
async function readToolJson(url, timeoutMs = 1000) {
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60000) {
throw new RangeError("timeoutMs must be an integer from 1 to 60000");
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
method: "GET",
redirect: "error",
signal: controller.signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} finally {
clearTimeout(timer);
}
}
The default 1000 ms value is an example budget, not a latency promise. Choose a budget appropriate to the operation and the deadline of the enclosing tool call. The 60000 ms upper bound here is a policy choice that keeps obviously excessive timer values out of the helper. Keep in mind that a busy event loop delays timers. Timers aren't real-time guarantees.
Pass the URL from trusted application configuration. Do not let model output or retrieved text choose an arbitrary network destination. Redirects fail here by design, but the helper omits an allowlist, authentication, DNS-rebinding protection, or a response-size limit. Those controls belong in your network and application boundaries.
Separate the failure cases
The helper rejects a non-success HTTP response. A successful response containing invalid JSON also rejects, and a stalled read aborts. Those are distinct operational failures. Preserve the cause in restricted diagnostics while returning an appropriate tool error to the client. Don't turn a failed lookup into an empty result that looks like a successful search.
Use a local fixture server to exercise a valid JSON response and an HTTP error. Add an endpoint that sends headers but delays the body, then another that delays headers. Both stalled cases should reject under the same deadline policy. Check malformed JSON and a redirect as well. Invalid timeout inputs must fail before a network request starts.
These checks establish the helper's behavior against local HTTP fixtures. They do not test an MCP transport, a remote provider, or a reverse proxy. Record that limitation directly in the test receipt instead of claiming the hosted service is ready.
Cancellation is not a remote rollback
The MCP cancellation documentation discusses request timeouts and cancellation. A downstream fetch deadline is one layer inside that larger request lifecycle. It does not implement MCP cancellation messages, propagate every client deadline, or set server-wide timeout policy.
And aborting a local request does not prove the remote service stopped processing it. This helper reads data and contains no retry loop. Do not copy the same pattern into a write-capable operation and assume an abort means the write failed. Reconcile the remote operation's outcome before deciding whether another submission is allowed.
Record bounded timing and failure categories without logging bearer tokens or customer payloads. Test the real hosted path separately, including the proxy's timeout behavior and the MCP client's response to a tool error. A container that starts successfully is only the beginning of that evidence.
Top comments (0)