Skip to content

fix: call onerror callback before throwing transport errors#1704

Open
ctonneslan wants to merge 2 commits intomodelcontextprotocol:mainfrom
ctonneslan:fix/transport-onerror-callback
Open

fix: call onerror callback before throwing transport errors#1704
ctonneslan wants to merge 2 commits intomodelcontextprotocol:mainfrom
ctonneslan:fix/transport-onerror-callback

Conversation

@ctonneslan
Copy link

Summary

  • Several transport implementations threw errors without first invoking the onerror callback, causing errors to be silently swallowed
  • Adds try-catch with onerror reporting to send() methods in:
    • StdioClientTransport (client stdio)
    • StdioServerTransport (server stdio)
    • WebSocketClientTransport (client websocket)
    • StreamableHTTPServerTransport (server streamable HTTP)

Fixes #1395

Test plan

  • Existing transport tests still pass
  • Errors thrown by send() are reported via onerror before propagating
  • Error objects are preserved (same instance thrown and reported)

@ctonneslan ctonneslan requested a review from a team as a code owner March 19, 2026 00:21
@changeset-bot
Copy link

changeset-bot bot commented Mar 19, 2026

⚠️ No Changeset found

Latest commit: a6d1460

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 19, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1704

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1704

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1704

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1704

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1704

commit: a6d1460

Several transport implementations threw errors without first calling
the onerror callback, causing errors to be silently swallowed when
callers didn't handle the thrown error. This adds proper onerror
reporting to stdio, websocket, and streamable HTTP transports.

Fixes modelcontextprotocol#1395
Copy link

@travisbreaks travisbreaks left a comment

Choose a reason for hiding this comment

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

The core change is correct: calling onerror before throwing/rejecting ensures error handlers see the error before promise rejection propagates. This matches how Node.js streams work (emit 'error' before destroy).

Overlap note: PR #1705 by the same author includes all of these onerror additions plus the close() re-entrancy guard. If #1705 lands, this PR is redundant. Worth coordinating with the maintainers on which to merge first, or consolidating.

One concern on the event store wrapping:

try {
    primingEventId = await this._eventStore.storeEvent(streamId, {} as JSONRPCMessage);
} catch (error) {
    const err = error as Error;
    this.onerror?.(err);
    throw err;
}

If this.onerror itself throws, the original error is lost. A safer pattern:

} catch (error) {
    const err = error as Error;
    try { this.onerror?.(err); } catch { /* handler error should not mask transport error */ }
    throw err;
}

This is defensive, but onerror is a user-provided callback so it could do anything.

Since onerror is a user-provided callback, it could throw. Without
defensive wrapping, a throwing onerror handler would mask the original
transport error (the reject/throw would never execute).

Addresses review feedback from @travisbreaks.
Copy link
Author

@ctonneslan ctonneslan left a comment

Choose a reason for hiding this comment

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

Good catch on the defensive wrapping — pushed a fix. All 10 onerror call sites across the 4 transport files are now wrapped in try/catch so a throwing user callback can't mask the original transport error.

Re: overlap with #1705 — this PR is actually complementary, not redundant. #1705 fixes close() re-entrancy, this one fixes send() error handling. They touch different code paths. Updated #1705's description to note the relationship.

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.

Some transport errors are silently swallowed due to missing onerror callback usage

2 participants