Skip to content

fix: The MCP server's upstream call has no deadline and no error handling - #525

Merged
Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
Wittig18:solver/issue-349-25-a0
Oct 19, 2026
Merged

Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
Wittig18:solver/issue-349-25-a0

Conversation

@Wittig18

Copy link
Copy Markdown

Summary

bazaar_search in src/mcp/server.ts now has a 10s deadline, checks the response status and content type before parsing, and returns structured errors that name the upstream. The deadline is AbortSignal.timeout(10_000) passed to the fetch, as in src/alerts/delivery.ts. A non-2xx response, a response whose content-type isn't JSON, or a body that fails to parse each return an ERR_SERVICE_UNREACHABLE tool error with isError set. A timeout returns the same code with a message saying the upstream did not respond within 10000ms. Every message includes the upstream host. I also gave paid_call the same deadline and a timeout branch in handlePaidCallError. paid_call still returns non-JSON bodies as text, so it doesn't reject them. I haven't run any tests, since I can't run commands here.

Changed files

  • src/mcp/server.ts
  • tests/mcp.test.ts

Test plan

Run npx vitest run tests/mcp.test.ts. The new describe block 'bazaar_search upstream failures (#349)' covers the deadline signal being passed, a timeout, a 502 HTML response, a text/html 200, and a JSON content-type with an unparseable body. I changed the existing okFetchImpl fixture to send content-type: application/json, because without it the new content-type check would reject the existing success test. Then run the full npm test; Load test (prices) already fails on main.

This PR was created as a draft by the issue solver bot. It will remain a
draft until repository CI passes.

Closes #349

@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@Wittig18 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Wittig18
Wittig18 marked this pull request as ready for review October 18, 2026 23:01
@Miracle656
Miracle656 merged commit 443174e into Divinity-Labs:main Oct 19, 2026
@Miracle656

Copy link
Copy Markdown
Collaborator

Merged. The title understates it — this fixes two distinct failure modes, and the worse one is the hang.

bazaar_search awaited ssrfSafeFetch with no deadline, so an upstream that accepts the connection and then goes quiet never returns. That's worse than an error: an MCP client gets no answer at all and no reason, and the tool call just sits there. A 10s AbortSignal.timeout is the right fix and matching the existing deadline in src/alerts/delivery.ts is the right value — one house number for outbound calls beats a new one per call site.

The second one: await res.json() on a non-JSON body threw a raw parse error at the client. Checking content-type first and still guarding the parse is the correct belt-and-braces — a server can advertise JSON and send something else, so the content-type check alone wouldn't be enough.

Distinguishing timeouts from other failures matters more than it looks. Without it, an upstream that times out reports whatever generic message happens to surface, and the operator chases a JSON problem that doesn't exist. name === 'TimeoutError' || 'AbortError' is the right test for AbortSignal.timeout.

And naming the upstream host in every message is the difference between a debuggable failure and a bare ERR_SERVICE_UNREACHABLE. I checked whether that leaks anything: the host comes from the caller's own url argument, so it's their input coming back to them. Nothing disclosed.

Two merge resolutions

Both against work that landed earlier today, neither your fault:

  1. tests/mcp.test.ts conflicted with fix(mcp): validate tool arguments with zod and constrain http methods #555's argument-validation suite. Additive on both sides — both blocks kept.
  2. fix(mcp): validate tool arguments with zod and constrain http methods #555's two paid_call tests asserted the fetch options exactly:
expect(mockFetch).toHaveBeenCalledWith('http://example.com/paid', { method: 'POST' })

Your new signal makes that exact match fail. Those tests are about the method being passed through, not about the options object being empty otherwise, so I loosened them to expect.objectContaining({ method: 'POST' }) with a comment saying why. Your change is correct; the assertion was over-tight.

Verified on the merge result rather than your branch: tsc --noEmit silent, 901 tests across 83 suites, nothing red.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The MCP server's upstream call has no deadline and no error handling

3 participants