Skip to content

Commit 4c4c8a0

Browse files
committed
Add CLI and config file support
1 parent 0870a81 commit 4c4c8a0

20 files changed

+1456
-57
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ server/build
44
client/dist
55
client/tsconfig.app.tsbuildinfo
66
client/tsconfig.node.tsbuildinfo
7+
bin/build
8+
cli/build

README.md

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ You can pass both arguments and environment variables to your MCP server. Argume
2121
npx @modelcontextprotocol/inspector build/index.js arg1 arg2
2222

2323
# Pass environment variables only
24-
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 node build/index.js
24+
npx @modelcontextprotocol/inspector -e key=value -e key2=$VALUE2 node build/index.js
2525

2626
# Pass both environment variables and arguments
27-
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 node build/index.js arg1 arg2
27+
npx @modelcontextprotocol/inspector -e key=value -e key2=$VALUE2 node build/index.js arg1 arg2
2828

2929
# Use -- to separate inspector flags from server arguments
30-
npx @modelcontextprotocol/inspector -e KEY=$VALUE -- node build/index.js -e server-flag
30+
npx @modelcontextprotocol/inspector -e key=$VALUE -- node build/index.js -e server-flag
3131
```
3232

3333
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
@@ -36,6 +36,77 @@ The inspector runs both a client UI (default port 5173) and an MCP proxy server
3636
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector node build/index.js
3737
```
3838

39+
### Using a Configuration File
40+
41+
The inspector supports configuration files to store settings for different MCP servers. This is useful when working with multiple servers or complex configurations:
42+
43+
```bash
44+
npx @modelcontextprotocol/inspector --config path/to/config.json --server everything
45+
```
46+
47+
Example configuration file:
48+
49+
```json
50+
{
51+
"mcpServers": {
52+
"everything": {
53+
"command": "npx",
54+
"args": ["@modelcontextprotocol/server-everything"],
55+
"env": {
56+
"hello": "Hello MCP!"
57+
}
58+
},
59+
"my-server": {
60+
"command": "node",
61+
"args": ["build/index.js", "arg1", "arg2"],
62+
"env": {
63+
"key": "value",
64+
"key2": "value2"
65+
}
66+
}
67+
}
68+
}
69+
```
70+
71+
### CLI Mode
72+
73+
CLI mode enables programmatic interaction with MCP servers from the command line, ideal for scripting, automation, and integration with coding assistants. This creates an efficient feedback loop for MCP server development.
74+
75+
```bash
76+
npx @modelcontextprotocol/inspector --cli node build/index.js
77+
```
78+
79+
The CLI mode supports most operations across tools, resources, and prompts. A few examples:
80+
81+
```bash
82+
# Basic usage
83+
npx @modelcontextprotocol/inspector --cli node build/index.js
84+
85+
# With config file
86+
npx @modelcontextprotocol/inspector --cli --config path/to/config.json --server myserver
87+
88+
# List available tools
89+
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
90+
91+
# Call a specific tool
92+
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-args key=value --tool-args another=value2
93+
94+
# List available resources
95+
npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
96+
97+
# List available prompts
98+
npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list
99+
100+
# Connect to a remote MCP server
101+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
102+
103+
# Call a tool on a remote server
104+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-args param=value
105+
106+
# List resources from a remote server
107+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method resources/list
108+
```
109+
39110
For more details on ways to use the inspector, see the [Inspector section of the MCP docs site](https://modelcontextprotocol.io/docs/tools/inspector). For help with debugging, see the [Debugging guide](https://modelcontextprotocol.io/docs/tools/debugging).
40111

41112
### From this repository

bin/cli.js

Lines changed: 157 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,34 @@
11
#!/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";
46
import { spawnPromise } from "spawn-rx";
57
import { fileURLToPath } from "url";
6-
78
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+
}
921
function delay(ms) {
1022
return new Promise((resolve) => setTimeout(resolve, ms));
1123
}
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) {
4125
const inspectorServerPath = resolve(
4226
__dirname,
4327
"..",
4428
"server",
4529
"build",
4630
"index.js",
4731
);
48-
4932
// Path to the client entry point
5033
const inspectorClientPath = resolve(
5134
__dirname,
@@ -54,63 +37,183 @@ async function main() {
5437
"bin",
5538
"cli.js",
5639
);
57-
5840
const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173";
5941
const SERVER_PORT = process.env.SERVER_PORT ?? "3000";
60-
6142
console.log("Starting MCP inspector...");
62-
6343
const abort = new AbortController();
64-
6544
let cancelled = false;
6645
process.on("SIGINT", () => {
6746
cancelled = true;
6847
abort.abort();
6948
});
70-
7149
const server = spawnPromise(
7250
"node",
7351
[
7452
inspectorServerPath,
75-
...(command ? [`--env`, command] : []),
76-
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
53+
...(args.command ? [`--env`, args.command] : []),
54+
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
7755
],
7856
{
7957
env: {
8058
...process.env,
8159
PORT: SERVER_PORT,
82-
MCP_ENV_VARS: JSON.stringify(envVars),
60+
MCP_ENV_VARS: JSON.stringify(args.envArgs),
8361
},
8462
signal: abort.signal,
8563
echoOutput: true,
8664
},
8765
);
88-
8966
const client = spawnPromise("node", [inspectorClientPath], {
9067
env: { ...process.env, PORT: CLIENT_PORT },
9168
signal: abort.signal,
9269
echoOutput: true,
9370
});
94-
9571
// Make sure our server/client didn't immediately fail
9672
await Promise.any([server, client, delay(2 * 1000)]);
9773
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
9874
console.log(
9975
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
10076
);
101-
10277
try {
10378
await Promise.any([server, client]);
10479
} catch (e) {
105-
if (!cancelled || process.env.DEBUG) throw e;
80+
if (!cancelled || process.env.DEBUG) {
81+
throw e;
82+
}
10683
}
107-
108-
return 0;
10984
}
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();
11693
});
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();

bin/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@modelcontextprotocol/inspector-bin",
3+
"version": "0.5.1",
4+
"description": "Model Context Protocol inspector",
5+
"license": "MIT",
6+
"author": "Anthropic, PBC (https://anthropic.com)",
7+
"homepage": "https://modelcontextprotocol.io",
8+
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
9+
"type": "module",
10+
"bin": {
11+
"mcp-inspector": "./cli.js"
12+
},
13+
"files": [
14+
"cli.js"
15+
],
16+
"scripts": {
17+
"build": "tsc",
18+
"postbuild": "chmod +x build/index.js && cp build/index.js cli.js",
19+
"test": "./tests/cli-tests.sh"
20+
},
21+
"dependencies": {},
22+
"devDependencies": {}
23+
}

0 commit comments

Comments
 (0)