vreko-mcp-server 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../../../packages/sentry-privacy/src/index.ts","../src/http-client.ts","../src/utils/logger.ts","../src/validation.ts"],"sourcesContent":["/**\n * Vreko MCP Server - Thin Proxy to API\n *\n * Thin proxy server that delegates all business logic to the API.\n * All workspace context, capabilities, and analytics are handled by apps/api.\n *\n * @module apps/mcp-server\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { URL } from \"node:url\";\nimport { createSentryConfig, Sentry } from \"@vreko/sentry-privacy\";\nimport { destroyPools, fetchWithPooling } from \"./http-client.js\";\nimport { logger } from \"./utils/logger.js\";\nimport {\n\tgetAllowedCorsOrigin,\n\tgetMaxBodySize,\n\tvalidateApiKeyWithDatabase,\n\tvalidateWorkspace,\n\tvalidateWorkspaceId,\n} from \"./validation.js\";\n\n// Sentry must be initialized before any other logic, including error handlers.\n// Key priority: SENTRY_DSN_MCP (per-surface key set by bootstrap) → SENTRY_DSN (fallback).\nSentry.init(\n\tcreateSentryConfig({\n\t\tdsn: process.env.SENTRY_DSN_MCP || process.env.SENTRY_DSN || \"\",\n\t\tsurface: \"mcp-proxy\",\n\t}),\n);\n\n// A4: Process-level error handlers - must be registered at module load\nprocess.once(\"uncaughtException\", (error: Error) => {\n\tSentry.captureException(error, { tags: { component: \"uncaughtException\" } });\n\tlogger.error(\"Uncaught exception\", { message: error.message, stack: error.stack });\n\tprocess.exit(1);\n});\nprocess.once(\"unhandledRejection\", (reason: unknown) => {\n\tSentry.captureException(reason instanceof Error ? reason : new Error(String(reason)), {\n\t\ttags: { component: \"unhandledRejection\" },\n\t\tlevel: \"warning\",\n\t});\n\tlogger.warn(\"Unhandled rejection\", { reason: String(reason) });\n});\n\nconst PORT = Number.parseInt(process.env.PORT || \"8080\", 10);\nconst _NODE_ENV = process.env.NODE_ENV || \"development\";\nconst MCP_VERSION = process.env.MCP_VERSION || \"2025-03-26\";\nconst API_URL = process.env.VREKO_API_URL || \"https://api.vreko.dev\";\n\n// Graceful shutdown state\nlet isShuttingDown = false;\nlet isReady = false;\nconst startTime = Date.now();\n\nasync function proxyToApi(endpoint: string, method: string, body: unknown, headers: Record<string, string>) {\n\ttry {\n\t\tconst res = await fetchWithPooling(`${API_URL}/api/v1/mcp${endpoint}`, {\n\t\t\tmethod,\n\t\t\theaders: { \"Content-Type\": \"application/json\", Accept: \"application/json, text/event-stream\", ...headers },\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\t\tconst resBody = await res.text();\n\t\tconst resHeaders: Record<string, string> = {\n\t\t\t/* intentionally empty */\n\t\t};\n\t\tres.headers.forEach((v, k) => {\n\t\t\tresHeaders[k] = v;\n\t\t});\n\t\treturn { status: res.status, body: resBody, headers: resHeaders };\n\t} catch (_e) {\n\t\treturn {\n\t\t\tstatus: 503,\n\t\t\tbody: JSON.stringify({ error: \"SERVICE_UNAVAILABLE\" }),\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t};\n\t}\n}\n\nfunction setCors(req: IncomingMessage, res: ServerResponse) {\n\tconst origin = getAllowedCorsOrigin(req.headers.origin, process.env.CORS_ORIGIN || \"*\");\n\tres.setHeader(\"Access-Control-Allow-Origin\", origin || \"*\");\n\tres.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\tres.setHeader(\n\t\t\"Access-Control-Allow-Headers\",\n\t\t\"Content-Type, Authorization, mcp-session-id, x-api-key, x-workspace-id\",\n\t);\n\tres.setHeader(\"Access-Control-Expose-Headers\", \"mcp-session-id\");\n}\n\nconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\tsetCors(req, res);\n\tif (req.method === \"OPTIONS\") {\n\t\tres.writeHead(200);\n\t\tres.end();\n\t\treturn;\n\t}\n\n\t// Origin validation for production security (defense-in-depth)\n\tconst nodeEnv = process.env.NODE_ENV || \"development\";\n\tconst requestOrigin = req.headers.origin;\n\tif (nodeEnv === \"production\" && requestOrigin) {\n\t\tconst allowedOrigins = (process.env.CORS_ORIGIN || \"\")\n\t\t\t.split(\",\")\n\t\t\t.map((o) => o.trim())\n\t\t\t.filter(Boolean);\n\t\t// Allow requests without origin (e.g., CLI, MCP clients) or from allowed origins\n\t\tif (allowedOrigins.length > 0 && !allowedOrigins.includes(requestOrigin) && allowedOrigins[0] !== \"*\") {\n\t\t\tlogger.warn(\"Blocked request from unauthorized origin\", { origin: requestOrigin });\n\t\t\tres.writeHead(403, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ error: \"FORBIDDEN\", message: \"Origin not allowed\" }));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tconst requestId = randomUUID();\n\tconst url = new URL(req.url || \"\", `http://${req.headers.host}`);\n\tlogger.info(\"Request\", { requestId, method: req.method, path: url.pathname });\n\n\tconst maxSize = getMaxBodySize();\n\tconst chunks: Buffer[] = [];\n\tlet size = 0;\n\tlet aborted = false;\n\n\treq.on(\"data\", (chunk) => {\n\t\tif (aborted) {\n\t\t\treturn;\n\t\t}\n\t\tsize += chunk.length;\n\t\tif (size > maxSize) {\n\t\t\taborted = true;\n\t\t\tres.writeHead(413, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ error: \"PAYLOAD_TOO_LARGE\" }));\n\t\t\treq.destroy();\n\t\t\treturn;\n\t\t}\n\t\tchunks.push(chunk);\n\t});\n\n\treq.on(\"end\", async () => {\n\t\tif (aborted) {\n\t\t\treturn;\n\t\t}\n\t\tconst body = chunks.length === 0 ? \"\" : Buffer.concat(chunks, size).toString(\"utf8\");\n\n\t\ttry {\n\t\t\tlet result: { status: number; body: string; headers: Record<string, string> } | undefined;\n\t\t\tconst path = url.pathname;\n\n\t\t\t// SSE streaming for MCP GET (server-to-client notifications)\n\t\t\t// Must be handled before body buffering logic since SSE is a long-lived stream\n\t\t\tif (path === \"/mcp\" && req.method === \"GET\") {\n\t\t\t\tconst sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\t\t\t\tif (!sessionId) {\n\t\t\t\t\tres.writeHead(400, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\tres.end(JSON.stringify({ error: \"mcp-session-id header required\" }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst upstreamHeaders: Record<string, string> = {\n\t\t\t\t\t\"mcp-session-id\": sessionId,\n\t\t\t\t\tAccept: \"text/event-stream\",\n\t\t\t\t};\n\t\t\t\tif (req.headers[\"x-api-key\"]) {\n\t\t\t\t\tupstreamHeaders[\"x-api-key\"] = req.headers[\"x-api-key\"] as string;\n\t\t\t\t}\n\t\t\t\tif (req.headers.authorization?.startsWith(\"Bearer \")) {\n\t\t\t\t\tupstreamHeaders.Authorization = req.headers.authorization as string;\n\t\t\t\t}\n\t\t\t\tif (req.headers[\"x-workspace-path\"]) {\n\t\t\t\t\tupstreamHeaders[\"x-workspace-path\"] = req.headers[\"x-workspace-path\"] as string;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst upstream = await fetchWithPooling(`${API_URL}/api/v1/mcp`, {\n\t\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\t\theaders: upstreamHeaders,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Copy upstream status and headers\n\t\t\t\t\tconst responseHeaders: Record<string, string> = {};\n\t\t\t\t\tupstream.headers.forEach((v, k) => {\n\t\t\t\t\t\tresponseHeaders[k] = v;\n\t\t\t\t\t});\n\t\t\t\t\tres.writeHead(upstream.status, responseHeaders);\n\n\t\t\t\t\t// Pipe SSE stream without buffering\n\t\t\t\t\tif (upstream.body) {\n\t\t\t\t\t\tconst reader = (upstream.body as ReadableStream<Uint8Array>).getReader();\n\t\t\t\t\t\treq.on(\"close\", () =>\n\t\t\t\t\t\t\treader.cancel().catch(() => {\n\t\t\t\t\t\t\t\t/* stream already closed */\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\t(async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\t\t\t\tif (done) {\n\t\t\t\t\t\t\t\t\t\tres.end();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tres.write(value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (pipeErr) {\n\t\t\t\t\t\t\t\tlogger.warn(\"SSE pipe closed unexpectedly\", {\n\t\t\t\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\t\t\t\terror: pipeErr instanceof Error ? pipeErr.message : String(pipeErr),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tSentry.captureException(\n\t\t\t\t\t\t\t\t\tpipeErr instanceof Error ? pipeErr : new Error(String(pipeErr)),\n\t\t\t\t\t\t\t\t\t{ tags: { component: \"sse-pipe\" }, extra: { sessionId } },\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tres.end();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tres.end();\n\t\t\t\t\t}\n\t\t\t\t} catch (sseErr) {\n\t\t\t\t\tlogger.warn(\"SSE upstream connect failed\", {\n\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\terror: sseErr instanceof Error ? sseErr.message : String(sseErr),\n\t\t\t\t\t});\n\t\t\t\t\tSentry.captureException(sseErr instanceof Error ? sseErr : new Error(String(sseErr)), {\n\t\t\t\t\t\ttags: { component: \"sse-upstream\" },\n\t\t\t\t\t\textra: { sessionId },\n\t\t\t\t\t});\n\t\t\t\t\tres.writeHead(503, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\tres.end(JSON.stringify({ error: \"SERVICE_UNAVAILABLE\" }));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Health endpoints - Kubernetes-style probes\n\t\t\tif (path === \"/health\" || path === \"/health/live\") {\n\t\t\t\t// Liveness probe: Is the process alive?\n\t\t\t\tresult = {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tstatus: isShuttingDown ? \"shutting_down\" : \"alive\",\n\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\tuptime: Math.floor((Date.now() - startTime) / 1000),\n\t\t\t\t\t\tversion: MCP_VERSION,\n\t\t\t\t\t}),\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t};\n\t\t\t} else if (path === \"/health/ready\") {\n\t\t\t\t// Readiness probe: Is the app ready to serve?\n\t\t\t\t// Returns 503 if shutting down or API is unreachable\n\t\t\t\tif (isShuttingDown) {\n\t\t\t\t\tresult = {\n\t\t\t\t\t\tstatus: 503,\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tstatus: \"not_ready\",\n\t\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t\tmessage: \"Server is shutting down\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\t// Check API connectivity (live check on every probe - no cached boolean)\n\t\t\t\t\tconst apiCheck = await proxyToApi(\"/health\", \"GET\", null, {});\n\t\t\t\t\tconst isApiReady = apiCheck.status === 200;\n\t\t\t\t\t// Self-heal: if startup check failed but API is now reachable, mark ready\n\t\t\t\t\tif (isApiReady && !isReady) {\n\t\t\t\t\t\tisReady = true;\n\t\t\t\t\t\tlogger.info(\"MCP Server recovered - API reachable on readiness probe\");\n\t\t\t\t\t}\n\t\t\t\t\tresult = {\n\t\t\t\t\t\tstatus: isApiReady ? 200 : 503,\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tstatus: isApiReady ? \"ready\" : \"not_ready\",\n\t\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t\tapi: isApiReady ? \"connected\" : \"disconnected\",\n\t\t\t\t\t\t\tuptime: Math.floor((Date.now() - startTime) / 1000),\n\t\t\t\t\t\t\tversion: MCP_VERSION,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t} else if (path === \"/health/startup\") {\n\t\t\t\t// Startup probe: Has the app started successfully?\n\t\t\t\t// Returns 200 once the server has completed startup\n\t\t\t\tresult = {\n\t\t\t\t\tstatus: isReady ? 200 : 503,\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tstatus: isReady ? \"started\" : \"starting\",\n\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t}),\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t};\n\t\t\t} else if (path === \"/mcp\") {\n\t\t\t\tconst sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\t\t\t\tlet parsed = {};\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(body || \"{}\");\n\t\t\t\t} catch {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end(JSON.stringify({ error: \"BAD_REQUEST\" }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst workspace =\n\t\t\t\t\t(parsed as Record<string, unknown>).workspace ||\n\t\t\t\t\turl.searchParams.get(\"workspace\") ||\n\t\t\t\t\treq.headers[\"x-workspace-path\"] ||\n\t\t\t\t\t\"default\";\n\t\t\t\tif (!validateWorkspace(workspace as string).valid) {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end(JSON.stringify({ error: \"BAD_REQUEST\" }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst workspaceId = (parsed as Record<string, unknown>).workspaceId || req.headers[\"x-workspace-id\"];\n\t\t\t\tif (workspaceId && !validateWorkspaceId(workspaceId as string).valid) {\n\t\t\t\t\tres.writeHead(401);\n\t\t\t\t\tres.end(JSON.stringify({ error: \"UNAUTHORIZED\" }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// P0-C: DB-backed API key validation before proxying\n\t\t\t\tconst apiKey = req.headers[\"x-api-key\"] as string | undefined;\n\t\t\t\tif (apiKey) {\n\t\t\t\t\tconst validation = await validateApiKeyWithDatabase(apiKey);\n\t\t\t\t\tif (!validation.valid) {\n\t\t\t\t\t\tif (validation.transient) {\n\t\t\t\t\t\t\t// Transient network error - return 503 so MCP clients can retry\n\t\t\t\t\t\t\t// (clients do not retry 401, so mapping to 503 preserves retryability)\n\t\t\t\t\t\t\tres.writeHead(503, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\t\t\tres.end(\n\t\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t\t\t\tcode: -32003,\n\t\t\t\t\t\t\t\t\t\tmessage: \"Auth service temporarily unavailable\",\n\t\t\t\t\t\t\t\t\t\tdata: { reason: \"auth_service_unavailable\", retryable: true },\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tid: (parsed as Record<string, unknown>)?.id ?? null,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Permanent auth failure - key invalid or revoked\n\t\t\t\t\t\tconst errorResponse = {\n\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\t\tcode: -32001,\n\t\t\t\t\t\t\t\tmessage: \"Unauthorized\",\n\t\t\t\t\t\t\t\tdata: { reason: validation.error || \"key_not_found_or_revoked\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tid: (parsed as Record<string, unknown>)?.id ?? null,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tres.writeHead(401, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\t\tres.end(JSON.stringify(errorResponse));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Add user context to headers for upstream\n\t\t\t\t\tif (validation.userId) {\n\t\t\t\t\t\tlogger.debug(\"API key validated\", { userId: validation.userId, tier: validation.tier });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst headers: Record<string, string> = { \"x-workspace-path\": workspace as string };\n\t\t\t\tif (sessionId) {\n\t\t\t\t\theaders[\"mcp-session-id\"] = sessionId;\n\t\t\t\t}\n\t\t\t\tif (workspaceId) {\n\t\t\t\t\theaders[\"x-workspace-id\"] = workspaceId as string;\n\t\t\t\t}\n\t\t\t\tif (apiKey) {\n\t\t\t\t\theaders[\"x-api-key\"] = apiKey;\n\t\t\t\t}\n\t\t\t\t// Support Bearer token (OAuth session) - Better Auth bearer plugin\n\t\t\t\tif (req.headers.authorization?.startsWith(\"Bearer \")) {\n\t\t\t\t\theaders.Authorization = req.headers.authorization as string;\n\t\t\t\t}\n\t\t\t\tresult = await proxyToApi(\"\", req.method || \"POST\", parsed, headers);\n\t\t\t} else if (path === \"/auth/link-workspace\") {\n\t\t\t\tif (req.method !== \"POST\") {\n\t\t\t\t\tres.writeHead(405);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet parsed = {};\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(body);\n\t\t\t\t} catch {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresult = await proxyToApi(\n\t\t\t\t\t\"/auth/link-workspace\",\n\t\t\t\t\t\"POST\",\n\t\t\t\t\tparsed,\n\t\t\t\t\treq.headers.authorization ? { Authorization: req.headers.authorization } : {},\n\t\t\t\t);\n\t\t\t} else if (path === \"/bridge/push\") {\n\t\t\t\tif (req.method !== \"POST\") {\n\t\t\t\t\tres.writeHead(405);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet parsed = {};\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(body);\n\t\t\t\t} catch {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresult = await proxyToApi(\"/bridge/push\", \"POST\", parsed, {});\n\t\t\t} else if (path === \"/bridge/status\") {\n\t\t\t\tif (req.method !== \"GET\") {\n\t\t\t\t\tres.writeHead(405);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst wsId = url.searchParams.get(\"workspaceId\");\n\t\t\t\tresult = await proxyToApi(\n\t\t\t\t\twsId ? `/bridge/status?workspaceId=${encodeURIComponent(wsId)}` : \"/bridge/status\",\n\t\t\t\t\t\"GET\",\n\t\t\t\t\tnull,\n\t\t\t\t\t{},\n\t\t\t\t);\n\t\t\t} else if (path === \"/capabilities/false-positive\") {\n\t\t\t\tif (req.method !== \"POST\") {\n\t\t\t\t\tres.writeHead(405);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet parsed = {};\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(body);\n\t\t\t\t} catch {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresult = await proxyToApi(\"/capabilities/false-positive\", \"POST\", parsed, {});\n\t\t\t} else if (path === \"/capabilities\") {\n\t\t\t\tif (req.method !== \"GET\") {\n\t\t\t\t\tres.writeHead(405);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst userId = url.searchParams.get(\"userId\");\n\t\t\t\tif (!userId) {\n\t\t\t\t\tres.writeHead(400);\n\t\t\t\t\tres.end(JSON.stringify({ error: \"BAD_REQUEST\" }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresult = await proxyToApi(`/capabilities?userId=${encodeURIComponent(userId)}`, \"GET\", null, {});\n\t\t\t} else {\n\t\t\t\tres.writeHead(404, { \"Content-Type\": \"application/json\" });\n\t\t\t\tres.end(JSON.stringify({ error: \"NOT_FOUND\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Forward mcp-session-id from upstream so clients can make follow-up requests.\n\t\t\t// The GET/SSE handler already forwards all upstream headers; the POST path\n\t\t\t// must also forward mcp-session-id or initialize handshakes silently fail.\n\t\t\tconst responseHeaders: Record<string, string> = {\n\t\t\t\t\"Content-Type\": result.headers[\"content-type\"] || \"application/json\",\n\t\t\t};\n\t\t\tif (result.headers[\"mcp-session-id\"]) {\n\t\t\t\tresponseHeaders[\"mcp-session-id\"] = result.headers[\"mcp-session-id\"];\n\t\t\t}\n\t\t\tres.writeHead(result.status, responseHeaders);\n\t\t\tres.end(result.body);\n\t\t} catch (e) {\n\t\t\tlogger.error(\"Error\", { requestId, error: String(e) });\n\t\t\tres.writeHead(500, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ error: \"INTERNAL_ERROR\" }));\n\t\t}\n\t});\n});\n\n// Graceful shutdown - 2026 Enterprise Pattern\nconst SHUTDOWN_TIMEOUT_MS = 25000; // Leave 5s buffer for kill_timeout=30s\n\nasync function gracefulShutdown(signal: string) {\n\tif (isShuttingDown) {\n\t\tlogger.warn(\"Shutdown already in progress\");\n\t\treturn;\n\t}\n\n\tisShuttingDown = true;\n\tlogger.info(\"Shutdown initiated\", { signal, pid: process.pid });\n\n\t// Mark as not ready immediately (stop new traffic)\n\tisReady = false;\n\tlogger.info(\"Service marked as not ready\");\n\n\t// C2: Close all SSE/keep-alive connections immediately\n\tserver.closeAllConnections();\n\tlogger.info(\"SSE connections closed\");\n\n\t// Wait for in-flight requests (5 second drain period)\n\tlogger.info(\"Waiting for in-flight requests to complete...\");\n\tawait new Promise((resolve) => setTimeout(resolve, 5000));\n\n\t// C1: Destroy connection pools before forced exit\n\tdestroyPools();\n\tlogger.info(\"Connection pools destroyed\");\n\n\t// Flush Sentry before closing\n\ttry {\n\t\tawait Sentry.close(2000);\n\t} catch {\n\t\t// Sentry flush must not block shutdown\n\t}\n\n\t// Close server\n\tlogger.info(\"Closing server...\");\n\tserver.close(() => {\n\t\tlogger.info(\"Server closed, exiting\");\n\t\tprocess.exit(0);\n\t});\n\n\t// Force exit after timeout\n\tsetTimeout(() => {\n\t\tlogger.error(\"Forced exit due to timeout\");\n\t\tprocess.exit(1);\n\t}, SHUTDOWN_TIMEOUT_MS);\n}\n\nprocess.once(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\nprocess.once(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\n\n// Start server and mark as ready\n// MUST bind to 0.0.0.0 for Fly.io deployment (localhost binding will fail health checks)\nserver.listen(PORT, \"0.0.0.0\", () => {\n\tlogger.info(\"MCP Server started\", { port: PORT, host: \"0.0.0.0\", version: MCP_VERSION, apiUrl: API_URL });\n\n\t// E3: Replace fixed 1-second timer with actual API connectivity check\n\t(async () => {\n\t\ttry {\n\t\t\tconst check = await fetchWithPooling(`${API_URL}/health`, {\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t});\n\t\t\tif (check.ok) {\n\t\t\t\tisReady = true;\n\t\t\t\tlogger.info(\"MCP Server ready - API reachable\", { status: check.status });\n\t\t\t} else {\n\t\t\t\tlogger.warn(\"MCP Server started but API not reachable\", { status: check.status });\n\t\t\t\t// isReady stays false; /health/ready will return 503 until next probe interval restores it\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlogger.warn(\"MCP Server started but API health check failed\", {\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t}\n\t})();\n});\n\nexport default server;\n","import type { NodeOptions } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\ntype AnyIntegration = { name: string };\n\nexport { Sentry };\n\nexport type Surface = \"daemon\" | \"extension\" | \"cli\" | \"api\" | \"mcp-proxy\";\nexport type Tier = \"free\" | \"pro\" | \"team\" | \"pioneer\";\n\nexport interface SentryPrivacyOptions {\n\tdsn: string;\n\tsurface: Surface;\n\tworkspaceHash?: string;\n\ttier?: Tier;\n\trelease?: string;\n\tenvironment?: string;\n}\n\nconst EXTRA_ALLOWLIST = new Set([\n\t\"requestId\",\n\t\"workspaceHash\",\n\t\"sessionId\",\n\t\"tier\",\n\t\"cliVersion\",\n\t\"daemonVersion\",\n\t\"extensionVersion\",\n\t\"platform\",\n\t\"nodeVersion\",\n]);\n\nconst BLOCKED_BREADCRUMB_CATEGORIES = new Set([\"fetch\", \"xhr\", \"console\"]);\n\n// Matches /Users/<name>/ and /home/<name>/\nconst HOME_PATH_RE = /\\/(Users|home)\\/[^/]+\\//g;\n// GitHub personal/oauth/user/server tokens\nconst GH_TOKEN_RE = /(ghp|gho|ghu|ghs)_[A-Za-z0-9]{36}/g;\n// OpenAI-style secret keys\nconst API_KEY_RE = /sk-[A-Za-z0-9]{32,}/g;\n\nconst MAX_STRING_LEN = 2048;\n\nfunction scrubString(value: string): string {\n\tif (value.length > MAX_STRING_LEN) {\n\t\treturn `[TRUNCATED:${value.length}]`;\n\t}\n\treturn value\n\t\t.replace(HOME_PATH_RE, \"/~/\")\n\t\t.replace(GH_TOKEN_RE, \"[REDACTED_GH_TOKEN]\")\n\t\t.replace(API_KEY_RE, \"[REDACTED_API_KEY]\");\n}\n\nfunction scrubValue(value: unknown): unknown {\n\tif (typeof value === \"string\") {\n\t\treturn scrubString(value);\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.map(scrubValue);\n\t}\n\tif (value !== null && typeof value === \"object\") {\n\t\treturn scrubRecord(value as Record<string, unknown>);\n\t}\n\treturn value;\n}\n\nfunction scrubRecord(obj: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [k, v] of Object.entries(obj)) {\n\t\tresult[k] = scrubValue(v);\n\t}\n\treturn result;\n}\n\nexport interface NextjsSentryOptions {\n\tdsn: string;\n\trelease?: string;\n\tenvironment?: string;\n\ttracesSampleRate?: number;\n\tignoreErrors?: (string | RegExp)[];\n}\n\nexport function createNextjsSentryConfig(options: NextjsSentryOptions): Record<string, unknown> {\n\tconst { dsn, release, environment, tracesSampleRate, ignoreErrors } = options;\n\tconst resolvedEnv = resolveDeploymentEnv(environment);\n\treturn {\n\t\tdsn,\n\t\tenvironment: resolvedEnv,\n\t\trelease:\n\t\t\trelease ??\n\t\t\tprocess.env.VERCEL_GIT_COMMIT_SHA ??\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ??\n\t\t\tprocess.env.npm_package_version,\n\t\tsendDefaultPii: false,\n\t\ttracesSampleRate: tracesSampleRate ?? (resolvedEnv === \"development\" ? 1.0 : 0.1),\n\t\tignoreErrors: ignoreErrors ?? [\"NetworkError\", \"Failed to fetch\", \"Load failed\", \"AbortError\"],\n\t\tbeforeSend(event: Record<string, unknown>) {\n\t\t\tconst req = event.request as Record<string, unknown> | undefined;\n\t\t\tif (req?.headers) {\n\t\t\t\tconst headers = req.headers as Record<string, unknown>;\n\t\t\t\tdelete headers.authorization;\n\t\t\t\tdelete headers.cookie;\n\t\t\t\tdelete headers[\"x-api-key\"];\n\t\t\t}\n\t\t\tconst user = event.user as Record<string, unknown> | undefined;\n\t\t\tif (user?.email) {\n\t\t\t\tuser.email = \"[REDACTED]\";\n\t\t\t}\n\t\t\tconst exception = event.exception as Record<string, unknown> | undefined;\n\t\t\tif (exception?.values) {\n\t\t\t\tfor (const exc of exception.values as Array<Record<string, unknown>>) {\n\t\t\t\t\tif (exc.value && typeof exc.value === \"string\") {\n\t\t\t\t\t\texc.value = scrubString(exc.value);\n\t\t\t\t\t}\n\t\t\t\t\tconst st = exc.stacktrace as Record<string, unknown> | undefined;\n\t\t\t\t\tif (st?.frames) {\n\t\t\t\t\t\tfor (const frame of st.frames as Array<Record<string, unknown>>) {\n\t\t\t\t\t\t\tif (typeof frame.filename === \"string\") {\n\t\t\t\t\t\t\t\tframe.filename = scrubString(frame.filename);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (typeof frame.abs_path === \"string\") {\n\t\t\t\t\t\t\t\tframe.abs_path = scrubString(frame.abs_path);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn event;\n\t\t},\n\t};\n}\n\n/**\n * Resolve the deployment environment from explicit override, Doppler-aware\n * detection, or platform signals.\n *\n * NODE_ENV is unreliable: Doppler sets it to \"production\" even for local dev\n * configs. DEPLOYMENT_ENV is the authoritative discriminator - it is set\n * explicitly per Doppler config (dev → \"development\", stg → \"staging\",\n * prd → \"production\"). Falls back to platform signals and then NODE_ENV.\n */\nfunction resolveDeploymentEnv(override?: string): string {\n\tif (override) return override;\n\tconst explicit = process.env.DEPLOYMENT_ENV;\n\tif (explicit === \"development\" || explicit === \"staging\" || explicit === \"production\") {\n\t\treturn explicit;\n\t}\n\t// Staging Fly.io deploy: FLY_ALLOC_ID is set but DEPLOYMENT_ENV=staging\n\t// must be present in doppler stg config to reach this branch correctly.\n\t// Vercel preview deployments are staging, not production.\n\tif (process.env.VERCEL_ENV === \"preview\") return \"staging\";\n\t// NODE_ENV fallback - only reliable when Doppler is not in the picture\n\t// (e.g. unit tests, or non-Doppler local dev with a hand-crafted .env).\n\treturn process.env.NODE_ENV ?? \"production\";\n}\n\nexport function createSentryConfig(options: SentryPrivacyOptions): NodeOptions {\n\tconst { dsn, surface, workspaceHash, tier, release, environment } = options;\n\n\tconst resolvedEnv = resolveDeploymentEnv(environment);\n\n\treturn {\n\t\tdsn,\n\t\tenvironment: resolvedEnv,\n\t\trelease: release ?? process.env.GIT_SHA ?? process.env.npm_package_version,\n\t\tsendDefaultPii: false,\n\t\tattachStacktrace: true,\n\t\t// Full sampling in development; reduced in staging/production to limit overhead.\n\t\ttracesSampleRate: resolvedEnv === \"development\" ? 1.0 : 0.1,\n\n\t\tinitialScope: {\n\t\t\ttags: {\n\t\t\t\tsurface,\n\t\t\t\t...(tier ? { tier } : {}),\n\t\t\t},\n\t\t\t...(workspaceHash ? { user: { id: workspaceHash } } : {}),\n\t\t},\n\n\t\t// Drop auto-capture integrations that can produce breadcrumbs with sensitive data\n\t\tintegrations(defaultIntegrations: AnyIntegration[]) {\n\t\t\treturn defaultIntegrations.filter((i) => ![\"Breadcrumbs\", \"Console\", \"Http\"].includes(i.name));\n\t\t},\n\n\t\tbeforeSend(event) {\n\t\t\t// Strip extra keys outside allowlist\n\t\t\tif (event.extra) {\n\t\t\t\tconst filtered: Record<string, unknown> = {};\n\t\t\t\tfor (const key of EXTRA_ALLOWLIST) {\n\t\t\t\t\tif (key in event.extra) {\n\t\t\t\t\t\tfiltered[key] = event.extra[key];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tevent.extra = filtered;\n\t\t\t}\n\n\t\t\t// Clear runtime args (may contain file paths)\n\t\t\tif (event.contexts?.runtime) {\n\t\t\t\t(event.contexts.runtime as Record<string, unknown>).args = undefined;\n\t\t\t}\n\n\t\t\t// Clear request body (may contain RPC payloads)\n\t\t\tif (event.request) {\n\t\t\t\tevent.request.data = undefined;\n\t\t\t}\n\n\t\t\t// Clear all breadcrumb data payloads\n\t\t\tif (event.breadcrumbs?.values) {\n\t\t\t\tconst breadcrumbs = event.breadcrumbs.values();\n\t\t\t\tfor (const crumb of breadcrumbs) {\n\t\t\t\t\tcrumb.data = undefined;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Scrub strings in exception messages and stack frame filenames\n\t\t\tif (event.exception?.values) {\n\t\t\t\tfor (const exc of event.exception.values) {\n\t\t\t\t\tif (exc.value) {\n\t\t\t\t\t\texc.value = scrubString(exc.value);\n\t\t\t\t\t}\n\t\t\t\t\tif (exc.stacktrace?.frames) {\n\t\t\t\t\t\tfor (const frame of exc.stacktrace.frames) {\n\t\t\t\t\t\t\tif (frame.filename) {\n\t\t\t\t\t\t\t\tframe.filename = scrubString(frame.filename);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (frame.abs_path) {\n\t\t\t\t\t\t\t\tframe.abs_path = scrubString(frame.abs_path);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Scrub event message\n\t\t\tif (event.message) {\n\t\t\t\tevent.message = scrubString(event.message);\n\t\t\t}\n\n\t\t\treturn event;\n\t\t},\n\n\t\tbeforeBreadcrumb(breadcrumb) {\n\t\t\tconst cat = breadcrumb.category ?? \"\";\n\t\t\tif (BLOCKED_BREADCRUMB_CATEGORIES.has(cat)) {\n\t\t\t\t// Explicit opt-in via _allow sentinel bypasses the filter\n\t\t\t\tconst data = breadcrumb.data as Record<string, unknown> | undefined;\n\t\t\t\tif (data?._allow === true) {\n\t\t\t\t\tconst { _allow: _, ...rest } = data;\n\t\t\t\t\tbreadcrumb.data = rest as Record<string, unknown>;\n\t\t\t\t\treturn breadcrumb;\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn breadcrumb;\n\t\t},\n\t};\n}\n","/**\n * HTTP Client with Connection Pooling via undici\n *\n * Uses undici's Agent (ships with Node.js 20+) for proper connection pooling.\n * The native fetch() in Node.js is backed by undici - setGlobalDispatcher\n * wires our pooled Agent into every fetch() call site without changing call\n * signatures.\n *\n * Previous approach used node:http Agent passed via the `agent` option on\n * globalThis.fetch - that option is silently ignored because undici does not\n * read it, so pooling was a no-op.\n *\n * @module http-client\n */\n\nimport { Agent, setGlobalDispatcher } from \"undici\";\n\nconst globalAgent = new Agent({\n\tconnections: 50,\n\tkeepAliveTimeout: 30_000,\n\tkeepAliveMaxTimeout: 60_000,\n\tpipelining: 1,\n});\n\nsetGlobalDispatcher(globalAgent);\n\n/**\n * Thin wrapper around global fetch().\n *\n * Connection pooling is handled by the undici Agent registered above.\n * Call sites are unchanged - drop-in replacement for the old implementation.\n */\nexport async function fetchWithPooling(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n\treturn fetch(input, init);\n}\n\n/**\n * Get connection pool statistics for monitoring.\n */\nexport function getPoolStats() {\n\treturn { dispatcher: \"undici-global-agent\", connections: 50, pooling: true };\n}\n\n/**\n * Drain and destroy all pooled connections.\n * Call this on server shutdown.\n */\nexport function destroyPools(): void {\n\tvoid globalAgent.destroy();\n}\n","/**\n * Shared logger for mcp-server\n *\n * Replaces three inline `const logger` objects in:\n * - src/index.ts\n * - src/metrics/capability-metrics.ts\n * - src/analytics/posthog.ts\n *\n * Log-level control via LOG_LEVEL env var:\n * - \"silent\" → suppresses info/debug output\n * - \"debug\" → enables debug output\n * - anything else (default) → info/warn/error enabled, debug suppressed\n *\n * @module utils/logger\n */\n\ntype LogContext = Record<string, unknown>;\n\nfunction fmt(level: string, msg: string, ctx?: LogContext): string {\n\treturn `[${level}] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : \"\"}\\n`;\n}\n\nexport const logger = {\n\tinfo(msg: string, ctx?: LogContext): void {\n\t\tif (process.env.LOG_LEVEL !== \"silent\") {\n\t\t\tprocess.stdout.write(fmt(\"INFO\", msg, ctx));\n\t\t}\n\t},\n\n\twarn(msg: string, ctx?: LogContext): void {\n\t\tprocess.stderr.write(fmt(\"WARN\", msg, ctx));\n\t},\n\n\terror(msg: string, ctx?: LogContext): void {\n\t\tprocess.stderr.write(fmt(\"ERROR\", msg, ctx));\n\t},\n\n\tdebug(msg: string, ctx?: LogContext): void {\n\t\tif (process.env.LOG_LEVEL === \"debug\") {\n\t\t\tprocess.stdout.write(fmt(\"DEBUG\", msg, ctx));\n\t\t}\n\t},\n};\n","/**\n * Security validation utilities for MCP server\n *\n * Implements security validations:\n * - P0-4: API key format validation (format-only quick check)\n * - P0-7: Workspace path injection prevention\n * - P0-8: CORS origin validation\n * - P1-3: Request body size limits\n * - Database-backed API key verification via Better Auth\n *\n * @see packages/auth/src/auth.ts - Better Auth configuration\n */\n\nimport { fetchWithPooling } from \"./http-client.js\";\n\n// API URL for database-backed verification\nconst API_URL = process.env.VREKO_API_URL || \"https://api.vreko.dev\";\n\n// Security constants\nconst MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB - P1-3\n// ✅ CONSOLIDATED: Use sk_live_/sk_test_ prefix (aligned with Better Auth)\n// Better Auth generates 64-char keys, but accept 32+ for backwards compatibility\nconst API_KEY_PATTERN = /^sk_(live|test)_[a-zA-Z0-9_-]{32,}$/; // P0-4\n// Workspace ID: unified 12-char hex format (48 bits entropy)\n// Supports both: 12-char hex (new unified format) and ws_ + 32-char hex (legacy)\nconst WORKSPACE_ID_PATTERN = /^([a-f0-9]{12}|ws_[a-f0-9]{32})$/;\nconst WORKSPACE_ID_LENGTHS = [12, 35]; // 12 (unified) or 35 (legacy: ws_ + 32)\nconst DANGEROUS_CHARS = /[;<>|&$`\\\\]/; // Command injection protection\nconst PATH_DANGEROUS_CHARS = /[<>|&;$`\\\\]/; // Path injection protection\n\nexport interface ValidationResult {\n\tvalid: boolean;\n\terror?: string;\n}\n\n/**\n * Validates API key format and prefix\n * @param apiKey - The API key to validate\n * @returns Validation result\n */\nexport function validateApiKey(apiKey: string | undefined): ValidationResult {\n\tif (!apiKey || apiKey.trim() === \"\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Missing API key\",\n\t\t};\n\t}\n\n\t// Check for valid prefix and format\n\tif (!API_KEY_PATTERN.test(apiKey)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid API key format. Must start with sk_live_ or sk_test_ followed by at least 32 alphanumeric characters\",\n\t\t};\n\t}\n\n\t// Check for dangerous characters (command injection prevention)\n\tif (DANGEROUS_CHARS.test(apiKey)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid API key format. Contains illegal characters\",\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Database-backed API key validation via Better Auth\n *\n * Calls the API's verifyApiKey endpoint to:\n * - Verify key against database (Argon2 hash)\n * - Check expiration and revocation status\n * - Apply rate limiting\n * - Return user info and tier\n *\n * @param apiKey - The API key to validate\n * @returns Validation result with user info\n */\nexport async function validateApiKeyWithDatabase(apiKey: string): Promise<{\n\tvalid: boolean;\n\ttransient?: boolean;\n\tuserId?: string;\n\ttier?: \"free\" | \"pro\" | \"enterprise\";\n\terror?: string;\n}> {\n\t// Quick format check before making HTTP call\n\tconst formatResult = validateApiKey(apiKey);\n\tif (!formatResult.valid) {\n\t\treturn { valid: false, error: formatResult.error };\n\t}\n\n\ttry {\n\t\t// Call API's auth.verifyApiKey oRPC endpoint with connection pooling\n\t\tconst response = await fetchWithPooling(`${API_URL}/orpc/auth.verifyApiKey`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tapiKey,\n\t\t\t\t// Request MCP tools permission\n\t\t\t\trequiredPermissions: {\n\t\t\t\t\tmcp: [\"tools\"],\n\t\t\t\t},\n\t\t\t}),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) {\n\t\t\t\treturn { valid: false, error: \"Invalid or expired API key\" };\n\t\t\t}\n\t\t\treturn { valid: false, error: `API verification failed: ${response.status}` };\n\t\t}\n\n\t\tconst result = (await response.json()) as {\n\t\t\tvalid: boolean;\n\t\t\tuserId?: string;\n\t\t\tpermissions?: Record<string, string[]>;\n\t\t\trateLimit?: {\n\t\t\t\tenabled: boolean;\n\t\t\t\tremaining: number;\n\t\t\t\tmax: number;\n\t\t\t\tresetAt?: string;\n\t\t\t};\n\t\t};\n\n\t\tif (!result.valid) {\n\t\t\treturn { valid: false, error: \"Invalid API key\" };\n\t\t}\n\n\t\t// Determine tier based on permissions (pro has more permissions)\n\t\tconst permissions = result.permissions || {};\n\t\tconst hasPro = permissions.api?.includes(\"write\") || permissions[\"vreko:snapshot\"]?.includes(\"write\");\n\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tuserId: result.userId,\n\t\t\ttier: hasPro ? \"pro\" : \"free\",\n\t\t};\n\t} catch (_error) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\ttransient: true,\n\t\t\terror: \"auth_service_unavailable\",\n\t\t};\n\t}\n}\n\n// NOTE: Format-only validation (validateApiKey) is kept for quick pre-checks\n// Database-backed validation (validateApiKeyWithDatabase) should be used for full verification\n\n/**\n * Validates workspace ID format (P0-4 equivalent for workspace auth)\n * @param workspaceId - The workspace ID to validate\n * @returns Validation result\n */\nexport function validateWorkspaceId(workspaceId: string | undefined): ValidationResult {\n\tif (!workspaceId || workspaceId.trim() === \"\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Missing workspace ID\",\n\t\t};\n\t}\n\n\t// Check for valid format: 12-char hex (unified) or ws_ + 32-char hex (legacy)\n\tif (!WORKSPACE_ID_PATTERN.test(workspaceId)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace ID format. Must be 12 lowercase hex characters (unified) or ws_ followed by 32 lowercase hex characters (legacy)\",\n\t\t};\n\t}\n\n\t// Length validation (redundant with regex but explicit for security)\n\tif (!WORKSPACE_ID_LENGTHS.includes(workspaceId.length)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace ID length\",\n\t\t};\n\t}\n\n\t// Check for dangerous characters (injection prevention)\n\tif (DANGEROUS_CHARS.test(workspaceId)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace ID format. Contains illegal characters\",\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Quick boolean check for workspace ID validity\n * Exported for use in other modules (e.g., packages/platform/src/lib/workspace-id.ts)\n *\n * @param workspaceId - The workspace ID to validate\n * @returns true if valid, false otherwise\n */\nexport function isValidWorkspaceId(workspaceId: string): boolean {\n\treturn validateWorkspaceId(workspaceId).valid;\n}\n\n/**\n * Validates workspace path for security\n *\n * Accepts:\n * - \"default\" - Zero-config mode (server will use anonymous/default workspace)\n * - Absolute paths - Traditional workspace path (e.g., \"/Users/user/project\")\n *\n * @param workspace - The workspace path to validate\n * @returns Validation result\n */\nexport function validateWorkspace(workspace: string | undefined): ValidationResult {\n\t// Allow \"default\" for zero-config mode\n\tif (workspace === \"default\") {\n\t\treturn { valid: true };\n\t}\n\n\tif (!workspace || workspace.trim() === \"\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Missing workspace parameter\",\n\t\t};\n\t}\n\n\t// Check for path traversal attempts\n\tif (workspace.includes(\"..\")) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace path. Path traversal detected\",\n\t\t};\n\t}\n\n\t// Must be absolute path (unless it's \"default\")\n\tif (!workspace.startsWith(\"/\")) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace path. Must be an absolute path or 'default'\",\n\t\t};\n\t}\n\n\t// Check for null bytes (security vulnerability)\n\tif (workspace.includes(\"\\0\")) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace path. Contains null bytes\",\n\t\t};\n\t}\n\n\t// Check for suspicious characters (path injection prevention)\n\tif (PATH_DANGEROUS_CHARS.test(workspace)) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Invalid workspace path. Contains illegal characters\",\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Validates CORS origin configuration\n * @param origin - The CORS origin to validate\n * @param nodeEnv - Current NODE_ENV\n * @returns Validation result\n */\nexport function validateCorsOrigin(origin: string | undefined, nodeEnv: string): ValidationResult {\n\t// Wildcard not allowed in production\n\tif (nodeEnv === \"production\" && origin === \"*\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: \"Wildcard CORS origin (*) not allowed in production\",\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Get allowed CORS origin for request\n * @param requestOrigin - Origin from request header\n * @param allowedOrigins - Configured allowed origins (comma-separated or single)\n * @returns The origin to use in response, or null if not allowed\n */\nexport function getAllowedCorsOrigin(requestOrigin: string | undefined, allowedOrigins: string): string | null {\n\tif (allowedOrigins === \"*\") {\n\t\treturn \"*\";\n\t}\n\n\tif (!requestOrigin) {\n\t\treturn allowedOrigins.split(\",\")[0] || null;\n\t}\n\n\tconst origins = allowedOrigins.split(\",\").map((o) => o.trim());\n\n\tif (origins.includes(requestOrigin)) {\n\t\treturn requestOrigin;\n\t}\n\n\treturn null;\n}\n\n/**\n * Get max body size in bytes\n */\nexport function getMaxBodySize(): number {\n\treturn MAX_BODY_SIZE;\n}\n"],"mappings":";;;AASA,SAAS,kBAAkB;AAC3B,SAAS,oBAA+D;AACxE,SAAS,WAAW;;;ACVpB,YAAYA,YAAY;;;AAkBxB,IAAMC,kBAAkB,oBAAIC,IAAI;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACA;AAED,IAAMC,gCAAgC,oBAAID,IAAI;EAAC;EAAS;EAAO;CAAU;AAGzE,IAAME,eAAe;AAErB,IAAMC,cAAc;AAEpB,IAAMC,aAAa;AAEnB,IAAMC,iBAAiB;AAEvB,SAASC,YAAYC,OAAa;AACjC,MAAIA,MAAMC,SAASH,gBAAgB;AAClC,WAAO,cAAcE,MAAMC,MAAM;EAClC;AACA,SAAOD,MACLE,QAAQP,cAAc,KAAA,EACtBO,QAAQN,aAAa,qBAAA,EACrBM,QAAQL,YAAY,oBAAA;AACvB;AARSE,OAAAA,aAAAA,aAAAA;AAuCF,SAASI,yBAAyBC,SAA4B;AACpE,QAAM,EAAEC,KAAKC,SAASC,aAAaC,kBAAkBC,aAAY,IAAKL;AACtE,QAAMM,cAAcC,qBAAqBJ,WAAAA;AACzC,SAAO;IACNF;IACAE,aAAaG;IACbJ,SACCA,WACAM,QAAQC,IAAIC,yBACZF,QAAQC,IAAIE,qCACZH,QAAQC,IAAIG;IACbC,gBAAgB;IAChBT,kBAAkBA,qBAAqBE,gBAAgB,gBAAgB,IAAM;IAC7ED,cAAcA,gBAAgB;MAAC;MAAgB;MAAmB;MAAe;;IACjFS,WAAWC,OAA8B;AACxC,YAAMC,MAAMD,MAAME;AAClB,UAAID,KAAKE,SAAS;AACjB,cAAMA,UAAUF,IAAIE;AACpB,eAAOA,QAAQC;AACf,eAAOD,QAAQE;AACf,eAAOF,QAAQ,WAAA;MAChB;AACA,YAAMG,OAAON,MAAMM;AACnB,UAAIA,MAAMC,OAAO;AAChBD,aAAKC,QAAQ;MACd;AACA,YAAMC,YAAYR,MAAMQ;AACxB,UAAIA,WAAWC,QAAQ;AACtB,mBAAWC,OAAOF,UAAUC,QAA0C;AACrE,cAAIC,IAAI7B,SAAS,OAAO6B,IAAI7B,UAAU,UAAU;AAC/C6B,gBAAI7B,QAAQD,YAAY8B,IAAI7B,KAAK;UAClC;AACA,gBAAM8B,KAAKD,IAAIE;AACf,cAAID,IAAIE,QAAQ;AACf,uBAAWC,SAASH,GAAGE,QAA0C;AAChE,kBAAI,OAAOC,MAAMC,aAAa,UAAU;AACvCD,sBAAMC,WAAWnC,YAAYkC,MAAMC,QAAQ;cAC5C;AACA,kBAAI,OAAOD,MAAME,aAAa,UAAU;AACvCF,sBAAME,WAAWpC,YAAYkC,MAAME,QAAQ;cAC5C;YACD;UACD;QACD;MACD;AACA,aAAOhB;IACR;EACD;AACD;AAhDgBhB,OAAAA,0BAAAA,0BAAAA;AA2DhB,SAASQ,qBAAqByB,UAAiB;AAC9C,MAAIA,SAAU,QAAOA;AACrB,QAAMC,WAAWzB,QAAQC,IAAIyB;AAC7B,MAAID,aAAa,iBAAiBA,aAAa,aAAaA,aAAa,cAAc;AACtF,WAAOA;EACR;AAIA,MAAIzB,QAAQC,IAAI0B,eAAe,UAAW,QAAO;AAGjD,SAAO3B,QAAQC,IAAI2B,YAAY;AAChC;AAbS7B,OAAAA,sBAAAA,sBAAAA;AAeF,SAAS8B,mBAAmBrC,SAA6B;AAC/D,QAAM,EAAEC,KAAKqC,SAASC,eAAeC,MAAMtC,SAASC,YAAW,IAAKH;AAEpE,QAAMM,cAAcC,qBAAqBJ,WAAAA;AAEzC,SAAO;IACNF;IACAE,aAAaG;IACbJ,SAASA,WAAWM,QAAQC,IAAIgC,WAAWjC,QAAQC,IAAIG;IACvDC,gBAAgB;IAChB6B,kBAAkB;;IAElBtC,kBAAkBE,gBAAgB,gBAAgB,IAAM;IAExDqC,cAAc;MACbC,MAAM;QACLN;QACA,GAAIE,OAAO;UAAEA;QAAK,IAAI,CAAC;MACxB;MACA,GAAID,gBAAgB;QAAElB,MAAM;UAAEwB,IAAIN;QAAc;MAAE,IAAI,CAAC;IACxD;;IAGAO,aAAaC,qBAAqC;AACjD,aAAOA,oBAAoBC,OAAO,CAACC,MAAM,CAAC;QAAC;QAAe;QAAW;QAAQC,SAASD,EAAEE,IAAI,CAAA;IAC7F;IAEArC,WAAWC,OAAK;AAEf,UAAIA,MAAMqC,OAAO;AAChB,cAAMC,WAAoC,CAAC;AAC3C,mBAAWC,OAAOlE,iBAAiB;AAClC,cAAIkE,OAAOvC,MAAMqC,OAAO;AACvBC,qBAASC,GAAAA,IAAOvC,MAAMqC,MAAME,GAAAA;UAC7B;QACD;AACAvC,cAAMqC,QAAQC;MACf;AAGA,UAAItC,MAAMwC,UAAUC,SAAS;AAC3BzC,cAAMwC,SAASC,QAAoCC,OAAOC;MAC5D;AAGA,UAAI3C,MAAME,SAAS;AAClBF,cAAME,QAAQ0C,OAAOD;MACtB;AAGA,UAAI3C,MAAM6C,aAAapC,QAAQ;AAC9B,cAAMoC,cAAc7C,MAAM6C,YAAYpC,OAAM;AAC5C,mBAAWqC,SAASD,aAAa;AAChCC,gBAAMF,OAAOD;QACd;MACD;AAGA,UAAI3C,MAAMQ,WAAWC,QAAQ;AAC5B,mBAAWC,OAAOV,MAAMQ,UAAUC,QAAQ;AACzC,cAAIC,IAAI7B,OAAO;AACd6B,gBAAI7B,QAAQD,YAAY8B,IAAI7B,KAAK;UAClC;AACA,cAAI6B,IAAIE,YAAYC,QAAQ;AAC3B,uBAAWC,SAASJ,IAAIE,WAAWC,QAAQ;AAC1C,kBAAIC,MAAMC,UAAU;AACnBD,sBAAMC,WAAWnC,YAAYkC,MAAMC,QAAQ;cAC5C;AACA,kBAAID,MAAME,UAAU;AACnBF,sBAAME,WAAWpC,YAAYkC,MAAME,QAAQ;cAC5C;YACD;UACD;QACD;MACD;AAGA,UAAIhB,MAAM+C,SAAS;AAClB/C,cAAM+C,UAAUnE,YAAYoB,MAAM+C,OAAO;MAC1C;AAEA,aAAO/C;IACR;IAEAgD,iBAAiBC,YAAU;AAC1B,YAAMC,MAAMD,WAAWE,YAAY;AACnC,UAAI5E,8BAA8B6E,IAAIF,GAAAA,GAAM;AAE3C,cAAMN,OAAOK,WAAWL;AACxB,YAAIA,MAAMS,WAAW,MAAM;AAC1B,gBAAM,EAAEA,QAAQC,GAAG,GAAGC,KAAAA,IAASX;AAC/BK,qBAAWL,OAAOW;AAClB,iBAAON;QACR;AACA,eAAO;MACR;AACA,aAAOA;IACR;EACD;AACD;AAnGgB3B,OAAAA,oBAAAA,oBAAAA;;;AC5IhB,SAAS,OAAO,2BAA2B;AAE3C,IAAM,cAAc,IAAI,MAAM;AAAA,EAC7B,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,YAAY;AACb,CAAC;AAED,oBAAoB,WAAW;AAQ/B,eAAsB,iBAAiB,OAA0B,MAAuC;AACvG,SAAO,MAAM,OAAO,IAAI;AACzB;AAaO,SAAS,eAAqB;AACpC,OAAK,YAAY,QAAQ;AAC1B;;;AC/BA,SAAS,IAAI,OAAe,KAAa,KAA0B;AAClE,SAAO,IAAI,KAAK,KAAK,GAAG,GAAG,MAAM,IAAI,KAAK,UAAU,GAAG,CAAC,KAAK,EAAE;AAAA;AAChE;AAEO,IAAM,SAAS;AAAA,EACrB,KAAK,KAAa,KAAwB;AACzC,QAAI,QAAQ,IAAI,cAAc,UAAU;AACvC,cAAQ,OAAO,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AAAA,IAC3C;AAAA,EACD;AAAA,EAEA,KAAK,KAAa,KAAwB;AACzC,YAAQ,OAAO,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,KAAa,KAAwB;AAC1C,YAAQ,OAAO,MAAM,IAAI,SAAS,KAAK,GAAG,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,KAAa,KAAwB;AAC1C,QAAI,QAAQ,IAAI,cAAc,SAAS;AACtC,cAAQ,OAAO,MAAM,IAAI,SAAS,KAAK,GAAG,CAAC;AAAA,IAC5C;AAAA,EACD;AACD;;;AC1BA,IAAM,UAAU,QAAQ,IAAI,iBAAiB;AAG7C,IAAM,gBAAgB,KAAK,OAAO;AAGlC,IAAM,kBAAkB;AAGxB,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB,CAAC,IAAI,EAAE;AACpC,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAYtB,SAAS,eAAe,QAA8C;AAC5E,MAAI,CAAC,UAAU,OAAO,KAAK,MAAM,IAAI;AACpC,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,CAAC,gBAAgB,KAAK,MAAM,GAAG;AAClC,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,gBAAgB,KAAK,MAAM,GAAG;AACjC,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO,EAAE,OAAO,KAAK;AACtB;AAcA,eAAsB,2BAA2B,QAM9C;AAEF,QAAM,eAAe,eAAe,MAAM;AAC1C,MAAI,CAAC,aAAa,OAAO;AACxB,WAAO,EAAE,OAAO,OAAO,OAAO,aAAa,MAAM;AAAA,EAClD;AAEA,MAAI;AAEH,UAAM,WAAW,MAAM,iBAAiB,GAAG,OAAO,2BAA2B;AAAA,MAC5E,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,gBAAgB;AAAA,MACjB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACpB;AAAA;AAAA,QAEA,qBAAqB;AAAA,UACpB,KAAK,CAAC,OAAO;AAAA,QACd;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,SAAS,WAAW,KAAK;AAC5B,eAAO,EAAE,OAAO,OAAO,OAAO,6BAA6B;AAAA,MAC5D;AACA,aAAO,EAAE,OAAO,OAAO,OAAO,4BAA4B,SAAS,MAAM,GAAG;AAAA,IAC7E;AAEA,UAAM,SAAU,MAAM,SAAS,KAAK;AAYpC,QAAI,CAAC,OAAO,OAAO;AAClB,aAAO,EAAE,OAAO,OAAO,OAAO,kBAAkB;AAAA,IACjD;AAGA,UAAM,cAAc,OAAO,eAAe,CAAC;AAC3C,UAAM,SAAS,YAAY,KAAK,SAAS,OAAO,KAAK,YAAY,gBAAgB,GAAG,SAAS,OAAO;AAEpG,WAAO;AAAA,MACN,OAAO;AAAA,MACP,QAAQ,OAAO;AAAA,MACf,MAAM,SAAS,QAAQ;AAAA,IACxB;AAAA,EACD,SAAS,QAAQ;AAChB,WAAO;AAAA,MACN,OAAO;AAAA,MACP,WAAW;AAAA,MACX,OAAO;AAAA,IACR;AAAA,EACD;AACD;AAUO,SAAS,oBAAoB,aAAmD;AACtF,MAAI,CAAC,eAAe,YAAY,KAAK,MAAM,IAAI;AAC9C,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,CAAC,qBAAqB,KAAK,WAAW,GAAG;AAC5C,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,CAAC,qBAAqB,SAAS,YAAY,MAAM,GAAG;AACvD,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,gBAAgB,KAAK,WAAW,GAAG;AACtC,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO,EAAE,OAAO,KAAK;AACtB;AAuBO,SAAS,kBAAkB,WAAiD;AAElF,MAAI,cAAc,WAAW;AAC5B,WAAO,EAAE,OAAO,KAAK;AAAA,EACtB;AAEA,MAAI,CAAC,aAAa,UAAU,KAAK,MAAM,IAAI;AAC1C,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,UAAU,SAAS,IAAI,GAAG;AAC7B,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,CAAC,UAAU,WAAW,GAAG,GAAG;AAC/B,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,UAAU,SAAS,IAAI,GAAG;AAC7B,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,qBAAqB,KAAK,SAAS,GAAG;AACzC,WAAO;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO,EAAE,OAAO,KAAK;AACtB;AA0BO,SAAS,qBAAqB,eAAmC,gBAAuC;AAC9G,MAAI,mBAAmB,KAAK;AAC3B,WAAO;AAAA,EACR;AAEA,MAAI,CAAC,eAAe;AACnB,WAAO,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK;AAAA,EACxC;AAEA,QAAM,UAAU,eAAe,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAE7D,MAAI,QAAQ,SAAS,aAAa,GAAG;AACpC,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAKO,SAAS,iBAAyB;AACxC,SAAO;AACR;;;AJ3RA,OAAO;AAAA,EACN,mBAAmB;AAAA,IAClB,KAAK,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,cAAc;AAAA,IAC7D,SAAS;AAAA,EACV,CAAC;AACF;AAGA,QAAQ,KAAK,qBAAqB,CAAC,UAAiB;AACnD,SAAO,iBAAiB,OAAO,EAAE,MAAM,EAAE,WAAW,oBAAoB,EAAE,CAAC;AAC3E,SAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC;AACjF,UAAQ,KAAK,CAAC;AACf,CAAC;AACD,QAAQ,KAAK,sBAAsB,CAAC,WAAoB;AACvD,SAAO,iBAAiB,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC,GAAG;AAAA,IACrF,MAAM,EAAE,WAAW,qBAAqB;AAAA,IACxC,OAAO;AAAA,EACR,CAAC;AACD,SAAO,KAAK,uBAAuB,EAAE,QAAQ,OAAO,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,IAAM,OAAO,OAAO,SAAS,QAAQ,IAAI,QAAQ,QAAQ,EAAE;AAC3D,IAAM,YAAY,QAAQ,IAAI,YAAY;AAC1C,IAAM,cAAc,QAAQ,IAAI,eAAe;AAC/C,IAAMkC,WAAU,QAAQ,IAAI,iBAAiB;AAG7C,IAAI,iBAAiB;AACrB,IAAI,UAAU;AACd,IAAM,YAAY,KAAK,IAAI;AAE3B,eAAe,WAAW,UAAkB,QAAgB,MAAe,SAAiC;AAC3G,MAAI;AACH,UAAM,MAAM,MAAM,iBAAiB,GAAGA,QAAO,cAAc,QAAQ,IAAI;AAAA,MACtE;AAAA,MACA,SAAS,EAAE,gBAAgB,oBAAoB,QAAQ,uCAAuC,GAAG,QAAQ;AAAA,MACzG,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACrC,CAAC;AACD,UAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,UAAM,aAAqC;AAAA;AAAA,IAE3C;AACA,QAAI,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAC7B,iBAAW,CAAC,IAAI;AAAA,IACjB,CAAC;AACD,WAAO,EAAE,QAAQ,IAAI,QAAQ,MAAM,SAAS,SAAS,WAAW;AAAA,EACjE,SAAS,IAAI;AACZ,WAAO;AAAA,MACN,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,sBAAsB,CAAC;AAAA,MACrD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC/C;AAAA,EACD;AACD;AAEA,SAAS,QAAQ,KAAsB,KAAqB;AAC3D,QAAM,SAAS,qBAAqB,IAAI,QAAQ,QAAQ,QAAQ,IAAI,eAAe,GAAG;AACtF,MAAI,UAAU,+BAA+B,UAAU,GAAG;AAC1D,MAAI,UAAU,gCAAgC,4BAA4B;AAC1E,MAAI;AAAA,IACH;AAAA,IACA;AAAA,EACD;AACA,MAAI,UAAU,iCAAiC,gBAAgB;AAChE;AAEA,IAAM,SAAS,aAAa,OAAO,KAAsB,QAAwB;AAChF,UAAQ,KAAK,GAAG;AAChB,MAAI,IAAI,WAAW,WAAW;AAC7B,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI;AACR;AAAA,EACD;AAGA,QAAM,UAAU,QAAQ,IAAI,YAAY;AACxC,QAAM,gBAAgB,IAAI,QAAQ;AAClC,MAAI,YAAY,gBAAgB,eAAe;AAC9C,UAAM,kBAAkB,QAAQ,IAAI,eAAe,IACjD,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEhB,QAAI,eAAe,SAAS,KAAK,CAAC,eAAe,SAAS,aAAa,KAAK,eAAe,CAAC,MAAM,KAAK;AACtG,aAAO,KAAK,4CAA4C,EAAE,QAAQ,cAAc,CAAC;AACjF,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,aAAa,SAAS,qBAAqB,CAAC,CAAC;AAC7E;AAAA,IACD;AAAA,EACD;AAEA,QAAM,YAAY,WAAW;AAC7B,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,UAAU,IAAI,QAAQ,IAAI,EAAE;AAC/D,SAAO,KAAK,WAAW,EAAE,WAAW,QAAQ,IAAI,QAAQ,MAAM,IAAI,SAAS,CAAC;AAE5E,QAAM,UAAU,eAAe;AAC/B,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,MAAI,UAAU;AAEd,MAAI,GAAG,QAAQ,CAAC,UAAU;AACzB,QAAI,SAAS;AACZ;AAAA,IACD;AACA,YAAQ,MAAM;AACd,QAAI,OAAO,SAAS;AACnB,gBAAU;AACV,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,oBAAoB,CAAC,CAAC;AACtD,UAAI,QAAQ;AACZ;AAAA,IACD;AACA,WAAO,KAAK,KAAK;AAAA,EAClB,CAAC;AAED,MAAI,GAAG,OAAO,YAAY;AACzB,QAAI,SAAS;AACZ;AAAA,IACD;AACA,UAAM,OAAO,OAAO,WAAW,IAAI,KAAK,OAAO,OAAO,QAAQ,IAAI,EAAE,SAAS,MAAM;AAEnF,QAAI;AACH,UAAI;AACJ,YAAM,OAAO,IAAI;AAIjB,UAAI,SAAS,UAAU,IAAI,WAAW,OAAO;AAC5C,cAAM,YAAY,IAAI,QAAQ,gBAAgB;AAC9C,YAAI,CAAC,WAAW;AACf,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iCAAiC,CAAC,CAAC;AACnE;AAAA,QACD;AAEA,cAAM,kBAA0C;AAAA,UAC/C,kBAAkB;AAAA,UAClB,QAAQ;AAAA,QACT;AACA,YAAI,IAAI,QAAQ,WAAW,GAAG;AAC7B,0BAAgB,WAAW,IAAI,IAAI,QAAQ,WAAW;AAAA,QACvD;AACA,YAAI,IAAI,QAAQ,eAAe,WAAW,SAAS,GAAG;AACrD,0BAAgB,gBAAgB,IAAI,QAAQ;AAAA,QAC7C;AACA,YAAI,IAAI,QAAQ,kBAAkB,GAAG;AACpC,0BAAgB,kBAAkB,IAAI,IAAI,QAAQ,kBAAkB;AAAA,QACrE;AAEA,YAAI;AACH,gBAAM,WAAW,MAAM,iBAAiB,GAAGA,QAAO,eAAe;AAAA,YAChE,QAAQ;AAAA,YACR,SAAS;AAAA,UACV,CAAC;AAGD,gBAAMC,mBAA0C,CAAC;AACjD,mBAAS,QAAQ,QAAQ,CAAC,GAAG,MAAM;AAClC,YAAAA,iBAAgB,CAAC,IAAI;AAAA,UACtB,CAAC;AACD,cAAI,UAAU,SAAS,QAAQA,gBAAe;AAG9C,cAAI,SAAS,MAAM;AAClB,kBAAM,SAAU,SAAS,KAAoC,UAAU;AACvE,gBAAI;AAAA,cAAG;AAAA,cAAS,MACf,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,cAE5B,CAAC;AAAA,YACF;AACA,aAAC,YAAY;AACZ,kBAAI;AACH,uBAAO,MAAM;AACZ,wBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,sBAAI,MAAM;AACT,wBAAI,IAAI;AACR;AAAA,kBACD;AACA,sBAAI,MAAM,KAAK;AAAA,gBAChB;AAAA,cACD,SAAS,SAAS;AACjB,uBAAO,KAAK,gCAAgC;AAAA,kBAC3C;AAAA,kBACA,OAAO,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,OAAO;AAAA,gBACnE,CAAC;AACD,uBAAO;AAAA,kBACN,mBAAmB,QAAQ,UAAU,IAAI,MAAM,OAAO,OAAO,CAAC;AAAA,kBAC9D,EAAE,MAAM,EAAE,WAAW,WAAW,GAAG,OAAO,EAAE,UAAU,EAAE;AAAA,gBACzD;AACA,oBAAI,IAAI;AAAA,cACT;AAAA,YACD,GAAG;AAAA,UACJ,OAAO;AACN,gBAAI,IAAI;AAAA,UACT;AAAA,QACD,SAAS,QAAQ;AAChB,iBAAO,KAAK,+BAA+B;AAAA,YAC1C;AAAA,YACA,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,UAChE,CAAC;AACD,iBAAO,iBAAiB,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC,GAAG;AAAA,YACrF,MAAM,EAAE,WAAW,eAAe;AAAA,YAClC,OAAO,EAAE,UAAU;AAAA,UACpB,CAAC;AACD,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,sBAAsB,CAAC,CAAC;AAAA,QACzD;AACA;AAAA,MACD;AAGA,UAAI,SAAS,aAAa,SAAS,gBAAgB;AAElD,iBAAS;AAAA,UACR,QAAQ;AAAA,UACR,MAAM,KAAK,UAAU;AAAA,YACpB,QAAQ,iBAAiB,kBAAkB;AAAA,YAC3C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YAClC,QAAQ,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAAA,YAClD,SAAS;AAAA,UACV,CAAC;AAAA,UACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC/C;AAAA,MACD,WAAW,SAAS,iBAAiB;AAGpC,YAAI,gBAAgB;AACnB,mBAAS;AAAA,YACR,QAAQ;AAAA,YACR,MAAM,KAAK,UAAU;AAAA,cACpB,QAAQ;AAAA,cACR,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,cAClC,SAAS;AAAA,YACV,CAAC;AAAA,YACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC/C;AAAA,QACD,OAAO;AAEN,gBAAM,WAAW,MAAM,WAAW,WAAW,OAAO,MAAM,CAAC,CAAC;AAC5D,gBAAM,aAAa,SAAS,WAAW;AAEvC,cAAI,cAAc,CAAC,SAAS;AAC3B,sBAAU;AACV,mBAAO,KAAK,2DAA2D;AAAA,UACxE;AACA,mBAAS;AAAA,YACR,QAAQ,aAAa,MAAM;AAAA,YAC3B,MAAM,KAAK,UAAU;AAAA,cACpB,QAAQ,aAAa,UAAU;AAAA,cAC/B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,cAClC,KAAK,aAAa,cAAc;AAAA,cAChC,QAAQ,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAAA,cAClD,SAAS;AAAA,YACV,CAAC;AAAA,YACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC/C;AAAA,QACD;AAAA,MACD,WAAW,SAAS,mBAAmB;AAGtC,iBAAS;AAAA,UACR,QAAQ,UAAU,MAAM;AAAA,UACxB,MAAM,KAAK,UAAU;AAAA,YACpB,QAAQ,UAAU,YAAY;AAAA,YAC9B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,CAAC;AAAA,UACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC/C;AAAA,MACD,WAAW,SAAS,QAAQ;AAC3B,cAAM,YAAY,IAAI,QAAQ,gBAAgB;AAC9C,YAAI,SAAS,CAAC;AACd,YAAI;AACH,mBAAS,KAAK,MAAM,QAAQ,IAAI;AAAA,QACjC,QAAQ;AACP,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,cAAc,CAAC,CAAC;AAChD;AAAA,QACD;AAEA,cAAM,YACJ,OAAmC,aACpC,IAAI,aAAa,IAAI,WAAW,KAChC,IAAI,QAAQ,kBAAkB,KAC9B;AACD,YAAI,CAAC,kBAAkB,SAAmB,EAAE,OAAO;AAClD,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,cAAc,CAAC,CAAC;AAChD;AAAA,QACD;AAEA,cAAM,cAAe,OAAmC,eAAe,IAAI,QAAQ,gBAAgB;AACnG,YAAI,eAAe,CAAC,oBAAoB,WAAqB,EAAE,OAAO;AACrE,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACjD;AAAA,QACD;AAGA,cAAM,SAAS,IAAI,QAAQ,WAAW;AACtC,YAAI,QAAQ;AACX,gBAAM,aAAa,MAAM,2BAA2B,MAAM;AAC1D,cAAI,CAAC,WAAW,OAAO;AACtB,gBAAI,WAAW,WAAW;AAGzB,kBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,kBAAI;AAAA,gBACH,KAAK,UAAU;AAAA,kBACd,SAAS;AAAA,kBACT,OAAO;AAAA,oBACN,MAAM;AAAA,oBACN,SAAS;AAAA,oBACT,MAAM,EAAE,QAAQ,4BAA4B,WAAW,KAAK;AAAA,kBAC7D;AAAA,kBACA,IAAK,QAAoC,MAAM;AAAA,gBAChD,CAAC;AAAA,cACF;AACA;AAAA,YACD;AAEA,kBAAM,gBAAgB;AAAA,cACrB,SAAS;AAAA,cACT,OAAO;AAAA,gBACN,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,MAAM,EAAE,QAAQ,WAAW,SAAS,2BAA2B;AAAA,cAChE;AAAA,cACA,IAAK,QAAoC,MAAM;AAAA,YAChD;AACA,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI,IAAI,KAAK,UAAU,aAAa,CAAC;AACrC;AAAA,UACD;AAEA,cAAI,WAAW,QAAQ;AACtB,mBAAO,MAAM,qBAAqB,EAAE,QAAQ,WAAW,QAAQ,MAAM,WAAW,KAAK,CAAC;AAAA,UACvF;AAAA,QACD;AAEA,cAAM,UAAkC,EAAE,oBAAoB,UAAoB;AAClF,YAAI,WAAW;AACd,kBAAQ,gBAAgB,IAAI;AAAA,QAC7B;AACA,YAAI,aAAa;AAChB,kBAAQ,gBAAgB,IAAI;AAAA,QAC7B;AACA,YAAI,QAAQ;AACX,kBAAQ,WAAW,IAAI;AAAA,QACxB;AAEA,YAAI,IAAI,QAAQ,eAAe,WAAW,SAAS,GAAG;AACrD,kBAAQ,gBAAgB,IAAI,QAAQ;AAAA,QACrC;AACA,iBAAS,MAAM,WAAW,IAAI,IAAI,UAAU,QAAQ,QAAQ,OAAO;AAAA,MACpE,WAAW,SAAS,wBAAwB;AAC3C,YAAI,IAAI,WAAW,QAAQ;AAC1B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,YAAI,SAAS,CAAC;AACd,YAAI;AACH,mBAAS,KAAK,MAAM,IAAI;AAAA,QACzB,QAAQ;AACP,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,iBAAS,MAAM;AAAA,UACd;AAAA,UACA;AAAA,UACA;AAAA,UACA,IAAI,QAAQ,gBAAgB,EAAE,eAAe,IAAI,QAAQ,cAAc,IAAI,CAAC;AAAA,QAC7E;AAAA,MACD,WAAW,SAAS,gBAAgB;AACnC,YAAI,IAAI,WAAW,QAAQ;AAC1B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,YAAI,SAAS,CAAC;AACd,YAAI;AACH,mBAAS,KAAK,MAAM,IAAI;AAAA,QACzB,QAAQ;AACP,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,iBAAS,MAAM,WAAW,gBAAgB,QAAQ,QAAQ,CAAC,CAAC;AAAA,MAC7D,WAAW,SAAS,kBAAkB;AACrC,YAAI,IAAI,WAAW,OAAO;AACzB,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,cAAM,OAAO,IAAI,aAAa,IAAI,aAAa;AAC/C,iBAAS,MAAM;AAAA,UACd,OAAO,8BAA8B,mBAAmB,IAAI,CAAC,KAAK;AAAA,UAClE;AAAA,UACA;AAAA,UACA,CAAC;AAAA,QACF;AAAA,MACD,WAAW,SAAS,gCAAgC;AACnD,YAAI,IAAI,WAAW,QAAQ;AAC1B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,YAAI,SAAS,CAAC;AACd,YAAI;AACH,mBAAS,KAAK,MAAM,IAAI;AAAA,QACzB,QAAQ;AACP,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,iBAAS,MAAM,WAAW,gCAAgC,QAAQ,QAAQ,CAAC,CAAC;AAAA,MAC7E,WAAW,SAAS,iBAAiB;AACpC,YAAI,IAAI,WAAW,OAAO;AACzB,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI;AACR;AAAA,QACD;AACA,cAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,YAAI,CAAC,QAAQ;AACZ,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,cAAc,CAAC,CAAC;AAChD;AAAA,QACD;AACA,iBAAS,MAAM,WAAW,wBAAwB,mBAAmB,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,MAChG,OAAO;AACN,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAC9C;AAAA,MACD;AAKA,YAAM,kBAA0C;AAAA,QAC/C,gBAAgB,OAAO,QAAQ,cAAc,KAAK;AAAA,MACnD;AACA,UAAI,OAAO,QAAQ,gBAAgB,GAAG;AACrC,wBAAgB,gBAAgB,IAAI,OAAO,QAAQ,gBAAgB;AAAA,MACpE;AACA,UAAI,UAAU,OAAO,QAAQ,eAAe;AAC5C,UAAI,IAAI,OAAO,IAAI;AAAA,IACpB,SAAS,GAAG;AACX,aAAO,MAAM,SAAS,EAAE,WAAW,OAAO,OAAO,CAAC,EAAE,CAAC;AACrD,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iBAAiB,CAAC,CAAC;AAAA,IACpD;AAAA,EACD,CAAC;AACF,CAAC;AAGD,IAAM,sBAAsB;AAE5B,eAAe,iBAAiB,QAAgB;AAC/C,MAAI,gBAAgB;AACnB,WAAO,KAAK,8BAA8B;AAC1C;AAAA,EACD;AAEA,mBAAiB;AACjB,SAAO,KAAK,sBAAsB,EAAE,QAAQ,KAAK,QAAQ,IAAI,CAAC;AAG9D,YAAU;AACV,SAAO,KAAK,6BAA6B;AAGzC,SAAO,oBAAoB;AAC3B,SAAO,KAAK,wBAAwB;AAGpC,SAAO,KAAK,+CAA+C;AAC3D,QAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAGxD,eAAa;AACb,SAAO,KAAK,4BAA4B;AAGxC,MAAI;AACH,UAAM,OAAO,MAAM,GAAI;AAAA,EACxB,QAAQ;AAAA,EAER;AAGA,SAAO,KAAK,mBAAmB;AAC/B,SAAO,MAAM,MAAM;AAClB,WAAO,KAAK,wBAAwB;AACpC,YAAQ,KAAK,CAAC;AAAA,EACf,CAAC;AAGD,aAAW,MAAM;AAChB,WAAO,MAAM,4BAA4B;AACzC,YAAQ,KAAK,CAAC;AAAA,EACf,GAAG,mBAAmB;AACvB;AAEA,QAAQ,KAAK,WAAW,MAAM,iBAAiB,SAAS,CAAC;AACzD,QAAQ,KAAK,UAAU,MAAM,iBAAiB,QAAQ,CAAC;AAIvD,OAAO,OAAO,MAAM,WAAW,MAAM;AACpC,SAAO,KAAK,sBAAsB,EAAE,MAAM,MAAM,MAAM,WAAW,SAAS,aAAa,QAAQD,SAAQ,CAAC;AAGxG,GAAC,YAAY;AACZ,QAAI;AACH,YAAM,QAAQ,MAAM,iBAAiB,GAAGA,QAAO,WAAW;AAAA,QACzD,QAAQ;AAAA,QACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,MACvC,CAAC;AACD,UAAI,MAAM,IAAI;AACb,kBAAU;AACV,eAAO,KAAK,sCAAsC,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,MAC3E,OAAO;AACN,eAAO,KAAK,4CAA4C,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,MAEjF;AAAA,IACD,SAAS,KAAK;AACb,aAAO,KAAK,kDAAkD;AAAA,QAC7D,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACvD,CAAC;AAAA,IACF;AAAA,EACD,GAAG;AACJ,CAAC;AAED,IAAO,gBAAQ;","names":["Sentry","EXTRA_ALLOWLIST","Set","BLOCKED_BREADCRUMB_CATEGORIES","HOME_PATH_RE","GH_TOKEN_RE","API_KEY_RE","MAX_STRING_LEN","scrubString","value","length","replace","createNextjsSentryConfig","options","dsn","release","environment","tracesSampleRate","ignoreErrors","resolvedEnv","resolveDeploymentEnv","process","env","VERCEL_GIT_COMMIT_SHA","NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA","npm_package_version","sendDefaultPii","beforeSend","event","req","request","headers","authorization","cookie","user","email","exception","values","exc","st","stacktrace","frames","frame","filename","abs_path","override","explicit","DEPLOYMENT_ENV","VERCEL_ENV","NODE_ENV","createSentryConfig","surface","workspaceHash","tier","GIT_SHA","attachStacktrace","initialScope","tags","id","integrations","defaultIntegrations","filter","i","includes","name","extra","filtered","key","contexts","runtime","args","undefined","data","breadcrumbs","crumb","message","beforeBreadcrumb","breadcrumb","cat","category","has","_allow","_","rest","API_URL","responseHeaders"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "vreko-mcp-server",
3
+ "version": "3.1.1",
4
+ "description": "Vreko MCP Proxy - HTTP reverse proxy forwarding MCP requests to the API server",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "dependencies": {
16
+ "execa": "9.5.2",
17
+ "ky": "1.7.2",
18
+ "llm-guard": "^0.1.8",
19
+ "opossum": "9.0.0",
20
+ "zod": "3.25.76",
21
+ "@vreko/mcp": "0.1.2",
22
+ "@vreko/sentry-privacy": "0.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "24.5.1",
26
+ "typescript": "5.9.2",
27
+ "tsup": "8.5.0",
28
+ "vitest": "3.2.6",
29
+ "@vitest/coverage-v8": "3.2.6",
30
+ "@vreko/platform": "0.7.2"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.9.0"
34
+ },
35
+ "scripts": {
36
+ "dev": "SERVICE_NAME=mcp-server tsx src/index.ts",
37
+ "build": "doppler run --config dev -- tsup",
38
+ "build:docker": "tsup",
39
+ "docker-build": "docker build -t vreko-mcp:${GIT_SHA:-latest} -f apps/mcp-server/Dockerfile .",
40
+ "start": "node dist/index.js",
41
+ "validate": "node scripts/validate-bundle.js",
42
+ "validate:fix": "node scripts/validate-bundle.js --fix",
43
+ "docker:test": "./scripts/docker-test.sh",
44
+ "docker:shell": "./scripts/docker-test.sh --shell",
45
+ "deploy": "./scripts/deploy-fly.sh",
46
+ "deploy:dry-run": "./scripts/deploy-fly.sh --dry-run",
47
+ "deploy:skip-build": "./scripts/deploy-fly.sh --skip-build",
48
+ "deploy:status": "fly status -a vreko-mcp",
49
+ "deploy:logs": "fly logs -a vreko-mcp",
50
+ "deploy:rollback": "fly releases -a vreko-mcp",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "test:coverage": "vitest run --coverage",
54
+ "type-check": "tsc --noEmit",
55
+ "lint": "biome check .",
56
+ "format": "biome format ."
57
+ }
58
+ }