Skip to content

Commit 111e282

Browse files
feat: add session token authentication to proxy server
- Generate random session token on server startup using crypto.randomBytes - Add authentication middleware requiring Bearer token for all sensitive endpoints - Apply auth to /mcp, /stdio, /sse, and /message endpoints - Display session token in console on startup - Add DANGEROUSLY_OMIT_AUTH environment variable to disable auth for development - Update README with authentication documentation This prevents unauthorized access to the proxy server's ability to execute local processes and connect to MCP servers.
1 parent e8e9909 commit 111e282

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,35 @@ The inspector supports bearer token authentication for SSE connections. Enter yo
137137

138138
The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
139139

140+
#### Authentication
141+
142+
The MCP Inspector proxy server requires authentication by default. When starting the server, a random session token is generated and printed to the console:
143+
144+
```
145+
🔑 Session token: 3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
146+
147+
🔗 Open inspector with token pre-filled:
148+
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
149+
```
150+
151+
This token must be included as a Bearer token in the Authorization header for all requests to the server.
152+
153+
**Option 1: Use the pre-filled URL** - Click the link shown in the console to open the inspector with the token already configured.
154+
155+
**Option 2: Manual configuration** - If you already have the inspector open:
156+
157+
1. Click the "Configuration" button in the sidebar
158+
2. Find "Proxy Session Token" and enter the token displayed in the proxy console
159+
3. Click "Save" to apply the configuration
160+
161+
The token will be saved in your browser's local storage for future use.
162+
163+
If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGEROUSLY_OMIT_AUTH` environment variable:
164+
165+
```bash
166+
DANGEROUSLY_OMIT_AUTH=true npm start
167+
```
168+
140169
#### Local-only Binding
141170

142171
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:

server/src/index.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
1919
import express from "express";
2020
import { findActualExecutable } from "spawn-rx";
2121
import mcpProxy from "./mcpProxy.js";
22-
import { randomUUID } from "node:crypto";
22+
import { randomUUID, randomBytes } from "node:crypto";
2323

2424
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
2525
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
@@ -89,6 +89,25 @@ 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');
93+
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
94+
95+
const authMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => {
96+
if (authDisabled) {
97+
return next();
98+
}
99+
100+
const authHeader = req.headers.authorization;
101+
if (!authHeader || authHeader !== `Bearer ${sessionToken}`) {
102+
res.status(401).json({
103+
error: "Unauthorized",
104+
message: "Authentication required. Use the session token shown in the console when starting the server."
105+
});
106+
return;
107+
}
108+
next();
109+
};
110+
92111
const createTransport = async (req: express.Request): Promise<Transport> => {
93112
const query = req.query;
94113
console.log("Query parameters:", JSON.stringify(query));
@@ -150,7 +169,7 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
150169
}
151170
};
152171

153-
app.get("/mcp", async (req, res) => {
172+
app.get("/mcp", authMiddleware, async (req, res) => {
154173
const sessionId = req.headers["mcp-session-id"] as string;
155174
console.log(`Received GET message for sessionId ${sessionId}`);
156175
try {
@@ -169,7 +188,7 @@ app.get("/mcp", async (req, res) => {
169188
}
170189
});
171190

172-
app.post("/mcp", async (req, res) => {
191+
app.post("/mcp", authMiddleware, async (req, res) => {
173192
const sessionId = req.headers["mcp-session-id"] as string | undefined;
174193
let serverTransport: Transport | undefined;
175194
if (!sessionId) {
@@ -239,7 +258,7 @@ app.post("/mcp", async (req, res) => {
239258
}
240259
});
241260

242-
app.delete("/mcp", async (req, res) => {
261+
app.delete("/mcp", authMiddleware, async (req, res) => {
243262
const sessionId = req.headers["mcp-session-id"] as string | undefined;
244263
console.log(`Received DELETE message for sessionId ${sessionId}`);
245264
let serverTransport: Transport | undefined;
@@ -266,7 +285,7 @@ app.delete("/mcp", async (req, res) => {
266285
}
267286
});
268287

269-
app.get("/stdio", async (req, res) => {
288+
app.get("/stdio", authMiddleware, async (req, res) => {
270289
try {
271290
console.log("New STDIO connection request");
272291
let serverTransport: Transport | undefined;
@@ -328,7 +347,7 @@ app.get("/stdio", async (req, res) => {
328347
}
329348
});
330349

331-
app.get("/sse", async (req, res) => {
350+
app.get("/sse", authMiddleware, async (req, res) => {
332351
try {
333352
console.log(
334353
"New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp",
@@ -377,7 +396,7 @@ app.get("/sse", async (req, res) => {
377396
}
378397
});
379398

380-
app.post("/message", async (req, res) => {
399+
app.post("/message", authMiddleware, async (req, res) => {
381400
try {
382401
const sessionId = req.query.sessionId;
383402
console.log(`Received POST message for sessionId ${sessionId}`);
@@ -421,6 +440,17 @@ const HOST = process.env.HOST || '127.0.0.1';
421440
const server = app.listen(PORT, HOST);
422441
server.on("listening", () => {
423442
console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`);
443+
if (!authDisabled) {
444+
console.log(`🔑 Session token: ${sessionToken}`);
445+
console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`);
446+
447+
// Display clickable URL with pre-filled token
448+
const clientPort = process.env.CLIENT_PORT || '6274';
449+
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
450+
console.log(`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}`);
451+
} else {
452+
console.log(`⚠️ WARNING: Authentication is disabled. This is not recommended.`);
453+
}
424454
});
425455
server.on("error", (err) => {
426456
if (err.message.includes(`EADDRINUSE`)) {

0 commit comments

Comments
 (0)