Merge pull request #529 from modelcontextprotocol/fweinberger/client-localhost
fix: bind client to localhost to match server
This commit is contained in:
commit
eb5be0d853
5 changed files with 36 additions and 30 deletions
|
|
@ -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
|
1. Fork the repository and clone it locally
|
||||||
2. Install dependencies with `npm install`
|
2. Install dependencies with `npm install`
|
||||||
3. Run `npm run dev` to start both client and server in development mode
|
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
|
## Development Process & Pull Requests
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,20 +168,20 @@ DANGEROUSLY_OMIT_AUTH=true npm start
|
||||||
|
|
||||||
#### Local-only Binding
|
#### 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
|
```bash
|
||||||
HOST=0.0.0.0 npm start
|
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
|
#### 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):
|
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
|
```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
|
### Configuration
|
||||||
|
|
|
||||||
|
|
@ -39,19 +39,20 @@ const server = http.createServer((request, response) => {
|
||||||
return handler(request, response, handlerOptions);
|
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", () => {
|
server.on("listening", () => {
|
||||||
console.log(
|
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) => {
|
server.on("error", (err) => {
|
||||||
if (err.message.includes(`EADDRINUSE`)) {
|
if (err.message.includes(`EADDRINUSE`)) {
|
||||||
console.error(
|
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 {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
server.listen(port);
|
server.listen(port, host);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,14 @@ function delay(ms) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms, true));
|
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) {
|
async function startDevServer(serverOptions) {
|
||||||
const { SERVER_PORT, CLIENT_PORT, sessionToken, envVars, abort } =
|
const { SERVER_PORT, CLIENT_PORT, sessionToken, envVars, abort } =
|
||||||
serverOptions;
|
serverOptions;
|
||||||
|
|
@ -23,7 +31,7 @@ async function startDevServer(serverOptions) {
|
||||||
cwd: resolve(__dirname, "../..", "server"),
|
cwd: resolve(__dirname, "../..", "server"),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PORT: SERVER_PORT,
|
SERVER_PORT: SERVER_PORT,
|
||||||
CLIENT_PORT: CLIENT_PORT,
|
CLIENT_PORT: CLIENT_PORT,
|
||||||
MCP_PROXY_TOKEN: sessionToken,
|
MCP_PROXY_TOKEN: sessionToken,
|
||||||
MCP_ENV_VARS: JSON.stringify(envVars),
|
MCP_ENV_VARS: JSON.stringify(envVars),
|
||||||
|
|
@ -82,7 +90,7 @@ async function startProdServer(serverOptions) {
|
||||||
{
|
{
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PORT: SERVER_PORT,
|
SERVER_PORT: SERVER_PORT,
|
||||||
CLIENT_PORT: CLIENT_PORT,
|
CLIENT_PORT: CLIENT_PORT,
|
||||||
MCP_PROXY_TOKEN: sessionToken,
|
MCP_PROXY_TOKEN: sessionToken,
|
||||||
MCP_ENV_VARS: JSON.stringify(envVars),
|
MCP_ENV_VARS: JSON.stringify(envVars),
|
||||||
|
|
@ -102,20 +110,19 @@ async function startDevClient(clientOptions) {
|
||||||
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
|
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
|
||||||
clientOptions;
|
clientOptions;
|
||||||
const clientCommand = "npx";
|
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, {
|
const client = spawn(clientCommand, clientArgs, {
|
||||||
cwd: resolve(__dirname, ".."),
|
cwd: resolve(__dirname, ".."),
|
||||||
env: { ...process.env, PORT: CLIENT_PORT },
|
env: { ...process.env, CLIENT_PORT: CLIENT_PORT },
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
echoOutput: true,
|
echoOutput: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-open browser after vite starts
|
// Auto-open browser after vite starts
|
||||||
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
|
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
|
||||||
const url = authDisabled
|
const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken);
|
||||||
? `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
|
// Give vite time to start before opening browser
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -139,7 +146,8 @@ async function startDevClient(clientOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startProdClient(clientOptions) {
|
async function startProdClient(clientOptions) {
|
||||||
const { CLIENT_PORT, authDisabled, sessionToken, abort } = clientOptions;
|
const { CLIENT_PORT, authDisabled, sessionToken, abort, cancelled } =
|
||||||
|
clientOptions;
|
||||||
const inspectorClientPath = resolve(
|
const inspectorClientPath = resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
"../..",
|
"../..",
|
||||||
|
|
@ -148,16 +156,14 @@ async function startProdClient(clientOptions) {
|
||||||
"client.js",
|
"client.js",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-open browser with token
|
// Only auto-open browser if not cancelled
|
||||||
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
|
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && !cancelled) {
|
||||||
const url = authDisabled
|
const url = getClientUrl(CLIENT_PORT, authDisabled, sessionToken);
|
||||||
? `http://127.0.0.1:${CLIENT_PORT}`
|
|
||||||
: `http://127.0.0.1:${CLIENT_PORT}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
|
|
||||||
open(url);
|
open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
await spawnPromise("node", [inspectorClientPath], {
|
await spawnPromise("node", [inspectorClientPath], {
|
||||||
env: { ...process.env, PORT: CLIENT_PORT },
|
env: { ...process.env, CLIENT_PORT: CLIENT_PORT },
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
echoOutput: true,
|
echoOutput: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,10 @@ const originValidationMiddleware = (
|
||||||
|
|
||||||
// Default origins based on CLIENT_PORT or use environment variable
|
// Default origins based on CLIENT_PORT or use environment variable
|
||||||
const clientPort = process.env.CLIENT_PORT || "6274";
|
const clientPort = process.env.CLIENT_PORT || "6274";
|
||||||
const defaultOrigins = [
|
const defaultOrigin = `http://localhost:${clientPort}`;
|
||||||
`http://localhost:${clientPort}`,
|
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [
|
||||||
`http://127.0.0.1:${clientPort}`,
|
defaultOrigin,
|
||||||
];
|
];
|
||||||
const allowedOrigins =
|
|
||||||
process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins;
|
|
||||||
|
|
||||||
if (origin && !allowedOrigins.includes(origin)) {
|
if (origin && !allowedOrigins.includes(origin)) {
|
||||||
console.error(`Invalid origin: ${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 PORT = parseInt(process.env.SERVER_PORT || "6277", 10);
|
||||||
const HOST = process.env.HOST || "127.0.0.1";
|
const HOST = process.env.HOST || "localhost";
|
||||||
|
|
||||||
const server = app.listen(PORT, HOST);
|
const server = app.listen(PORT, HOST);
|
||||||
server.on("listening", () => {
|
server.on("listening", () => {
|
||||||
|
|
@ -544,7 +542,8 @@ server.on("listening", () => {
|
||||||
|
|
||||||
// Display clickable URL with pre-filled token
|
// Display clickable URL with pre-filled token
|
||||||
const clientPort = process.env.CLIENT_PORT || "6274";
|
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(
|
console.log(
|
||||||
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n`,
|
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue