@@ -5,11 +5,9 @@ import { JSONRPCMessage, isInitializeRequest } from '@modelcontextprotocol/sdk/t
55import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' ;
66import { HttpStreamTransportConfig } from './types.js' ;
77import { logger } from '../../core/Logger.js' ;
8- import { APIKeyAuthProvider } from '../../auth/providers/apikey.js' ;
9- import { DEFAULT_AUTH_ERROR } from '../../auth/types.js' ;
10- import { getRequestHeader } from '../../utils/headers.js' ;
11- import { OAuthAuthProvider } from '../../auth/providers/oauth.js' ;
128import { ProtectedResourceMetadata } from '../../auth/metadata/protected-resource.js' ;
9+ import { handleAuthentication } from '../utils/auth-handler.js' ;
10+ import { initializeOAuthMetadata } from '../utils/oauth-metadata.js' ;
1311
1412export class HttpStreamTransport extends AbstractTransport {
1513 readonly type = 'http-stream' ;
@@ -31,14 +29,8 @@ export class HttpStreamTransport extends AbstractTransport {
3129 this . _endpoint = config . endpoint || '/mcp' ;
3230 this . _enableJsonResponse = config . responseMode === 'batch' ;
3331
34- if ( this . _config . auth ?. provider instanceof OAuthAuthProvider ) {
35- const oauthProvider = this . _config . auth . provider as OAuthAuthProvider ;
36- this . _oauthMetadata = new ProtectedResourceMetadata ( {
37- authorizationServers : ( oauthProvider as any ) . config . authorizationServers ,
38- resource : ( oauthProvider as any ) . config . resource ,
39- } ) ;
40- logger . debug ( 'OAuth metadata endpoint enabled for HTTP Stream transport' ) ;
41- }
32+ // Initialize OAuth metadata if OAuth provider is configured
33+ this . _oauthMetadata = initializeOAuthMetadata ( this . _config . auth , 'HTTP Stream' ) ;
4234
4335 logger . debug (
4436 `HttpStreamTransport configured with: ${ JSON . stringify ( {
@@ -114,84 +106,73 @@ export class HttpStreamTransport extends AbstractTransport {
114106 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
115107 let transport : StreamableHTTPServerTransport ;
116108
117- if ( sessionId && this . _transports [ sessionId ] ) {
118- if ( this . _config . auth ?. endpoints ?. messages !== false ) {
119- const isAuthenticated = await this . handleAuthentication ( req , res , 'message' ) ;
120- if ( ! isAuthenticated ) return ;
121- }
109+ // Determine if this is an initialize request (needs body parsing)
110+ const body = req . method === 'POST' ? await this . readRequestBody ( req ) : null ;
111+ const isInitialize = ! sessionId && body && isInitializeRequest ( body ) ;
112+
113+ // Perform authentication check once at the beginning
114+ const authEndpoint = isInitialize ? 'sse' : 'messages' ;
115+ if ( this . _config . auth ?. endpoints ?. [ authEndpoint ] !== false ) {
116+ const isAuthenticated = await handleAuthentication (
117+ req ,
118+ res ,
119+ this . _config . auth ,
120+ isInitialize ? 'initialize' : 'message'
121+ ) ;
122+ if ( ! isAuthenticated ) return ;
123+ }
122124
125+ // Handle different request scenarios
126+ if ( sessionId && this . _transports [ sessionId ] ) {
127+ // Existing session
123128 transport = this . _transports [ sessionId ] ;
124129 logger . debug ( `Reusing existing session: ${ sessionId } ` ) ;
125- } else if ( ! sessionId && req . method === 'POST' ) {
126- const body = await this . readRequestBody ( req ) ;
130+ } else if ( isInitialize ) {
131+ // New session initialization
132+ logger . info ( 'Creating new session for initialization request' ) ;
133+
134+ transport = new StreamableHTTPServerTransport ( {
135+ sessionIdGenerator : ( ) => randomUUID ( ) ,
136+ onsessioninitialized : ( sessionId : string ) => {
137+ logger . info ( `Session initialized: ${ sessionId } ` ) ;
138+ this . _transports [ sessionId ] = transport ;
139+ } ,
140+ enableJsonResponse : this . _enableJsonResponse ,
141+ } ) ;
127142
128- if ( isInitializeRequest ( body ) ) {
129- if ( this . _config . auth ?. endpoints ?. sse ) {
130- const isAuthenticated = await this . handleAuthentication ( req , res , 'initialize' ) ;
131- if ( ! isAuthenticated ) return ;
143+ transport . onclose = ( ) => {
144+ if ( transport . sessionId ) {
145+ logger . info ( `Transport closed for session: ${ transport . sessionId } ` ) ;
146+ delete this . _transports [ transport . sessionId ] ;
132147 }
148+ } ;
133149
134- logger . info ( 'Creating new session for initialization request' ) ;
135-
136- transport = new StreamableHTTPServerTransport ( {
137- sessionIdGenerator : ( ) => randomUUID ( ) ,
138- onsessioninitialized : ( sessionId : string ) => {
139- logger . info ( `Session initialized: ${ sessionId } ` ) ;
140- this . _transports [ sessionId ] = transport ;
141- } ,
142- enableJsonResponse : this . _enableJsonResponse ,
143- } ) ;
144-
145- transport . onclose = ( ) => {
146- if ( transport . sessionId ) {
147- logger . info ( `Transport closed for session: ${ transport . sessionId } ` ) ;
148- delete this . _transports [ transport . sessionId ] ;
149- }
150- } ;
151-
152- transport . onerror = ( error ) => {
153- logger . error ( `Transport error for session: ${ error } ` ) ;
154- if ( transport . sessionId ) {
155- delete this . _transports [ transport . sessionId ] ;
156- }
157- } ;
150+ transport . onerror = ( error ) => {
151+ logger . error ( `Transport error for session: ${ error } ` ) ;
152+ if ( transport . sessionId ) {
153+ delete this . _transports [ transport . sessionId ] ;
154+ }
155+ } ;
158156
159- transport . onmessage = async ( message : JSONRPCMessage ) => {
160- if ( this . _onmessage ) {
161- await this . _onmessage ( message ) ;
162- }
163- } ;
164-
165- await transport . handleRequest ( req , res , body ) ;
166- return ;
167- } else {
168- if ( this . _config . auth ?. endpoints ?. messages !== false ) {
169- const isAuthenticated = await this . handleAuthentication ( req , res , 'message' ) ;
170- if ( ! isAuthenticated ) return ;
157+ transport . onmessage = async ( message : JSONRPCMessage ) => {
158+ if ( this . _onmessage ) {
159+ await this . _onmessage ( message ) ;
171160 }
161+ } ;
172162
173- this . sendError ( res , 400 , - 32000 , 'Bad Request: No valid session ID provided' ) ;
174- return ;
175- }
163+ await transport . handleRequest ( req , res , body ) ;
164+ return ;
176165 } else if ( ! sessionId ) {
177- if ( this . _config . auth ?. endpoints ?. messages !== false ) {
178- const isAuthenticated = await this . handleAuthentication ( req , res , 'message' ) ;
179- if ( ! isAuthenticated ) return ;
180- }
181-
166+ // No session ID and not an initialize request
182167 this . sendError ( res , 400 , - 32000 , 'Bad Request: No valid session ID provided' ) ;
183168 return ;
184169 } else {
185- if ( this . _config . auth ?. endpoints ?. messages !== false ) {
186- const isAuthenticated = await this . handleAuthentication ( req , res , 'message' ) ;
187- if ( ! isAuthenticated ) return ;
188- }
189-
170+ // Session ID provided but not found
190171 this . sendError ( res , 404 , - 32001 , 'Session not found' ) ;
191172 return ;
192173 }
193174
194- const body = await this . readRequestBody ( req ) ;
175+ // Existing session - handle request
195176 await transport . handleRequest ( req , res , body ) ;
196177 }
197178
@@ -228,61 +209,6 @@ export class HttpStreamTransport extends AbstractTransport {
228209 ) ;
229210 }
230211
231- private async handleAuthentication ( req : IncomingMessage , res : ServerResponse , context : string ) : Promise < boolean > {
232- if ( ! this . _config . auth ?. provider ) {
233- return true ;
234- }
235-
236- const isApiKey = this . _config . auth . provider instanceof APIKeyAuthProvider ;
237- if ( isApiKey ) {
238- const provider = this . _config . auth . provider as APIKeyAuthProvider ;
239- const headerValue = getRequestHeader ( req . headers , provider . getHeaderName ( ) ) ;
240-
241- if ( ! headerValue ) {
242- const error = provider . getAuthError ?.( ) || DEFAULT_AUTH_ERROR ;
243- res . setHeader ( 'WWW-Authenticate' , `ApiKey realm="MCP Server", header="${ provider . getHeaderName ( ) } "` ) ;
244- res . writeHead ( error . status ) . end (
245- JSON . stringify ( {
246- error : error . message ,
247- status : error . status ,
248- type : 'authentication_error' ,
249- } )
250- ) ;
251- return false ;
252- }
253- }
254-
255- const authResult = await this . _config . auth . provider . authenticate ( req ) ;
256- if ( ! authResult ) {
257- const error = this . _config . auth . provider . getAuthError ?.( ) || DEFAULT_AUTH_ERROR ;
258- logger . warn ( `Authentication failed for ${ context } :` ) ;
259- logger . warn ( `- Client IP: ${ req . socket . remoteAddress } ` ) ;
260- logger . warn ( `- Error: ${ error . message } ` ) ;
261-
262- if ( isApiKey ) {
263- const provider = this . _config . auth . provider as APIKeyAuthProvider ;
264- res . setHeader ( 'WWW-Authenticate' , `ApiKey realm="MCP Server", header="${ provider . getHeaderName ( ) } "` ) ;
265- } else if ( this . _config . auth . provider instanceof OAuthAuthProvider ) {
266- const provider = this . _config . auth . provider as OAuthAuthProvider ;
267- res . setHeader ( 'WWW-Authenticate' , provider . getWWWAuthenticateHeader ( 'invalid_token' , 'Missing or invalid authentication token' ) ) ;
268- }
269-
270- res . writeHead ( error . status ) . end (
271- JSON . stringify ( {
272- error : error . message ,
273- status : error . status ,
274- type : 'authentication_error' ,
275- } )
276- ) ;
277- return false ;
278- }
279-
280- logger . info ( `Authentication successful for ${ context } :` ) ;
281- logger . info ( `- Client IP: ${ req . socket . remoteAddress } ` ) ;
282- logger . info ( `- Auth Type: ${ this . _config . auth . provider . constructor . name } ` ) ;
283- return true ;
284- }
285-
286212 async send ( message : JSONRPCMessage ) : Promise < void > {
287213 if ( ! this . _isRunning ) {
288214 logger . warn ( 'Attempted to send message, but HTTP transport is not running' ) ;
0 commit comments