@@ -3,6 +3,9 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
3
3
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
4
4
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
5
5
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
7
+ import { LoggingLevel } from '@modelcontextprotocol/sdk/types.js'
8
+ import { ConnStatus } from './types'
6
9
7
10
// Connection constants
8
11
export const REASON_AUTH_NEEDED = 'authentication-needed'
@@ -73,6 +76,29 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
73
76
}
74
77
}
75
78
79
+ /**
80
+ * Extended StdioServerTransport class
81
+ */
82
+ export class StdioServerTransportExt extends StdioServerTransport {
83
+ /**
84
+ * Send a log message through the transport
85
+ * @param level The log level ('error' | 'debug' | 'info' | 'notice' | 'warning' | 'critical' | 'alert' | 'emergency')
86
+ * @param data The data object to send (should be JSON serializable)
87
+ * @param logger Optional logger name, defaults to 'mcp-remote'
88
+ */
89
+ sendMessage ( level : LoggingLevel , data : any , logger : string = 'mcp-remote' ) {
90
+ return this . send ( {
91
+ jsonrpc : '2.0' ,
92
+ method : 'notifications/message' ,
93
+ params : {
94
+ level,
95
+ logger,
96
+ data,
97
+ } ,
98
+ } )
99
+ }
100
+ }
101
+
76
102
/**
77
103
* Type for the auth initialization function
78
104
*/
@@ -99,9 +125,21 @@ export async function connectToRemoteServer(
99
125
headers : Record < string , string > ,
100
126
authInitializer : AuthInitializer ,
101
127
transportStrategy : TransportStrategy = 'http-first' ,
128
+ localTransport : StdioServerTransportExt | null = null ,
102
129
recursionReasons : Set < string > = new Set ( ) ,
103
130
) : Promise < Transport > {
104
- log ( `[${ pid } ] Connecting to remote server: ${ serverUrl } ` )
131
+ const _log = ( level : LoggingLevel , message : any , status : ConnStatus ) => {
132
+ // If localTransport is provided (proxy mode), send the message to it
133
+ if ( localTransport ) {
134
+ localTransport . sendMessage ( level , {
135
+ status,
136
+ message,
137
+ } )
138
+ }
139
+ log ( message )
140
+ }
141
+
142
+ _log ( 'info' , `[${ pid } ] Connecting to remote server: ${ serverUrl } ` , 'connecting' )
105
143
const url = new URL ( serverUrl )
106
144
107
145
// Create transport with eventSourceInit to pass Authorization header if present
@@ -121,7 +159,7 @@ export async function connectToRemoteServer(
121
159
} ,
122
160
}
123
161
124
- log ( `Using transport strategy: ${ transportStrategy } ` )
162
+ _log ( 'info' , `Using transport strategy: ${ transportStrategy } ` , 'connecting' )
125
163
// Determine if we should attempt to fallback on error
126
164
// Choose transport based on user strategy and recursion history
127
165
const shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first'
@@ -154,7 +192,7 @@ export async function connectToRemoteServer(
154
192
await testClient . connect ( testTransport )
155
193
}
156
194
}
157
- log ( `Connected to remote server using ${ transport . constructor . name } ` )
195
+ _log ( 'info' , `Connected to remote server using ${ transport . constructor . name } ` , 'connected' )
158
196
159
197
return transport
160
198
} catch ( error ) {
@@ -167,16 +205,16 @@ export async function connectToRemoteServer(
167
205
error . message . includes ( '404' ) ||
168
206
error . message . includes ( 'Not Found' ) )
169
207
) {
170
- log ( `Received error: ${ error . message } ` )
208
+ _log ( 'error' , `Received error: ${ error . message } ` , 'error' )
171
209
172
210
// If we've already tried falling back once, throw an error
173
211
if ( recursionReasons . has ( REASON_TRANSPORT_FALLBACK ) ) {
174
212
const errorMessage = `Already attempted transport fallback. Giving up.`
175
- log ( errorMessage )
213
+ _log ( 'error' , errorMessage , 'error_final' )
176
214
throw new Error ( errorMessage )
177
215
}
178
216
179
- log ( `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` )
217
+ _log ( 'info' , `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` , 'reconnecting' )
180
218
181
219
// Add to recursion reasons set
182
220
recursionReasons . add ( REASON_TRANSPORT_FALLBACK )
@@ -189,45 +227,55 @@ export async function connectToRemoteServer(
189
227
headers ,
190
228
authInitializer ,
191
229
sseTransport ? 'http-only' : 'sse-only' ,
230
+ localTransport ,
192
231
recursionReasons ,
193
232
)
194
233
} else if ( error instanceof UnauthorizedError || ( error instanceof Error && error . message . includes ( 'Unauthorized' ) ) ) {
195
- log ( ' Authentication required. Initializing auth...')
234
+ _log ( 'info' , ' Authentication required. Initializing auth...' , 'authenticating ')
196
235
197
236
// Initialize authentication on-demand
198
237
const { waitForAuthCode, skipBrowserAuth } = await authInitializer ( )
199
238
200
239
if ( skipBrowserAuth ) {
201
- log ( ' Authentication required but skipping browser auth - using shared auth')
240
+ _log ( 'info' , ' Authentication required but skipping browser auth - using shared auth' , 'authenticating ')
202
241
} else {
203
- log ( ' Authentication required. Waiting for authorization...')
242
+ _log ( 'info' , ' Authentication required. Waiting for authorization...' , 'authenticating ')
204
243
}
205
244
206
245
// Wait for the authorization code from the callback
207
246
const code = await waitForAuthCode ( )
208
247
209
248
try {
210
- log ( ' Completing authorization...')
249
+ _log ( 'info' , ' Completing authorization...' , 'authenticating ')
211
250
await transport . finishAuth ( code )
212
251
213
252
if ( recursionReasons . has ( REASON_AUTH_NEEDED ) ) {
214
253
const errorMessage = `Already attempted reconnection for reason: ${ REASON_AUTH_NEEDED } . Giving up.`
215
- log ( errorMessage )
254
+ _log ( 'error' , errorMessage , 'error_final' )
216
255
throw new Error ( errorMessage )
217
256
}
218
257
219
258
// Track this reason for recursion
220
259
recursionReasons . add ( REASON_AUTH_NEEDED )
221
- log ( `Recursively reconnecting for reason: ${ REASON_AUTH_NEEDED } ` )
260
+ _log ( 'info' , `Recursively reconnecting for reason: ${ REASON_AUTH_NEEDED } ` , 'reconnecting' )
222
261
223
262
// Recursively call connectToRemoteServer with the updated recursion tracking
224
- return connectToRemoteServer ( client , serverUrl , authProvider , headers , authInitializer , transportStrategy , recursionReasons )
263
+ return connectToRemoteServer (
264
+ client ,
265
+ serverUrl ,
266
+ authProvider ,
267
+ headers ,
268
+ authInitializer ,
269
+ transportStrategy ,
270
+ localTransport ,
271
+ recursionReasons ,
272
+ )
225
273
} catch ( authError ) {
226
- log ( ' Authorization error:' , authError )
274
+ _log ( 'error' , ` Authorization error: ${ authError } ` , 'error_final' )
227
275
throw authError
228
276
}
229
277
} else {
230
- log ( ' Connection error:' , error )
278
+ _log ( 'error' , ` Connection error: ${ error } ` , 'error_final' )
231
279
throw error
232
280
}
233
281
}
0 commit comments