Merge branch 'main' into chore/bump-version-0.21.2
This commit is contained in:
commit
509f4aba8b
1 changed files with 45 additions and 20 deletions
|
|
@ -72,13 +72,26 @@ const is401Error = (error: unknown): boolean => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a sanitized JSON error response to the client without exposing
|
||||||
|
* stack traces or internal error details. The full error is expected to
|
||||||
|
* have already been logged server-side via console.error.
|
||||||
|
*/
|
||||||
|
const sendErrorResponse = (
|
||||||
|
res: express.Response,
|
||||||
|
status: number,
|
||||||
|
message: string,
|
||||||
|
) => {
|
||||||
|
res.status(status).json({ error: message });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefer forwarding the upstream MCP 401 (WWW-Authenticate + body) so the browser
|
* Prefer forwarding the upstream MCP 401 (WWW-Authenticate + body) so the browser
|
||||||
* matches direct-mode OAuth behavior. Falls back to JSON-encoding `error` if unknown.
|
* matches direct-mode OAuth behavior. Falls back to a generic 401 JSON response
|
||||||
|
* when no upstream details were captured, to avoid leaking stack traces.
|
||||||
*/
|
*/
|
||||||
const sendProxiedUnauthorized = (
|
const sendProxiedUnauthorized = (
|
||||||
res: express.Response,
|
res: express.Response,
|
||||||
error: unknown,
|
|
||||||
headerHolder?: ProxyHeaderHolder,
|
headerHolder?: ProxyHeaderHolder,
|
||||||
) => {
|
) => {
|
||||||
const captured = headerHolder?.lastUpstream401;
|
const captured = headerHolder?.lastUpstream401;
|
||||||
|
|
@ -92,7 +105,7 @@ const sendProxiedUnauthorized = (
|
||||||
delete headerHolder.lastUpstream401;
|
delete headerHolder.lastUpstream401;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.status(401).json(error);
|
sendErrorResponse(res, 401, "Unauthorized");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to get HTTP headers.
|
// Function to get HTTP headers.
|
||||||
|
|
@ -509,7 +522,7 @@ app.get(
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /mcp route:", error);
|
console.error("Error in /mcp route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -545,7 +558,7 @@ app.post(
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /mcp route:", error);
|
console.error("Error in /mcp route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("New StreamableHttp connection request");
|
console.log("New StreamableHttp connection request");
|
||||||
|
|
@ -592,11 +605,11 @@ app.post(
|
||||||
"Received 401 Unauthorized from MCP server:",
|
"Received 401 Unauthorized from MCP server:",
|
||||||
error instanceof Error ? error.message : error,
|
error instanceof Error ? error.message : error,
|
||||||
);
|
);
|
||||||
sendProxiedUnauthorized(res, error, streamableHeaderHolder);
|
sendProxiedUnauthorized(res, streamableHeaderHolder);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error("Error in /mcp POST route:", error);
|
console.error("Error in /mcp POST route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -627,7 +640,7 @@ app.delete(
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /mcp route:", error);
|
console.error("Error in /mcp route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -732,11 +745,11 @@ app.get(
|
||||||
console.error(
|
console.error(
|
||||||
"Received 401 Unauthorized from MCP server. Authentication failure.",
|
"Received 401 Unauthorized from MCP server. Authentication failure.",
|
||||||
);
|
);
|
||||||
sendProxiedUnauthorized(res, error, undefined);
|
sendProxiedUnauthorized(res, undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error("Error in /stdio route:", error);
|
console.error("Error in /stdio route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -781,20 +794,33 @@ app.get(
|
||||||
console.error(
|
console.error(
|
||||||
"Received 401 Unauthorized from MCP server. Authentication failure.",
|
"Received 401 Unauthorized from MCP server. Authentication failure.",
|
||||||
);
|
);
|
||||||
sendProxiedUnauthorized(res, error, sseHeaderHolder);
|
sendProxiedUnauthorized(res, sseHeaderHolder);
|
||||||
return;
|
return;
|
||||||
} else if (error instanceof SseError && error.code === 404) {
|
} else if (error instanceof SseError && error.code === 404) {
|
||||||
console.error(
|
console.error(
|
||||||
"Received 404 not found from MCP server. Does the MCP server support SSE?",
|
"Received 404 not found from MCP server. Does the MCP server support SSE?",
|
||||||
);
|
);
|
||||||
res.status(404).json(error);
|
sendErrorResponse(
|
||||||
|
res,
|
||||||
|
404,
|
||||||
|
"MCP server returned 404. Does it support SSE?",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
} else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
} else if (
|
||||||
|
error instanceof Error &&
|
||||||
|
(error.message.includes("ECONNREFUSED") ||
|
||||||
|
(error.cause && String(error.cause).includes("ECONNREFUSED")))
|
||||||
|
) {
|
||||||
console.error("Connection refused. Is the MCP server running?");
|
console.error("Connection refused. Is the MCP server running?");
|
||||||
res.status(500).json(error);
|
sendErrorResponse(
|
||||||
|
res,
|
||||||
|
500,
|
||||||
|
"Connection refused. Is the MCP server running?",
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
console.error("Error in /sse route:", error);
|
console.error("Error in /sse route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -824,7 +850,7 @@ app.post(
|
||||||
await transport.handlePostMessage(req, res);
|
await transport.handlePostMessage(req, res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /message route:", error);
|
console.error("Error in /message route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -882,9 +908,8 @@ app.post(
|
||||||
body: responseBody,
|
body: responseBody,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({
|
console.error("Error in /fetch route:", error);
|
||||||
error: error instanceof Error ? error.message : String(error),
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -900,7 +925,7 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /config route:", error);
|
console.error("Error in /config route:", error);
|
||||||
res.status(500).json(error);
|
sendErrorResponse(res, 500, "Internal server error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue