Merge pull request #504 from modelcontextprotocol/jerome/fix/linting

Fixing linting issues from previous bug fix
This commit is contained in:
Jerome 2025-06-13 18:22:19 +01:00 • committed by GitHub
commit 5bc0eed36f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 304 additions and 257 deletions

0
.git-blame-ignore-revs Normal file
View file

View file

@ -349,7 +349,7 @@ const App = () => {
const headers: HeadersInit = {}; const headers: HeadersInit = {};
const proxyAuthToken = getMCPProxyAuthToken(config); const proxyAuthToken = getMCPProxyAuthToken(config);
if (proxyAuthToken) { if (proxyAuthToken) {
headers['Authorization'] = `Bearer ${proxyAuthToken}`; headers["Authorization"] = `Bearer ${proxyAuthToken}`;
} }
fetch(`${getMCPProxyAddress(config)}/config`, { headers }) fetch(`${getMCPProxyAddress(config)}/config`, { headers })

View file

@ -84,12 +84,6 @@ Object.defineProperty(window, "sessionStorage", {
value: sessionStorageMock, value: sessionStorageMock,
}); });
Object.defineProperty(window, "location", {
value: {
origin: "http://localhost:3000",
},
});
describe("AuthDebugger", () => { describe("AuthDebugger", () => {
const defaultAuthState = EMPTY_DEBUGGER_STATE; const defaultAuthState = EMPTY_DEBUGGER_STATE;
@ -104,7 +98,7 @@ describe("AuthDebugger", () => {
jest.clearAllMocks(); jest.clearAllMocks();
sessionStorageMock.getItem.mockReturnValue(null); sessionStorageMock.getItem.mockReturnValue(null);
// Supress // Suppress console errors in tests to avoid JSDOM navigation noise
jest.spyOn(console, "error").mockImplementation(() => {}); jest.spyOn(console, "error").mockImplementation(() => {});
mockDiscoverOAuthMetadata.mockResolvedValue(mockOAuthMetadata); mockDiscoverOAuthMetadata.mockResolvedValue(mockOAuthMetadata);
@ -447,18 +441,6 @@ describe("AuthDebugger", () => {
it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => { it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => {
const updateAuthState = jest.fn(); const updateAuthState = jest.fn();
// Mock window.location.href setter
const originalLocation = window.location;
const locationMock = {
...originalLocation,
href: "",
origin: "http://localhost:3000",
};
Object.defineProperty(window, "location", {
writable: true,
value: locationMock,
});
// Setup mocks for OAuth flow // Setup mocks for OAuth flow
mockStartAuthorization.mockResolvedValue({ mockStartAuthorization.mockResolvedValue({
authorizationUrl: new URL( authorizationUrl: new URL(

View file

@ -519,6 +519,7 @@ describe("Sidebar Environment Variables", () => {
label: "Request Timeout", label: "Request Timeout",
description: "Timeout for requests to the MCP server (ms)", description: "Timeout for requests to the MCP server (ms)",
value: 5000, value: 5000,
is_session_item: false,
}, },
}), }),
); );
@ -544,6 +545,7 @@ describe("Sidebar Environment Variables", () => {
description: description:
"Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
value: "http://localhost:8080", value: "http://localhost:8080",
is_session_item: false,
}, },
}), }),
); );
@ -569,6 +571,7 @@ describe("Sidebar Environment Variables", () => {
description: description:
"Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
value: 10000, value: 10000,
is_session_item: false,
}, },
}), }),
); );
@ -591,6 +594,7 @@ describe("Sidebar Environment Variables", () => {
label: "Request Timeout", label: "Request Timeout",
description: "Timeout for requests to the MCP server (ms)", description: "Timeout for requests to the MCP server (ms)",
value: 0, value: 0,
is_session_item: false,
}, },
}), }),
); );
@ -637,6 +641,7 @@ describe("Sidebar Environment Variables", () => {
label: "Request Timeout", label: "Request Timeout",
description: "Timeout for requests to the MCP server (ms)", description: "Timeout for requests to the MCP server (ms)",
value: 3000, value: 3000,
is_session_item: false,
}, },
}), }),
); );

View file

