@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,540 @@
1
+ "use strict";
2
+ /**
3
+ * MCP tools: session, browser, device, user, project, upload, analytics, diagnostics.
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerRestTools = registerRestTools;
40
+ const zod_1 = require("zod");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const services_1 = require("../../services");
44
+ const time_range_1 = require("../../utils/time-range");
45
+ const helpers_1 = require("./helpers");
46
+ function registerRestTools(server) {
47
+ server.registerTool("sbox_get_session", {
48
+ title: "Get Session Details",
49
+ description: "Get enriched session details by session ID (ekey). Include video URL.",
50
+ inputSchema: {
51
+ session_id: zod_1.z.string().describe("Session ID (ekey)"),
52
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
53
+ },
54
+ }, (async (args) => {
55
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
56
+ if (typeof tokenOrErr !== "string")
57
+ return tokenOrErr;
58
+ const out = await services_1.sessionService.getSessionDetail(args.session_id, tokenOrErr);
59
+ if (!out.success) {
60
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
61
+ return (0, helpers_1.errorContent)(out.message);
62
+ }
63
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
64
+ }));
65
+ server.registerTool("sbox_list_browsers", {
66
+ title: "List Supported Browsers",
67
+ description: "List supported browsers with version and platform.",
68
+ inputSchema: {
69
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
70
+ },
71
+ }, (async (args) => {
72
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
73
+ if (typeof tokenOrErr !== "string")
74
+ return tokenOrErr;
75
+ const out = await services_1.browserService.listSupportedBrowsers(tokenOrErr);
76
+ if (!out.success) {
77
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
78
+ return (0, helpers_1.errorContent)(out.message);
79
+ }
80
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
81
+ }));
82
+ server.registerTool("sbox_list_devices", {
83
+ title: "List Supported Devices",
84
+ description: "List supported devices (iOS, Android, mobile web) with OS and availability.",
85
+ inputSchema: {
86
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
87
+ },
88
+ }, (async (args) => {
89
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
90
+ if (typeof tokenOrErr !== "string")
91
+ return tokenOrErr;
92
+ const out = await services_1.deviceService.listSupportedDevices(tokenOrErr);
93
+ if (!out.success) {
94
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
95
+ return (0, helpers_1.errorContent)(out.message);
96
+ }
97
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
98
+ }));
99
+ server.registerTool("sbox_list_playwright_versions", {
100
+ title: "List Playwright Versions",
101
+ description: "List all Playwright versions supported on SBOX (with bundled Chromium, WebKit, Firefox versions). Uses GET /e34/api/playwright.",
102
+ inputSchema: {
103
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
104
+ },
105
+ }, (async (args) => {
106
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
107
+ if (typeof tokenOrErr !== "string")
108
+ return tokenOrErr;
109
+ const out = await services_1.browserService.listPlaywrightVersions(tokenOrErr);
110
+ if (!out.success) {
111
+ (0, helpers_1.clearTokenOnAuthError)(out.message ?? "");
112
+ return (0, helpers_1.errorContent)(out.message ?? "Playwright is not enabled");
113
+ }
114
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
115
+ }));
116
+ server.registerTool("sbox_list_active_users", {
117
+ title: "List Active Users (Top N)",
118
+ description: "Get top N active users by session count with project names.",
119
+ inputSchema: {
120
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
121
+ limit: zod_1.z.number().optional().describe("Max number of users (default 10)"),
122
+ },
123
+ }, (async (args) => {
124
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
125
+ if (typeof tokenOrErr !== "string")
126
+ return tokenOrErr;
127
+ const out = await services_1.userService.getActiveUsersTopN(tokenOrErr, args.limit ?? 10);
128
+ if (!out.success) {
129
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
130
+ return (0, helpers_1.errorContent)(out.message);
131
+ }
132
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
133
+ }));
134
+ server.registerTool("sbox_sessions_per_project", {
135
+ title: "Sessions Per Project",
136
+ description: "Get session counts (total, passed, failed, running) and avg duration per project for the last X days.",
137
+ inputSchema: {
138
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
139
+ days: zod_1.z.number().optional().describe("Last N days (default 14)"),
140
+ },
141
+ }, (async (args) => {
142
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
143
+ if (typeof tokenOrErr !== "string")
144
+ return tokenOrErr;
145
+ const out = await services_1.sessionsPerProjectService.getSessionsPerProject(tokenOrErr, args.days ?? 14);
146
+ if (!out.success) {
147
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
148
+ return (0, helpers_1.errorContent)(out.message);
149
+ }
150
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
151
+ }));
152
+ server.registerTool("sbox_list_my_projects", {
153
+ title: "List My Projects",
154
+ description: "List projects assigned to the current user (My Projects page). Returns name, description, and whether a test token exists. Token value is not exposed.",
155
+ inputSchema: {
156
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
157
+ },
158
+ }, (async (args) => {
159
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
160
+ if (typeof tokenOrErr !== "string")
161
+ return tokenOrErr;
162
+ const out = await services_1.projectService.listMyProjects(tokenOrErr);
163
+ if (!out.success) {
164
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
165
+ return (0, helpers_1.errorContent)(out.message);
166
+ }
167
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
168
+ }));
169
+ server.registerTool("sbox_start_manual_session", {
170
+ title: "Start Manual Session",
171
+ description: "Start a manual test session. Requires project name; optional browser, version, url.",
172
+ inputSchema: {
173
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
174
+ project_name: zod_1.z.string().describe("Project name"),
175
+ browser_name: zod_1.z.string().optional(),
176
+ browser_version: zod_1.z.string().optional(),
177
+ url: zod_1.z.string().optional().describe("Initial URL to open"),
178
+ },
179
+ }, (async (args) => {
180
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
181
+ if (typeof tokenOrErr !== "string")
182
+ return tokenOrErr;
183
+ const body = {
184
+ projectName: args.project_name,
185
+ ...(args.browser_name && { browserName: args.browser_name }),
186
+ ...(args.browser_version && { browserVersion: args.browser_version }),
187
+ ...(args.url && { url: args.url }),
188
+ };
189
+ const out = await services_1.sessionService.startManualSession(body, tokenOrErr);
190
+ if (!out.success) {
191
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
192
+ return (0, helpers_1.errorContent)(out.message);
193
+ }
194
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
195
+ }));
196
+ server.registerTool("sbox_update_session_status", {
197
+ title: "Update Session Status",
198
+ description: "Set a session's result to passed or failed. Uses POST /e34/api/test-data?sessionId=&passed=.",
199
+ inputSchema: {
200
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
201
+ session_id: zod_1.z.string().describe("Session ID (ekey) to update"),
202
+ passed: zod_1.z.boolean().describe("true = passed, false = failed"),
203
+ },
204
+ }, (async (args) => {
205
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
206
+ if (typeof tokenOrErr !== "string")
207
+ return tokenOrErr;
208
+ const out = await services_1.sessionService.updateSessionStatusBySessionId(args.session_id, args.passed, tokenOrErr);
209
+ if (!out.success) {
210
+ (0, helpers_1.clearTokenOnAuthError)(out.message ?? "");
211
+ return (0, helpers_1.errorContent)(out.message ?? "Update failed");
212
+ }
213
+ return (0, helpers_1.textContent)(JSON.stringify({ success: true }));
214
+ }));
215
+ server.registerTool("sbox_list_sessions", {
216
+ title: "List Sessions",
217
+ description: "Show last X sessions (same API as sessions page). Filter by project, status, browser, build; sort by arrival or other field.",
218
+ inputSchema: {
219
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
220
+ limit: zod_1.z.number().optional().describe("Max sessions to return (default 25, max 100)"),
221
+ offset: zod_1.z.number().optional().describe("Pagination offset (default 0)"),
222
+ project_name: zod_1.z.string().optional().describe("Filter by project name"),
223
+ status: zod_1.z.string().optional().describe("Filter by result status (e.g. passed, failed)"),
224
+ browser: zod_1.z.string().optional().describe("Filter by browser name"),
225
+ build: zod_1.z.string().optional().describe("Filter by build name"),
226
+ sort_by: zod_1.z.string().optional().describe("Sort field (default: arrival)"),
227
+ sort_direction: zod_1.z.enum(["asc", "desc"]).optional().describe("Sort direction (default: desc)"),
228
+ },
229
+ }, (async (args) => {
230
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
231
+ if (typeof tokenOrErr !== "string")
232
+ return tokenOrErr;
233
+ const out = await services_1.sessionService.listSessions({
234
+ limit: args.limit,
235
+ offset: args.offset,
236
+ projectName: args.project_name,
237
+ status: args.status,
238
+ browser: args.browser,
239
+ build: args.build,
240
+ sortBy: args.sort_by,
241
+ sortDirection: args.sort_direction,
242
+ }, tokenOrErr);
243
+ if (!out.success) {
244
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
245
+ return (0, helpers_1.errorContent)(out.message);
246
+ }
247
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
248
+ }));
249
+ server.registerTool("sbox_delete_session", {
250
+ title: "Delete Manual Session",
251
+ description: "Delete a manual test session by session ID.",
252
+ inputSchema: {
253
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
254
+ session_id: zod_1.z.string().describe("Session ID to delete"),
255
+ },
256
+ }, (async (args) => {
257
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
258
+ if (typeof tokenOrErr !== "string")
259
+ return tokenOrErr;
260
+ const out = await services_1.sessionService.deleteSession(args.session_id, tokenOrErr);
261
+ if (!out.success) {
262
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
263
+ return (0, helpers_1.errorContent)(out.message);
264
+ }
265
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
266
+ }));
267
+ server.registerTool("sbox_upload_mobile_app", {
268
+ title: "Upload Mobile App to SBOX",
269
+ description: "Upload a mobile app (APK/IPA) or other file to SBOX for the given project. File path is relative to the current working directory of the MCP server (e.g. ./app.apk). Max size 200MB.",
270
+ inputSchema: {
271
+ token: zod_1.z.string().describe("JWT from sbox_open_login (login in browser first)"),
272
+ project_name: zod_1.z.string().describe("Project name to upload the app to"),
273
+ file_path: zod_1.z.string().describe("Path to the file (APK/IPA); relative to MCP server cwd or absolute"),
274
+ },
275
+ }, (async (args) => {
276
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
277
+ if (typeof tokenOrErr !== "string")
278
+ return tokenOrErr;
279
+ const cwd = process.cwd();
280
+ const resolved = path.isAbsolute(args.file_path) ? path.normalize(args.file_path) : path.resolve(cwd, args.file_path);
281
+ if (!path.isAbsolute(args.file_path) && !resolved.startsWith(cwd)) {
282
+ return (0, helpers_1.errorContent)("Relative file path must be inside current working directory");
283
+ }
284
+ let buffer;
285
+ try {
286
+ buffer = await fs.promises.readFile(resolved);
287
+ }
288
+ catch (e) {
289
+ const msg = e instanceof Error ? e.message : "Failed to read file";
290
+ return (0, helpers_1.errorContent)(msg);
291
+ }
292
+ if (buffer.length > helpers_1.MAX_UPLOAD_SIZE) {
293
+ return (0, helpers_1.errorContent)("File too large (max 200MB)");
294
+ }
295
+ const filename = path.basename(resolved);
296
+ const out = await services_1.uploadService.uploadMobileApp(args.project_name, buffer, filename, tokenOrErr);
297
+ if (!out.success) {
298
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
299
+ return (0, helpers_1.errorContent)(out.message);
300
+ }
301
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
302
+ }));
303
+ const timeRangeSchema = {
304
+ token: zod_1.z.string().optional().describe("JWT from sbox_open_login or use cached token"),
305
+ value: zod_1.z.number().describe("Number of units (e.g. 24 for 24 hours)"),
306
+ unit: zod_1.z.enum(["hour", "minute", "day"]).describe("Time unit: hour, minute, or day"),
307
+ };
308
+ server.registerTool("sbox_sessions_per_browser", {
309
+ title: "Sessions Per Browser",
310
+ description: "Number of sessions ran per browser (and version) in the last X time. Time can be in hours, minutes, or days.",
311
+ inputSchema: timeRangeSchema,
312
+ }, (async (args) => {
313
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
314
+ if (typeof tokenOrErr !== "string")
315
+ return tokenOrErr;
316
+ const out = await services_1.analyticsService.sessionsPerBrowser(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
317
+ if (!out.success) {
318
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
319
+ return (0, helpers_1.errorContent)(out.message);
320
+ }
321
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
322
+ }));
323
+ server.registerTool("sbox_sessions_count_by_builds", {
324
+ title: "Sessions Count By Builds",
325
+ description: "Session counts grouped by build name in the last X time (hours, minutes, or days).",
326
+ inputSchema: timeRangeSchema,
327
+ }, (async (args) => {
328
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
329
+ if (typeof tokenOrErr !== "string")
330
+ return tokenOrErr;
331
+ const out = await services_1.analyticsService.sessionsByBuild(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
332
+ if (!out.success) {
333
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
334
+ return (0, helpers_1.errorContent)(out.message);
335
+ }
336
+ return (0, helpers_1.textContent)(JSON.stringify(out.data, null, 2));
337
+ }));
338
+ server.registerTool("sbox_sessions_failed_count", {
339
+ title: "Sessions Failed Count",
340
+ description: "Number of sessions that failed (or timed out) in the last X time (hours, minutes, or days).",
341
+ inputSchema: timeRangeSchema,
342
+ }, (async (args) => {
343
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
344
+ if (typeof tokenOrErr !== "string")
345
+ return tokenOrErr;
346
+ const out = await services_1.analyticsService.failedCount(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
347
+ if (!out.success) {
348
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
349
+ return (0, helpers_1.errorContent)(out.message);
350
+ }
351
+ return (0, helpers_1.textContent)(JSON.stringify({ failedCount: out.count }));
352
+ }));
353
+ server.registerTool("sbox_most_active_project", {
354
+ title: "Most Active Project",
355
+ description: "Project with the maximum number of sessions in the last X time (hours, minutes, or days).",
356
+ inputSchema: timeRangeSchema,
357
+ }, (async (args) => {
358
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
359
+ if (typeof tokenOrErr !== "string")
360
+ return tokenOrErr;
361
+ const out = await services_1.analyticsService.mostActiveProject(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
362
+ if (!out.success) {
363
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
364
+ return (0, helpers_1.errorContent)(out.message);
365
+ }
366
+ return (0, helpers_1.textContent)(JSON.stringify({ projectName: out.projectName, count: out.count }));
367
+ }));
368
+ server.registerTool("sbox_most_active_user", {
369
+ title: "Most Active User",
370
+ description: "User with the maximum number of sessions in the last X time (hours, minutes, or days).",
371
+ inputSchema: timeRangeSchema,
372
+ }, (async (args) => {
373
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
374
+ if (typeof tokenOrErr !== "string")
375
+ return tokenOrErr;
376
+ const out = await services_1.analyticsService.mostActiveUser(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
377
+ if (!out.success) {
378
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
379
+ return (0, helpers_1.errorContent)(out.message);
380
+ }
381
+ return (0, helpers_1.textContent)(JSON.stringify({ userName: out.userName, count: out.count }));
382
+ }));
383
+ server.registerTool("sbox_total_tests", {
384
+ title: "Total Tests Count",
385
+ description: "Total number of tests (sessions) in the last N days. Matches the Monitoring dashboard total.",
386
+ inputSchema: {
387
+ token: zod_1.z.string().optional().describe("JWT from sbox_open_login or use cached token"),
388
+ days: zod_1.z.number().min(1).max(365).describe("Number of days to count (e.g. 7, 14, 30)"),
389
+ },
390
+ }, (async (args) => {
391
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
392
+ if (typeof tokenOrErr !== "string")
393
+ return tokenOrErr;
394
+ const out = await services_1.analyticsService.totalTests(tokenOrErr, args.days);
395
+ if (!out.success) {
396
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
397
+ return (0, helpers_1.errorContent)(out.message);
398
+ }
399
+ return (0, helpers_1.textContent)(JSON.stringify({ totalTests: out.total, days: args.days }));
400
+ }));
401
+ server.registerTool("sbox_tests_per_test_name", {
402
+ title: "Tests Per Test Name",
403
+ description: "Test counts grouped by test name in the last X time (hours, minutes, or days). Matches Monitoring dashboard.",
404
+ inputSchema: timeRangeSchema,
405
+ }, (async (args) => {
406
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
407
+ if (typeof tokenOrErr !== "string")
408
+ return tokenOrErr;
409
+ const out = await services_1.analyticsService.testsPerTestName(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
410
+ if (!out.success) {
411
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
412
+ return (0, helpers_1.errorContent)(out.message);
413
+ }
414
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
415
+ }));
416
+ server.registerTool("sbox_manual_tests_per_test_name", {
417
+ title: "Manual Tests Per Test Name",
418
+ description: "Manual test counts grouped by test name in the last X time. Matches Monitoring dashboard.",
419
+ inputSchema: timeRangeSchema,
420
+ }, (async (args) => {
421
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
422
+ if (typeof tokenOrErr !== "string")
423
+ return tokenOrErr;
424
+ const out = await services_1.analyticsService.manualTestsPerTestName(tokenOrErr, (0, time_range_1.parseTimeToRange)(args.value, args.unit));
425
+ if (!out.success) {
426
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
427
+ return (0, helpers_1.errorContent)(out.message);
428
+ }
429
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
430
+ }));
431
+ const diagnosticsDaysSchema = {
432
+ token: zod_1.z.string().optional().describe("JWT from sbox_open_login or use cached token"),
433
+ days: zod_1.z.number().min(7).max(365).describe("Number of days (e.g. 7, 14, 30, 90). Min 7, max 365."),
434
+ };
435
+ server.registerTool("sbox_executors_count", {
436
+ title: "Total Executors Count",
437
+ description: "Total number of executors (nodes). Matches Diagnostics dashboard KPI.",
438
+ inputSchema: { token: zod_1.z.string().optional().describe("JWT from sbox_open_login or use cached token") },
439
+ }, (async (args) => {
440
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
441
+ if (typeof tokenOrErr !== "string")
442
+ return tokenOrErr;
443
+ const out = await services_1.diagnosticsService.getExecutorsCount(tokenOrErr);
444
+ if (!out.success) {
445
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
446
+ return (0, helpers_1.errorContent)(out.message);
447
+ }
448
+ return (0, helpers_1.textContent)(JSON.stringify({ count: out.count }));
449
+ }));
450
+ server.registerTool("sbox_cpu_usage_per_node", {
451
+ title: "CPU Usage Per Node",
452
+ description: "CPU usage over time per node for the last N days. Matches Diagnostics dashboard.",
453
+ inputSchema: diagnosticsDaysSchema,
454
+ }, (async (args) => {
455
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
456
+ if (typeof tokenOrErr !== "string")
457
+ return tokenOrErr;
458
+ const out = await services_1.diagnosticsService.getCpuUsagePerNode(tokenOrErr, args.days);
459
+ if (!out.success) {
460
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
461
+ return (0, helpers_1.errorContent)(out.message);
462
+ }
463
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
464
+ }));
465
+ server.registerTool("sbox_tests_queued_hourly", {
466
+ title: "Tests Queued Hourly",
467
+ description: "Queued tests per hour (avg and max) for the last N days. Matches Diagnostics dashboard.",
468
+ inputSchema: diagnosticsDaysSchema,
469
+ }, (async (args) => {
470
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
471
+ if (typeof tokenOrErr !== "string")
472
+ return tokenOrErr;
473
+ const out = await services_1.diagnosticsService.getTestsQueuedHourly(tokenOrErr, args.days);
474
+ if (!out.success) {
475
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
476
+ return (0, helpers_1.errorContent)(out.message);
477
+ }
478
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
479
+ }));
480
+ server.registerTool("sbox_nodes_active_tests", {
481
+ title: "Nodes Active Tests",
482
+ description: "Currently running tests over time (per node) for the last N days. Matches Diagnostics dashboard.",
483
+ inputSchema: diagnosticsDaysSchema,
484
+ }, (async (args) => {
485
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
486
+ if (typeof tokenOrErr !== "string")
487
+ return tokenOrErr;
488
+ const out = await services_1.diagnosticsService.getNodesActiveTests(tokenOrErr, args.days);
489
+ if (!out.success) {
490
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
491
+ return (0, helpers_1.errorContent)(out.message);
492
+ }
493
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
494
+ }));
495
+ server.registerTool("sbox_queue_waiting_time", {
496
+ title: "Queue Waiting Time",
497
+ description: "Queue waiting time (avg and max duration in seconds) over the last N days. Matches Diagnostics dashboard.",
498
+ inputSchema: diagnosticsDaysSchema,
499
+ }, (async (args) => {
500
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
501
+ if (typeof tokenOrErr !== "string")
502
+ return tokenOrErr;
503
+ const out = await services_1.diagnosticsService.getQueueWaitingTime(tokenOrErr, args.days);
504
+ if (!out.success) {
505
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
506
+ return (0, helpers_1.errorContent)(out.message);
507
+ }
508
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
509
+ }));
510
+ server.registerTool("sbox_tests_active_graph", {
511
+ title: "Tests Running Across Executors",
512
+ description: "Tests running across executors over time (by node) for the last N days. Matches Diagnostics dashboard.",
513
+ inputSchema: diagnosticsDaysSchema,
514
+ }, (async (args) => {
515
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
516
+ if (typeof tokenOrErr !== "string")
517
+ return tokenOrErr;
518
+ const out = await services_1.diagnosticsService.getTestsActiveGraph(tokenOrErr, args.days);
519
+ if (!out.success) {
520
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
521
+ return (0, helpers_1.errorContent)(out.message);
522
+ }
523
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
524
+ }));
525
+ server.registerTool("sbox_tests_starting_stats", {
526
+ title: "Tests In Starting State",
527
+ description: "Tests in starting state over time (by node) for the last N days. Matches Diagnostics dashboard.",
528
+ inputSchema: diagnosticsDaysSchema,
529
+ }, (async (args) => {
530
+ const tokenOrErr = (0, helpers_1.requireToken)(args.token);
531
+ if (typeof tokenOrErr !== "string")
532
+ return tokenOrErr;
533
+ const out = await services_1.diagnosticsService.getTestsStartingStats(tokenOrErr, args.days);
534
+ if (!out.success) {
535
+ (0, helpers_1.clearTokenOnAuthError)(out.message);
536
+ return (0, helpers_1.errorContent)(out.message);
537
+ }
538
+ return (0, helpers_1.textContent)(JSON.stringify(out.data));
539
+ }));
540
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP protocol server entrypoint. Runs over stdio for Cursor, IntelliJ, Windsurf.
3
+ * Start with: npm run mcp
4
+ * SDK is required by path so Node resolves the CJS .js file (subpath exports can fail when pkg has "type": "module").
5
+ */
6
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * MCP protocol server entrypoint. Runs over stdio for Cursor, IntelliJ, Windsurf.
4
+ * Start with: npm run mcp
5
+ * SDK is required by path so Node resolves the CJS .js file (subpath exports can fail when pkg has "type": "module").
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
+ const path_1 = __importDefault(require("path"));
12
+ const index_1 = require("./mcp/tools/index");
13
+ const sdkServerDir = path_1.default.join(__dirname, "..", "node_modules", "@modelcontextprotocol", "sdk", "dist", "cjs", "server");
14
+ const { McpServer } = require(path_1.default.join(sdkServerDir, "mcp.js"));
15
+ const { StdioServerTransport } = require(path_1.default.join(sdkServerDir, "stdio.js"));
16
+ const server = new McpServer({
17
+ name: "sbox-mcp",
18
+ version: "1.0.0",
19
+ });
20
+ (0, index_1.registerSboxTools)(server);
21
+ async function main() {
22
+ const transport = new StdioServerTransport();
23
+ await server.connect(transport);
24
+ }
25
+ main().catch((err) => {
26
+ process.stderr.write(`SBOX MCP error: ${err instanceof Error ? err.message : String(err)}\n`);
27
+ process.exit(1);
28
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * JWT auth middleware. Extracts token and attaches to request; does not validate with backend.
3
+ * Validation happens per-request in controllers when calling SBOX APIs.
4
+ */
5
+ import { Request, Response, NextFunction } from "express";
6
+ export interface AuthenticatedRequest extends Request {
7
+ token: string | null;
8
+ }
9
+ export declare function authMiddleware(req: Request, _res: Response, next: NextFunction): void;
10
+ export declare function requireAuth(req: Request, res: Response, next: NextFunction): void;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * JWT auth middleware. Extracts token and attaches to request; does not validate with backend.
4
+ * Validation happens per-request in controllers when calling SBOX APIs.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.authMiddleware = authMiddleware;
8
+ exports.requireAuth = requireAuth;
9
+ const sbox_api_client_1 = require("../adapters/sbox-api.client");
10
+ function authMiddleware(req, _res, next) {
11
+ const authHeader = req.headers.authorization;
12
+ const token = (0, sbox_api_client_1.extractToken)(authHeader);
13
+ req.token = token ?? null;
14
+ next();
15
+ }
16
+ function requireAuth(req, res, next) {
17
+ const token = req.token;
18
+ if (!token) {
19
+ res.status(401).json({ success: false, message: "Authorization required", code: 401 });
20
+ return;
21
+ }
22
+ next();
23
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * When MCP_DEBUG (or DEBUG) is set, log each request and response. Off by default.
3
+ * Sanitizes bodies to avoid logging tokens, passwords, or authorization headers.
4
+ */
5
+ import { Request, Response, NextFunction } from "express";
6
+ export declare function debugLogMiddleware(req: Request, res: Response, next: NextFunction): void;