22
33import open from "open" ;
44import { resolve , dirname } from "path" ;
5- import { spawnPromise } from "spawn-rx" ;
5+ import { spawnPromise , spawn } from "spawn-rx" ;
66import { fileURLToPath } from "url" ;
7+ import { randomBytes } from "crypto" ;
78
89const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
910
1011function delay ( ms ) {
1112 return new Promise ( ( resolve ) => setTimeout ( resolve , ms , true ) ) ;
1213}
1314
15+ async function startDevServer ( serverOptions ) {
16+ const { SERVER_PORT , CLIENT_PORT , sessionToken, envVars, abort } =
17+ serverOptions ;
18+ const serverCommand = "npx" ;
19+ const serverArgs = [ "tsx" , "watch" , "--clear-screen=false" , "src/index.ts" ] ;
20+ const isWindows = process . platform === "win32" ;
21+
22+ const spawnOptions = {
23+ cwd : resolve ( __dirname , "../.." , "server" ) ,
24+ env : {
25+ ...process . env ,
26+ PORT : SERVER_PORT ,
27+ CLIENT_PORT : CLIENT_PORT ,
28+ MCP_PROXY_TOKEN : sessionToken ,
29+ MCP_ENV_VARS : JSON . stringify ( envVars ) ,
30+ } ,
31+ signal : abort . signal ,
32+ echoOutput : true ,
33+ } ;
34+
35+ // For Windows, we need to use stdin: 'ignore' to simulate < NUL
36+ if ( isWindows ) {
37+ spawnOptions . stdin = "ignore" ;
38+ }
39+
40+ const server = spawn ( serverCommand , serverArgs , spawnOptions ) ;
41+
42+ // Give server time to start
43+ const serverOk = await Promise . race ( [
44+ new Promise ( ( resolve ) => {
45+ server . subscribe ( {
46+ complete : ( ) => resolve ( false ) ,
47+ error : ( ) => resolve ( false ) ,
48+ next : ( ) => { } , // We're using echoOutput
49+ } ) ;
50+ } ) ,
51+ delay ( 3000 ) . then ( ( ) => true ) ,
52+ ] ) ;
53+
54+ return { server, serverOk } ;
55+ }
56+
57+ async function startProdServer ( serverOptions ) {
58+ const {
59+ SERVER_PORT ,
60+ CLIENT_PORT ,
61+ sessionToken,
62+ envVars,
63+ abort,
64+ command,
65+ mcpServerArgs,
66+ } = serverOptions ;
67+ const inspectorServerPath = resolve (
68+ __dirname ,
69+ "../.." ,
70+ "server" ,
71+ "build" ,
72+ "index.js" ,
73+ ) ;
74+
75+ const server = spawnPromise (
76+ "node" ,
77+ [
78+ inspectorServerPath ,
79+ ...( command ? [ `--env` , command ] : [ ] ) ,
80+ ...( mcpServerArgs ? [ `--args=${ mcpServerArgs . join ( " " ) } ` ] : [ ] ) ,
81+ ] ,
82+ {
83+ env : {
84+ ...process . env ,
85+ PORT : SERVER_PORT ,
86+ CLIENT_PORT : CLIENT_PORT ,
87+ MCP_PROXY_TOKEN : sessionToken ,
88+ MCP_ENV_VARS : JSON . stringify ( envVars ) ,
89+ } ,
90+ signal : abort . signal ,
91+ echoOutput : true ,
92+ } ,
93+ ) ;
94+
95+ // Make sure server started before starting client
96+ const serverOk = await Promise . race ( [ server , delay ( 2 * 1000 ) ] ) ;
97+
98+ return { server, serverOk } ;
99+ }
100+
101+ async function startDevClient ( clientOptions ) {
102+ const { CLIENT_PORT , authDisabled, sessionToken, abort, cancelled } =
103+ clientOptions ;
104+ const clientCommand = "npx" ;
105+ const clientArgs = [ "vite" , "--port" , CLIENT_PORT ] ;
106+
107+ const client = spawn ( clientCommand , clientArgs , {
108+ cwd : resolve ( __dirname , ".." ) ,
109+ env : { ...process . env , PORT : CLIENT_PORT } ,
110+ signal : abort . signal ,
111+ echoOutput : true ,
112+ } ) ;
113+
114+ // Auto-open browser after vite starts
115+ if ( process . env . MCP_AUTO_OPEN_ENABLED !== "false" ) {
116+ const url = authDisabled
117+ ? `http://127.0.0.1:${ CLIENT_PORT } `
118+ : `http://127.0.0.1:${ CLIENT_PORT } /?MCP_PROXY_AUTH_TOKEN=${ sessionToken } ` ;
119+
120+ // Give vite time to start before opening browser
121+ setTimeout ( ( ) => {
122+ open ( url ) ;
123+ console . log ( `\n🔗 Opening browser at: ${ url } \n` ) ;
124+ } , 3000 ) ;
125+ }
126+
127+ await new Promise ( ( resolve ) => {
128+ client . subscribe ( {
129+ complete : resolve ,
130+ error : ( err ) => {
131+ if ( ! cancelled || process . env . DEBUG ) {
132+ console . error ( "Client error:" , err ) ;
133+ }
134+ resolve ( null ) ;
135+ } ,
136+ next : ( ) => { } , // We're using echoOutput
137+ } ) ;
138+ } ) ;
139+ }
140+
141+ async function startProdClient ( clientOptions ) {
142+ const { CLIENT_PORT , authDisabled, sessionToken, abort } = clientOptions ;
143+ const inspectorClientPath = resolve (
144+ __dirname ,
145+ "../.." ,
146+ "client" ,
147+ "bin" ,
148+ "client.js" ,
149+ ) ;
150+
151+ // Auto-open browser with token
152+ if ( process . env . MCP_AUTO_OPEN_ENABLED !== "false" ) {
153+ const url = authDisabled
154+ ? `http://127.0.0.1:${ CLIENT_PORT } `
155+ : `http://127.0.0.1:${ CLIENT_PORT } /?MCP_PROXY_AUTH_TOKEN=${ sessionToken } ` ;
156+ open ( url ) ;
157+ }
158+
159+ await spawnPromise ( "node" , [ inspectorClientPath ] , {
160+ env : { ...process . env , PORT : CLIENT_PORT } ,
161+ signal : abort . signal ,
162+ echoOutput : true ,
163+ } ) ;
164+ }
165+
14166async function main ( ) {
15167 // Parse command line arguments
16168 const args = process . argv . slice ( 2 ) ;
17169 const envVars = { } ;
18170 const mcpServerArgs = [ ] ;
19171 let command = null ;
20172 let parsingFlags = true ;
173+ let isDev = false ;
21174
22175 for ( let i = 0 ; i < args . length ; i ++ ) {
23176 const arg = args [ i ] ;
@@ -27,6 +180,11 @@ async function main() {
27180 continue ;
28181 }
29182
183+ if ( parsingFlags && arg === "--dev" ) {
184+ isDev = true ;
185+ continue ;
186+ }
187+
30188 if ( parsingFlags && arg === "-e" && i + 1 < args . length ) {
31189 const envVar = args [ ++ i ] ;
32190 const equalsIndex = envVar . indexOf ( "=" ) ;
@@ -38,34 +196,25 @@ async function main() {
38196 } else {
39197 envVars [ envVar ] = "" ;
40198 }
41- } else if ( ! command ) {
199+ } else if ( ! command && ! isDev ) {
42200 command = arg ;
43- } else {
201+ } else if ( ! isDev ) {
44202 mcpServerArgs . push ( arg ) ;
45203 }
46204 }
47205
48- const inspectorServerPath = resolve (
49- __dirname ,
50- "../.." ,
51- "server" ,
52- "build" ,
53- "index.js" ,
54- ) ;
55-
56- // Path to the client entry point
57- const inspectorClientPath = resolve (
58- __dirname ,
59- "../.." ,
60- "client" ,
61- "bin" ,
62- "client.js" ,
63- ) ;
64-
65206 const CLIENT_PORT = process . env . CLIENT_PORT ?? "6274" ;
66207 const SERVER_PORT = process . env . SERVER_PORT ?? "6277" ;
67208
68- console . log ( "Starting MCP inspector..." ) ;
209+ console . log (
210+ isDev
211+ ? "Starting MCP inspector in development mode..."
212+ : "Starting MCP inspector..." ,
213+ ) ;
214+
215+ // Generate session token for authentication
216+ const sessionToken = randomBytes ( 32 ) . toString ( "hex" ) ;
217+ const authDisabled = ! ! process . env . DANGEROUSLY_OMIT_AUTH ;
69218
70219 const abort = new AbortController ( ) ;
71220
@@ -74,42 +223,41 @@ async function main() {
74223 cancelled = true ;
75224 abort . abort ( ) ;
76225 } ) ;
226+
77227 let server , serverOk ;
228+
78229 try {
79- server = spawnPromise (
80- "node" ,
81- [
82- inspectorServerPath ,
83- ...( command ? [ `--env` , command ] : [ ] ) ,
84- ...( mcpServerArgs ? [ `--args=${ mcpServerArgs . join ( " " ) } ` ] : [ ] ) ,
85- ] ,
86- {
87- env : {
88- ...process . env ,
89- PORT : SERVER_PORT ,
90- MCP_ENV_VARS : JSON . stringify ( envVars ) ,
91- } ,
92- signal : abort . signal ,
93- echoOutput : true ,
94- } ,
95- ) ;
230+ const serverOptions = {
231+ SERVER_PORT ,
232+ CLIENT_PORT ,
233+ sessionToken,
234+ envVars,
235+ abort,
236+ command,
237+ mcpServerArgs,
238+ } ;
96239
97- // Make sure server started before starting client
98- serverOk = await Promise . race ( [ server , delay ( 2 * 1000 ) ] ) ;
240+ const result = isDev
241+ ? await startDevServer ( serverOptions )
242+ : await startProdServer ( serverOptions ) ;
243+
244+ server = result . server ;
245+ serverOk = result . serverOk ;
99246 } catch ( error ) { }
100247
101248 if ( serverOk ) {
102249 try {
103- // Only auto-open when auth is disabled
104- const authDisabled = ! ! process . env . DANGEROUSLY_OMIT_AUTH ;
105- if ( process . env . MCP_AUTO_OPEN_ENABLED !== "false" && authDisabled ) {
106- open ( `http://127.0.0.1:${ CLIENT_PORT } ` ) ;
107- }
108- await spawnPromise ( "node" , [ inspectorClientPath ] , {
109- env : { ...process . env , PORT : CLIENT_PORT } ,
110- signal : abort . signal ,
111- echoOutput : true ,
112- } ) ;
250+ const clientOptions = {
251+ CLIENT_PORT ,
252+ authDisabled,
253+ sessionToken,
254+ abort,
255+ cancelled,
256+ } ;
257+
258+ await ( isDev
259+ ? startDevClient ( clientOptions )
260+ : startProdClient ( clientOptions ) ) ;
113261 } catch ( e ) {
114262 if ( ! cancelled || process . env . DEBUG ) throw e ;
115263 }
0 commit comments