@ -246,7 +246,7 @@ export function useConnection({
const proxyAuthToken = getMCPProxyAuthToken(config); const proxyAuthToken = getMCPProxyAuthToken(config);
const headers: HeadersInit = {}; const headers: HeadersInit = {};
if (proxyAuthToken) { if (proxyAuthToken) {
headers['Authorization'] = `Bearer ${proxyAuthToken}`; headers["Authorization"] = `Bearer ${proxyAuthToken}`;
} }
const proxyHealthResponse = await fetch(proxyHealthUrl, { headers }); const proxyHealthResponse = await fetch(proxyHealthUrl, { headers });
const proxyHealth = await proxyHealthResponse.json(); const proxyHealth = await proxyHealthResponse.json();
@ -335,7 +335,7 @@ export function useConnection({
const proxyAuthToken = getMCPProxyAuthToken(config); const proxyAuthToken = getMCPProxyAuthToken(config);
const proxyHeaders: HeadersInit = {}; const proxyHeaders: HeadersInit = {};
if (proxyAuthToken) { if (proxyAuthToken) {
proxyHeaders['Authorization'] = `Bearer ${proxyAuthToken}`; proxyHeaders["Authorization"] = `Bearer ${proxyAuthToken}`;
} }
// Create appropriate transport // Create appropriate transport
@ -356,7 +356,11 @@ export function useConnection({
fetch: ( fetch: (
url: string | URL | globalThis.Request, url: string | URL | globalThis.Request,
init: RequestInit | undefined, init: RequestInit | undefined,
) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), ) =>
fetch(url, {
...init,
headers: { ...headers, ...proxyHeaders },
}),
}, },
requestInit: { requestInit: {
headers: { ...headers, ...proxyHeaders }, headers: { ...headers, ...proxyHeaders },
@ -372,7 +376,11 @@ export function useConnection({
fetch: ( fetch: (
url: string | URL | globalThis.Request, url: string | URL | globalThis.Request,
init: RequestInit | undefined, init: RequestInit | undefined,
) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), ) =>
fetch(url, {
...init,
headers: { ...headers, ...proxyHeaders },
}),
}, },
requestInit: { requestInit: {
headers: { ...headers, ...proxyHeaders }, headers: { ...headers, ...proxyHeaders },
@ -388,7 +396,11 @@ export function useConnection({
fetch: ( fetch: (
url: string | URL | globalThis.Request, url: string | URL | globalThis.Request,
init: RequestInit | undefined, init: RequestInit | undefined,
) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), ) =>
fetch(url, {
...init,
headers: { ...headers, ...proxyHeaders },
}),
}, },
requestInit: { requestInit: {
headers: { ...headers, ...proxyHeaders }, headers: { ...headers, ...proxyHeaders },
@ -471,7 +483,8 @@ export function useConnection({
if (isProxyAuthError(error)) { if (isProxyAuthError(error)) {
toast({ toast({
title: "Proxy Authentication Required", title: "Proxy Authentication Required",
description: "Please enter the session token from the proxy server console in the Configuration settings.", description:
"Please enter the session token from the proxy server console in the Configuration settings.",
variant: "destructive", variant: "destructive",
}); });
setConnectionStatus("error"); setConnectionStatus("error");

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "@modelcontextprotocol/inspector", "name": "@modelcontextprotocol/inspector",
"version": "0.14.0", "version": "0.14.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@modelcontextprotocol/inspector", "name": "@modelcontextprotocol/inspector",
"version": "0.14.0", "version": "0.14.1",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"client", "client",

View file

@ -89,33 +89,43 @@ app.use((req, res, next) => {
const webAppTransports: Map<string, Transport> = new Map<string, Transport>(); // Web app transports by web app sessionId const webAppTransports: Map<string, Transport> = new Map<string, Transport>(); // Web app transports by web app sessionId
const serverTransports: Map<string, Transport> = new Map<string, Transport>(); // Server Transports by web app sessionId const serverTransports: Map<string, Transport> = new Map<string, Transport>(); // Server Transports by web app sessionId
const sessionToken = randomBytes(32).toString('hex'); const sessionToken = randomBytes(32).toString("hex");
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH; const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
// Origin validation middleware to prevent DNS rebinding attacks // Origin validation middleware to prevent DNS rebinding attacks
const originValidationMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { const originValidationMiddleware = (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
const origin = req.headers.origin; const origin = req.headers.origin;
// 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 defaultOrigins = [
`http://localhost:${clientPort}`, `http://localhost:${clientPort}`,
`http://127.0.0.1:${clientPort}` `http://127.0.0.1:${clientPort}`,
]; ];
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || defaultOrigins; 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}`);
res.status(403).json({ res.status(403).json({
error: 'Forbidden - invalid origin', error: "Forbidden - invalid origin",
message: 'Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.' message:
"Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.",
}); });
return; return;
} }
next(); next();
}; };
const authMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { const authMiddleware = (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
if (authDisabled) { if (authDisabled) {
return next(); return next();
} }
@ -123,12 +133,13 @@ const authMiddleware = (req: express.Request, res: express.Response, next: expre
const sendUnauthorized = () => { const sendUnauthorized = () => {
res.status(401).json({ res.status(401).json({
error: "Unauthorized", error: "Unauthorized",
message: "Authentication required. Use the session token shown in the console when starting the server." message:
"Authentication required. Use the session token shown in the console when starting the server.",
}); });
}; };
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith("Bearer ")) {
sendUnauthorized(); sendUnauthorized();
return; return;
} }
@ -216,7 +227,11 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
} }
}; };
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { app.get(
"/mcp",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string; const sessionId = req.headers["mcp-session-id"] as string;
console.log(`Received GET message for sessionId ${sessionId}`); console.log(`Received GET message for sessionId ${sessionId}`);
try { try {
@ -233,9 +248,14 @@ app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
console.error("Error in /mcp route:", error); console.error("Error in /mcp route:", error);
res.status(500).json(error); res.status(500).json(error);
} }
}); },
);
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { app.post(
"/mcp",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined; const sessionId = req.headers["mcp-session-id"] as string | undefined;
let serverTransport: Transport | undefined; let serverTransport: Transport | undefined;
if (!sessionId) { if (!sessionId) {
@ -303,9 +323,14 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
res.status(500).json(error); res.status(500).json(error);
} }
} }
}); },
);
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { app.delete(
"/mcp",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined; const sessionId = req.headers["mcp-session-id"] as string | undefined;
console.log(`Received DELETE message for sessionId ${sessionId}`); console.log(`Received DELETE message for sessionId ${sessionId}`);
let serverTransport: Transport | undefined; let serverTransport: Transport | undefined;
@ -330,9 +355,14 @@ app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res)
res.status(500).json(error); res.status(500).json(error);
} }
} }
}); },
);
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => { app.get(
"/stdio",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
try { try {
console.log("New STDIO connection request"); console.log("New STDIO connection request");
let serverTransport: Transport | undefined; let serverTransport: Transport | undefined;
@ -392,9 +422,14 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
console.error("Error in /stdio route:", error); console.error("Error in /stdio route:", error);
res.status(500).json(error); res.status(500).json(error);
} }
}); },
);
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => { app.get(
"/sse",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
try { try {
console.log( console.log(
"New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp", "New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp",
@ -441,9 +476,14 @@ app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) =>
console.error("Error in /sse route:", error); console.error("Error in /sse route:", error);
res.status(500).json(error); res.status(500).json(error);
} }
}); },
);
app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => { app.post(
"/message",
originValidationMiddleware,
authMiddleware,
async (req, res) => {
try { try {
const sessionId = req.query.sessionId; const sessionId = req.query.sessionId;
console.log(`Received POST message for sessionId ${sessionId}`); console.log(`Received POST message for sessionId ${sessionId}`);
@ -460,7 +500,8 @@ app.post("/message", originValidationMiddleware, authMiddleware, async (req, res
console.error("Error in /message route:", error); console.error("Error in /message route:", error);
res.status(500).json(error); res.status(500).json(error);
} }
}); },
);
app.get("/health", (req, res) => { app.get("/health", (req, res) => {
res.json({ res.json({
@ -481,22 +522,28 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
} }
}); });
const PORT = parseInt(process.env.PORT || '6277', 10); const PORT = parseInt(process.env.PORT || "6277", 10);
const HOST = process.env.HOST || '127.0.0.1'; const HOST = process.env.HOST || "127.0.0.1";
const server = app.listen(PORT, HOST); const server = app.listen(PORT, HOST);
server.on("listening", () => { server.on("listening", () => {
console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`); console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`);
if (!authDisabled) { if (!authDisabled) {
console.log(`🔑 Session token: ${sessionToken}`); console.log(`🔑 Session token: ${sessionToken}`);
console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`); console.log(
`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`,
);
// 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 clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
console.log(`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`); console.log(
`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`,
);
} else { } else {
console.log(`⚠️ WARNING: Authentication is disabled. This is not recommended.`); console.log(
`⚠️ WARNING: Authentication is disabled. This is not recommended.`,
);
} }
}); });
server.on("error", (err) => { server.on("error", (err) => {