@@ -24,7 +24,7 @@ type PendingResolver = {
2424 reject : ( reason : unknown ) => void ;
2525} ;
2626
27- /** 用字符串键匹配 JSON-RPC id(兼容 number / string 回传)。 */
27+ /** Match JSON-RPC ids with string keys ( number or string echo from server). */
2828function pendingKey ( id : number | string ) : string {
2929 return String ( id ) ;
3030}
@@ -41,7 +41,7 @@ export class McpSseClient {
4141 private resolveEndpoint : ( ( ) => void ) | undefined ;
4242 private rejectEndpoint : ( ( reason : unknown ) => void ) | undefined ;
4343 private closed = false ;
44- /** SSE GET 已结束(非主动 close)时置位,后续 RPC 立即失败。 */
44+ /** Set when the SSE GET ends without an intentional close(); later RPCs fail fast. */
4545 private streamEnded = false ;
4646
4747 constructor ( deps : HttpDeps , sseUrl : string , authToken ?: string ) {
@@ -114,8 +114,15 @@ export class McpSseClient {
114114 private async openSse ( ) : Promise < void > {
115115 if ( this . abortController ) return ;
116116
117- // Keep the GET open until close(); timeouts apply only to endpoint wait / per-RPC.
117+ // use shared abortController:header wait use timer abort;after getting header, clearTimeout,
118+ // the long-lived stream is only ended by close()/session abort (compatible with Node 18, no AbortSignal.any).
118119 this . abortController = new AbortController ( ) ;
120+ const timeoutMs = this . deps . settings . timeout * 1000 ;
121+ let headerTimedOut = false ;
122+ const headerTimer = setTimeout ( ( ) => {
123+ headerTimedOut = true ;
124+ this . abortController ?. abort ( ) ;
125+ } , timeoutMs ) ;
119126
120127 const headers : Record < string , string > = {
121128 Accept : "text/event-stream" ,
@@ -130,11 +137,28 @@ export class McpSseClient {
130137 console . error ( `> GET ${ this . sseUrl } ` ) ;
131138 }
132139
133- const response = await fetch ( this . sseUrl , {
134- method : "GET" ,
135- headers,
136- signal : this . abortController . signal ,
137- } ) ;
140+ let response : Response ;
141+ try {
142+ response = await fetch ( this . sseUrl , {
143+ method : "GET" ,
144+ headers,
145+ signal : this . abortController . signal ,
146+ } ) ;
147+ } catch ( error ) {
148+ clearTimeout ( headerTimer ) ;
149+ if ( this . closed ) {
150+ throw new BailianError ( "MCP SSE session closed." , ExitCode . GENERAL ) ;
151+ }
152+ if ( headerTimedOut ) {
153+ throw new BailianError ( "MCP SSE timed out waiting for response headers." , ExitCode . TIMEOUT ) ;
154+ }
155+ throw new BailianError (
156+ `MCP SSE request failed: ${ error instanceof Error ? error . message : String ( error ) } ` ,
157+ ExitCode . NETWORK ,
158+ ) ;
159+ }
160+ // 已收到响应头:取消 header 等待,后续仅由 abortController 结束流。
161+ clearTimeout ( headerTimer ) ;
138162
139163 if ( this . deps . settings . verbose ) {
140164 console . error ( `< ${ response . status } ${ response . statusText } ` ) ;
@@ -148,9 +172,8 @@ export class McpSseClient {
148172 } catch {
149173 /* ignore */
150174 }
151- const error = new BailianError ( errMsg , ExitCode . GENERAL ) ;
152- this . rejectEndpoint ?.( error ) ;
153- throw error ;
175+ // Throw only — do not rejectEndpoint; this path never awaits endpointReady.
176+ throw new BailianError ( errMsg , ExitCode . GENERAL ) ;
154177 }
155178
156179 void this . consumeSse ( response ) . catch ( ( error ) => {
@@ -163,13 +186,12 @@ export class McpSseClient {
163186 ExitCode . GENERAL ,
164187 ) ;
165188 this . rejectEndpoint ?.( reason ) ;
166- // consumeSse 在正常结束路径已 markStreamEnded;此处覆盖解析/读取异常。
189+ // consumeSse already markStreamEnded on a clean end; cover parse/read failures here.
167190 if ( ! this . streamEnded ) {
168191 this . markStreamEnded ( reason ) ;
169192 }
170193 } ) ;
171194
172- const timeoutMs = this . deps . settings . timeout * 1000 ;
173195 const endpointTimeout = cancellableTimeoutReject (
174196 timeoutMs ,
175197 "MCP SSE timed out waiting for endpoint event." ,
@@ -185,7 +207,7 @@ export class McpSseClient {
185207 for await ( const event of parseSSE ( response ) ) {
186208 if ( this . closed ) break ;
187209
188- // 规范要求首事件为 event: endpoint;不接受无名事件以免误把 JSON 当 URL。
210+ // Spec requires event: endpoint; ignore unnamed events so JSON is not treated as a URL.
189211 if ( event . event === "endpoint" ) {
190212 const raw = event . data . trim ( ) ;
191213 if ( ! raw ) continue ;
@@ -197,7 +219,7 @@ export class McpSseClient {
197219 continue ;
198220 }
199221
200- // 缺省 event 类型在 SSE 中等同 message。
222+ // Omitted SSE event type defaults to " message".
201223 if ( event . event === "message" || event . event === undefined ) {
202224 let payload : JsonRpcResponse ;
203225 try {
@@ -225,8 +247,8 @@ export class McpSseClient {
225247 throw error ;
226248 }
227249
228- // 已拿到 endpoint 后流仍结束:标记会话死亡并唤醒 pending;不再 throw,
229- // 避免 void consumeSse().catch 之外再冒出未处理 rejection。
250+ // Stream ended after endpoint: mark session dead and wake pending; do not throw,
251+ // so void consumeSse().catch does not surface an extra unhandled rejection.
230252 this . markStreamEnded ( new BailianError ( "MCP SSE stream ended unexpectedly." , ExitCode . GENERAL ) ) ;
231253 }
232254
@@ -248,7 +270,7 @@ export class McpSseClient {
248270 const responsePromise = new Promise < JsonRpcResponse > ( ( resolve , reject ) => {
249271 this . pending . set ( key , { resolve, reject } ) ;
250272 } ) ;
251- // 流可能在 Promise.race 之前结束并 reject pending,先挂上 catch 避免 unhandledRejection。
273+ // Stream may end and reject pending before Promise.race; attach catch to avoid unhandledRejection.
252274 void responsePromise . catch ( ( ) => undefined ) ;
253275 const responseTimeout = cancellableTimeoutReject (
254276 timeoutMs ,
0 commit comments