Skip to content

Commit eb5be0d

Browse files
Merge pull request #529 from modelcontextprotocol/fweinberger/client-localhost
fix: bind client to localhost to match server
2 parents 5e92e88 + b28a10e commit eb5be0d

File tree

5 files changed

+36
-30
lines changed

5 files changed

+36
-30
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve
77
1. Fork the repository and clone it locally
88
2. Install dependencies with `npm install`
99
3. Run `npm run dev` to start both client and server in development mode
10-
4. Use the web UI at http://127.0.0.1:6274 to interact with the inspector
10+
4. Use the web UI at http://localhost:6274 to interact with the inspector
1111

1212
## Development Process & Pull Requests
1313

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,20 @@ DANGEROUSLY_OMIT_AUTH=true npm start
168168

169169
#### Local-only Binding
170170

171-
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:
171+
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:
172172

173173
```bash
174174
HOST=0.0.0.0 npm start
175175
```
176176

177-
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes.
177+
**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.
178178

179179
#### DNS Rebinding Protection
180180

181181
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):
182182

183183
```bash
184-
ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start
184+
ALLOWED_ORIGINS=http://localhost:6274,http://localhost:8000 npm start
185185
```
186186

187187
### Configuration

client/bin/client.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,20 @@ const server = http.createServer((request, response) => {
3939
return handler(request, response, handlerOptions);
4040
});
4141

42-
const port = process.env.PORT || 6274;
42+
const port = parseInt(process.env.CLIENT_PORT || "6274", 10);
43+
const host = process.env.HOST || "localhost";
4344
server.on("listening", () => {
4445
console.log(
45-
`🔍 MCP Inspector is up and running at http://127.0.0.1:${port} 🚀`,
46+
`🔍 MCP Inspector is up and running at http://${host}:${port} 🚀`,
4647
);
4748
});
4849
server.on("error", (err) => {
4950
if (err.message.includes(`EADDRINUSE`)) {
5051
console.error(
51-
`❌ MCP Inspector PORT IS IN USE at http://127.0.0.1:${port} ❌ `,
52+
`❌ MCP Inspector PORT IS IN USE at http://${host}:${port} ❌ `,
5253
);
5354
} else {
5455
throw err;
5556
}
5657
});
57-
server.listen(port);
58+
server.listen(port, host);

client/bin/start.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ function delay(ms) {
1212
return new Promise((resolve) => setTimeout(resolve, ms, true));
1313
}
1414

15+
function getClientUrl(port, authDisabled, sessionToken) {
16+
const host = process.env.HOST || "localhost";
17+
const baseUrl = `http://${host}:${port}`;
18+
return authDisabled
19+
? baseUrl
20+
: `${baseUrl}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
21+
}
22+
1523
async function startDevServer(serverOptions) {
1624
const { SERVER_PORT, CLIENT_PORT, sessionToken, envVars, abort } =
1725
serverOptions;
@@ -23,7 +31,7 @@ async function startDevServer(serverOptions) {
2331
cwd: resolve(__dirname, "../..", "server"),
2432
env: {
2533
...process.env,
26-
PORT: SERVER_PORT,
34+
SERVER_PORT: SERVER_PORT,
2735
CLIENT_PORT: CLIENT_PORT,
2836
MCP_PROXY_TOKEN: sessionToken,
2937
MCP_ENV_VARS: JSON.stringify(envVars),
@@ -82,7 +90,7 @@ async function startProdServer(serverOptions) {
8290
{
8391
env: {
8492
...process.env,
85-
PORT: SERVER_PORT,
93+
SERVER_PORT: SERVER_PORT,
8694
CLIENT_PORT: CLIENT_PORT,
8795
MCP_PROXY_TOKEN: sessionToken,
8896
MCP_ENV_VARS: JSON.stringify(envVars),
@@ -102,20 +110,19 @@ async function startDevClient(clientOptions) {
102110
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
103111
clientOptions;
104112
const clientCommand = "npx";
105-
const clientArgs = ["vite", "--port", CLIENT_PORT];
113+
const host = process.env.HOST || "localhost";
114+
const clientArgs = ["vite", "--port", CLIENT_PORT, "--host", host];
106115

107116
const client = spawn(clientCommand, clientArgs, {
108117
cwd: resolve(__dirname, ".."),
109-
env: { ...process.env, PORT: CLIENT_PORT },
118+
env: { ...process.env, CLIENT_PORT: CLIENT_PORT },
110119
signal: abort.signal,
111120
echoOutput: true,
112121
});
113122

114123
// Auto-open browser after vite starts
115124
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
116-
const url = authDisabled
117-
? `http://127.0.0.1:${CLIENT_PORT}`
118-
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
125+
const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken);
119126

120127
// Give vite time to start before opening browser
121128
setTimeout(() => {
@@ -139,7 +146,8 @@ async function startDevClient(clientOptions) {
139146
}
140147

141148
async function startProdClient(clientOptions) {
142-
const { CLIENT_PORT, authDisabled, sessionToken, abort } = clientOptions;
149+
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
150+
clientOptions;
143151
const inspectorClientPath = resolve(
144152
__dirname,
145153
"../..",
@@ -148,16 +156,14 @@ async function startProdClient(clientOptions) {
148156
"client.js",
149157
);
150158

151-
// Auto-open browser with token
152-
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
153-
const url = authDisabled
154-
? `http://127.0.0.1:${CLIENT_PORT}`
155-
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
159+
// Only auto-open browser if not cancelled
160+
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && !cancelled) {
161+
const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken);
156162
open(url);
157163
}
158164

159165
await spawnPromise("node", [inspectorClientPath], {
160-
env: { ...process.env, PORT: CLIENT_PORT },
166+
env: { ...process.env, CLIENT_PORT: CLIENT_PORT },
161167
signal: abort.signal,
162168
echoOutput: true,
163169
});

server/src/index.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,10 @@ const originValidationMiddleware = (
104104

105105
// Default origins based on CLIENT_PORT or use environment variable
106106
const clientPort = process.env.CLIENT_PORT || "6274";
107-
const defaultOrigins = [
108-
`http://localhost:${clientPort}`,
109-
`http://127.0.0.1:${clientPort}`,
107+
const defaultOrigin = `http://localhost:${clientPort}`;
108+
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [
109+
defaultOrigin,
110110
];
111-
const allowedOrigins =
112-
process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins;
113111

114112
if (origin && !allowedOrigins.includes(origin)) {
115113
console.error(`Invalid origin: ${origin}`);
@@ -530,8 +528,8 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
530528
}
531529
});
532530

533-
const PORT = parseInt(process.env.PORT || "6277", 10);
534-
const HOST = process.env.HOST || "127.0.0.1";
531+
const PORT = parseInt(process.env.SERVER_PORT || "6277", 10);
532+
const HOST = process.env.HOST || "localhost";
535533

536534
const server = app.listen(PORT, HOST);
537535
server.on("listening", () => {
@@ -544,7 +542,8 @@ server.on("listening", () => {
544542

545543
// Display clickable URL with pre-filled token
546544
const clientPort = process.env.CLIENT_PORT || "6274";
547-
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
545+
const clientHost = process.env.HOST || "localhost";
546+
const clientUrl = `http://${clientHost}:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
548547
console.log(
549548
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n`,
550549
);

0 commit comments

Comments
 (0)