Skip to content

Commit 6161405

Browse files
authored
Merge branch 'main' into update-readme
2 parents fa7eba2 + f7272d8 commit 6161405

25 files changed

+6740
-2975
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
.DS_Store
2-
node_modules
2+
node_modules/
3+
*-workspace/
34
server/build
45
client/dist
56
client/tsconfig.app.tsbuildinfo
67
client/tsconfig.node.tsbuildinfo
78
.vscode
9+
bin/build
10+
cli/build
11+
test-output

README.md

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

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

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

3333
# Use -- to separate inspector flags from server arguments
34-
npx @modelcontextprotocol/inspector -e KEY=$VALUE -- node build/index.js -e server-flag
34+
npx @modelcontextprotocol/inspector -e key=$VALUE -- node build/index.js -e server-flag
3535
```
3636

3737
The inspector runs both an MCP Inspector (MCPI) client UI (default port 6274) and an MCP Proxy (MCPP) server (default port 6277). Open the MCPI client UI in your browser to use the inspector. (These ports are derived from the T9 dialpad mapping of MCPI and MCPP respectively, as a mnemonic). You can customize the ports if needed:
@@ -63,6 +63,36 @@ The MCP Inspector supports the following configuration settings. To change them,
6363

6464
These settings can be adjusted in real-time through the UI and will persist across sessions.
6565

66+
The inspector also supports configuration files to store settings for different MCP servers. This is useful when working with multiple servers or complex configurations:
67+
68+
```bash
69+
npx @modelcontextprotocol/inspector --config path/to/config.json --server everything
70+
```
71+
72+
Example server configuration file:
73+
74+
```json
75+
{
76+
"mcpServers": {
77+
"everything": {
78+
"command": "npx",
79+
"args": ["@modelcontextprotocol/server-everything"],
80+
"env": {
81+
"hello": "Hello MCP!"
82+
}
83+
},
84+
"my-server": {
85+
"command": "node",
86+
"args": ["build/index.js", "arg1", "arg2"],
87+
"env": {
88+
"key": "value",
89+
"key2": "value2"
90+
}
91+
}
92+
}
93+
}
94+
```
95+
6696
### From this repository
6797

6898
If you're working on the inspector itself:
@@ -87,6 +117,57 @@ npm run build
87117
npm start
88118
```
89119
120+
### CLI Mode
121+
122+
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.
123+
124+
```bash
125+
npx @modelcontextprotocol/inspector --cli node build/index.js
126+
```
127+
128+
The CLI mode supports most operations across tools, resources, and prompts. A few examples:
129+
130+
```bash
131+
# Basic usage
132+
npx @modelcontextprotocol/inspector --cli node build/index.js
133+
134+
# With config file
135+
npx @modelcontextprotocol/inspector --cli --config path/to/config.json --server myserver
136+
137+
# List available tools
138+
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
139+
140+
# Call a specific tool
141+
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2
142+
143+
# List available resources
144+
npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
145+
146+
# List available prompts
147+
npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list
148+
149+
# Connect to a remote MCP server
150+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
151+
152+
# Call a tool on a remote server
153+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value
154+
155+
# List resources from a remote server
156+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method resources/list
157+
```
158+
159+
### UI Mode vs CLI Mode: When to Use Each
160+
161+
| Use Case | UI Mode | CLI Mode |
162+
| ------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
163+
| **Server Development** | Visual interface for interactive testing and debugging during development | Scriptable commands for quick testing and continuous integration; creates feedback loops with AI coding assistants like Cursor for rapid development |
164+
| **Resource Exploration** | Interactive browser with hierarchical navigation and JSON visualization | Programmatic listing and reading for automation and scripting |
165+
| **Tool Testing** | Form-based parameter input with real-time response visualization | Command-line tool execution with JSON output for scripting |
166+
| **Prompt Engineering** | Interactive sampling with streaming responses and visual comparison | Batch processing of prompts with machine-readable output |
167+
| **Debugging** | Request history, visualized errors, and real-time notifications | Direct JSON output for log analysis and integration with other tools |
168+
| **Automation** | N/A | Ideal for CI/CD pipelines, batch processing, and integration with coding assistants |
169+
| **Learning MCP** | Rich visual interface helps new users understand server capabilities | Simplified commands for focused learning of specific endpoints |
170+
90171
## License
91172

92173
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.

bin/cli.js

