Skip to content

Commit d06f3b5

Browse files
feat: Enable auto-open with authentication and unify start scripts
- Unified start.js to handle both dev and production modes with --dev flag - Generate session token in parent process, pass via MCP_PROXY_TOKEN env var - Enable browser auto-open even when authentication is enabled by including token in URL - Auto-reloads now work seamlessly with persistent tokens across hot reloads - Simplified npm scripts - dev and dev:windows now use the same unified script - Better developer experience with consistent token handling The token is generated once per session and remains stable through server/client reloads, making development smoother while maintaining security. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 21b85cd commit d06f3b5

File tree

3 files changed

+154
-46
lines changed

3 files changed

+154
-46
lines changed

client/bin/start.js

Lines changed: 148 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import open from "open";
44
import { resolve, dirname } from "path";
5-
import { spawnPromise } from "spawn-rx";
5+
import { spawnPromise, spawn } from "spawn-rx";
66
import { fileURLToPath } from "url";
7+
import { randomBytes } from "crypto";
78

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

@@ -18,6 +19,7 @@ async function main() {
1819
const mcpServerArgs = [];
1920
let command = null;
2021
let parsingFlags = true;
22+
let isDev = false;
2123

2224
for (let i = 0; i < args.length; i++) {
2325
const arg = args[i];
@@ -27,6 +29,11 @@ async function main() {
2729
continue;
2830
}
2931

32+
if (parsingFlags && arg === "--dev") {
33+
isDev = true;
34+
continue;
35+
}
36+
3037
if (parsingFlags && arg === "-e" && i + 1 < args.length) {
3138
const envVar = args[++i];
3239
const equalsIndex = envVar.indexOf("=");
@@ -38,34 +45,25 @@ async function main() {
3845
} else {
3946
envVars[envVar] = "";
4047
}
41-
} else if (!command) {
48+
} else if (!command && !isDev) {
4249
command = arg;
43-
} else {
50+
} else if (!isDev) {
4451
mcpServerArgs.push(arg);
4552
}
4653
}
4754

48-
const inspectorServerPath = resolve(
49-
__dirname,
50-
"../..",
51-
"server",
52-
"build",
53-
"index.js",
54-
);
55-
56-
// Path to the client entry point
57-
const inspectorClientPath = resolve(
58-
__dirname,
59-
"../..",
60-
"client",
61-
"bin",
62-
"client.js",
63-
);
64-
6555
const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
6656
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";
6757

68-
console.log("Starting MCP inspector...");
58+
console.log(
59+
isDev
60+
? "Starting MCP inspector in development mode..."
61+
: "Starting MCP inspector...",
62+
);
63+
64+
// Generate session token for authentication
65+
const sessionToken = randomBytes(32).toString("hex");
66+
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
6967

7068
const abort = new AbortController();
7169

@@ -74,42 +72,150 @@ async function main() {
7472
cancelled = true;
7573
abort.abort();
7674
});
75+
7776
let server, serverOk;
77+
7878
try {
79-
server = spawnPromise(
80-
"node",
81-
[
82-
inspectorServerPath,
83-
...(command ? [`--env`, command] : []),
84-
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
85-
],
86-
{
79+
if (isDev) {
80+
// Development mode - use tsx watch
81+
const serverCommand = "npx";
82+
const serverArgs = [
83+
"tsx",
84+
"watch",
85+
"--clear-screen=false",
86+
"src/index.ts",
87+
];
88+
const isWindows = process.platform === "win32";
89+
90+
const serverOptions = {
91+
cwd: resolve(__dirname, "../..", "server"),
8792
env: {
8893
...process.env,
8994
PORT: SERVER_PORT,
95+
CLIENT_PORT: CLIENT_PORT,
96+
MCP_PROXY_TOKEN: sessionToken,
9097
MCP_ENV_VARS: JSON.stringify(envVars),
9198
},
9299
signal: abort.signal,
93100
echoOutput: true,
94-
},
95-
);
101+
};
102+
103+
// For Windows, we need to use stdin: 'ignore' to simulate < NUL
104+
if (isWindows) {
105+
serverOptions.stdin = "ignore";
106+
}
107+
108+
server = spawn(serverCommand, serverArgs, serverOptions);
109+
110+
// Give server time to start
111+
serverOk = await Promise.race([
112+
new Promise((resolve) => {
113+
server.subscribe({
114+
complete: () => resolve(false),
115+
error: () => resolve(false),
116+
next: () => {}, // We're using echoOutput
117+
});
118+
}),
119+
delay(3000).then(() => true),
120+
]);
121+
} else {
122+
// Production mode - use built files
123+
const inspectorServerPath = resolve(
124+
__dirname,
125+
"../..",
126+
"server",
127+
"build",
128+
"index.js",
129+
);
130+
131+
server = spawnPromise(
132+
"node",
133+
[
134+
inspectorServerPath,
135+
...(command ? [`--env`, command] : []),
136+
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
137+
],
138+
{
139+
env: {
140+
...process.env,
141+
PORT: SERVER_PORT,
142+
CLIENT_PORT: CLIENT_PORT,
143+
MCP_PROXY_TOKEN: sessionToken,
144+
MCP_ENV_VARS: JSON.stringify(envVars),
145+
},
146+
signal: abort.signal,
147+
echoOutput: true,
148+
},
149+
);
96150

97-
// Make sure server started before starting client
98-
serverOk = await Promise.race([server, delay(2 * 1000)]);
151+
// Make sure server started before starting client
152+
serverOk = await Promise.race([server, delay(2 * 1000)]);
153+
}
99154
} catch (error) {}
100155

