feat(builder): require a bearer token on both HTTP routes when auth is on

POST /messages is gated too, and cross-checked against the session's own
identity - otherwise a valid token for one caller could drive another
caller's session by guessing its id.
This commit is contained in:
FilipBl4gojevic
2026-07-28 19:44:15 +02:00
parent 59d30a7639
commit 9153500629

View File

@@ -26,31 +26,57 @@ if (existsSync(envPath)) {
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { registerVectorTools } from "./vector/index.js";
import { loadAuthConfig, resolveIdentity } from "./auth.js";
function createMcpServer() {
function createMcpServer(identity: string) {
const server = new McpServer({
name: "vector-mcp-server",
version: "1.0.0",
});
registerVectorTools(server);
registerVectorTools(server, identity);
return server;
}
console.error('Vector MCP Server starting...');
const authConfig = loadAuthConfig();
if (authConfig.enabled) {
console.error(`Auth: enabled (${authConfig.identities.size} identit${authConfig.identities.size === 1 ? 'y' : 'ies'} configured)`);
} else {
console.error('Auth: DISABLED — every caller is treated as "anonymous" and shares one rate-limit bucket.');
console.error('Auth: set MCP_AUTH_TOKENS to require a bearer token. Do not run a public instance without it.');
}
const PORT = parseInt(process.env.PORT || '3000');
const transports = new Map<string, SSEServerTransport>();
const sessionIdentities = new Map<string, string>();
const httpServer = createServer(async (req, res) => {
const url = new URL(req.url!, `http://localhost:${PORT}`);
if (req.method === 'GET' && url.pathname === '/sse') {
const auth = resolveIdentity(req.headers.authorization, authConfig);
if (!auth.ok) {
res.writeHead(401, { 'Content-Type': 'text/plain', 'WWW-Authenticate': 'Bearer' });
res.end(auth.reason);
return;
}
const transport = new SSEServerTransport('/messages', res);
const session = createMcpServer();
const session = createMcpServer(auth.identity);
sessionIdentities.set(transport.sessionId, auth.identity);
transports.set(transport.sessionId, transport);
res.on('close', () => transports.delete(transport.sessionId));
res.on('close', () => {
transports.delete(transport.sessionId);
sessionIdentities.delete(transport.sessionId);
});
await session.connect(transport);
} else if (req.method === 'POST' && url.pathname === '/messages') {
const auth = resolveIdentity(req.headers.authorization, authConfig);
if (!auth.ok) {
res.writeHead(401, { 'Content-Type': 'text/plain', 'WWW-Authenticate': 'Bearer' });
res.end(auth.reason);
return;
}
const sessionId = url.searchParams.get('sessionId');
if (!sessionId) {
res.writeHead(400);
@@ -63,6 +89,12 @@ const httpServer = createServer(async (req, res) => {
res.end('Session not found');
return;
}
// A valid token for identity A must not be able to drive identity B's session.
if (sessionIdentities.get(sessionId) !== auth.identity) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('This session belongs to a different identity.');
return;
}
await transport.handlePostMessage(req, res);
} else {
res.writeHead(404);