diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1eeeb864..0129a9e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve 1. Fork the repository and clone it locally 2. Install dependencies with `npm install` 3. Run `npm run dev` to start both client and server in development mode -4. Use the web UI at http://127.0.0.1:6274 to interact with the inspector +4. Use the web UI at http://localhost:6274 to interact with the inspector ## Development Process & Pull Requests diff --git a/README.md b/README.md index 0c20df14..6368ed04 100644 --- a/README.md +++ b/README.md @@ -168,20 +168,20 @@ DANGEROUSLY_OMIT_AUTH=true npm start #### Local-only Binding -By default, the MCP Inspector proxy server binds only to `127.0.0.1` (localhost) to prevent network access. This ensures the server is not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable: +By default, both the MCP Inspector proxy server and client bind only to `localhost` to prevent network access. This ensures they are not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable: ```bash HOST=0.0.0.0 npm start ``` -**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes. +**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes and both services to network access. #### DNS Rebinding Protection To prevent DNS rebinding attacks, the MCP Inspector validates the `Origin` header on incoming requests. By default, only requests from the client origin are allowed (respects `CLIENT_PORT` if set, defaulting to port 6274). You can configure additional allowed origins by setting the `ALLOWED_ORIGINS` environment variable (comma-separated list): ```bash -ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start +ALLOWED_ORIGINS=http://localhost:6274,http://localhost:8000 npm start ``` ### Configuration diff --git a/client/bin/client.js b/client/bin/client.js index 7179e19e..e25ecb7c 100755 --- a/client/bin/client.js +++ b/client/bin/client.js @@ -39,19 +39,20 @@ const server = http.createServer((request, response) => { return handler(request, response, handlerOptions); }); -const port = process.env.PORT || 6274; +const port = parseInt(process.env.CLIENT_PORT || "6274", 10); +const host = process.env.HOST || "localhost"; server.on("listening", () => { console.log( - `šŸ” MCP Inspector is up and running at http://127.0.0.1:${port} šŸš€`, + `šŸ” MCP Inspector is up and running at http://${host}:${port} šŸš€`, ); }); server.on("error", (err) => { if (err.message.includes(`EADDRINUSE`)) { console.error( - `āŒ MCP Inspector PORT IS IN USE at http://127.0.0.1:${port} āŒ `, + `āŒ MCP Inspector PORT IS IN USE at http://${host}:${port} āŒ `, ); } else { throw err; } }); -server.listen(port); +server.listen(port, host); diff --git a/client/bin/start.js b/client/bin/start.js index ad75078a..bea3031f 100755 --- a/client/bin/start.js +++ b/client/bin/start.js @@ -12,6 +12,14 @@ function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms, true)); } +function getClientUrl(port, authDisabled, sessionToken) { + const host = process.env.HOST || "localhost"; + const baseUrl = `http://${host}:${port}`; + return authDisabled + ? baseUrl + : `${baseUrl}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`; +} + async function startDevServer(serverOptions) { const { SERVER_PORT, CLIENT_PORT, sessionToken, envVars, abort } = serverOptions; @@ -23,7 +31,7 @@ async function startDevServer(serverOptions) { cwd: resolve(__dirname, "../..", "server"), env: { ...process.env, - PORT: SERVER_PORT, + SERVER_PORT: SERVER_PORT, CLIENT_PORT: CLIENT_PORT, MCP_PROXY_TOKEN: sessionToken, MCP_ENV_VARS: JSON.stringify(envVars), @@ -82,7 +90,7 @@ async function startProdServer(serverOptions) { { env: { ...process.env, - PORT: SERVER_PORT, + SERVER_PORT: SERVER_PORT, CLIENT_PORT: CLIENT_PORT, MCP_PROXY_TOKEN: sessionToken, MCP_ENV_VARS: JSON.stringify(envVars), @@ -102,20 +110,19 @@ async function startDevClient(clientOptions) { const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } = clientOptions; const clientCommand = "npx"; - const clientArgs = ["vite", "--port", CLIENT_PORT]; + const host = process.env.HOST || "localhost"; + const clientArgs = ["vite", "--port", CLIENT_PORT, "--host", host]; const client = spawn(clientCommand, clientArgs, { cwd: resolve(__dirname, ".."), - env: { ...process.env, PORT: CLIENT_PORT }, + env: { ...process.env, CLIENT_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}`; + const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken); // Give vite time to start before opening browser setTimeout(() => { @@ -139,7 +146,8 @@ async function startDevClient(clientOptions) { } async function startProdClient(clientOptions) { - const { CLIENT_PORT, authDisabled, sessionToken, abort } = clientOptions; + const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } = + clientOptions; const inspectorClientPath = resolve( __dirname, "../..", @@ -148,16 +156,14 @@ async function startProdClient(clientOptions) { "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}`; + // Only auto-open browser if not cancelled + if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && !cancelled) { + const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken); open(url); } await spawnPromise("node", [inspectorClientPath], { - env: { ...process.env, PORT: CLIENT_PORT }, + env: { ...process.env, CLIENT_PORT: CLIENT_PORT }, signal: abort.signal, echoOutput: true, }); diff --git a/server/src/index.ts b/server/src/index.ts index 7653597a..89def8ba 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -104,12 +104,10 @@ const originValidationMiddleware = ( // Default origins based on CLIENT_PORT or use environment variable const clientPort = process.env.CLIENT_PORT || "6274"; - const defaultOrigins = [ - `http://localhost:${clientPort}`, - `http://127.0.0.1:${clientPort}`, + const defaultOrigin = `http://localhost:${clientPort}`; + const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [ + defaultOrigin, ]; - const allowedOrigins = - process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins; if (origin && !allowedOrigins.includes(origin)) { console.error(`Invalid origin: ${origin}`); @@ -530,8 +528,8 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => { } }); -const PORT = parseInt(process.env.PORT || "6277", 10); -const HOST = process.env.HOST || "127.0.0.1"; +const PORT = parseInt(process.env.SERVER_PORT || "6277", 10); +const HOST = process.env.HOST || "localhost"; const server = app.listen(PORT, HOST); server.on("listening", () => { @@ -544,7 +542,8 @@ server.on("listening", () => { // Display clickable URL with pre-filled token const clientPort = process.env.CLIENT_PORT || "6274"; - const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`; + const clientHost = process.env.HOST || "localhost"; + const clientUrl = `http://${clientHost}:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`; console.log( `\nšŸ”— Open inspector with token pre-filled:\n ${clientUrl}\n`, );