Skip to content

fix(runner): bound server scenarios with a per-scenario timeout - #454

Merged
pcarleton merged 2 commits into
modelcontextprotocol:mainfrom
aviseth:fix/server-runner-scenario-timeout
Sep 6, 2026
Merged

fix(runner): bound server scenarios with a per-scenario timeout#454
pcarleton merged 2 commits into
modelcontextprotocol:mainfrom
aviseth:fix/server-runner-scenario-timeout

Conversation

@aviseth

@aviseth aviseth commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Closes #427.

The client runner has bounded each scenario since it was written. The server runner never did, so a server that accepts the TCP connection and then stops answering stalled the whole run instead of failing its own scenario.

--timeout now works in server mode, same 30s default as client mode. When it fires, the scenario gets a scenario-timeout FAILURE naming the bound and the suite carries on.

The one decision I'd like a second opinion on: the timed-out scenario.run promise is left pending rather than cancelled. A read blocked on a socket has nothing to cancel through, so there is nothing to await — I swallow its rejection so a late failure can't land on whatever scenario is running by then. Threading an AbortSignal through RunContext would let scenarios actually tear down, but that touches every scenario and seemed like a separate change. Say the word and I'll do it that way instead.

Tested by pointing server-initialize at a server that completes the handshake and never writes a byte. With the runner change reverted the test hangs until vitest kills it at 30s. Full suite, typecheck and lint pass.

#316's per-call bounds still earn their keep — they fail faster and attribute the failure better. They're just no longer the only thing between a wedged server and an unbounded run.

aviseth and others added 2 commits August 12, 2026 20:41
The client runner has had a 30s per-scenario timeout since it was written,
but `runServerConformanceTest` awaited `scenario.run(ctx)` with no bound. A
server under test that accepts the TCP connection and then never answers an
initialize POST, a tools call, or an SSE read stalled the entire suite run,
not just its own scenario.

modelcontextprotocol#316 bounded the session-termination DELETEs in teardown, but that is a
call-site fix: covering the rest that way needs the same treatment on every
raw fetch in every scenario. Bounding at the runner makes those per-call
bounds defense-in-depth instead of the only protection.

`--timeout` now applies to server mode too, with the same 30s default as
client mode. On expiry the scenario reports a `scenario-timeout` FAILURE
naming the elapsed bound, so a wedged server reads as a failing scenario
rather than a hung process.

The timed-out scenario promise is left pending deliberately — a read blocked
on a socket has no cancellation channel — with its rejection swallowed so a
late failure can't surface against whichever scenario is running by then.

Fixes modelcontextprotocol#427
@aviseth

aviseth commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@pcarleton against #427, which you filed. No CI has run here — the workflow runs are sitting in action_required.

One open question from the description, if you have a view: I left the timed-out scenario.run pending rather than threading an AbortSignal through RunContext. The latter gives real teardown but touches every scenario. Happy to switch.

@pkg-pr-new

pkg-pr-new Bot commented Sep 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@modelcontextprotocol/conformance@454

commit: 6d64885

@pcarleton pcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, leaving the timed-out run pending is good enough for now; we can worry about threading an AbortSignal through RunContext if we find we need that later. One nit for a follow-up: validate --timeout as a positive integer, parseInt lets 30s through as 30 ms and NaN makes every scenario time out immediately.

@pcarleton
pcarleton enabled auto-merge (squash) September 6, 2026 19:14
@pcarleton
pcarleton merged commit 88ac3e5 into modelcontextprotocol:main Sep 6, 2026
4 checks passed
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.

Server runner has no per-scenario timeout; a wedged server stalls the whole suite

2 participants