diff --git a/packages/builder/src/index.ts b/packages/builder/src/index.ts index 418519e..68f2f44 100644 --- a/packages/builder/src/index.ts +++ b/packages/builder/src/index.ts @@ -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(); +const sessionIdentities = new Map(); 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);