@@ -5,6 +5,7 @@ import { EventStore, StreamableHTTPServerTransport, EventId, StreamId } from "./
55import { McpServer } from "./mcp.js" ;
66import { CallToolResult , JSONRPCMessage } from "../types.js" ;
77import { z } from "zod" ;
8+ import { AuthInfo } from "./auth/types.js" ;
89
910/**
1011 * Test server configuration for StreamableHTTPServerTransport tests
@@ -70,6 +71,61 @@ async function createTestServer(config: TestServerConfig = { sessionIdGenerator:
7071 return { server, transport, mcpServer, baseUrl } ;
7172}
7273
74+ /**
75+ * Helper to create and start authenticated test HTTP server with MCP setup
76+ */
77+ async function createTestAuthServer ( config : TestServerConfig = { sessionIdGenerator : ( ( ) => randomUUID ( ) ) } ) : Promise < {
78+ server : Server ;
79+ transport : StreamableHTTPServerTransport ;
80+ mcpServer : McpServer ;
81+ baseUrl : URL ;
82+ } > {
83+ const mcpServer = new McpServer (
84+ { name : "test-server" , version : "1.0.0" } ,
85+ { capabilities : { logging : { } } }
86+ ) ;
87+
88+ mcpServer . tool (
89+ "profile" ,
90+ "A user profile data tool" ,
91+ { active : z . boolean ( ) . describe ( "Profile status" ) } ,
92+ async ( { active } , { authInfo } ) : Promise < CallToolResult > => {
93+ return { content : [ { type : "text" , text : `${ active ? 'Active' : 'Inactive' } profile from token: ${ authInfo ?. token } !` } ] } ;
94+ }
95+ ) ;
96+
97+ const transport = new StreamableHTTPServerTransport ( {
98+ sessionIdGenerator : config . sessionIdGenerator ,
99+ enableJsonResponse : config . enableJsonResponse ?? false ,
100+ eventStore : config . eventStore
101+ } ) ;
102+
103+ await mcpServer . connect ( transport ) ;
104+
105+ const server = createServer ( async ( req : IncomingMessage & { auth ?: AuthInfo } , res ) => {
106+ try {
107+ if ( config . customRequestHandler ) {
108+ await config . customRequestHandler ( req , res ) ;
109+ } else {
110+ req . auth = { token : req . headers [ "authorization" ] ?. split ( " " ) [ 1 ] } as AuthInfo ;
111+ await transport . handleRequest ( req , res ) ;
112+ }
113+ } catch ( error ) {
114+ console . error ( "Error handling request:" , error ) ;
115+ if ( ! res . headersSent ) res . writeHead ( 500 ) . end ( ) ;
116+ }
117+ } ) ;
118+
119+ const baseUrl = await new Promise < URL > ( ( resolve ) => {
120+ server . listen ( 0 , "127.0.0.1" , ( ) => {
121+ const addr = server . address ( ) as AddressInfo ;
122+ resolve ( new URL ( `http://127.0.0.1:${ addr . port } ` ) ) ;
123+ } ) ;
124+ } ) ;
125+
126+ return { server, transport, mcpServer, baseUrl } ;
127+ }
128+
73129/**
74130 * Helper to stop test server
75131 */
@@ -120,10 +176,11 @@ async function readSSEEvent(response: Response): Promise<string> {
120176/**
121177 * Helper to send JSON-RPC request
122178 */
123- async function sendPostRequest ( baseUrl : URL , message : JSONRPCMessage | JSONRPCMessage [ ] , sessionId ?: string ) : Promise < Response > {
179+ async function sendPostRequest ( baseUrl : URL , message : JSONRPCMessage | JSONRPCMessage [ ] , sessionId ?: string , extraHeaders ?: Record < string , string > ) : Promise < Response > {
124180 const headers : Record < string , string > = {
125181 "Content-Type" : "application/json" ,
126182 Accept : "application/json, text/event-stream" ,
183+ ...extraHeaders
127184 } ;
128185
129186 if ( sessionId ) {
@@ -673,6 +730,105 @@ describe("StreamableHTTPServerTransport", () => {
673730 } ) ;
674731} ) ;
675732
733+ describe ( "StreamableHTTPServerTransport with AuthInfo" , ( ) => {
734+ let server : Server ;
735+ let transport : StreamableHTTPServerTransport ;
736+ let baseUrl : URL ;
737+ let sessionId : string ;
738+
739+ beforeEach ( async ( ) => {
740+ const result = await createTestAuthServer ( ) ;
741+ server = result . server ;
742+ transport = result . transport ;
743+ baseUrl = result . baseUrl ;
744+ } ) ;
745+
746+ afterEach ( async ( ) => {
747+ await stopTestServer ( { server, transport } ) ;
748+ } ) ;
749+
750+ async function initializeServer ( ) : Promise < string > {
751+ const response = await sendPostRequest ( baseUrl , TEST_MESSAGES . initialize ) ;
752+
753+ expect ( response . status ) . toBe ( 200 ) ;
754+ const newSessionId = response . headers . get ( "mcp-session-id" ) ;
755+ expect ( newSessionId ) . toBeDefined ( ) ;
756+ return newSessionId as string ;
757+ }
758+
759+ it ( "should call a tool with authInfo" , async ( ) => {
760+ sessionId = await initializeServer ( ) ;
761+
762+ const toolCallMessage : JSONRPCMessage = {
763+ jsonrpc : "2.0" ,
764+ method : "tools/call" ,
765+ params : {
766+ name : "profile" ,
767+ arguments : { active : true } ,
768+ } ,
769+ id : "call-1" ,
770+ } ;
771+
772+ const response = await sendPostRequest ( baseUrl , toolCallMessage , sessionId , { 'authorization' : 'Bearer test-token' } ) ;
773+ expect ( response . status ) . toBe ( 200 ) ;
774+
775+ const text = await readSSEEvent ( response ) ;
776+ const eventLines = text . split ( "\n" ) ;
777+ const dataLine = eventLines . find ( line => line . startsWith ( "data:" ) ) ;
778+ expect ( dataLine ) . toBeDefined ( ) ;
779+
780+ const eventData = JSON . parse ( dataLine ! . substring ( 5 ) ) ;
781+ expect ( eventData ) . toMatchObject ( {
782+ jsonrpc : "2.0" ,
783+ result : {
784+ content : [
785+ {
786+ type : "text" ,
787+ text : "Active profile from token: test-token!" ,
788+ } ,
789+ ] ,
790+ } ,
791+ id : "call-1" ,
792+ } ) ;
793+ } ) ;
794+
795+ it ( "should calls tool without authInfo when it is optional" , async ( ) => {
796+ sessionId = await initializeServer ( ) ;
797+
798+ const toolCallMessage : JSONRPCMessage = {
799+ jsonrpc : "2.0" ,
800+ method : "tools/call" ,
801+ params : {
802+ name : "profile" ,
803+ arguments : { active : false } ,
804+ } ,
805+ id : "call-1" ,
806+ } ;
807+
808+ const response = await sendPostRequest ( baseUrl , toolCallMessage , sessionId ) ;
809+ expect ( response . status ) . toBe ( 200 ) ;
810+
811+ const text = await readSSEEvent ( response ) ;
812+ const eventLines = text . split ( "\n" ) ;
813+ const dataLine = eventLines . find ( line => line . startsWith ( "data:" ) ) ;
814+ expect ( dataLine ) . toBeDefined ( ) ;
815+
816+ const eventData = JSON . parse ( dataLine ! . substring ( 5 ) ) ;
817+ expect ( eventData ) . toMatchObject ( {
818+ jsonrpc : "2.0" ,
819+ result : {
820+ content : [
821+ {
822+ type : "text" ,
823+ text : "Inactive profile from token: undefined!" ,
824+ } ,
825+ ] ,
826+ } ,
827+ id : "call-1" ,
828+ } ) ;
829+ } ) ;
830+ } ) ;
831+
676832// Test JSON Response Mode
677833describe ( "StreamableHTTPServerTransport with JSON Response Mode" , ( ) => {
678834 let server : Server ;
0 commit comments