101156
if (serverOk) {
102157
try {
103-
// Only auto-open when auth is disabled
104-
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
105-
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && authDisabled) {
106-
open(`http://127.0.0.1:${CLIENT_PORT}`);
158+
if (isDev) {
159+
// Development mode - use vite
160+
const clientCommand = "npx";
161+
const clientArgs = ["vite", "--port", CLIENT_PORT];
162+
163+
const client = spawn(clientCommand, clientArgs, {
164+
cwd: resolve(__dirname, ".."),
165+
env: { ...process.env, PORT: CLIENT_PORT },
166+
signal: abort.signal,
167+
echoOutput: true,
168+
});
169+
170+
// Auto-open browser after vite starts
171+
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
172+
const url = authDisabled
173+
? `http://127.0.0.1:${CLIENT_PORT}`
174+
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
175+
176+
// Give vite time to start before opening browser
177+
setTimeout(() => {
178+
open(url);
179+
console.log(`\n🔗 Opening browser at: ${url}\n`);
180+
}, 3000);
181+
}
182+
183+
await new Promise((resolve) => {
184+
client.subscribe({
185+
complete: resolve,
186+
error: (err) => {
187+
if (!cancelled || process.env.DEBUG) {
188+
console.error("Client error:", err);
189+
}
190+
resolve(null);
191+
},
192+
next: () => {}, // We're using echoOutput
193+
});
194+
});
195+
} else {
196+
// Production mode - use client.js
197+
const inspectorClientPath = resolve(
198+
__dirname,
199+
"../..",
200+
"client",
201+
"bin",
202+
"client.js",
203+
);
204+
205+
// Auto-open browser with token
206+
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
207+
const url = authDisabled
208+
? `http://127.0.0.1:${CLIENT_PORT}`
209+
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
210+
open(url);
211+
}
212+
213+
await spawnPromise("node", [inspectorClientPath], {
214+
env: { ...process.env, PORT: CLIENT_PORT },
215+
signal: abort.signal,
216+
echoOutput: true,
217+
});
107218
}
108-
await spawnPromise("node", [inspectorClientPath], {
109-
env: { ...process.env, PORT: CLIENT_PORT },
110-
signal: abort.signal,
111-
echoOutput: true,
112-
});
113219
} catch (e) {
114220
if (!cancelled || process.env.DEBUG) throw e;
115221
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"build-client": "cd client && npm run build",
2828
"build-cli": "cd cli && npm run build",
2929
"clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install",
30-
"dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
31-
"dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
30+
"dev": "node client/bin/start.js --dev",
31+
"dev:windows": "node client/bin/start.js --dev",
3232
"start": "node client/bin/start.js",
3333
"start-server": "cd server && npm run start",
3434
"start-client": "cd client && npm run preview",

server/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ app.use((req, res, next) => {
8989
const webAppTransports: Map<string, Transport> = new Map<string, Transport>(); // Web app transports by web app sessionId
9090
const serverTransports: Map<string, Transport> = new Map<string, Transport>(); // Server Transports by web app sessionId
9191

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

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

0 commit comments

Comments
 (0)