@mablhq/mabl-cli 2.65.3 → 2.67.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.
package/cli.js CHANGED
@@ -66,6 +66,7 @@ yargs
66
66
  .commandDir('./commands/test-runs')
67
67
  .commandDir('./commands/users')
68
68
  .commandDir('./commands/workspaces')
69
+ .commandDir('./commands/mcp')
69
70
  .demandCommand(1, '')
70
71
  .recommendCommands()
71
72
  .strict()
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = 'mcp <command>';
4
+ exports.describe = false;
5
+ exports.builder = (yargs) => yargs.commandDir('mcp_cmds').demandCommand();
@@ -0,0 +1,78 @@
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 util_1 = require("../../commandUtil/util");
7
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
+ const loggingProvider_1 = require("../../../providers/logging/loggingProvider");
11
+ const tools_1 = __importDefault(require("./tools"));
12
+ const constants_1 = require("../../constants");
13
+ exports.command = 'start [options]';
14
+ exports.describe = 'Start the MCP server';
15
+ exports.builder = (yargs) => {
16
+ yargs
17
+ .option(constants_1.CommandArgApiKey, {
18
+ alias: constants_1.CommandArgAliases.ApiKey,
19
+ describe: 'API key (found in the mabl web app)',
20
+ nargs: 1,
21
+ demand: 'API key argument required',
22
+ type: 'string',
23
+ })
24
+ .example('$0 mcp server', 'Start the MCP server');
25
+ };
26
+ exports.handler = (0, util_1.failWrapper)(startServer);
27
+ async function startServer(parsed) {
28
+ (0, loggingProvider_1.disableLogging)();
29
+ const server = new index_js_1.Server({
30
+ name: 'mabl',
31
+ version: '1.0.0',
32
+ }, {
33
+ capabilities: {
34
+ tools: {},
35
+ },
36
+ });
37
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, () => ({
38
+ tools: tools_1.default.map((tool) => ({
39
+ name: tool.schema.name,
40
+ description: tool.schema.description,
41
+ inputSchema: tool.schema.inputSchema.safeParse({}).success
42
+ ? { type: 'object', properties: {} }
43
+ : {
44
+ type: 'object',
45
+ properties: Object.fromEntries(Object.entries(tool.schema.inputSchema.shape).map(([key, value]) => {
46
+ const description = value._def.description;
47
+ return [
48
+ key,
49
+ {
50
+ type: 'string',
51
+ description: description || '',
52
+ },
53
+ ];
54
+ })),
55
+ },
56
+ })),
57
+ }));
58
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
59
+ const { name } = request.params;
60
+ const tool = tools_1.default.find((t) => t.schema.name === name);
61
+ if (!tool) {
62
+ throw new Error(`Unknown tool: ${name}`);
63
+ }
64
+ try {
65
+ const args = request.params.arguments || {};
66
+ const result = await tool.handle(args, parsed[constants_1.CommandArgApiKey]);
67
+ return {
68
+ content: result.content,
69
+ };
70
+ }
71
+ catch (error) {
72
+ throw error;
73
+ }
74
+ });
75
+ const transport = new stdio_js_1.StdioServerTransport();
76
+ await server.connect(transport);
77
+ console.error('Mabl MCP Server running on stdio');
78
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mcpMablTool_1 = require("./mcpMablTool");
4
+ const zod_1 = require("zod");
5
+ const analyzeFailure = (0, mcpMablTool_1.defineTool)({
6
+ schema: {
7
+ name: 'analyze-failure',
8
+ title: 'Analyze a mabl test execution failure',
9
+ description: 'Analyze a mabl test execution failure',
10
+ inputSchema: zod_1.z.object({
11
+ runId: zod_1.z.string().describe('The runId of the failed test run to analyze'),
12
+ }),
13
+ type: 'readOnly',
14
+ },
15
+ handle: (params) => {
16
+ const runId = params.runId;
17
+ const failureSynopsis = 'Failed at Step 6: The text on the page changed, causing an assertion failure.';
18
+ const failureSummary = 'The test is failing because the text on the page has changed from "step 1: create a test!" to "step 1: create a journey!". The assertion in the test is expecting the original text, which is why the test is failing. It appears that the application under test has been updated so that it now displays "step 1: create a journey!" instead of "step 1: create a test!". To resolve this failure, you should update the assertion in the test to expect the new text, "step 1: create a journey!".';
19
+ const summary = `RunId: ${runId} Status: failed FailureSynopsis: ${failureSynopsis} FailureDetails: ${failureSummary}`;
20
+ return Promise.resolve({
21
+ content: [
22
+ {
23
+ type: 'text',
24
+ text: summary,
25
+ },
26
+ ],
27
+ });
28
+ },
29
+ });
30
+ exports.default = [analyzeFailure];
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../../../commandUtil/util");
4
+ const mcpMablTool_1 = require("./mcpMablTool");
5
+ const mablApiClientFactory_1 = require("../../../../api/mablApiClientFactory");
6
+ const zod_1 = require("zod");
7
+ const getEnvironments = (0, mcpMablTool_1.defineTool)({
8
+ schema: {
9
+ name: 'get-environments',
10
+ title: 'Get all mabl environments',
11
+ description: 'Get all mabl environments',
12
+ inputSchema: zod_1.z.object({}),
13
+ type: 'readOnly',
14
+ },
15
+ handle: async (_, apiKey) => {
16
+ const arg = {
17
+ 'workspace-id': undefined,
18
+ output: 'json',
19
+ _: [],
20
+ $0: '',
21
+ };
22
+ const workspaceId = await (0, util_1.getWorkspaceId)(arg);
23
+ const limit = 500;
24
+ const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClientFromOptionalApiKey(apiKey);
25
+ const environments = await apiClient.getEnvironments(workspaceId, limit);
26
+ return {
27
+ content: [
28
+ {
29
+ type: 'text',
30
+ text: environments
31
+ .map((env) => `Name: ${env.name} ID: ${env.id}`)
32
+ .join('\n'),
33
+ },
34
+ ],
35
+ };
36
+ },
37
+ });
38
+ exports.default = [getEnvironments];
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mcpMablTool_1 = require("./mcpMablTool");
4
+ const zod_1 = require("zod");
5
+ const getRecentFailures = (0, mcpMablTool_1.defineTool)({
6
+ schema: {
7
+ name: 'get-recent-failures',
8
+ title: 'Get mabl tests with recent failures',
9
+ description: 'Get mabl tests with recent failures',
10
+ inputSchema: zod_1.z.object({}),
11
+ type: 'readOnly',
12
+ },
13
+ handle: () => Promise.resolve({
14
+ content: [
15
+ {
16
+ type: 'text',
17
+ text: 'Test: Sandbox training example ID: Pc0Kvc5gbqeDTWgf23LGLg-j Count: 8',
18
+ },
19
+ ],
20
+ }),
21
+ });
22
+ exports.default = [getRecentFailures];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mcpMablTool_1 = require("./mcpMablTool");
4
+ const zod_1 = require("zod");
5
+ const mablApiClientFactory_1 = require("../../../../api/mablApiClientFactory");
6
+ const getResults = (0, mcpMablTool_1.defineTool)({
7
+ schema: {
8
+ name: 'get-results',
9
+ title: 'Get recent mabl test run results',
10
+ description: 'Get recent mabl test run results',
11
+ inputSchema: zod_1.z.object({
12
+ testId: zod_1.z
13
+ .string()
14
+ .describe('The id of the mabl test to get recent results of'),
15
+ }),
16
+ type: 'readOnly',
17
+ },
18
+ handle: async (params, apiKey) => {
19
+ const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClientFromOptionalApiKey(apiKey);
20
+ const testId = params.testId;
21
+ const testRunResults = (await apiClient.getRecentTestRunsForTest(testId))
22
+ .test_script_executions || [];
23
+ return {
24
+ content: [
25
+ {
26
+ type: 'text',
27
+ text: testRunResults
28
+ .map((testRun) => {
29
+ const id = testRun.id;
30
+ const completedTimeMs = testRun.completed_time;
31
+ const status = testRun.status;
32
+ const envId = testRun.journey_parameters?.deployment?.environment_id;
33
+ const errorMessage = testRun.failure_summary?.error;
34
+ return `TestRunId: ${id} EnvironmentId: ${envId} Status: ${status} CompletedTimeMs: ${completedTimeMs} ErrorMessage: ${errorMessage}`;
35
+ })
36
+ .join('\n'),
37
+ },
38
+ ],
39
+ };
40
+ },
41
+ });
42
+ exports.default = [getResults];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../../../commandUtil/util");
4
+ const mcpMablTool_1 = require("./mcpMablTool");
5
+ const mablApiClientFactory_1 = require("../../../../api/mablApiClientFactory");
6
+ const zod_1 = require("zod");
7
+ const getTests = (0, mcpMablTool_1.defineTool)({
8
+ schema: {
9
+ name: 'get-tests',
10
+ title: 'Get all mabl tests',
11
+ description: 'Get all mabl tests',
12
+ inputSchema: zod_1.z.object({}),
13
+ type: 'readOnly',
14
+ },
15
+ handle: async (_, apiKey) => {
16
+ const arg = {
17
+ 'workspace-id': undefined,
18
+ output: 'json',
19
+ _: [],
20
+ $0: '',
21
+ };
22
+ const workspaceId = await (0, util_1.getWorkspaceId)(arg);
23
+ const limit = 50;
24
+ const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClientFromOptionalApiKey(apiKey);
25
+ const tests = await apiClient.getJourneys({
26
+ organization_id: workspaceId,
27
+ limit,
28
+ });
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: tests
34
+ .map((test) => `Test: ${test.name} ID: ${test.invariant_id}`)
35
+ .join('\n'),
36
+ },
37
+ ],
38
+ };
39
+ },
40
+ });
41
+ exports.default = [getTests];
@@ -0,0 +1,19 @@
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 getEnvironments_1 = __importDefault(require("./getEnvironments"));
7
+ const getTests_1 = __importDefault(require("./getTests"));
8
+ const getRecentFailures_1 = __importDefault(require("./getRecentFailures"));
9
+ const getResults_1 = __importDefault(require("./getResults"));
10
+ const analyzeFailure_1 = __importDefault(require("./analyzeFailure"));
11
+ const runTest_1 = __importDefault(require("./runTest"));
12
+ exports.default = [
13
+ ...getEnvironments_1.default,
14
+ ...getTests_1.default,
15
+ ...getRecentFailures_1.default,
16
+ ...getResults_1.default,
17
+ ...analyzeFailure_1.default,
18
+ ...runTest_1.default,
19
+ ];
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineTool = defineTool;
4
+ function defineTool(tool) {
5
+ return tool;
6
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mcpMablTool_1 = require("./mcpMablTool");
4
+ const zod_1 = require("zod");
5
+ const run_1 = require("../../../tests/tests_cmds/run");
6
+ const mablApiClientFactory_1 = require("../../../../api/mablApiClientFactory");
7
+ const runTest = (0, mcpMablTool_1.defineTool)({
8
+ schema: {
9
+ name: 'run-test',
10
+ title: 'Run a mabl test',
11
+ description: 'Run a mabl test',
12
+ inputSchema: zod_1.z.object({
13
+ environmentId: zod_1.z
14
+ .string()
15
+ .describe('The id of the environment to run the test in'),
16
+ testId: zod_1.z.string().describe('The id of the test to run'),
17
+ }),
18
+ type: 'destructive',
19
+ },
20
+ handle: async (params, apiKey) => {
21
+ const mablApiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClientFromOptionalApiKey(apiKey);
22
+ const result = await (0, run_1.run)({
23
+ id: params.testId,
24
+ 'environment-id': params.environmentId || '',
25
+ branch: 'master',
26
+ 'branch-changes-only': false,
27
+ browser: 'chrome',
28
+ 'browser-disable-isolation': false,
29
+ 'browser-ignore-certificate-errors': false,
30
+ 'enable-link': false,
31
+ headless: false,
32
+ height: 1024,
33
+ highlights: false,
34
+ 'keep-browser-open': false,
35
+ 'run-id': undefined,
36
+ width: 1024,
37
+ output: 'json',
38
+ url: 'http://sandbox-local.mabl.com:3000',
39
+ _: [],
40
+ $0: '',
41
+ }, false, mablApiClient);
42
+ return {
43
+ content: [
44
+ {
45
+ type: 'text',
46
+ text: `Test run ${result.success
47
+ ? 'succeeded'
48
+ : `failed ${result.testResults[0].errorMessage}`}`,
49
+ },
50
+ ],
51
+ };
52
+ },
53
+ });
54
+ exports.default = [runTest];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = run;
3
4
  const util_1 = require("../../commandUtil/util");
4
5
  const testsUtil_1 = require("../testsUtil");
5
6
  const constants_1 = require("../../constants");
@@ -218,7 +219,7 @@ Note: Setting the environment does not override the default URL. Please use the
218
219
  };
