@seleniumbox/sbox-mcp 0.2.0

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.
Files changed (141) hide show
  1. package/PUBLISHING.md +115 -0
  2. package/README.md +134 -0
  3. package/dist/adapters/auth.adapter.d.ts +46 -0
  4. package/dist/adapters/auth.adapter.js +54 -0
  5. package/dist/adapters/browser.adapter.d.ts +43 -0
  6. package/dist/adapters/browser.adapter.js +39 -0
  7. package/dist/adapters/device.adapter.d.ts +60 -0
  8. package/dist/adapters/device.adapter.js +40 -0
  9. package/dist/adapters/diagnostics.adapter.d.ts +73 -0
  10. package/dist/adapters/diagnostics.adapter.js +89 -0
  11. package/dist/adapters/index.d.ts +16 -0
  12. package/dist/adapters/index.js +20 -0
  13. package/dist/adapters/project.adapter.d.ts +38 -0
  14. package/dist/adapters/project.adapter.js +39 -0
  15. package/dist/adapters/sbox-api.client.d.ts +31 -0
  16. package/dist/adapters/sbox-api.client.js +104 -0
  17. package/dist/adapters/session.adapter.d.ts +77 -0
  18. package/dist/adapters/session.adapter.js +95 -0
  19. package/dist/adapters/stats.adapter.d.ts +72 -0
  20. package/dist/adapters/stats.adapter.js +108 -0
  21. package/dist/adapters/upload.adapter.d.ts +16 -0
  22. package/dist/adapters/upload.adapter.js +25 -0
  23. package/dist/adapters/user.adapter.d.ts +24 -0
  24. package/dist/adapters/user.adapter.js +25 -0
  25. package/dist/app.d.ts +2 -0
  26. package/dist/app.js +16 -0
  27. package/dist/config/env.d.ts +21 -0
  28. package/dist/config/env.js +55 -0
  29. package/dist/config/index.d.ts +1 -0
  30. package/dist/config/index.js +8 -0
  31. package/dist/controllers/analytics.controller.d.ts +10 -0
  32. package/dist/controllers/analytics.controller.js +127 -0
  33. package/dist/controllers/auth-device.controller.d.ts +7 -0
  34. package/dist/controllers/auth-device.controller.js +60 -0
  35. package/dist/controllers/auth.controller.d.ts +5 -0
  36. package/dist/controllers/auth.controller.js +20 -0
  37. package/dist/controllers/browser.controller.d.ts +5 -0
  38. package/dist/controllers/browser.controller.js +23 -0
  39. package/dist/controllers/device.controller.d.ts +5 -0
  40. package/dist/controllers/device.controller.js +23 -0
  41. package/dist/controllers/index.d.ts +6 -0
  42. package/dist/controllers/index.js +28 -0
  43. package/dist/controllers/project-stats.controller.d.ts +5 -0
  44. package/dist/controllers/project-stats.controller.js +29 -0
  45. package/dist/controllers/session.controller.d.ts +9 -0
  46. package/dist/controllers/session.controller.js +120 -0
  47. package/dist/controllers/upload.controller.d.ts +5 -0
  48. package/dist/controllers/upload.controller.js +44 -0
  49. package/dist/controllers/user.controller.d.ts +5 -0
  50. package/dist/controllers/user.controller.js +29 -0
  51. package/dist/device-flow.store.d.ts +7 -0
  52. package/dist/device-flow.store.js +23 -0
  53. package/dist/dto/auth.dto.d.ts +26 -0
  54. package/dist/dto/auth.dto.js +9 -0
  55. package/dist/dto/browser.dto.d.ts +13 -0
  56. package/dist/dto/browser.dto.js +2 -0
  57. package/dist/dto/device.dto.d.ts +15 -0
  58. package/dist/dto/device.dto.js +2 -0
  59. package/dist/dto/index.d.ts +6 -0
  60. package/dist/dto/index.js +22 -0
  61. package/dist/dto/project-stats.dto.d.ts +8 -0
  62. package/dist/dto/project-stats.dto.js +2 -0
  63. package/dist/dto/session.dto.d.ts +50 -0
  64. package/dist/dto/session.dto.js +9 -0
  65. package/dist/dto/user.dto.d.ts +6 -0
  66. package/dist/dto/user.dto.js +2 -0
  67. package/dist/index.d.ts +6 -0
  68. package/dist/index.js +34 -0
  69. package/dist/mcp/tools/auth-tools.d.ts +5 -0
  70. package/dist/mcp/tools/auth-tools.js +132 -0
  71. package/dist/mcp/tools/helpers.d.ts +26 -0
  72. package/dist/mcp/tools/helpers.js +53 -0
  73. package/dist/mcp/tools/index.d.ts +5 -0
  74. package/dist/mcp/tools/index.js +12 -0
  75. package/dist/mcp/tools/rest-tools.d.ts +5 -0
  76. package/dist/mcp/tools/rest-tools.js +540 -0
  77. package/dist/mcp-server.d.ts +6 -0
  78. package/dist/mcp-server.js +28 -0
  79. package/dist/middleware/auth.middleware.d.ts +10 -0
  80. package/dist/middleware/auth.middleware.js +23 -0
  81. package/dist/middleware/debug-log.middleware.d.ts +6 -0
  82. package/dist/middleware/debug-log.middleware.js +84 -0
  83. package/dist/middleware/error.middleware.d.ts +8 -0
  84. package/dist/middleware/error.middleware.js +24 -0
  85. package/dist/middleware/validate.middleware.d.ts +7 -0
  86. package/dist/middleware/validate.middleware.js +42 -0
  87. package/dist/routes/analytics.routes.d.ts +1 -0
  88. package/dist/routes/analytics.routes.js +15 -0
  89. package/dist/routes/auth.routes.d.ts +1 -0
  90. package/dist/routes/auth.routes.js +15 -0
  91. package/dist/routes/browser.routes.d.ts +1 -0
  92. package/dist/routes/browser.routes.js +11 -0
  93. package/dist/routes/device.routes.d.ts +1 -0
  94. package/dist/routes/device.routes.js +11 -0
  95. package/dist/routes/index.d.ts +1 -0
  96. package/dist/routes/index.js +27 -0
  97. package/dist/routes/mcp.routes.d.ts +6 -0
  98. package/dist/routes/mcp.routes.js +70 -0
  99. package/dist/routes/project-stats.routes.d.ts +1 -0
  100. package/dist/routes/project-stats.routes.js +11 -0
  101. package/dist/routes/session.routes.d.ts +1 -0
  102. package/dist/routes/session.routes.js +23 -0
  103. package/dist/routes/upload.routes.d.ts +1 -0
  104. package/dist/routes/upload.routes.js +20 -0
  105. package/dist/routes/user.routes.d.ts +1 -0
  106. package/dist/routes/user.routes.js +11 -0
  107. package/dist/server.d.ts +1 -0
  108. package/dist/server.js +17 -0
  109. package/dist/services/analytics.service.d.ts +88 -0
  110. package/dist/services/analytics.service.js +98 -0
  111. package/dist/services/auth.service.d.ts +27 -0
  112. package/dist/services/auth.service.js +68 -0
  113. package/dist/services/browser.service.d.ts +25 -0
  114. package/dist/services/browser.service.js +43 -0
  115. package/dist/services/device.service.d.ts +18 -0
  116. package/dist/services/device.service.js +58 -0
  117. package/dist/services/diagnostics.service.d.ts +87 -0
  118. package/dist/services/diagnostics.service.js +92 -0
  119. package/dist/services/enrichment.service.d.ts +9 -0
  120. package/dist/services/enrichment.service.js +112 -0
  121. package/dist/services/index.d.ts +20 -0
  122. package/dist/services/index.js +31 -0
  123. package/dist/services/project.service.d.ts +22 -0
  124. package/dist/services/project.service.js +31 -0
  125. package/dist/services/session.service.d.ts +62 -0
  126. package/dist/services/session.service.js +104 -0
  127. package/dist/services/sessions-per-project.service.d.ts +18 -0
  128. package/dist/services/sessions-per-project.service.js +57 -0
  129. package/dist/services/upload.service.d.ts +20 -0
  130. package/dist/services/upload.service.js +29 -0
  131. package/dist/services/user.service.d.ts +17 -0
  132. package/dist/services/user.service.js +39 -0
  133. package/dist/token-cache.d.ts +12 -0
  134. package/dist/token-cache.js +37 -0
  135. package/dist/utils/logger.d.ts +10 -0
  136. package/dist/utils/logger.js +27 -0
  137. package/dist/utils/open-browser.d.ts +6 -0
  138. package/dist/utils/open-browser.js +23 -0
  139. package/dist/utils/time-range.d.ts +11 -0
  140. package/dist/utils/time-range.js +16 -0
  141. package/package.json +42 -0
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * When MCP_DEBUG (or DEBUG) is set, log each request and response. Off by default.
4
+ * Sanitizes bodies to avoid logging tokens, passwords, or authorization headers.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.debugLogMiddleware = debugLogMiddleware;
8
+ const config_1 = require("../config");
9
+ const logger_1 = require("../utils/logger");
10
+ const SENSITIVE_KEYS = ["token", "password", "authorization", "cookie", "set-cookie"];
11
+ function sanitize(obj) {
12
+ if (obj == null)
13
+ return obj;
14
+ if (typeof obj !== "object")
15
+ return obj;
16
+ if (Array.isArray(obj))
17
+ return obj.map(sanitize);
18
+ const out = {};
19
+ for (const [k, v] of Object.entries(obj)) {
20
+ const keyLower = k.toLowerCase();
21
+ if (SENSITIVE_KEYS.some((s) => keyLower.includes(s))) {
22
+ out[k] = "[REDACTED]";
23
+ }
24
+ else {
25
+ out[k] = sanitize(v);
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function safeStringify(obj) {
31
+ try {
32
+ const s = sanitize(obj);
33
+ return typeof s === "object" ? JSON.stringify(s) : String(s);
34
+ }
35
+ catch {
36
+ return "[unserializable]";
37
+ }
38
+ }
39
+ function debugLogMiddleware(req, res, next) {
40
+ if (!config_1.config.debug) {
41
+ next();
42
+ return;
43
+ }
44
+ const method = req.method;
45
+ const url = req.originalUrl || req.url;
46
+ const query = Object.keys(req.query).length ? safeStringify(req.query) : undefined;
47
+ const body = req.body !== undefined && req.body !== null && Object.keys(req.body).length > 0
48
+ ? safeStringify(req.body)
49
+ : undefined;
50
+ logger_1.logger.info("MCP request", {
51
+ method,
52
+ url,
53
+ ...(query && { query }),
54
+ ...(body && { body }),
55
+ });
56
+ let responseLogged = false;
57
+ const logResponse = (meta) => {
58
+ if (!responseLogged) {
59
+ responseLogged = true;
60
+ logger_1.logger.info("MCP response", { method, url, statusCode: res.statusCode, ...meta });
61
+ }
62
+ };
63
+ res.on("finish", () => {
64
+ if (!responseLogged) {
65
+ logger_1.logger.info("MCP response", { method, url, statusCode: res.statusCode });
66
+ }
67
+ });
68
+ const originalJson = res.json.bind(res);
69
+ res.json = function (body) {
70
+ logResponse({ body: safeStringify(body) });
71
+ return originalJson(body);
72
+ };
73
+ const originalSend = res.send.bind(res);
74
+ res.send = function (body) {
75
+ if (typeof body === "string" && body.length > 0 && body.length < 2000) {
76
+ logResponse({ bodyPreview: body });
77
+ }
78
+ else {
79
+ logResponse({ bodyLength: typeof body === "string" ? body.length : "[buffer]" });
80
+ }
81
+ return originalSend(body);
82
+ };
83
+ next();
84
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Centralized error handling. No stack traces in production.
3
+ */
4
+ import { Request, Response, NextFunction } from "express";
5
+ export interface ApiError extends Error {
6
+ status?: number;
7
+ }
8
+ export declare function errorMiddleware(err: ApiError, _req: Request, res: Response, _next: NextFunction): void;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Centralized error handling. No stack traces in production.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.errorMiddleware = errorMiddleware;
7
+ const config_1 = require("../config");
8
+ const logger_1 = require("../utils/logger");
9
+ function errorMiddleware(err, _req, res, _next) {
10
+ const status = err.status ?? 500;
11
+ const message = err.message ?? "Internal server error";
12
+ if (config_1.config.nodeEnv === "production") {
13
+ logger_1.logger.error("Request error", { status, message });
14
+ }
15
+ else {
16
+ logger_1.logger.error("Request error", { status, message, stack: err.stack });
17
+ }
18
+ const body = {
19
+ success: false,
20
+ message,
21
+ code: status,
22
+ };
23
+ res.status(status).json(body);
24
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Zod validation middleware. Runs schema parse and attaches result to request.
3
+ */
4
+ import { Request, Response, NextFunction } from "express";
5
+ import { ZodSchema } from "zod";
6
+ export declare function validateBody<T>(schema: ZodSchema<T>): (req: Request, res: Response, next: NextFunction) => void;
7
+ export declare function validateQuery<T>(schema: ZodSchema<T>): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * Zod validation middleware. Runs schema parse and attaches result to request.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateBody = validateBody;
7
+ exports.validateQuery = validateQuery;
8
+ const zod_1 = require("zod");
9
+ function validateBody(schema) {
10
+ return (req, res, next) => {
11
+ try {
12
+ const parsed = schema.parse(req.body);
13
+ req.body = parsed;
14
+ next();
15
+ }
16
+ catch (e) {
17
+ if (e instanceof zod_1.ZodError) {
18
+ const msg = e.errors.map((x) => `${x.path.join(".")}: ${x.message}`).join("; ");
19
+ res.status(400).json({ success: false, message: msg, code: 400 });
20
+ return;
21
+ }
22
+ next(e);
23
+ }
24
+ };
25
+ }
26
+ function validateQuery(schema) {
27
+ return (req, res, next) => {
28
+ try {
29
+ const parsed = schema.parse(req.query);
30
+ req.query = parsed;
31
+ next();
32
+ }
33
+ catch (e) {
34
+ if (e instanceof zod_1.ZodError) {
35
+ const msg = e.errors.map((x) => `${x.path.join(".")}: ${x.message}`).join("; ");
36
+ res.status(400).json({ success: false, message: msg, code: 400 });
37
+ return;
38
+ }
39
+ next(e);
40
+ }
41
+ };
42
+ }
@@ -0,0 +1 @@
1
+ export declare const analyticsRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyticsRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const analytics_controller_1 = require("../controllers/analytics.controller");
7
+ const router = (0, express_1.Router)();
8
+ router.use(auth_middleware_1.authMiddleware);
9
+ router.use(auth_middleware_1.requireAuth);
10
+ router.get("/sessions-per-browser", analytics_controller_1.getSessionsPerBrowser);
11
+ router.get("/sessions-by-build", analytics_controller_1.getSessionsByBuild);
12
+ router.get("/failed-count", analytics_controller_1.getFailedCount);
13
+ router.get("/most-active-project", analytics_controller_1.getMostActiveProject);
14
+ router.get("/most-active-user", analytics_controller_1.getMostActiveUser);
15
+ exports.analyticsRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const authRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const validate_middleware_1 = require("../middleware/validate.middleware");
6
+ const auth_middleware_1 = require("../middleware/auth.middleware");
7
+ const auth_controller_1 = require("../controllers/auth.controller");
8
+ const auth_device_controller_1 = require("../controllers/auth-device.controller");
9
+ const auth_dto_1 = require("../dto/auth.dto");
10
+ const router = (0, express_1.Router)();
11
+ router.post("/initiate", auth_device_controller_1.initiate);
12
+ router.get("/status", auth_device_controller_1.status);
13
+ router.use(auth_middleware_1.authMiddleware);
14
+ router.post("/login", (0, validate_middleware_1.validateBody)(auth_dto_1.LoginBodySchema), auth_controller_1.login);
15
+ exports.authRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const browserRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.browserRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const browser_controller_1 = require("../controllers/browser.controller");
7
+ const router = (0, express_1.Router)();
8
+ router.use(auth_middleware_1.authMiddleware);
9
+ router.use(auth_middleware_1.requireAuth);
10
+ router.get("/", browser_controller_1.listBrowsers);
11
+ exports.browserRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const deviceRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deviceRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const device_controller_1 = require("../controllers/device.controller");
7
+ const router = (0, express_1.Router)();
8
+ router.use(auth_middleware_1.authMiddleware);
9
+ router.use(auth_middleware_1.requireAuth);
10
+ router.get("/", device_controller_1.listDevices);
11
+ exports.deviceRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const routes: import("express-serve-static-core").Router;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.routes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_routes_1 = require("./auth.routes");
6
+ const session_routes_1 = require("./session.routes");
7
+ const browser_routes_1 = require("./browser.routes");
8
+ const device_routes_1 = require("./device.routes");
9
+ const user_routes_1 = require("./user.routes");
10
+ const project_stats_routes_1 = require("./project-stats.routes");
11
+ const upload_routes_1 = require("./upload.routes");
12
+ const analytics_routes_1 = require("./analytics.routes");
13
+ const mcp_routes_1 = require("./mcp.routes");
14
+ const router = (0, express_1.Router)();
15
+ router.use("/auth", auth_routes_1.authRoutes);
16
+ router.use("/sessions", session_routes_1.sessionRoutes);
17
+ router.use("/browsers", browser_routes_1.browserRoutes);
18
+ router.use("/devices", device_routes_1.deviceRoutes);
19
+ router.use("/users", user_routes_1.userRoutes);
20
+ router.use("/projects", project_stats_routes_1.projectStatsRoutes);
21
+ router.use("/apps", upload_routes_1.uploadRoutes);
22
+ router.use("/analytics", analytics_routes_1.analyticsRoutes);
23
+ router.use("/mcp", mcp_routes_1.mcpRoutes);
24
+ router.get("/health", (_req, res) => {
25
+ res.status(200).json({ success: true, service: "sbox-mcp" });
26
+ });
27
+ exports.routes = router;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP over Streamable HTTP (POST/GET/DELETE /mcp).
3
+ * Supports Cursor config: "url": "https://sbox.e34.io/mcp"
4
+ * SDK is required by path so Node resolves the CJS build (same as mcp-server.ts).
5
+ */
6
+ export declare const mcpRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * MCP over Streamable HTTP (POST/GET/DELETE /mcp).
4
+ * Supports Cursor config: "url": "https://sbox.e34.io/mcp"
5
+ * SDK is required by path so Node resolves the CJS build (same as mcp-server.ts).
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.mcpRoutes = void 0;
12
+ const express_1 = require("express");
13
+ const path_1 = __importDefault(require("path"));
14
+ const crypto_1 = require("crypto");
15
+ const index_1 = require("../mcp/tools/index");
16
+ const sdkServerDir = path_1.default.join(__dirname, "..", "..", "node_modules", "@modelcontextprotocol", "sdk", "dist", "cjs", "server");
17
+ const { McpServer } = require(path_1.default.join(sdkServerDir, "mcp.js"));
18
+ const { StreamableHTTPServerTransport } = require(path_1.default.join(sdkServerDir, "streamableHttp.js"));
19
+ const sessionMap = new Map();
20
+ function createSboxMcpServer() {
21
+ const server = new McpServer({
22
+ name: "sbox-mcp",
23
+ version: "1.0.0",
24
+ });
25
+ (0, index_1.registerSboxTools)(server);
26
+ return server;
27
+ }
28
+ const router = (0, express_1.Router)({ mergeParams: true });
29
+ // Handle all methods and paths under /mcp (/, "", or subpaths) so POST /mcp and POST /mcp/ both work
30
+ const streamableHandler = async (req, res) => {
31
+ const sessionId = req.headers["mcp-session-id"];
32
+ if (sessionId) {
33
+ const entry = sessionMap.get(sessionId);
34
+ if (!entry) {
35
+ res.status(404).json({
36
+ jsonrpc: "2.0",
37
+ error: { code: -32001, message: "Session not found" },
38
+ id: null,
39
+ });
40
+ return;
41
+ }
42
+ await entry.transport.handleRequest(req, res, req.body);
43
+ return;
44
+ }
45
+ // New session: only POST with initialize is valid
46
+ if (req.method !== "POST") {
47
+ res.status(400).json({
48
+ jsonrpc: "2.0",
49
+ error: { code: -32000, message: "Bad Request: Mcp-Session-Id header is required" },
50
+ id: null,
51
+ });
52
+ return;
53
+ }
54
+ const transport = new StreamableHTTPServerTransport({
55
+ sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
56
+ onsessioninitialized: (id) => {
57
+ sessionMap.set(id, { server, transport });
58
+ },
59
+ onsessionclosed: (id) => {
60
+ sessionMap.delete(id);
61
+ },
62
+ });
63
+ const server = createSboxMcpServer();
64
+ await server.connect(transport);
65
+ await transport.handleRequest(req, res, req.body);
66
+ };
67
+ router.all("/", streamableHandler);
68
+ // When client sends POST /mcp (no trailing slash), some setups pass path as "" to the mounted router
69
+ router.use(streamableHandler);
70
+ exports.mcpRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const projectStatsRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.projectStatsRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const project_stats_controller_1 = require("../controllers/project-stats.controller");
7
+ const router = (0, express_1.Router)();
8
+ router.use(auth_middleware_1.authMiddleware);
9
+ router.use(auth_middleware_1.requireAuth);
10
+ router.get("/sessions-per-project", project_stats_controller_1.getSessionsPerProject);
11
+ exports.projectStatsRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const sessionRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const validate_middleware_1 = require("../middleware/validate.middleware");
7
+ const session_controller_1 = require("../controllers/session.controller");
8
+ const session_dto_1 = require("../dto/session.dto");
9
+ const router = (0, express_1.Router)();
10
+ router.use(auth_middleware_1.authMiddleware);
11
+ router.use(auth_middleware_1.requireAuth);
12
+ router.get("/", session_controller_1.listSessions);
13
+ router.get("/:ekey", session_controller_1.getSessionDetail);
14
+ router.put("/status", (0, validate_middleware_1.validateBody)(session_dto_1.UpdateSessionStatusBodySchema), session_controller_1.updateSessionStatus);
15
+ router.delete("/", (req, res, next) => {
16
+ if (!req.query.sessionId) {
17
+ res.status(400).json({ success: false, message: "sessionId required", code: 400 });
18
+ return;
19
+ }
20
+ next();
21
+ }, session_controller_1.deleteSession);
22
+ router.post("/manual", session_controller_1.startManualSession);
23
+ exports.sessionRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const uploadRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadRoutes = void 0;
7
+ const express_1 = require("express");
8
+ const multer_1 = __importDefault(require("multer"));
9
+ const auth_middleware_1 = require("../middleware/auth.middleware");
10
+ const upload_controller_1 = require("../controllers/upload.controller");
11
+ const MAX_FILE_SIZE = 200 * 1024 * 1024; // 200MB
12
+ const upload = (0, multer_1.default)({
13
+ storage: multer_1.default.memoryStorage(),
14
+ limits: { fileSize: MAX_FILE_SIZE },
15
+ });
16
+ const router = (0, express_1.Router)();
17
+ router.use(auth_middleware_1.authMiddleware);
18
+ router.use(auth_middleware_1.requireAuth);
19
+ router.post("/upload", upload.single("file"), upload_controller_1.uploadMobileApp);
20
+ exports.uploadRoutes = router;
@@ -0,0 +1 @@
1
+ export declare const userRoutes: import("express-serve-static-core").Router;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.userRoutes = void 0;
4
+ const express_1 = require("express");
5
+ const auth_middleware_1 = require("../middleware/auth.middleware");
6
+ const user_controller_1 = require("../controllers/user.controller");
7
+ const router = (0, express_1.Router)();
8
+ router.use(auth_middleware_1.authMiddleware);
9
+ router.use(auth_middleware_1.requireAuth);
10
+ router.get("/active", user_controller_1.getActiveUsers);
11
+ exports.userRoutes = router;
@@ -0,0 +1 @@
1
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const app_1 = __importDefault(require("./app"));
7
+ const config_1 = require("./config");
8
+ const logger_1 = require("./utils/logger");
9
+ const server = app_1.default.listen(config_1.config.port, () => {
10
+ logger_1.logger.info("SBOX MCP server listening", { port: config_1.config.port, env: config_1.config.nodeEnv, debug: config_1.config.debug });
11
+ });
12
+ process.on("SIGTERM", () => {
13
+ server.close(() => {
14
+ logger_1.logger.info("SBOX MCP server closed");
15
+ process.exit(0);
16
+ });
17
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Analytics service: sessions per browser, by build, failed count, most active project/user.
3
+ * Time range can be in hours, minutes, or days.
4
+ */
5
+ import type { StatsAdapter } from "../adapters/stats.adapter";
6
+ import type { TimeRange } from "../utils/time-range";
7
+ export interface SessionsPerBrowserDto {
8
+ browser: string;
9
+ version: string;
10
+ count: number;
11
+ }
12
+ export interface SessionsByBuildDto {
13
+ buildName: string;
14
+ sessionCount: number;
15
+ }
16
+ export interface TestNameCountDto {
17
+ testName: string;
18
+ count: number;
19
+ }
20
+ export interface AnalyticsService {
21
+ totalTests(token: string, days: number): Promise<{
22
+ success: true;
23
+ total: number;
24
+ } | {
25
+ success: false;
26
+ status: number;
27
+ message: string;
28
+ }>;
29
+ sessionsPerBrowser(token: string, timeRange: TimeRange): Promise<{
30
+ success: true;
31
+ data: SessionsPerBrowserDto[];
32
+ } | {
33
+ success: false;
34
+ status: number;
35
+ message: string;
36
+ }>;
37
+ sessionsByBuild(token: string, timeRange: TimeRange): Promise<{
38
+ success: true;
39
+ data: SessionsByBuildDto[];
40
+ } | {
41
+ success: false;
42
+ status: number;
43
+ message: string;
44
+ }>;
45
+ failedCount(token: string, timeRange: TimeRange): Promise<{
46
+ success: true;
47
+ count: number;
48
+ } | {
49
+ success: false;
50
+ status: number;
51
+ message: string;
52
+ }>;
53
+ mostActiveProject(token: string, timeRange: TimeRange): Promise<{
54
+ success: true;
55
+ projectName: string;
56
+ count: number;
57
+ } | {
58
+ success: false;
59
+ status: number;
60
+ message: string;
61
+ }>;
62
+ mostActiveUser(token: string, timeRange: TimeRange): Promise<{
63
+ success: true;
64
+ userName: string;
65
+ count: number;
66
+ } | {
67
+ success: false;
68
+ status: number;
69
+ message: string;
70
+ }>;
71
+ testsPerTestName(token: string, timeRange: TimeRange): Promise<{
72
+ success: true;
73
+ data: TestNameCountDto[];
74
+ } | {
75
+ success: false;
76
+ status: number;
77
+ message: string;
78
+ }>;
79
+ manualTestsPerTestName(token: string, timeRange: TimeRange): Promise<{
80
+ success: true;
81
+ data: TestNameCountDto[];
82
+ } | {
83
+ success: false;
84
+ status: number;
85
+ message: string;
86
+ }>;
87
+ }
88
+ export declare function createAnalyticsService(statsAdapter: StatsAdapter): AnalyticsService;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * Analytics service: sessions per browser, by build, failed count, most active project/user.
4
+ * Time range can be in hours, minutes, or days.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createAnalyticsService = createAnalyticsService;
8
+ function createAnalyticsService(statsAdapter) {
9
+ return {
10
+ async totalTests(token, days) {
11
+ const res = await statsAdapter.getTotalTests(token, days);
12
+ if (res.status !== 200) {
13
+ return { success: false, status: res.status, message: res.error ?? "Failed to get total tests" };
14
+ }
15
+ return { success: true, total: res.total };
16
+ },
17
+ async sessionsPerBrowser(token, timeRange) {
18
+ const res = await statsAdapter.getSessionsPerBrowser(token, timeRange);
19
+ if (res.status !== 200) {
20
+ return { success: false, status: res.status, message: res.error ?? "Failed to get sessions per browser" };
21
+ }
22
+ const data = res.items.map((i) => ({
23
+ browser: i.browser ?? "",
24
+ version: i.version ?? "",
25
+ count: i.count ?? 0,
26
+ }));
27
+ return { success: true, data };
28
+ },
29
+ async sessionsByBuild(token, timeRange) {
30
+ const res = await statsAdapter.getSessionsByBuild(token, timeRange);
31
+ if (res.status !== 200) {
32
+ return { success: false, status: res.status, message: res.error ?? "Failed to get sessions by build" };
33
+ }
34
+ const data = res.items.map((i) => ({
35
+ buildName: i.buildName ?? "Others",
36
+ sessionCount: i.session_count ?? 0,
37
+ }));
38
+ return { success: true, data };
39
+ },
40
+ async failedCount(token, timeRange) {
41
+ const res = await statsAdapter.getCountByStatus(token, timeRange);
42
+ if (res.status !== 200) {
43
+ return { success: false, status: res.status, message: res.error ?? "Failed to get failed count" };
44
+ }
45
+ let count = 0;
46
+ for (const item of res.items) {
47
+ const r = (item.result ?? "").toLowerCase();
48
+ if (r === "failed" || r === "timeout")
49
+ count += item.count ?? 0;
50
+ }
51
+ return { success: true, count };
52
+ },
53
+ async mostActiveProject(token, timeRange) {
54
+ const res = await statsAdapter.getCountPerProject(token, timeRange);
55
+ if (res.status !== 200) {
56
+ return { success: false, status: res.status, message: res.error ?? "Failed to get most active project" };
57
+ }
58
+ const top = res.items.sort((a, b) => (b.count ?? 0) - (a.count ?? 0))[0];
59
+ if (!top) {
60
+ return { success: true, projectName: "N/A", count: 0 };
61
+ }
62
+ return { success: true, projectName: top.project ?? "N/A", count: top.count ?? 0 };
63
+ },
64
+ async mostActiveUser(token, timeRange) {
65
+ const res = await statsAdapter.getCountPerUser(token, timeRange);
66
+ if (res.status !== 200) {
67
+ return { success: false, status: res.status, message: res.error ?? "Failed to get most active user" };
68
+ }
69
+ const top = res.items.sort((a, b) => (b.count ?? 0) - (a.count ?? 0))[0];
70
+ if (!top) {
71
+ return { success: true, userName: "N/A", count: 0 };
72
+ }
73
+ return { success: true, userName: top.user ?? "N/A", count: top.count ?? 0 };
74
+ },
75
+ async testsPerTestName(token, timeRange) {
76
+ const res = await statsAdapter.getCountPerTestName(token, timeRange);
77
+ if (res.status !== 200) {
78
+ return { success: false, status: res.status, message: res.error ?? "Failed to get tests per test name" };
79
+ }
80
+ const data = res.items.map((i) => ({
81
+ testName: i.testName ?? "",
82
+ count: i.count ?? 0,
83
+ }));
84
+ return { success: true, data };
85
+ },
86
+ async manualTestsPerTestName(token, timeRange) {
87
+ const res = await statsAdapter.getManualCountPerTestName(token, timeRange);
88
+ if (res.status !== 200) {
89
+ return { success: false, status: res.status, message: res.error ?? "Failed to get manual tests per test name" };
90
+ }
91
+ const data = res.items.map((i) => ({
92
+ testName: i.testName ?? "",
93
+ count: i.count ?? 0,
94
+ }));
95
+ return { success: true, data };
96
+ },
97
+ };
98
+ }