@@ -56,6 +56,18 @@ function is401Error(error: unknown) {
5656 ) ;
5757}
5858
59+ function createMcpClient ( ) {
60+ return new Client (
61+ {
62+ name : "continue-client" ,
63+ version : "1.0.0" ,
64+ } ,
65+ {
66+ capabilities : { } ,
67+ } ,
68+ ) ;
69+ }
70+
5971export type MCPExtras = {
6072 ide : IDE ;
6173} ;
@@ -85,19 +97,70 @@ class MCPConnection {
8597 // Don't construct transport in constructor to avoid blocking
8698 this . transport = { } as Transport ; // Will be set in connectClient
8799
88- this . client = new Client (
89- {
90- name : "continue-client" ,
91- version : "1.0.0" ,
92- } ,
93- {
94- capabilities : { } ,
95- } ,
96- ) ;
100+ this . client = createMcpClient ( ) ;
97101
98102 this . abortController = new AbortController ( ) ;
99103 }
100104
105+ private async resetClientAndTransport ( ) {
106+ try {
107+ await this . client . close ( ) ;
108+ } catch {
109+ // Ignore close errors while replacing stale clients/transports.
110+ }
111+
112+ try {
113+ await this . transport . close ?.( ) ;
114+ } catch {
115+ // Ignore close errors while replacing stale clients/transports.
116+ }
117+
118+ this . client = createMcpClient ( ) ;
119+ this . transport = { } as Transport ;
120+ }
121+
122+ private shouldReconnectAfterError ( error : unknown ) {
123+ if ( this . options . type !== "sse" || this . status === "disabled" ) {
124+ return false ;
125+ }
126+
127+ const message = (
128+ error instanceof Error ? error . message : String ( error )
129+ ) . toLowerCase ( ) ;
130+
131+ const sessionError =
132+ message . includes ( "session" ) &&
133+ ( message . includes ( "invalid" ) ||
134+ message . includes ( "unknown" ) ||
135+ message . includes ( "expired" ) ||
136+ message . includes ( "not found" ) ||
137+ message . includes ( "valid" ) ||
138+ message . includes ( "missing" ) ) ;
139+
140+ return (
141+ sessionError ||
142+ message . includes ( "connection closed" ) ||
143+ message . includes ( "transport closed" )
144+ ) ;
145+ }
146+
147+ private async withSseReconnectRetry < T > ( operation : ( ) => Promise < T > ) {
148+ try {
149+ return await operation ( ) ;
150+ } catch ( error ) {
151+ if ( ! this . shouldReconnectAfterError ( error ) ) {
152+ throw error ;
153+ }
154+
155+ await this . connectClient ( true , new AbortController ( ) . signal ) ;
156+ if ( this . status !== "connected" ) {
157+ throw error ;
158+ }
159+
160+ return await operation ( ) ;
161+ }
162+ }
163+
101164 async disconnect ( disable = false ) {
102165 this . abortController . abort ( ) ;
103166 await this . client . close ( ) ;
@@ -147,6 +210,7 @@ class MCPConnection {
147210
148211 this . abortController . abort ( ) ;
149212 this . abortController = new AbortController ( ) ;
213+ await this . resetClientAndTransport ( ) ;
150214
151215 // currently support oauth for sse transports only
152216 if ( this . options . type === "sse" ) {
@@ -613,11 +677,25 @@ Org-level secrets can only be used for MCP by Background Agents (https://docs.co
613677 }
614678
615679 async getResource ( uri : string ) {
616- return await this . client . readResource (
617- { uri } ,
618- {
619- timeout : this . options . timeout ,
620- } ,
680+ return await this . withSseReconnectRetry ( ( ) =>
681+ this . client . readResource (
682+ { uri } ,
683+ {
684+ timeout : this . options . timeout ,
685+ } ,
686+ ) ,
687+ ) ;
688+ }
689+
690+ async getPrompt ( ...args : Parameters < Client [ "getPrompt" ] > ) {
691+ return await this . withSseReconnectRetry ( ( ) =>
692+ this . client . getPrompt ( ...args ) ,
693+ ) ;
694+ }
695+
696+ async callTool ( ...args : Parameters < Client [ "callTool" ] > ) {
697+ return await this . withSseReconnectRetry ( ( ) =>
698+ this . client . callTool ( ...args ) ,
621699 ) ;
622700 }
623701}
0 commit comments