2
2
3
3
import open from "open" ;
4
4
import { resolve , dirname } from "path" ;
5
- import { spawnPromise } from "spawn-rx" ;
5
+ import { spawnPromise , spawn } from "spawn-rx" ;
6
6
import { fileURLToPath } from "url" ;
7
+ import { randomBytes } from "crypto" ;
7
8
8
9
const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
9
10
@@ -18,6 +19,7 @@ async function main() {
18
19
const mcpServerArgs = [ ] ;
19
20
let command = null ;
20
21
let parsingFlags = true ;
22
+ let isDev = false ;
21
23
22
24
for ( let i = 0 ; i < args . length ; i ++ ) {
23
25
const arg = args [ i ] ;
@@ -27,6 +29,11 @@ async function main() {
27
29
continue ;
28
30
}
29
31
32
+ if ( parsingFlags && arg === "--dev" ) {
33
+ isDev = true ;
34
+ continue ;
35
+ }
36
+
30
37
if ( parsingFlags && arg === "-e" && i + 1 < args . length ) {
31
38
const envVar = args [ ++ i ] ;
32
39
const equalsIndex = envVar . indexOf ( "=" ) ;
@@ -38,34 +45,25 @@ async function main() {
38
45
} else {
39
46
envVars [ envVar ] = "" ;
40
47
}
41
- } else if ( ! command ) {
48
+ } else if ( ! command && ! isDev ) {
42
49
command = arg ;
43
- } else {
50
+ } else if ( ! isDev ) {
44
51
mcpServerArgs . push ( arg ) ;
45
52
}
46
53
}
47
54
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
-
65
55
const CLIENT_PORT = process . env . CLIENT_PORT ?? "6274" ;
66
56
const SERVER_PORT = process . env . SERVER_PORT ?? "6277" ;
67
57
68
- console . log ( "Starting MCP inspector..." ) ;
58
+ console . log (
59
+ isDev
60
+ ? "Starting MCP inspector in development mode..."
61
+ : "Starting MCP inspector..." ,
62
+ ) ;
63
+
64
+ // Generate session token for authentication
65
+ const sessionToken = randomBytes ( 32 ) . toString ( "hex" ) ;
66
+ const authDisabled = ! ! process . env . DANGEROUSLY_OMIT_AUTH ;
69
67
70
68
const abort = new AbortController ( ) ;
71
69
@@ -74,42 +72,150 @@ async function main() {
74
72
cancelled = true ;
75
73
abort . abort ( ) ;
76
74
} ) ;
75
+
77
76
let server , serverOk ;
77
+
78
78
try {
79
- server = spawnPromise (
80
- "node" ,
81
- [
82
- inspectorServerPath ,
83
- ...( command ? [ `--env` , command ] : [ ] ) ,
84
- ...( mcpServerArgs ? [ `--args=${ mcpServerArgs . join ( " " ) } ` ] : [ ] ) ,
85
- ] ,
86
- {
79
+ if ( isDev ) {
80
+ // Development mode - use tsx watch
81
+ const serverCommand = "npx" ;
82
+ const serverArgs = [
83
+ "tsx" ,
84
+ "watch" ,
85
+ "--clear-screen=false" ,
86
+ "src/index.ts" ,
87
+ ] ;
88
+ const isWindows = process . platform === "win32" ;
89
+
90
+ const serverOptions = {
91
+ cwd : resolve ( __dirname , "../.." , "server" ) ,
87
92
env : {
88
93
...process . env ,
89
94
PORT : SERVER_PORT ,
95
+ CLIENT_PORT : CLIENT_PORT ,
96
+ MCP_PROXY_TOKEN : sessionToken ,
90
97
MCP_ENV_VARS : JSON . stringify ( envVars ) ,
91
98
} ,
92
99
signal : abort . signal ,
93
100
echoOutput : true ,
94
- } ,
95
- ) ;
101
+ } ;
102
+
103
+ // For Windows, we need to use stdin: 'ignore' to simulate < NUL
104
+ if ( isWindows ) {
105
+ serverOptions . stdin = "ignore" ;
106
+ }
107
+
108
+ server = spawn ( serverCommand , serverArgs , serverOptions ) ;
109
+
110
+ // Give server time to start
111
+ serverOk = await Promise . race ( [
112
+ new Promise ( ( resolve ) => {
113
+ server . subscribe ( {
114
+ complete : ( ) => resolve ( false ) ,
115
+ error : ( ) => resolve ( false ) ,
116
+ next : ( ) => { } , // We're using echoOutput
117
+ } ) ;
118
+ } ) ,
119
+ delay ( 3000 ) . then ( ( ) => true ) ,
120
+ ] ) ;
121
+ } else {
122
+ // Production mode - use built files
123
+ const inspectorServerPath = resolve (
124
+ __dirname ,
125
+ "../.." ,
126
+ "server" ,
127
+ "build" ,
128
+ "index.js" ,
129
+ ) ;
130
+
131
+ server = spawnPromise (
132
+ "node" ,
133
+ [
134
+ inspectorServerPath ,
135
+ ...( command ? [ `--env` , command ] : [ ] ) ,
136
+ ...( mcpServerArgs ? [ `--args=${ mcpServerArgs . join ( " " ) } ` ] : [ ] ) ,
137
+ ] ,
138
+ {
139
+ env : {
140
+ ...process . env ,
141
+ PORT : SERVER_PORT ,
142
+ CLIENT_PORT : CLIENT_PORT ,
143
+ MCP_PROXY_TOKEN : sessionToken ,
144
+ MCP_ENV_VARS : JSON . stringify ( envVars ) ,
145
+ } ,
146
+ signal : abort . signal ,
147
+ echoOutput : true ,
148
+ } ,
149
+ ) ;
96
150
97
- // Make sure server started before starting client
98
- serverOk = await Promise . race ( [ server , delay ( 2 * 1000 ) ] ) ;
151
+ // Make sure server started before starting client
152
+ serverOk = await Promise . race ( [ server , delay ( 2 * 1000 ) ] ) ;
153
+ }
99
154
} catch ( error ) { }
100
155
101
156
if ( serverOk ) {
102
157
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 } ` ) ;
158
+ if ( isDev ) {
159
+ // Development mode - use vite
160
+ const clientCommand = "npx" ;
161
+ const clientArgs = [ "vite" , "--port" , CLIENT_PORT ] ;
162
+
163
+ const client = spawn ( clientCommand , clientArgs , {
164
+ cwd : resolve ( __dirname , ".." ) ,
165
+ env : { ...process . env , PORT : CLIENT_PORT } ,
166
+ signal : abort . signal ,
167
+ echoOutput : true ,
168
+ } ) ;
169
+
170
+ // Auto-open browser after vite starts
171
+ if ( process . env . MCP_AUTO_OPEN_ENABLED !== "false" ) {
172
+ const url = authDisabled
173
+ ? `http://127.0.0.1:${ CLIENT_PORT } `
174
+ : `http://127.0.0.1:${ CLIENT_PORT } /?MCP_PROXY_AUTH_TOKEN=${ sessionToken } ` ;
175
+
176
+ // Give vite time to start before opening browser
177
+ setTimeout ( ( ) => {
178
+ open ( url ) ;
179
+ console . log ( `\n🔗 Opening browser at: ${ url } \n` ) ;
180
+ } , 3000 ) ;
181
+ }
182
+
183
+ await new Promise ( ( resolve ) => {
184
+ client . subscribe ( {
185
+ complete : resolve ,
186
+ error : ( err ) => {
187
+ if ( ! cancelled || process . env . DEBUG ) {
188
+ console . error ( "Client error:" , err ) ;
189
+ }
190
+ resolve ( null ) ;
191
+ } ,
192
+ next : ( ) => { } , // We're using echoOutput
193
+ } ) ;
194
+ } ) ;
195
+ } else {
196
+ // Production mode - use client.js
197
+ const inspectorClientPath = resolve (
198
+ __dirname ,
199
+ "../.." ,
200
+ "client" ,
201
+ "bin" ,
202
+ "client.js" ,
203
+ ) ;
204
+
205
+ // Auto-open browser with token
206
+ if ( process . env . MCP_AUTO_OPEN_ENABLED !== "false" ) {
207
+ const url = authDisabled
208
+ ? `http://127.0.0.1:${ CLIENT_PORT } `
209
+ : `http://127.0.0.1:${ CLIENT_PORT } /?MCP_PROXY_AUTH_TOKEN=${ sessionToken } ` ;
210
+ open ( url ) ;
211
+ }
212
+
213
+ await spawnPromise ( "node" , [ inspectorClientPath ] , {
214
+ env : { ...process . env , PORT : CLIENT_PORT } ,
215
+ signal : abort . signal ,
216
+ echoOutput : true ,
217
+ } ) ;
107
218
}
108
- await spawnPromise ( "node" , [ inspectorClientPath ] , {
109
- env : { ...process . env , PORT : CLIENT_PORT } ,
110
- signal : abort . signal ,
111
- echoOutput : true ,
112
- } ) ;
113
219
} catch ( e ) {
114
220
if ( ! cancelled || process . env . DEBUG ) throw e ;
115
221
}
0 commit comments