Skip to content

Re-enable inspector auto-open & reloads post security fixes #513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ The MCP Inspector proxy server requires authentication by default. When starting
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
```

This token must be included as a Bearer token in the Authorization header for all requests to the server. When authentication is enabled, auto-open is disabled by default to ensure you use the secure URL.
This token must be included as a Bearer token in the Authorization header for all requests to the server. The inspector will automatically open your browser with the token pre-filled in the URL.

**Recommended: Use the pre-filled URL** - Click or copy the link shown in the console to open the inspector with the token already configured.
**Automatic browser opening** - The inspector now automatically opens your browser with the token pre-filled in the URL when authentication is enabled.

**Alternative: Manual configuration** - If you already have the inspector open:

Expand Down Expand Up @@ -188,13 +188,13 @@ ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:800

The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI:

| Setting | Description | Default |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------- |
| `MCP_SERVER_REQUEST_TIMEOUT` | Timeout for requests to the MCP server (ms) | 10000 |
| `MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS` | Reset timeout on progress notifications | true |
| `MCP_REQUEST_MAX_TOTAL_TIMEOUT` | Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications) | 60000 |
| `MCP_PROXY_FULL_ADDRESS` | Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577 | "" |
| `MCP_AUTO_OPEN_ENABLED` | Enable automatic browser opening when inspector starts. Only as environment var, not configurable in browser. | true |
| Setting | Description | Default |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `MCP_SERVER_REQUEST_TIMEOUT` | Timeout for requests to the MCP server (ms) | 10000 |
| `MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS` | Reset timeout on progress notifications | true |
| `MCP_REQUEST_MAX_TOTAL_TIMEOUT` | Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications) | 60000 |
| `MCP_PROXY_FULL_ADDRESS` | Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577 | "" |
| `MCP_AUTO_OPEN_ENABLED` | Enable automatic browser opening when inspector starts (works with authentication enabled). Only as environment var, not configurable in browser. | true |

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

Expand Down
248 changes: 198 additions & 50 deletions client/bin/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,175 @@

import open from "open";
import { resolve, dirname } from "path";
import { spawnPromise } from "spawn-rx";
import { spawnPromise, spawn } from "spawn-rx";
import { fileURLToPath } from "url";
import { randomBytes } from "crypto";

const __dirname = dirname(fileURLToPath(import.meta.url));

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms, true));
}

async function startDevServer(serverOptions) {
const { SERVER_PORT, CLIENT_PORT, sessionToken, envVars, abort } =
serverOptions;
const serverCommand = "npx";
const serverArgs = ["tsx", "watch", "--clear-screen=false", "src/index.ts"];
const isWindows = process.platform === "win32";

const spawnOptions = {
cwd: resolve(__dirname, "../..", "server"),
env: {
...process.env,
PORT: SERVER_PORT,
CLIENT_PORT: CLIENT_PORT,
MCP_PROXY_TOKEN: sessionToken,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal,
echoOutput: true,
};

// For Windows, we need to use stdin: 'ignore' to simulate < NUL
if (isWindows) {
spawnOptions.stdin = "ignore";
}

const server = spawn(serverCommand, serverArgs, spawnOptions);

// Give server time to start
const serverOk = await Promise.race([
new Promise((resolve) => {
server.subscribe({
complete: () => resolve(false),
error: () => resolve(false),
next: () => {}, // We're using echoOutput
});
}),
delay(3000).then(() => true),
]);

return { server, serverOk };
}

async function startProdServer(serverOptions) {
const {
SERVER_PORT,
CLIENT_PORT,
sessionToken,
envVars,
abort,
command,
mcpServerArgs,
} = serverOptions;
const inspectorServerPath = resolve(
__dirname,
"../..",
"server",
"build",
"index.js",
);

const server = spawnPromise(
"node",
[
inspectorServerPath,
...(command ? [`--env`, command] : []),
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
],
{
env: {
...process.env,
PORT: SERVER_PORT,
CLIENT_PORT: CLIENT_PORT,
MCP_PROXY_TOKEN: sessionToken,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal,
echoOutput: true,
},
);

// Make sure server started before starting client
const serverOk = await Promise.race([server, delay(2 * 1000)]);

return { server, serverOk };
}

async function startDevClient(clientOptions) {
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
clientOptions;
const clientCommand = "npx";
const clientArgs = ["vite", "--port", CLIENT_PORT];

const client = spawn(clientCommand, clientArgs, {
cwd: resolve(__dirname, ".."),
env: { ...process.env, PORT: CLIENT_PORT },
signal: abort.signal,
echoOutput: true,
});

// Auto-open browser after vite starts
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
const url = authDisabled
? `http://127.0.0.1:${CLIENT_PORT}`
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;

// Give vite time to start before opening browser
setTimeout(() => {
open(url);
console.log(`\n🔗 Opening browser at: ${url}\n`);
}, 3000);
}

