1
1
#!/usr/bin/env node
2
-
3
- import { resolve , dirname } from "path" ;
2
+ import { Command } from "commander" ;
3
+ import fs from "node:fs" ;
4
+ import path from "node:path" ;
5
+ import { dirname , resolve } from "path" ;
4
6
import { spawnPromise } from "spawn-rx" ;
5
7
import { fileURLToPath } from "url" ;
6
-
7
8
const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
8
-
9
+ function handleError ( error ) {
10
+ let message ;
11
+ if ( error instanceof Error ) {
12
+ message = error . message ;
13
+ } else if ( typeof error === "string" ) {
14
+ message = error ;
15
+ } else {
16
+ message = "Unknown error" ;
17
+ }
18
+ console . error ( message ) ;
19
+ process . exit ( 1 ) ;
20
+ }
9
21
function delay ( ms ) {
10
22
return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
11
23
}
12
-
13
- async function main ( ) {
14
- // Parse command line arguments
15
- const args = process . argv . slice ( 2 ) ;
16
- const envVars = { } ;
17
- const mcpServerArgs = [ ] ;
18
- let command = null ;
19
- let parsingFlags = true ;
20
-
21
- for ( let i = 0 ; i < args . length ; i ++ ) {
22
- const arg = args [ i ] ;
23
-
24
- if ( parsingFlags && arg === "--" ) {
25
- parsingFlags = false ;
26
- continue ;
27
- }
28
-
29
- if ( parsingFlags && arg === "-e" && i + 1 < args . length ) {
30
- const [ key , value ] = args [ ++ i ] . split ( "=" ) ;
31
- if ( key && value ) {
32
- envVars [ key ] = value ;
33
- }
34
- } else if ( ! command ) {
35
- command = arg ;
36
- } else {
37
- mcpServerArgs . push ( arg ) ;
38
- }
39
- }
40
-
24
+ async function runWebClient ( args ) {
41
25
const inspectorServerPath = resolve (
42
26
__dirname ,
43
27
".." ,
44
28
"server" ,
45
29
"build" ,
46
30
"index.js" ,
47
31
) ;
48
-
49
32
// Path to the client entry point
50
33
const inspectorClientPath = resolve (
51
34
__dirname ,
@@ -54,63 +37,183 @@ async function main() {
54
37
"bin" ,
55
38
"cli.js" ,
56
39
) ;
57
-
58
40
const CLIENT_PORT = process . env . CLIENT_PORT ?? "5173" ;
59
41
const SERVER_PORT = process . env . SERVER_PORT ?? "3000" ;
60
-
61
42
console . log ( "Starting MCP inspector..." ) ;
62
-
63
43
const abort = new AbortController ( ) ;
64
-
65
44
let cancelled = false ;
66
45
process . on ( "SIGINT" , ( ) => {
67
46
cancelled = true ;
68
47
abort . abort ( ) ;
69
48
} ) ;
70
-
71
49
const server = spawnPromise (
72
50
"node" ,
73
51
[
74
52
inspectorServerPath ,
75
- ...( command ? [ `--env` , command ] : [ ] ) ,
76
- ...( mcpServerArgs ? [ `--args=${ mcpServerArgs . join ( " " ) } ` ] : [ ] ) ,
53
+ ...( args . command ? [ `--env` , args . command ] : [ ] ) ,
54
+ ...( args . args ? [ `--args=${ args . args . join ( " " ) } ` ] : [ ] ) ,
77
55
] ,
78
56
{
79
57
env : {
80
58
...process . env ,
81
59
PORT : SERVER_PORT ,
82
- MCP_ENV_VARS : JSON . stringify ( envVars ) ,
60
+ MCP_ENV_VARS : JSON . stringify ( args . envArgs ) ,
83
61
} ,
84
62
signal : abort . signal ,
85
63
echoOutput : true ,
86
64
} ,
87
65
) ;
88
-
89
66
const client = spawnPromise ( "node" , [ inspectorClientPath ] , {
90
67
env : { ...process . env , PORT : CLIENT_PORT } ,
91
68
signal : abort . signal ,
92
69
echoOutput : true ,
93
70
} ) ;
94
-
95
71
// Make sure our server/client didn't immediately fail
96
72
await Promise . any ( [ server , client , delay ( 2 * 1000 ) ] ) ;
97
73
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${ SERVER_PORT } ` ;
98
74
console . log (
99
75
`\n🔍 MCP Inspector is up and running at http://localhost:${ CLIENT_PORT } ${ portParam } 🚀` ,
100
76
) ;
101
-
102
77
try {
103
78
await Promise . any ( [ server , client ] ) ;
104
79
} catch ( e ) {
105
- if ( ! cancelled || process . env . DEBUG ) throw e ;
80
+ if ( ! cancelled || process . env . DEBUG ) {
81
+ throw e ;
82
+ }
106
83
}
107
-
108
- return 0 ;
109
84
}
110
-
111
- main ( )
112
- . then ( ( _ ) => process . exit ( 0 ) )
113
- . catch ( ( e ) => {
114
- console . error ( e ) ;
115
- process . exit ( 1 ) ;
85
+ async function runCli ( args ) {
86
+ const projectRoot = resolve ( __dirname , ".." ) ;
87
+ const cliPath = resolve ( projectRoot , "cli" , "build" , "index.js" ) ;
88
+ const abort = new AbortController ( ) ;
89
+ let cancelled = false ;
90
+ process . on ( "SIGINT" , ( ) => {
91
+ cancelled = true ;
92
+ abort . abort ( ) ;
116
93
} ) ;
94
+ try {
95
+ await spawnPromise ( "node" , [ cliPath , args . command , ...args . args ] , {
96
+ env : { ...process . env , ...args . envArgs } ,
97
+ signal : abort . signal ,
98
+ echoOutput : true ,
99
+ } ) ;
100
+ } catch ( e ) {
101
+ if ( ! cancelled || process . env . DEBUG ) {
102
+ throw e ;
103
+ }
104
+ }
105
+ }
106
+ function loadConfigFile ( configPath , serverName ) {
107
+ try {
108
+ const resolvedConfigPath = path . isAbsolute ( configPath )
109
+ ? configPath
110
+ : path . resolve ( process . cwd ( ) , configPath ) ;
111
+ if ( ! fs . existsSync ( resolvedConfigPath ) ) {
112
+ throw new Error ( `Config file not found: ${ resolvedConfigPath } ` ) ;
113
+ }
114
+ const configContent = fs . readFileSync ( resolvedConfigPath , "utf8" ) ;
115
+ const parsedConfig = JSON . parse ( configContent ) ;
116
+ if ( ! parsedConfig . mcpServers || ! parsedConfig . mcpServers [ serverName ] ) {
117
+ const availableServers = Object . keys ( parsedConfig . mcpServers || { } ) . join (
118
+ ", " ,
119
+ ) ;
120
+ throw new Error (
121
+ `Server '${ serverName } ' not found in config file. Available servers: ${ availableServers } ` ,
122
+ ) ;
123
+ }
124
+ const serverConfig = parsedConfig . mcpServers [ serverName ] ;
125
+ return serverConfig ;
126
+ } catch ( err ) {
127
+ if ( err instanceof SyntaxError ) {
128
+ throw new Error ( `Invalid JSON in config file: ${ err . message } ` ) ;
129
+ }
130
+ throw err ;
131
+ }
132
+ }
133
+ function parseKeyValuePair ( value , previous = { } ) {
134
+ const parts = value . split ( "=" ) ;
135
+ const key = parts [ 0 ] ;
136
+ const val = parts . slice ( 1 ) . join ( "=" ) ;
137
+ if ( val === undefined || val === "" ) {
138
+ throw new Error (
139
+ `Invalid parameter format: ${ value } . Use key=value format.` ,
140
+ ) ;
141
+ }
142
+ return { ...previous , [ key ] : val } ;
143
+ }
144
+ function parseArgs ( ) {
145
+ const program = new Command ( ) ;
146
+ const argSeparatorIndex = process . argv . indexOf ( "--" ) ;
147
+ let preArgs = process . argv ;
148
+ let postArgs = [ ] ;
149
+ if ( argSeparatorIndex !== - 1 ) {
150
+ preArgs = process . argv . slice ( 0 , argSeparatorIndex ) ;
151
+ postArgs = process . argv . slice ( argSeparatorIndex + 1 ) ;
152
+ }
153
+ program
154
+ . name ( "inspector-bin" )
155
+ . allowExcessArguments ( )
156
+ . allowUnknownOption ( )
157
+ . option (
158
+ "-e <env>" ,
159
+ "environment variables in KEY=VALUE format" ,
160
+ parseKeyValuePair ,
161
+ { } ,
162
+ )
163
+ . option ( "--config <path>" , "config file path" )
164
+ . option ( "--server <n>" , "server name from config file" )
165
+ . option ( "--cli" , "enable CLI mode" ) ;
166
+ // Parse only the arguments before --
167
+ program . parse ( preArgs ) ;
168
+ const options = program . opts ( ) ;
169
+ const remainingArgs = program . args ;
170
+ // Add back any arguments that came after --
171
+ const finalArgs = [ ...remainingArgs , ...postArgs ] ;
172
+ // Validate that config and server are provided together
173
+ if (
174
+ ( options . config && ! options . server ) ||
175
+ ( ! options . config && options . server )
176
+ ) {
177
+ throw new Error (
178
+ "Both --config and --server must be provided together. If you specify one, you must specify the other." ,
179
+ ) ;
180
+ }
181
+ // If config file is specified, load and use the options from the file. We must merge the args
182
+ // from the command line and the file together, or we will miss the method options (--method,
183
+ // etc.)
184
+ if ( options . config && options . server ) {
185
+ const config = loadConfigFile ( options . config , options . server ) ;
186
+ return {
187
+ command : config . command ,
188
+ args : [ ...( config . args || [ ] ) , ...finalArgs ] ,
189
+ envArgs : { ...( config . env || { } ) , ...( options . e || { } ) } ,
190
+ cli : options . cli || false ,
191
+ } ;
192
+ }
193
+ // Otherwise use command line arguments
194
+ const command = finalArgs [ 0 ] || "" ;
195
+ const args = finalArgs . slice ( 1 ) ;
196
+ return {
197
+ command,
198
+ args,
199
+ envArgs : options . e || { } ,
200
+ cli : options . cli || false ,
201
+ } ;
202
+ }
203
+ async function main ( ) {
204
+ process . on ( "uncaughtException" , ( error ) => {
205
+ handleError ( error ) ;
206
+ } ) ;
207
+ try {
208
+ const args = parseArgs ( ) ;
209
+ console . log ( args ) ;
210
+ if ( args . cli ) {
211
+ runCli ( args ) ;
212
+ } else {
213
+ await runWebClient ( args ) ;
214
+ }
215
+ } catch ( error ) {
216
+ handleError ( error ) ;
217
+ }
218
+ }
219
+ main ( ) ;
0 commit comments