@@ -3,7 +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 type { MCPLogMessageParams } from './types'
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
7
+ import { LoggingLevel } from '@modelcontextprotocol/sdk/types.js'
8
+ import { ConnStatus } from './types'
7
9
8
10
// Connection constants
9
11
export const REASON_AUTH_NEEDED = 'authentication-needed'
@@ -74,6 +76,29 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
74
76
}
75
77
}
76
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 "default"
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
+
77
102
/**
78
103
* Type for the auth initialization function
79
104
*/
@@ -100,9 +125,21 @@ export async function connectToRemoteServer(
100
125
headers : Record < string , string > ,
101
126
authInitializer : AuthInitializer ,
102
127
transportStrategy : TransportStrategy = 'http-first' ,
128
+ localTransport : StdioServerTransportExt | null = null ,
103
129
recursionReasons : Set < string > = new Set ( ) ,
104
130
) : Promise < Transport > {
105
- 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' )
106
143
const url = new URL ( serverUrl )
107
144
108
145
// Create transport with eventSourceInit to pass Authorization header if present
@@ -122,7 +159,7 @@ export async function connectToRemoteServer(
122
159
} ,
123
160
}
124
161
125
- log ( `Using transport strategy: ${ transportStrategy } ` )
162
+ _log ( 'info' , `Using transport strategy: ${ transportStrategy } ` , 'connecting' )
126
163
// Determine if we should attempt to fallback on error
127
164
// Choose transport based on user strategy and recursion history
128
165
const shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first'
@@ -155,7 +192,7 @@ export async function connectToRemoteServer(
155
192
await testClient . connect ( testTransport )
156
193
}
157
194
}
158
- log ( `Connected to remote server using ${ transport . constructor . name } ` )
195
+ _log ( 'info' , `Connected to remote server using ${ transport . constructor . name } ` , 'connected' )
159
196
160
197
return transport
161
198
} catch ( error ) {
@@ -168,16 +205,16 @@ export async function connectToRemoteServer(
168
205
error . message . includes ( '404' ) ||
169
206
error . message . includes ( 'Not Found' ) )
170
207
) {
171
- log ( `Received error: ${ error . message } ` )
208
+ _log ( 'error' , `Received error: ${ error . message } ` , 'error' )
172
209
173
210
// If we've already tried falling back once, throw an error
174
211
if ( recursionReasons . has ( REASON_TRANSPORT_FALLBACK ) ) {
175
212
const errorMessage = `Already attempted transport fallback. Giving up.`
176
- log ( errorMessage )
213
+ _log ( 'error' , errorMessage , 'error_final' )
177
214
throw new Error ( errorMessage )
178
215
}
179
216
180
- log ( `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` )
217
+ _log ( 'info' , `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` , 'reconnecting' )
181
218
182
219
// Add to recursion reasons set
183
220
recursionReasons . add ( REASON_TRANSPORT_FALLBACK )
@@ -190,45 +227,55 @@ export async function connectToRemoteServer(
190
227
headers ,
191
228
authInitializer ,
192
229
sseTransport ? 'http-only' : 'sse-only' ,
230
+ localTransport ,
193
231
recursionReasons ,
194
232
)
195
233
} else if ( error instanceof UnauthorizedError || ( error instanceof Error && error . message . includes ( 'Unauthorized' ) ) ) {
196
- log ( ' Authentication required. Initializing auth...')
234
+ _log ( 'info' , ' Authentication required. Initializing auth...' , 'authenticating ')
197
235
198
236
// Initialize authentication on-demand
199
237
const { waitForAuthCode, skipBrowserAuth } = await authInitializer ( )
200
238
201
239
if ( skipBrowserAuth ) {
202
- log ( ' Authentication required but skipping browser auth - using shared auth')
240
+ _log ( 'info' , ' Authentication required but skipping browser auth - using shared auth' , 'authenticating ')
203
241
} else {
204
- log ( ' Authentication required. Waiting for authorization...')
242
+ _log ( 'info' , ' Authentication required. Waiting for authorization...' , 'authenticating ')
205
243
}
206
244
207
245
// Wait for the authorization code from the callback
208
246
const code = await waitForAuthCode ( )
209
247
210
248
try {
211
- log ( ' Completing authorization...')
249
+ _log ( 'info' , ' Completing authorization...' , 'authenticating ')
212
250
await transport . finishAuth ( code )
213
251
214
252
if ( recursionReasons . has ( REASON_AUTH_NEEDED ) ) {
215
253
const errorMessage = `Already attempted reconnection for reason: ${ REASON_AUTH_NEEDED } . Giving up.`
216
- log ( errorMessage )
254
+ _log ( 'error' , errorMessage , 'error_final' )
217
255
throw new Error ( errorMessage )
218
256
}
219
257
220
258
// Track this reason for recursion
221
259
recursionReasons . add ( REASON_AUTH_NEEDED )
222
- log ( `Recursively reconnecting for reason: ${ REASON_AUTH_NEEDED } ` )
260
+ _log ( 'info' , `Recursively reconnecting for reason: ${ REASON_AUTH_NEEDED } ` , 'reconnecting' )
223
261
224
262
// Recursively call connectToRemoteServer with the updated recursion tracking
225
- 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
+ )
226
273
} catch ( authError ) {
227
- log ( ' Authorization error:' , authError )
274
+ _log ( 'error' , ` Authorization error: ${ authError } ` , 'error_final' )
228
275
throw authError
229
276
}
230
277
} else {
231
- log ( ' Connection error:' , error )
278
+ _log ( 'error' , ` Connection error: ${ error } ` , 'error_final' )
232
279
throw error
233
280
}
234
281
}
@@ -488,19 +535,3 @@ export function setupSignalHandlers(cleanup: () => Promise<void>) {
488
535
export function getServerUrlHash ( serverUrl : string ) : string {
489
536
return crypto . createHash ( 'md5' ) . update ( serverUrl ) . digest ( 'hex' )
490
537
}
491
-
492
- /**
493
- * Helper function to send log messages through stdio MCP transport, for proxy logging purposes
494
- * @param transport The transport to send the message through
495
- * @param params Message parameters including level, logger name, and data
496
- *
497
- * In accordance with the official MCP specification
498
- * @see https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/logging#log-message-notifications
499
- */
500
- export function sendLog ( transport : Transport , params : MCPLogMessageParams ) {
501
- return transport . send ( {
502
- jsonrpc : '2.0' ,
503
- method : 'notifications/message' ,
504
- params : { ...params } ,
505
- } )
506
- }
0 commit comments