1
1
import { createServer , type Server , IncomingMessage , ServerResponse } from "node:http" ;
2
- import { AddressInfo } from "node:net" ;
2
+ import { createServer as netCreateServer , AddressInfo } from "node:net" ;
3
3
import { randomUUID } from "node:crypto" ;
4
4
import { EventStore , StreamableHTTPServerTransport , EventId , StreamId } from "./streamableHttp.js" ;
5
5
import { McpServer } from "./mcp.js" ;
6
6
import { CallToolResult , JSONRPCMessage } from "../types.js" ;
7
7
import { z } from "zod" ;
8
8
import { AuthInfo } from "./auth/types.js" ;
9
9
10
+ async function getFreePort ( ) {
11
+ return new Promise ( res => {
12
+ const srv = netCreateServer ( ) ;
13
+ srv . listen ( 0 , ( ) => {
14
+ const address = srv . address ( ) !
15
+ if ( typeof address === "string" ) {
16
+ throw new Error ( "Unexpected address type: " + typeof address ) ;
17
+ }
18
+ const port = ( address as AddressInfo ) . port ;
19
+ srv . close ( ( err ) => res ( port ) )
20
+ } ) ;
21
+ } )
22
+ }
23
+
10
24
/**
11
25
* Test server configuration for StreamableHTTPServerTransport tests
12
26
*/
@@ -1441,7 +1455,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => {
1441
1455
it ( "should accept requests with allowed host headers" , async ( ) => {
1442
1456
const result = await createTestServerWithDnsProtection ( {
1443
1457
sessionIdGenerator : undefined ,
1444
- allowedHosts : [ 'localhost:3001 ' ] ,
1458
+ allowedHosts : [ 'localhost' ] ,
1445
1459
enableDnsRebindingProtection : true ,
1446
1460
} ) ;
1447
1461
server = result . server ;
@@ -1563,7 +1577,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => {
1563
1577
it ( "should skip all validations when enableDnsRebindingProtection is false" , async ( ) => {
1564
1578
const result = await createTestServerWithDnsProtection ( {
1565
1579
sessionIdGenerator : undefined ,
1566
- allowedHosts : [ 'localhost:3001 ' ] ,
1580
+ allowedHosts : [ 'localhost' ] ,
1567
1581
allowedOrigins : [ 'http://localhost:3000' ] ,
1568
1582
enableDnsRebindingProtection : false ,
1569
1583
} ) ;
@@ -1591,7 +1605,7 @@ describe("StreamableHTTPServerTransport DNS rebinding protection", () => {
1591
1605
it ( "should validate both host and origin when both are configured" , async ( ) => {
1592
1606
const result = await createTestServerWithDnsProtection ( {
1593
1607
sessionIdGenerator : undefined ,
1594
- allowedHosts : [ 'localhost:3001 ' ] ,
1608
+ allowedHosts : [ 'localhost' ] ,
1595
1609
allowedOrigins : [ 'http://localhost:3001' ] ,
1596
1610
enableDnsRebindingProtection : true ,
1597
1611
} ) ;
@@ -1649,6 +1663,17 @@ async function createTestServerWithDnsProtection(config: {
1649
1663
{ capabilities : { logging : { } } }
1650
1664
) ;
1651
1665
1666
+ const port = await getFreePort ( ) ;
1667
+
1668
+ if ( config . allowedHosts ) {
1669
+ config . allowedHosts = config . allowedHosts . map ( host => {
1670
+ if ( host . includes ( ':' ) ) {
1671
+ return host ;
1672
+ }
1673
+ return `localhost:${ port } ` ;
1674
+ } ) ;
1675
+ }
1676
+
1652
1677
const transport = new StreamableHTTPServerTransport ( {
1653
1678
sessionIdGenerator : config . sessionIdGenerator ,
1654
1679
allowedHosts : config . allowedHosts ,
@@ -1672,10 +1697,9 @@ async function createTestServerWithDnsProtection(config: {
1672
1697
} ) ;
1673
1698
1674
1699
await new Promise < void > ( ( resolve ) => {
1675
- httpServer . listen ( 3001 , ( ) => resolve ( ) ) ;
1700
+ httpServer . listen ( port , ( ) => resolve ( ) ) ;
1676
1701
} ) ;
1677
1702
1678
- const port = ( httpServer . address ( ) as AddressInfo ) . port ;
1679
1703
const serverUrl = new URL ( `http://localhost:${ port } /` ) ;
1680
1704
1681
1705
return {
0 commit comments