11#!/usr/bin/env node
22
33import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
4- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
54import { createRequire } from "module" ;
65const require = createRequire ( import . meta. url ) ;
76const packageJson = require ( "../package.json" ) ;
87import "dotenv/config" ;
9- import http from "http" ;
10- import { randomUUID } from "crypto" ;
118import logger from "./logger.js" ;
129import { BrowserStackMcpServer } from "./server-factory.js" ;
1310
@@ -18,6 +15,11 @@ async function main() {
1815 ) ;
1916
2017 const remoteMCP = process . env . REMOTE_MCP === "true" ;
18+ if ( remoteMCP ) {
19+ logger . info ( "Running in remote MCP mode" ) ;
20+ return ;
21+ }
22+
2123 const username = process . env . BROWSERSTACK_USERNAME ;
2224 const accessKey = process . env . BROWSERSTACK_ACCESS_KEY ;
2325
@@ -29,136 +31,14 @@ async function main() {
2931 throw new Error ( "BROWSERSTACK_ACCESS_KEY environment variable is required" ) ;
3032 }
3133
34+ const transport = new StdioServerTransport ( ) ;
35+
3236 const mcpServer = new BrowserStackMcpServer ( {
3337 "browserstack-username" : username ,
3438 "browserstack-access-key" : accessKey ,
3539 } ) ;
3640
37- if ( remoteMCP ) {
38- // ββ HTTP Transport (Remote MCP) ββββββββββββββββββββββββββββββββββββββ
39- const port = parseInt ( process . env . MCP_PORT || "3100" , 10 ) ;
40-
41- // Create a new transport for each session
42- const transports = new Map < string , StreamableHTTPServerTransport > ( ) ;
43-
44- const httpServer = http . createServer ( async ( req , res ) => {
45- // CORS headers for browser clients
46- res . setHeader ( "Access-Control-Allow-Origin" , "*" ) ;
47- res . setHeader (
48- "Access-Control-Allow-Methods" ,
49- "GET, POST, DELETE, OPTIONS" ,
50- ) ;
51- res . setHeader (
52- "Access-Control-Allow-Headers" ,
53- "Content-Type, mcp-session-id" ,
54- ) ;
55- res . setHeader ( "Access-Control-Expose-Headers" , "mcp-session-id" ) ;
56-
57- if ( req . method === "OPTIONS" ) {
58- res . writeHead ( 204 ) ;
59- res . end ( ) ;
60- return ;
61- }
62-
63- const url = new URL ( req . url || "/" , `http://localhost:${ port } ` ) ;
64-
65- // Health check
66- if ( url . pathname === "/health" || url . pathname === "/healthz" ) {
67- res . writeHead ( 200 , { "Content-Type" : "application/json" } ) ;
68- res . end ( JSON . stringify ( { status : "ok" , version : packageJson . version } ) ) ;
69- return ;
70- }
71-
72- // MCP endpoint
73- if ( url . pathname === "/mcp" ) {
74- // Check for existing session
75- const sessionId = req . headers [ "mcp-session-id" ] as string | undefined ;
76-
77- if ( sessionId && transports . has ( sessionId ) ) {
78- // Existing session β reuse transport
79- const transport = transports . get ( sessionId ) ! ;
80- await transport . handleRequest ( req , res ) ;
81- return ;
82- }
83-
84- if ( req . method === "POST" && ! sessionId ) {
85- // New session β create transport and connect server
86- const transport = new StreamableHTTPServerTransport ( {
87- sessionIdGenerator : ( ) => randomUUID ( ) ,
88- } ) ;
89-
90- transport . onclose = ( ) => {
91- if ( transport . sessionId ) {
92- transports . delete ( transport . sessionId ) ;
93- logger . info ( "Session closed: %s" , transport . sessionId ) ;
94- }
95- } ;
96-
97- // Connect to a NEW server instance for this session
98- const sessionServer = new BrowserStackMcpServer ( {
99- "browserstack-username" : username ! ,
100- "browserstack-access-key" : accessKey ! ,
101- } ) ;
102-
103- await sessionServer . getInstance ( ) . connect ( transport ) ;
104-
105- if ( transport . sessionId ) {
106- transports . set ( transport . sessionId , transport ) ;
107- logger . info ( "New session: %s" , transport . sessionId ) ;
108- }
109-
110- await transport . handleRequest ( req , res ) ;
111- return ;
112- }
113-
114- // Invalid request
115- res . writeHead ( 400 , { "Content-Type" : "application/json" } ) ;
116- res . end (
117- JSON . stringify ( {
118- error : "Invalid request. POST to /mcp to start a session." ,
119- } ) ,
120- ) ;
121- return ;
122- }
123-
124- // Root β info page
125- if ( url . pathname === "/" ) {
126- res . writeHead ( 200 , { "Content-Type" : "application/json" } ) ;
127- res . end (
128- JSON . stringify ( {
129- name : "BrowserStack MCP Server" ,
130- version : packageJson . version ,
131- endpoint : `http://localhost:${ port } /mcp` ,
132- health : `http://localhost:${ port } /health` ,
133- instructions : "Connect your MCP client to the /mcp endpoint." ,
134- } ) ,
135- ) ;
136- return ;
137- }
138-
139- res . writeHead ( 404 ) ;
140- res . end ( "Not found" ) ;
141- } ) ;
142-
143- httpServer . listen ( port , ( ) => {
144- logger . info ( "Remote MCP server running at http://localhost:%d/mcp" , port ) ;
145- console . log ( `\nπ BrowserStack MCP Server (Remote Mode)` ) ;
146- console . log ( ` Version: ${ packageJson . version } ` ) ;
147- console . log ( ` Endpoint: http://localhost:${ port } /mcp` ) ;
148- console . log ( ` Health: http://localhost:${ port } /health` ) ;
149- console . log (
150- `\n Connect from any MCP client using the endpoint URL above.` ,
151- ) ;
152- console . log (
153- ` In VS Code: Add MCP Server β HTTP β http://localhost:${ port } /mcp` ,
154- ) ;
155- console . log ( ` In Claude Code: /mcp add http://localhost:${ port } /mcp\n` ) ;
156- } ) ;
157- } else {
158- // ββ Stdio Transport (Local MCP β default) ββββββββββββββββββββββββββββ
159- const transport = new StdioServerTransport ( ) ;
160- await mcpServer . getInstance ( ) . connect ( transport ) ;
161- }
41+ await mcpServer . getInstance ( ) . connect ( transport ) ;
16242}
16343
16444main ( ) . catch ( console . error ) ;
0 commit comments