Skip to content

fix: bind client to localhost to match server #529

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions client/bin/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
34 changes: 20 additions & 14 deletions client/bin/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand All @@ -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(() => {
Expand All @@ -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,
"../..",
Expand All @@ -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,
});
Expand Down
15 changes: 7 additions & 8 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down Expand Up @@ -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", () => {
Expand All @@ -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`,
);
Expand Down