@@ -3,7 +3,9 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
33import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
44import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
55import { 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'
79
810// Connection constants
911export const REASON_AUTH_NEEDED = 'authentication-needed'
@@ -74,6 +76,29 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
7476 }
7577}
7678
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+
77102/**
78103 * Type for the auth initialization function
79104 */
@@ -100,9 +125,21 @@ export async function connectToRemoteServer(
100125 headers : Record < string , string > ,
101126 authInitializer : AuthInitializer ,
102127 transportStrategy : TransportStrategy = 'http-first' ,
128+ localTransport : StdioServerTransportExt | null = null ,
103129 recursionReasons : Set < string > = new Set ( ) ,
104130) : 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' )
106143 const url = new URL ( serverUrl )
107144
108145 // Create transport with eventSourceInit to pass Authorization header if present
@@ -122,7 +159,7 @@ export async function connectToRemoteServer(
122159 } ,
123160 }
124161
125- log ( `Using transport strategy: ${ transportStrategy } ` )
162+ _log ( 'info' , `Using transport strategy: ${ transportStrategy } ` , 'connecting' )
126163 // Determine if we should attempt to fallback on error
127164 // Choose transport based on user strategy and recursion history
128165 const shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first'
@@ -155,7 +192,7 @@ export async function connectToRemoteServer(
155192 await testClient . connect ( testTransport )
156193 }
157194 }
158- log ( `Connected to remote server using ${ transport . constructor . name } ` )
195+ _log ( 'info' , `Connected to remote server using ${ transport . constructor . name } ` , 'connected' )
159196
160197 return transport
161198 } catch ( error ) {
@@ -168,16 +205,16 @@ export async function connectToRemoteServer(
168205 error . message . includes ( '404' ) ||
169206 error . message . includes ( 'Not Found' ) )
170207 ) {
171- log ( `Received error: ${ error . message } ` )
208+ _log ( 'error' , `Received error: ${ error . message } ` , 'error' )
172209
173210 // If we've already tried falling back once, throw an error
174211 if ( recursionReasons . has ( REASON_TRANSPORT_FALLBACK ) ) {
175212 const errorMessage = `Already attempted transport fallback. Giving up.`
176- log ( errorMessage )
213+ _log ( 'error' , errorMessage , 'error_final' )
177214 throw new Error ( errorMessage )
178215 }
179216
180- log ( `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` )
217+ _log ( 'info' , `Recursively reconnecting for reason: ${ REASON_TRANSPORT_FALLBACK } ` , 'reconnecting' )
181218
182219 // Add to recursion reasons set
183220 recursionReasons . add ( REASON_TRANSPORT_FALLBACK )
@@ -190,45 +227,55 @@ export async function connectToRemoteServer(
190227 headers ,
191228 authInitializer ,
192229 sseTransport ? 'http-only' : 'sse-only' ,
230+ localTransport ,
193231 recursionReasons ,
194232 )
195233 } 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 ')
197235
198236 // Initialize authentication on-demand
199237 const { waitForAuthCode, skipBrowserAuth } = await authInitializer ( )
200238
201239 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 ')
203241 } else {
204- log ( ' Authentication required. Waiting for authorization...')
242+ _log ( 'info' , ' Authentication required. Waiting for authorization...' , 'authenticating ')
205243 }
206244
207245 // Wait for the authorization code from the callback
208246 const code = await waitForAuthCode ( )
209247
210248 try {
211- log ( ' Completing authorization...')
249+ _log ( 'info' , ' Completing authorization...' , 'authenticating ')
212250 await transport . finishAuth ( code )
213251
214252 if ( recursionReasons . has ( REASON_AUTH_NEEDED ) ) {
215253 const errorMessage = `Already attempted reconnection for reason: ${ REASON_AUTH_NEEDED } . Giving up.`
216- log ( errorMessage )
254+ _log ( 'error' , errorMessage , 'error_final' )
217255 throw new Error ( errorMessage )
218256 }
219257
220258 // Track this reason for recursion
221259 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' )
223261
224262 // 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+ )
226273 } catch ( authError ) {
227- log ( ' Authorization error:' , authError )
274+ _log ( 'error' , ` Authorization error: ${ authError } ` , 'error_final' )
228275 throw authError
229276 }
230277 } else {
231- log ( ' Connection error:' , error )
278+ _log ( 'error' , ` Connection error: ${ error } ` , 'error_final' )
232279 throw error
233280 }
234281 }
@@ -488,19 +535,3 @@ export function setupSignalHandlers(cleanup: () => Promise<void>) {
488535export function getServerUrlHash ( serverUrl : string ) : string {
489536 return crypto . createHash ( 'md5' ) . update ( serverUrl ) . digest ( 'hex' )
490537}
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