Skip to content

fix(server): the mcp relay reaches its runtime again within seconds, and says why when it cannot - #272

Closed
SamHatoum wants to merge 1 commit into
mainfrom
fix/relay-recovery
Closed

SamHatoum wants to merge 1 commit into
mainfrom
fix/relay-recovery

Conversation

@SamHatoum

Copy link
Copy Markdown
Collaborator

What this changes

auto-runtime mcp connects an agent to the runtime and starts one in the background when none runs. Once connected, it handled one failure: a refused connection. Even then it waited as long as a restart can take before it started a runtime: about seventy seconds on paper and nearly ninety in practice, because each check adds a little time. An agent whose runtime had stopped, for example one closed in a terminal, saw its next call fail before the relay came back. Claude Desktop gives up on a call after about sixty seconds.

Reaching the runtime again

  • A refused connection: the relay waits through a restart only when a restart mark says one is under way. Otherwise it checks for a second, then starts a runtime, which answers in about two seconds.
  • No reply after five seconds: the relay asks the runtime whether it answers. If it does, the relay keeps waiting and checks again every five seconds. A newer MCP client can get a tool call's whole answer in one piece at the end, so a long call on a healthy runtime is never cut off. If the runtime does not answer, the relay gives up on the request and recovers.
  • A stream that ends with no answer: this happens when the runtime stops partway through a call, and it now counts as a loss too.
  • A runtime that does not answer: the relay asks twice more. If the relay saw an agent start that runtime, it stops it, kills it if it outlives the time a stop can take, and starts another. A runtime started in a terminal, or a program the relay never saw answer, is left alone, and the agent is told what to stop or to set PORT.
  • A runtime that asks for a credential, such as one with PUBLIC_URL or API_KEYS: the relay reads the 401 as such and leaves the runtime alone. The agent is told to stop it or to connect over HTTP, instead of getting a raw 401.

What is sent again

A request is sent again once the runtime runs only when it never reached the runtime, or when sending it twice changes nothing: initialize, the lists, reads and ping. A tool call that may have reached the runtime is answered with words that say to check whether it took effect, so a definition never runs twice.

Several requests that fail at the same moment share one recovery. A recovery that would take longer than 45 seconds answers that the runtime is still coming, so the agent hears back before it gives up, and the recovery carries on so the retry works.

Probe and commands

The probe tells a runtime that does not answer from one that is down, and one that asks for a credential from one that runs without controls. It takes a second per check, down from two. status, stop and restart name both new states.

Timings, from the new tests on real processes

Scenario Before After
A runtime in a terminal is stopped, then the agent calls a tool about 88 s, and the call fails answered in about 4 s by a runtime the relay starts
The runtime the relay started is frozen (SIGSTOP) the agent waits until its own timeout stopped, replaced, and the request answered in about 22 s
A program on the port never answers the agent waits until its own timeout told in about 13 s
The runtime asks for a credential a raw 401 told in about 2 s

Tests

  • mcp-relay.test.ts and relay-recovery.test.ts cover the relay on fake transports and timers: each kind of loss, the five-second watch, the race in which an answer starts while the runtime is being checked, the 45-second cap and the shared recovery.
  • background-start.test.ts and runtime-recovery.test.ts cover recovery on a fake computer: the brief wait, a restart mark, a runtime that asks for a credential, a hung runtime the relay started (stopped, then killed), and one it did not start (left alone).
  • agent-recovery.test.ts runs the four scenarios above with real processes.
  • The fakes the tests share live in src/testing/relays/.

The npm README says what happens when the runtime stops or hangs while an agent is connected.

Checklist

  • The PR title is a conventional commit with a workspace scope, such as feat(reasoning): .... It becomes the squash commit and the changelog entry.
  • pnpm check passes locally, with every file at 100% coverage.
  • I've signed the Contributor License Agreement (the CLA bot asks on your first PR).

🤖 Generated with Claude Code

…and says why when it cannot

The relay of `auto-runtime mcp` started a runtime again only after a
connection was refused, and only once it had waited as long as a
restart can take, about seventy seconds and in practice nearer ninety,
so an agent whose runtime had stopped saw its next call fail before the
relay came back. It now waits that long only while a restart mark says
a restart is under way, and otherwise looks for a second and starts a
runtime, which answers in about two seconds.

It also notices more than a refused connection. A request that has not
started to be answered in five seconds has the runtime asked whether it
answers, and is waited for as long as it does, as a long tool call is;
one whose runtime does not answer is given up. A runtime that does not
answer is asked twice more, then stopped, and killed past the time a
stop can take, and another started, when this relay saw an agent start
it; one that runs in a terminal, or a program the relay never saw
answer, is left alone, and the agent is told what to stop. A runtime
that asks for a credential, which a 401 from it now reads as, is left
alone too, and the agent is told to stop it or connect over HTTP.

A request is sent again once the runtime runs only when it never reached
the runtime or sending it twice changes nothing, so a tool call whose
answer was cut off, or whose stream ended without one, is answered with
words that say to check whether it took effect. Losses at once share one
recovery, and a recovery longer than forty-five seconds answers that the
runtime is still coming, so an agent is answered before it gives up. The
probe tells a runtime that does not answer from one that is down, and
one that asks for a credential from one without controls, in a second
each, and the status, stop and restart commands name both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rami-hatoum

Copy link
Copy Markdown
Contributor

Superseded by #278, now on main, which reaches the runtime again on the protocol's own means: a heartbeat on every answer, liveness by ping, a resend rule decided at the loss so that no tool call runs twice, and signals only through the handle of a runtime this relay started. Thank you for the groundwork; the design built on what this change found.

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.

2 participants