Lines changed: 155 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +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, true));
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 envVar = args[++i];
31-
const equalsIndex = envVar.indexOf("=");
32-
33-
if (equalsIndex !== -1) {
34-
const key = envVar.substring(0, equalsIndex);
35-
const value = envVar.substring(equalsIndex + 1);
36-
envVars[key] = value;
37-
} else {
38-
envVars[envVar] = "";
39-
}
40-
} else if (!command) {
41-
command = arg;
42-
} else {
43-
mcpServerArgs.push(arg);
44-
}
45-
}
46-
24+
async function runWebClient(args) {
4725
const inspectorServerPath = resolve(
4826
__dirname,
4927
"..",
5028
"server",
5129
"build",
5230
"index.js",
5331
);
54-
5532
// Path to the client entry point
5633
const inspectorClientPath = resolve(
5734
__dirname,
@@ -60,43 +37,38 @@ async function main() {
6037
"bin",
6138
"cli.js",
6239
);
63-
6440
const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
6541
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";
66-
6742
console.log("Starting MCP inspector...");
68-
6943
const abort = new AbortController();
70-
7144
let cancelled = false;
7245
process.on("SIGINT", () => {
7346
cancelled = true;
7447
abort.abort();
7548
});
76-
let server, serverOk;
49+
let server;
50+
let serverOk;
7751
try {
7852
server = spawnPromise(
7953
"node",
8054
[
8155
inspectorServerPath,
82-
...(command ? [`--env`, command] : []),
83-
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
56+
...(args.command ? [`--env`, args.command] : []),
57+
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
8458
],
8559
{
8660
env: {
8761
...process.env,
8862
PORT: SERVER_PORT,
89-
MCP_ENV_VARS: JSON.stringify(envVars),
63+
MCP_ENV_VARS: JSON.stringify(args.envArgs),
9064
},
9165
signal: abort.signal,
9266
echoOutput: true,
9367
},
9468
);
95-
9669
// Make sure server started before starting client
9770
serverOk = await Promise.race([server, delay(2 * 1000)]);
9871
} catch (error) {}
99-
10072
if (serverOk) {
10173
try {
10274
await spawnPromise("node", [inspectorClientPath], {
@@ -108,13 +80,138 @@ async function main() {
10880
if (!cancelled || process.env.DEBUG) throw e;
10981
}
11082
}
111-
112-
return 0;
11383
}
114-
115-
main()
116-
.then((_) => process.exit(0))
117-
.catch((e) => {
118-
console.error(e);
119-
process.exit(1);
84+
async function runCli(args) {
85+
const projectRoot = resolve(__dirname, "..");
86+
const cliPath = resolve(projectRoot, "cli", "build", "index.js");
87+
const abort = new AbortController();
88+
let cancelled = false;
89+
process.on("SIGINT", () => {
90+
cancelled = true;
91+
abort.abort();
12092
});
93+
try {
94+
await spawnPromise("node", [cliPath, args.command, ...args.args], {
95+
env: { ...process.env, ...args.envArgs },
96+
signal: abort.signal,
97+
echoOutput: true,
98+
});
99+
} catch (e) {
100+
if (!cancelled || process.env.DEBUG) {
101+
throw e;
102+
}
103+
}
104+
}
105+
function loadConfigFile(configPath, serverName) {
106+
try {
107+
const resolvedConfigPath = path.isAbsolute(configPath)
108+
? configPath
109+
: path.resolve(process.cwd(), configPath);
110+
if (!fs.existsSync(resolvedConfigPath)) {
111+
throw new Error(`Config file not found: ${resolvedConfigPath}`);
112+
}
113+
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
114+
const parsedConfig = JSON.parse(configContent);
115+
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
116+
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
117+
", ",
118+
);
119+
throw new Error(
120+
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
121+
);
122+
}
123+
const serverConfig = parsedConfig.mcpServers[serverName];
124+
return serverConfig;
125+
} catch (err) {
126+
if (err instanceof SyntaxError) {
127+
throw new Error(`Invalid JSON in config file: ${err.message}`);
128+
}
129+
throw err;
130+
}
131+
}
132+
function parseKeyValuePair(value, previous = {}) {
133+
const parts = value.split("=");
134+
const key = parts[0];
135+
const val = parts.slice(1).join("=");
136+
if (val === undefined || val === "") {
137+
throw new Error(
138+
`Invalid parameter format: ${value}. Use key=value format.`,
139+
);
140+
}
141+
return { ...previous, [key]: val };
142+
}
143+
function parseArgs() {
144+
const program = new Command();
145+
const argSeparatorIndex = process.argv.indexOf("--");
146+
let preArgs = process.argv;
147+
let postArgs = [];
148+
if (argSeparatorIndex !== -1) {
149+
preArgs = process.argv.slice(0, argSeparatorIndex);
150+
postArgs = process.argv.slice(argSeparatorIndex + 1);
151+
}
152+
program
153+
.name("inspector-bin")
154+
.allowExcessArguments()
155+
.allowUnknownOption()
156+
.option(
157+
"-e <env>",
158+
"environment variables in KEY=VALUE format",
159+
parseKeyValuePair,
160+
{},
161+
)
162+
.option("--config <path>", "config file path")
163+
.option("--server <n>", "server name from config file")
164+
.option("--cli", "enable CLI mode");
165+
// Parse only the arguments before --
166+
program.parse(preArgs);
167+
const options = program.opts();
168+
const remainingArgs = program.args;
169+
// Add back any arguments that came after --
170+
const finalArgs = [...remainingArgs, ...postArgs];
171+
// Validate that config and server are provided together
172+
if (
173+
(options.config && !options.server) ||
174+
(!options.config && options.server)
175+
) {
176+
throw new Error(
177+
"Both --config and --server must be provided together. If you specify one, you must specify the other.",
178+
);
179+
}
180+
// If config file is specified, load and use the options from the file. We must merge the args
181+
// from the command line and the file together, or we will miss the method options (--method,
182+
// etc.)
183+
if (options.config && options.server) {
184+
const config = loadConfigFile(options.config, options.server);
185+
return {
186+
command: config.command,
187+
args: [...(config.args || []), ...finalArgs],
188+
envArgs: { ...(config.env || {}), ...(options.e || {}) },
189+
cli: options.cli || false,
190+
};
191+
}
192+
// Otherwise use command line arguments
193+
const command = finalArgs[0] || "";
194+
const args = finalArgs.slice(1);
195+
return {
196+
command,
197+
args,
198+
envArgs: options.e || {},
199+
cli: options.cli || false,
200+
};
201+
}
202+
async function main() {
203+
process.on("uncaughtException", (error) => {
204+
handleError(error);
205+
});
206+
try {
207+
const args = parseArgs();
208+
if (args.cli) {
209+
runCli(args);
210+
} else {
211+
await runWebClient(args);
212+
}
213+
} catch (error) {
214+
handleError(error);
215+
}
216+
}
217+
main();

0 commit comments

Comments
 (0)