219
220
  const exitCodeOnError = 1;
220
221
  exports.handler = (0, util_1.failWrapper)(run, exitCodeOnError);
221
- async function run(parsed) {
222
+ async function run(parsed, killProcessOnFailure = true, apiClient) {
222
223
  const commandStartTime = Date.now();
223
224
  let workspaceId;
224
225
  try {
@@ -236,7 +237,7 @@ async function run(parsed) {
236
237
  const browserType = (0, testsUtil_1.parseBrowserType)(parsed[constants_1.CommandArgBrowser]);
237
238
  let scenario;
238
239
  if (parsed[constants_1.CommandArgScenarioId]) {
239
- const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
240
+ apiClient ??= await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
240
241
  scenario = await apiClient.getScenario(parsed[constants_1.CommandArgScenarioId]);
241
242
  if (!scenario) {
242
243
  throw new Error(`Scenario with ID ${parsed[constants_1.CommandArgScenarioId]} not found`);
@@ -295,13 +296,14 @@ async function run(parsed) {
295
296
  (0, testsUtil_1.cleanupTestResources)(testContext);
296
297
  });
297
298
  }
298
- const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
299
+ apiClient ??= await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
299
300
  await (0, runUtils_1.logTestResults)(results, parsed, commandStartTime, generateRunCommandTemplate(parsed, results), apiClient, browserType, parsed[constants_1.CommandArgVerbose]);
300
301
  if (!testRunnerConfig.keepBrowserOpen) {
301
- if (!results.success) {
302
+ if (!results.success && killProcessOnFailure) {
302
303
  process.exit(1);
303
304
  }
304
305
  }
306
+ return results;
305
307
  }
306
308
  function generateRunCommandTemplate(parsed, testResults) {
307
309
  const testResult = testResults.testResults[0];