@mcpjam/inspector 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  [![npm version](https://img.shields.io/npm/v/@mcpjam/inspector?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@mcpjam/inspector)
16
16
  [![npm downloads](https://img.shields.io/npm/dm/@mcpjam/inspector?style=for-the-badge&color=green)](https://www.npmjs.com/package/@mcpjam/inspector)
17
+ [![Docker Pulls](https://img.shields.io/docker/pulls/mcpjam/mcp-inspector?style=for-the-badge)](https://hub.docker.com/r/mcpjam/mcp-inspector)
17
18
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
18
19
  [![Node.js](https://img.shields.io/badge/Node.js-22.7.5+-green.svg?style=for-the-badge&logo=node.js)](https://nodejs.org/)
19
20
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.8+-blue.svg?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
package/cli/build/cli.js CHANGED
@@ -60,6 +60,9 @@ async function runWebClient(args) {
60
60
  ...process.env,
61
61
  PORT: SERVER_PORT,
62
62
  MCP_ENV_VARS: JSON.stringify(args.envArgs),
63
+ MCP_SERVER_CONFIGS: args.serverConfigs
64
+ ? JSON.stringify(args.serverConfigs)
65
+ : undefined,
63
66
  },
64
67
  signal: abort.signal,
65
68
  echoOutput: true,
@@ -73,15 +76,13 @@ async function runWebClient(args) {
73
76
  if (serverOk) {
74
77
  try {
75
78
  console.log("\x1b[32m%s\x1b[0m", "✅ Server initialized successfully"); // Green color
76
- console.log("\x1b[36m%s\x1b[0m", `🌐 Opening browser at http://127.0.0.1:${CLIENT_PORT}`);
77
- if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
78
- // Note: We need to import 'open' if we want to auto-open browser
79
- // import open from "open";
80
- // open(`http://127.0.0.1:${CLIENT_PORT}`);
81
- }
82
79
  console.log("\x1b[33m%s\x1b[0m", "🖥️ Starting client interface...");
83
80
  await spawnPromise("node", [inspectorClientPath], {
84
- env: { ...process.env, PORT: CLIENT_PORT },
81
+ env: {
82
+ ...process.env,
83
+ PORT: CLIENT_PORT,
84
+ MCP_AUTO_OPEN_ENABLED: process.env.MCP_AUTO_OPEN_ENABLED ?? "true",
85
+ },
85
86
  signal: abort.signal,
86
87
  echoOutput: true,
87
88
  });
@@ -138,6 +139,28 @@ function loadConfigFile(configPath, serverName) {
138
139
  throw err;
139
140
  }
140
141
  }
142
+ function loadAllServersFromConfig(configPath) {
143
+ try {
144
+ const resolvedConfigPath = path.isAbsolute(configPath)
145
+ ? configPath
146
+ : path.resolve(process.cwd(), configPath);
147
+ if (!fs.existsSync(resolvedConfigPath)) {
148
+ throw new Error(`Config file not found: ${resolvedConfigPath}`);
149
+ }
150
+ const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
151
+ const parsedConfig = JSON.parse(configContent);
152
+ if (!parsedConfig.mcpServers) {
153
+ throw new Error("No 'mcpServers' section found in config file");
154
+ }
155
+ return parsedConfig.mcpServers;
156
+ }
157
+ catch (err) {
158
+ if (err instanceof SyntaxError) {
159
+ throw new Error(`Invalid JSON in config file: ${err.message}`);
160
+ }
161
+ throw err;
162
+ }
163
+ }
141
164
  function parseKeyValuePair(value, previous = {}) {
142
165
  const parts = value.split("=");
143
166
  const key = parts[0];
@@ -170,22 +193,33 @@ function parseArgs() {
170
193
  const remainingArgs = program.args;
171
194
  // Add back any arguments that came after --
172
195
  const finalArgs = [...remainingArgs, ...postArgs];
173
- // Validate that config and server are provided together
174
- if ((options.config && !options.server) ||
175
- (!options.config && options.server)) {
176
- throw new Error("Both --config and --server must be provided together. If you specify one, you must specify the other.");
177
- }
178
- // If config file is specified, load and use the options from the file. We must merge the args
179
- // from the command line and the file together, or we will miss the method options (--method,
180
- // etc.)
181
- if (options.config && options.server) {
182
- const config = loadConfigFile(options.config, options.server);
183
- return {
184
- command: config.command,
185
- args: [...(config.args || []), ...finalArgs],
186
- envArgs: { ...(config.env || {}), ...(options.e || {}) },
187
- cli: options.cli || false,
188
- };
196
+ // Validate server option
197
+ if (!options.config && options.server) {
198
+ throw new Error("--server option requires --config to be specified.");
199
+ }
200
+ // If config file is specified
201
+ if (options.config) {
202
+ if (options.server) {
203
+ // Single server mode: load specific server from config
204
+ const config = loadConfigFile(options.config, options.server);
205
+ return {
206
+ command: config.command,
207
+ args: [...(config.args || []), ...finalArgs],
208
+ envArgs: { ...(config.env || {}), ...(options.e || {}) },
209
+ cli: options.cli || false,
210
+ };
211
+ }
212
+ else {
213
+ // Multiple servers mode: load all servers from config
214
+ const serverConfigs = loadAllServersFromConfig(options.config);
215
+ return {
216
+ command: "", // No single command in multi-server mode
217
+ args: finalArgs,
218
+ envArgs: options.e || {},
219
+ cli: options.cli || false,
220
+ serverConfigs,
221
+ };
222
+ }
189
223
  }
190
224
  // Otherwise use command line arguments
191
225
  const command = finalArgs[0] || "";
@@ -4,6 +4,7 @@ import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import handler from "serve-handler";
6
6
  import http from "http";
7
+ import open from "open";
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
10
  const distPath = join(__dirname, "../dist");
@@ -39,19 +40,32 @@ const server = http.createServer((request, response) => {
39
40
  return handler(request, response, handlerOptions);
40
41
  });
41
42
 
42
- const port = process.env.PORT || 6274;
43
+ const defaultPort = process.env.PORT || 6274;
44
+ let port = Number(defaultPort);
45
+
46
+ // Try ports sequentially until one works
47
+ server.on("error", (err) => {
48
+ if (err.code === "EADDRINUSE") {
49
+ console.log(`⚠️ Port ${port} was in use, trying ${port + 1}`);
50
+ port++;
51
+ server.listen(port);
52
+ } else {
53
+ console.error(`❌ MCPJam Inspector failed to start: ${err.message}`);
54
+ }
55
+ });
56
+
43
57
  server.on("listening", () => {
58
+ const url = `http://127.0.0.1:${port}`;
44
59
  console.log(
45
- `🔍 MCPJam Inspector is up and running at http://127.0.0.1:${port} 🚀`,
60
+ `🔍 MCPJam Inspector is up and running at \u001B]8;;${url}\u0007${url}\u001B]8;;\u0007 🚀`,
46
61
  );
47
- });
48
- server.on("error", (err) => {
49
- if (err.message.includes(`EADDRINUSE`)) {
50
- console.error(
51
- `❌ MCPJam Inspector PORT IS IN USE at http://127.0.0.1:${port} ❌ `,
62
+
63
+ if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
64
+ console.log(
65
+ `🌐 Opening browser at \u001B]8;;${url}\u0007${url}\u001B]8;;\u0007`,
52
66
  );
53
- } else {
54
- throw err;
67
+ open(url);
55
68
  }
56
69
  });
70
+
57
71
  server.listen(port);
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import open from "open";
4
3
  import { resolve, dirname } from "path";
5
4
  import { spawnPromise } from "spawn-rx";
6
5
  import { fileURLToPath } from "url";
@@ -87,6 +86,7 @@ async function main() {
87
86
  abort.abort();
88
87
  console.log("\n\x1b[31m%s\x1b[0m", "⚠️ Shutting down MCP Inspector..."); // Red color
89
88
  });
89
+
90
90
  let server, serverOk;
91
91
  try {
92
92
  server = spawnPromise(
@@ -116,19 +116,14 @@ async function main() {
116
116
  if (serverOk) {
117
117
  try {
118
118
  console.log("\x1b[32m%s\x1b[0m", "✅ Server initialized successfully"); // Green color
119
- console.log(
120
- "\x1b[36m%s\x1b[0m",
121
- `🌐 Opening browser at http://127.0.0.1:${CLIENT_PORT}`,
122
- );
123
-
124
- if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
125
- open(`http://127.0.0.1:${CLIENT_PORT}`);
126
- }
127
-
128
119
  console.log("\x1b[33m%s\x1b[0m", "🖥️ Starting client interface...");
129
120
 
130
121
  await spawnPromise("node", [inspectorClientPath], {
131
- env: { ...process.env, PORT: CLIENT_PORT },
122
+ env: {
123
+ ...process.env,
124
+ PORT: CLIENT_PORT,
125
+ MCP_AUTO_OPEN_ENABLED: process.env.MCP_AUTO_OPEN_ENABLED ?? "true",
126
+ },
132
127
  signal: abort.signal,
133
128
  echoOutput: true,
134
129
  });
@@ -1,4 +1,4 @@
1
- import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-B_8Xm9gw.js";
1
+ import { u as useToast, r as reactExports, S as SESSION_KEYS, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, I as InspectorOAuthClientProvider, a as auth } from "./index-Bgrnc5s2.js";
2
2
  const OAuthCallback = ({ onConnect }) => {
3
3
  const { toast } = useToast();
4
4
  const hasProcessedRef = reactExports.useRef(false);
@@ -45,6 +45,7 @@ const OAuthCallback = ({ onConnect }) => {
45
45
  onConnect(serverUrl);
46
46
  };
47
47
  handleCallback().finally(() => {
48
+ sessionStorage.removeItem(SESSION_KEYS.TRANSPORT_TYPE);
48
49
  window.history.replaceState({}, document.title, "/");
49
50
  });
50
51
  }, [toast, onConnect]);
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-B_8Xm9gw.js";
1
+ import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-Bgrnc5s2.js";
2
2
  const OAuthDebugCallback = ({ onConnect }) => {
3
3
  reactExports.useEffect(() => {
4
4
  let isProcessed = false;