await new Promise((resolve) => {
client.subscribe({
complete: resolve,
error: (err) => {
if (!cancelled || process.env.DEBUG) {
console.error("Client error:", err);
}
resolve(null);
},
next: () => {}, // We're using echoOutput
});
});
}

async function startProdClient(clientOptions) {
const { CLIENT_PORT, authDisabled, sessionToken, abort } = clientOptions;
const inspectorClientPath = resolve(
__dirname,
"../..",
"client",
"bin",
"client.js",
);

// Auto-open browser with token
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
const url = authDisabled
? `http://127.0.0.1:${CLIENT_PORT}`
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
open(url);
}

await spawnPromise("node", [inspectorClientPath], {
env: { ...process.env, PORT: CLIENT_PORT },
signal: abort.signal,
echoOutput: true,
});
}

async function main() {
// Parse command line arguments
const args = process.argv.slice(2);
const envVars = {};
const mcpServerArgs = [];
let command = null;
let parsingFlags = true;
let isDev = false;

for (let i = 0; i < args.length; i++) {
const arg = args[i];
Expand All @@ -27,6 +180,11 @@ async function main() {
continue;
}

if (parsingFlags && arg === "--dev") {
isDev = true;
continue;
}

if (parsingFlags && arg === "-e" && i + 1 < args.length) {
const envVar = args[++i];
const equalsIndex = envVar.indexOf("=");
Expand All @@ -38,34 +196,25 @@ async function main() {
} else {
envVars[envVar] = "";
}
} else if (!command) {
} else if (!command && !isDev) {
command = arg;
} else {
} else if (!isDev) {
mcpServerArgs.push(arg);
}
}

const inspectorServerPath = resolve(
__dirname,
"../..",
"server",
"build",
"index.js",
);

// Path to the client entry point
const inspectorClientPath = resolve(
__dirname,
"../..",
"client",
"bin",
"client.js",
);

const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";

console.log("Starting MCP inspector...");
console.log(
isDev
? "Starting MCP inspector in development mode..."
: "Starting MCP inspector...",
);

// Generate session token for authentication
const sessionToken = randomBytes(32).toString("hex");
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;

const abort = new AbortController();

Expand All @@ -74,42 +223,41 @@ async function main() {
cancelled = true;
abort.abort();
});

let server, serverOk;

try {
server = spawnPromise(
"node",
[
inspectorServerPath,
...(command ? [`--env`, command] : []),
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
],
{
env: {
...process.env,
PORT: SERVER_PORT,
MCP_ENV_VARS: JSON.stringify(envVars),
},
signal: abort.signal,
echoOutput: true,
},
);
const serverOptions = {
SERVER_PORT,
CLIENT_PORT,
sessionToken,
envVars,
abort,
command,
mcpServerArgs,
};

// Make sure server started before starting client
serverOk = await Promise.race([server, delay(2 * 1000)]);
const result = isDev
? await startDevServer(serverOptions)
: await startProdServer(serverOptions);

server = result.server;
serverOk = result.serverOk;
} catch (error) {}

if (serverOk) {
try {
// Only auto-open when auth is disabled
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && authDisabled) {
open(`http://127.0.0.1:${CLIENT_PORT}`);
}
await spawnPromise("node", [inspectorClientPath], {
env: { ...process.env, PORT: CLIENT_PORT },
signal: abort.signal,
echoOutput: true,
});
const clientOptions = {
CLIENT_PORT,
authDisabled,
sessionToken,
abort,
cancelled,
};

await (isDev
? startDevClient(clientOptions)
: startProdClient(clientOptions));
} catch (e) {
if (!cancelled || process.env.DEBUG) throw e;
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"build-client": "cd client && npm run build",
"build-cli": "cd cli && npm run build",
"clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install",
"dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
"dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
"dev": "node client/bin/start.js --dev",
"dev:windows": "node client/bin/start.js --dev",
"start": "node client/bin/start.js",
"start-server": "cd server && npm run start",
"start-client": "cd client && npm run preview",
Expand Down
6 changes: 4 additions & 2 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ app.use((req, res, next) => {
const webAppTransports: Map<string, Transport> = new Map<string, Transport>(); // Web app transports by web app sessionId
const serverTransports: Map<string, Transport> = new Map<string, Transport>(); // Server Transports by web app sessionId

const sessionToken = randomBytes(32).toString("hex");
// Use provided token from environment or generate a new one
const sessionToken =
process.env.MCP_PROXY_TOKEN || randomBytes(32).toString("hex");
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;

// Origin validation middleware to prevent DNS rebinding attacks
Expand Down Expand Up @@ -544,7 +546,7 @@ server.on("listening", () => {
const clientPort = process.env.CLIENT_PORT || "6274";
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
console.log(
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`,
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n`,
);
} else {
console.log(
Expand Down