@debugg-ai/debugg-ai-mcp 2.9.7 → 3.0.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.
@@ -25,7 +25,7 @@ export const TriggerCrawlInputSchema = z.object({
25
25
  credentialRole: z.string().optional(),
26
26
  username: z.string().optional(),
27
27
  password: z.string().optional(),
28
- headless: z.boolean().optional(),
28
+ // headless is intentionally NOT accepted — the MCP always runs headless (D7).
29
29
  timeoutSeconds: z.number().int().positive().max(1800, 'timeoutSeconds cannot exceed 1800 (30 min)').optional(),
30
30
  repoName: z.string().optional(),
31
31
  }).strict();
@@ -220,3 +220,57 @@ export const GetTestSuiteResultsInputSchema = z.object({
220
220
  ...suiteIdentifier,
221
221
  ...projectIdentifier,
222
222
  }).strict();
223
+ // ── Consolidated action-based tool schemas (epic yg7o6: 20 → 8 tools) ─────────
224
+ // Each entity tool takes a required `action` discriminator; params validate
225
+ // per-action. Delete branches carry an optional `confirm` (guarded by D2).
226
+ const _page = z.number().int().min(1).optional();
227
+ const _pageSize = z.number().int().min(1).optional();
228
+ export const ProjectInputSchema = z.discriminatedUnion('action', [
229
+ z.object({ action: z.literal('get'), uuid: z.string().uuid() }).strict(),
230
+ z.object({ action: z.literal('list'), q: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
231
+ z.object({
232
+ action: z.literal('create'),
233
+ name: z.string().min(1),
234
+ platform: z.string().min(1),
235
+ teamUuid: z.string().uuid().optional(),
236
+ teamName: z.string().min(1).optional(),
237
+ repoUuid: z.string().uuid().optional(),
238
+ repoName: z.string().min(1).optional(),
239
+ }).strict(),
240
+ ]).superRefine((v, ctx) => {
241
+ if (v.action === 'create') {
242
+ if (v.teamUuid && v.teamName)
243
+ ctx.addIssue({ code: 'custom', message: 'Provide teamUuid OR teamName, not both.' });
244
+ if (v.repoUuid && v.repoName)
245
+ ctx.addIssue({ code: 'custom', message: 'Provide repoUuid OR repoName, not both.' });
246
+ if (!v.teamUuid && !v.teamName)
247
+ ctx.addIssue({ code: 'custom', message: 'Must provide teamUuid or teamName.' });
248
+ if (!v.repoUuid && !v.repoName)
249
+ ctx.addIssue({ code: 'custom', message: 'Must provide repoUuid or repoName.' });
250
+ }
251
+ });
252
+ export const EnvironmentInputSchema = z.discriminatedUnion('action', [
253
+ z.object({ action: z.literal('get'), uuid: z.string().uuid(), projectUuid: z.string().uuid().optional() }).strict(),
254
+ z.object({ action: z.literal('list'), projectUuid: z.string().uuid().optional(), q: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
255
+ z.object({ action: z.literal('create'), name: z.string().min(1), url: z.string().url('url is required for standard environments'), description: z.string().optional(), projectUuid: z.string().uuid().optional(), credentials: z.array(CredentialSeedSchema).optional() }).strict(),
256
+ z.object({ action: z.literal('update'), uuid: z.string().uuid(), name: z.string().min(1).optional(), url: z.string().url().optional(), description: z.string().optional(), projectUuid: z.string().uuid().optional(), addCredentials: z.array(CredentialSeedSchema).optional(), updateCredentials: z.array(CredentialUpdateSchema).optional(), removeCredentialIds: z.array(z.string().uuid()).optional() }).strict(),
257
+ z.object({ action: z.literal('delete'), uuid: z.string().uuid(), projectUuid: z.string().uuid().optional(), confirm: z.boolean().optional() }).strict(),
258
+ ]);
259
+ export const TestSuiteInputSchema = z.discriminatedUnion('action', [
260
+ z.object({ action: z.literal('list'), ...projectIdentifier, search: z.string().optional(), page: _page, pageSize: z.number().int().min(1).max(100).optional() }).strict(),
261
+ z.object({ action: z.literal('create'), name: z.string().min(1), description: z.string().min(1), ...projectIdentifier }).strict(),
262
+ z.object({ action: z.literal('run'), ...suiteIdentifier, ...projectIdentifier, targetUrl: z.string().url().optional() }).strict(),
263
+ z.object({ action: z.literal('results'), ...suiteIdentifier, ...projectIdentifier }).strict(),
264
+ z.object({ action: z.literal('delete'), ...suiteIdentifier, ...projectIdentifier, confirm: z.boolean().optional() }).strict(),
265
+ ]);
266
+ export const TestCaseInputSchema = z.discriminatedUnion('action', [
267
+ z.object({ action: z.literal('create'), name: z.string().min(1), description: z.string().min(1), agentTaskDescription: z.string().min(1), ...suiteIdentifier, ...projectIdentifier, relativeUrl: z.string().regex(/^\//, 'Must start with /').optional(), maxSteps: z.number().int().min(1).max(100).optional() }).strict(),
268
+ z.object({ action: z.literal('update'), testUuid: z.string().uuid(), name: z.string().min(1).optional(), description: z.string().min(1).optional(), agentTaskDescription: z.string().min(1).optional() }).strict(),
269
+ z.object({ action: z.literal('delete'), testUuid: z.string().uuid(), confirm: z.boolean().optional() }).strict(),
270
+ ]);
271
+ // NOTE: D6 (recency sort) deferred — the backend listExecutions has no ordering
272
+ // param; threading real sort needs a backend change. Tracked, not shipped here.
273
+ export const ExecutionsInputSchema = z.discriminatedUnion('action', [
274
+ z.object({ action: z.literal('get'), uuid: z.string().uuid() }).strict(),
275
+ z.object({ action: z.literal('list'), projectUuid: z.string().uuid().optional(), status: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
276
+ ]);
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Destructive-action guard (epic debugg_ai_mcp-yg7o6, decision D2).
3
+ *
4
+ * Consolidated entity tools expose a `delete` action. Before any delete runs we
5
+ * require confirmation:
6
+ * - If the client supports elicitation (ctx.elicit present), prompt for it.
7
+ * - Otherwise fall back to a required `confirm: true` argument.
8
+ *
9
+ * This keeps deletes safe on EVERY client without depending on the elicitation
10
+ * epic — that epic only has to populate `ctx.elicit`; the confirm-arg path here
11
+ * ships standalone.
12
+ */
13
+ /** Action names treated as destructive. Currently just `delete`. */
14
+ export const DESTRUCTIVE_ACTIONS = new Set(['delete']);
15
+ export function isDestructiveAction(action) {
16
+ return DESTRUCTIVE_ACTIONS.has(action);
17
+ }
18
+ function refusal(error, message) {
19
+ return {
20
+ content: [{ type: 'text', text: JSON.stringify({ error, message }, null, 2) }],
21
+ isError: true,
22
+ };
23
+ }
24
+ /**
25
+ * Gate a destructive action.
26
+ *
27
+ * @returns `null` to proceed, or a `ToolResponse` (isError) to abort the call.
28
+ */
29
+ export async function ensureConfirmed(action, label, input, ctx) {
30
+ if (!isDestructiveAction(action))
31
+ return null;
32
+ if (ctx.elicit) {
33
+ const res = await ctx.elicit({
34
+ message: `Delete ${label}? This cannot be undone.`,
35
+ requestedSchema: {
36
+ type: 'object',
37
+ properties: { confirm: { type: 'boolean', description: 'Confirm deletion of ' + label } },
38
+ required: ['confirm'],
39
+ },
40
+ });
41
+ if (res.action === 'accept' && res.content?.confirm === true)
42
+ return null;
43
+ return refusal('confirmation_declined', `Deletion of ${label} was not confirmed.`);
44
+ }
45
+ if (input.confirm === true)
46
+ return null;
47
+ return refusal('confirmation_required', `Refusing to delete ${label} without confirmation. Pass confirm:true, or use an elicitation-capable client.`);
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "2.9.7",
3
+ "version": "3.0.1",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,39 +0,0 @@
1
- import { Logger } from '../utils/logger.js';
2
- import { handleExternalServiceError } from '../utils/errors.js';
3
- import { DebuggAIServerClient } from '../services/index.js';
4
- import { config } from '../config/index.js';
5
- const logger = new Logger({ module: 'deleteProjectHandler' });
6
- function notFound(uuid) {
7
- return {
8
- content: [{ type: 'text', text: JSON.stringify({
9
- error: 'NotFound',
10
- message: `Project ${uuid} not found.`,
11
- uuid,
12
- }, null, 2) }],
13
- isError: true,
14
- };
15
- }
16
- export async function deleteProjectHandler(input, _context) {
17
- const start = Date.now();
18
- logger.toolStart('delete_project', { uuid: input.uuid });
19
- try {
20
- const client = new DebuggAIServerClient(config.api.key);
21
- await client.init();
22
- try {
23
- await client.deleteProject(input.uuid);
24
- logger.toolComplete('delete_project', Date.now() - start);
25
- return {
26
- content: [{ type: 'text', text: JSON.stringify({ deleted: true, uuid: input.uuid }, null, 2) }],
27
- };
28
- }
29
- catch (err) {
30
- if (err?.statusCode === 404 || err?.response?.status === 404)
31
- return notFound(input.uuid);
32
- throw err;
33
- }
34
- }
35
- catch (error) {
36
- logger.toolError('delete_project', error, Date.now() - start);
37
- throw handleExternalServiceError(error, 'DebuggAI', 'delete_project');
38
- }
39
- }
@@ -1,45 +0,0 @@
1
- import { Logger } from '../utils/logger.js';
2
- import { handleExternalServiceError } from '../utils/errors.js';
3
- import { DebuggAIServerClient } from '../services/index.js';
4
- import { config } from '../config/index.js';
5
- const logger = new Logger({ module: 'updateProjectHandler' });
6
- function notFound(uuid) {
7
- return {
8
- content: [{ type: 'text', text: JSON.stringify({
9
- error: 'NotFound',
10
- message: `Project ${uuid} not found.`,
11
- uuid,
12
- }, null, 2) }],
13
- isError: true,
14
- };
15
- }
16
- export async function updateProjectHandler(input, _context) {
17
- const start = Date.now();
18
- logger.toolStart('update_project', {
19
- uuid: input.uuid,
20
- patchKeys: Object.keys(input).filter(k => k !== 'uuid'),
21
- });
22
- try {
23
- const client = new DebuggAIServerClient(config.api.key);
24
- await client.init();
25
- try {
26
- const project = await client.updateProject(input.uuid, {
27
- name: input.name,
28
- description: input.description,
29
- });
30
- logger.toolComplete('update_project', Date.now() - start);
31
- return {
32
- content: [{ type: 'text', text: JSON.stringify({ updated: true, project }, null, 2) }],
33
- };
34
- }
35
- catch (err) {
36
- if (err?.statusCode === 404 || err?.response?.status === 404)
37
- return notFound(input.uuid);
38
- throw err;
39
- }
40
- }
41
- catch (error) {
42
- logger.toolError('update_project', error, Date.now() - start);
43
- throw handleExternalServiceError(error, 'DebuggAI', 'update_project');
44
- }
45
- }
@@ -1,46 +0,0 @@
1
- import { CreateEnvironmentInputSchema } from '../types/index.js';
2
- import { createEnvironmentHandler } from '../handlers/createEnvironmentHandler.js';
3
- const DESCRIPTION = `Create a new environment under a DebuggAI project. Both name and url are required (backend rejects standard environments without a URL). Optional description. Defaults to the project resolved from the current git repo; pass projectUuid to target a different project (get UUIDs via search_projects).
4
-
5
- OPTIONAL credentials seed: pass \`credentials: [{label, username, password, role?}]\` to create login credentials alongside the environment in a single call. Each cred is created best-effort; failures go to \`credentialWarnings\` without blocking env creation. Passwords are write-only and NEVER returned.
6
-
7
- Returns the created environment's uuid (and the seeded credentials, if any). Reference the env uuid when running check_app_in_browser.`;
8
- export function buildCreateEnvironmentTool() {
9
- return {
10
- name: 'create_environment',
11
- title: 'Create Project Environment',
12
- description: DESCRIPTION,
13
- inputSchema: {
14
- type: 'object',
15
- properties: {
16
- name: {
17
- type: 'string',
18
- description: 'Short label for the environment (e.g. "staging", "production"). Required.',
19
- minLength: 1,
20
- },
21
- url: {
22
- type: 'string',
23
- description: 'Base URL for the environment (e.g. https://staging.example.com). Required.',
24
- },
25
- description: {
26
- type: 'string',
27
- description: 'Optional: free-text description.',
28
- },
29
- projectUuid: {
30
- type: 'string',
31
- description: 'Optional: UUID of the target project. Defaults to the project resolved from the current git repo.',
32
- },
33
- },
34
- required: ['name', 'url'],
35
- additionalProperties: false,
36
- },
37
- };
38
- }
39
- export function buildValidatedCreateEnvironmentTool() {
40
- const tool = buildCreateEnvironmentTool();
41
- return {
42
- ...tool,
43
- inputSchema: CreateEnvironmentInputSchema,
44
- handler: createEnvironmentHandler,
45
- };
46
- }
@@ -1,27 +0,0 @@
1
- import { CreateProjectInputSchema } from '../types/index.js';
2
- import { createProjectHandler } from '../handlers/createProjectHandler.js';
3
- const DESCRIPTION = `Create a new DebuggAI project. Required: name, platform (e.g. "web"), plus a team and a repo. Team and repo each accept EITHER a UUID or a name: pass teamUuid OR teamName, and repoUuid OR repoName. Name resolution does a backend search with case-insensitive exact match (returns AmbiguousMatch with candidates on multiple hits, NotFound on no hit). The repo must be GitHub-linked to the account. Returns {created: true, project: {uuid, name, slug, platform, repoName, ...}}.`;
4
- export function buildCreateProjectTool() {
5
- return {
6
- name: 'create_project',
7
- title: 'Create Project',
8
- description: DESCRIPTION,
9
- inputSchema: {
10
- type: 'object',
11
- properties: {
12
- name: { type: 'string', description: 'Project name. Required.', minLength: 1 },
13
- platform: { type: 'string', description: 'Platform (e.g. "web"). Required.', minLength: 1 },
14
- teamUuid: { type: 'string', description: 'Team UUID. Provide teamUuid OR teamName, not both.' },
15
- teamName: { type: 'string', description: 'Team name (backend-resolved, case-insensitive exact match). Provide teamUuid OR teamName, not both.' },
16
- repoUuid: { type: 'string', description: 'GitHub repo UUID. Provide repoUuid OR repoName, not both. Repo must be GitHub-linked.' },
17
- repoName: { type: 'string', description: 'GitHub repo name, e.g. "org/repo" (backend-resolved, case-insensitive exact match). Provide repoUuid OR repoName, not both.' },
18
- },
19
- required: ['name', 'platform'],
20
- additionalProperties: false,
21
- },
22
- };
23
- }
24
- export function buildValidatedCreateProjectTool() {
25
- const tool = buildCreateProjectTool();
26
- return { ...tool, inputSchema: CreateProjectInputSchema, handler: createProjectHandler };
27
- }
@@ -1,23 +0,0 @@
1
- import { DeleteEnvironmentInputSchema } from '../types/index.js';
2
- import { deleteEnvironmentHandler } from '../handlers/deleteEnvironmentHandler.js';
3
- const DESCRIPTION = `Delete an environment by UUID. Returns {deleted: true, uuid}. Destructive — cascades per backend behavior (credentials under the env are typically removed). Defaults to the project resolved from the current git repo; pass projectUuid to target a different project. Returns isError:true with NotFound when the uuid doesn't exist or was already deleted.`;
4
- export function buildDeleteEnvironmentTool() {
5
- return {
6
- name: 'delete_environment',
7
- title: 'Delete Environment',
8
- description: DESCRIPTION,
9
- inputSchema: {
10
- type: 'object',
11
- properties: {
12
- uuid: { type: 'string', description: 'UUID of the environment to delete. Required.' },
13
- projectUuid: { type: 'string', description: 'Optional: UUID of the target project. Defaults to git-auto-detect.' },
14
- },
15
- required: ['uuid'],
16
- additionalProperties: false,
17
- },
18
- };
19
- }
20
- export function buildValidatedDeleteEnvironmentTool() {
21
- const tool = buildDeleteEnvironmentTool();
22
- return { ...tool, inputSchema: DeleteEnvironmentInputSchema, handler: deleteEnvironmentHandler };
23
- }
@@ -1,22 +0,0 @@
1
- import { DeleteProjectInputSchema } from '../types/index.js';
2
- import { deleteProjectHandler } from '../handlers/deleteProjectHandler.js';
3
- const DESCRIPTION = `Delete a project by UUID. Returns {deleted:true, uuid}. **DESTRUCTIVE** — removes the project and its associated environments, credentials, and test history. No undo. Returns isError:true + NotFound when already deleted or uuid doesn't exist.`;
4
- export function buildDeleteProjectTool() {
5
- return {
6
- name: 'delete_project',
7
- title: 'Delete Project',
8
- description: DESCRIPTION,
9
- inputSchema: {
10
- type: 'object',
11
- properties: {
12
- uuid: { type: 'string', description: 'UUID of the project to delete. Required.' },
13
- },
14
- required: ['uuid'],
15
- additionalProperties: false,
16
- },
17
- };
18
- }
19
- export function buildValidatedDeleteProjectTool() {
20
- const tool = buildDeleteProjectTool();
21
- return { ...tool, inputSchema: DeleteProjectInputSchema, handler: deleteProjectHandler };
22
- }
@@ -1,35 +0,0 @@
1
- import { SearchEnvironmentsInputSchema } from '../types/index.js';
2
- import { searchEnvironmentsHandler } from '../handlers/searchEnvironmentsHandler.js';
3
- const DESCRIPTION = `Search or look up environments, with credentials expanded inline per environment.
4
-
5
- Two modes:
6
- - uuid mode: {"uuid": "<env-uuid>"} → single env with full detail + its credentials. NotFound if the uuid doesn't exist.
7
- - filter mode: omit uuid, optionally {"q": "<keyword>", "projectUuid", "page", "pageSize"} → paginated envs, each with its credentials.
8
-
9
- Project resolution: if projectUuid is omitted, the current git repo's origin is auto-resolved to a DebuggAI project. Returns {error:"NoProjectResolved", environments:[]} if neither is available.
10
-
11
- Credentials are returned inline per env as {uuid, label, username, role}. Password is NEVER returned — the handler defensively strips it regardless of what the service layer provides.
12
-
13
- Response: {project, filter, pageInfo, environments[]} — each environment includes a credentials[] array.`;
14
- export function buildSearchEnvironmentsTool() {
15
- return {
16
- name: 'search_environments',
17
- title: 'Search Environments',
18
- description: DESCRIPTION,
19
- inputSchema: {
20
- type: 'object',
21
- properties: {
22
- uuid: { type: 'string', description: 'Environment UUID. Returns single env with credentials inline. Mutually exclusive with projectUuid/q filter params.' },
23
- projectUuid: { type: 'string', description: 'Override the auto-detected project. Used in filter mode.' },
24
- q: { type: 'string', description: 'Free-text search over environment name. Mutually exclusive with uuid.' },
25
- page: { type: 'number', description: 'Page number (1-indexed).' },
26
- pageSize: { type: 'number', description: 'Page size (1..200). Default 20.' },
27
- },
28
- additionalProperties: false,
29
- },
30
- };
31
- }
32
- export function buildValidatedSearchEnvironmentsTool() {
33
- const tool = buildSearchEnvironmentsTool();
34
- return { ...tool, inputSchema: SearchEnvironmentsInputSchema, handler: searchEnvironmentsHandler };
35
- }
@@ -1,31 +0,0 @@
1
- import { SearchExecutionsInputSchema } from '../types/index.js';
2
- import { searchExecutionsHandler } from '../handlers/searchExecutionsHandler.js';
3
- const DESCRIPTION = `Search or look up workflow executions (history of check_app_in_browser, trigger_crawl, and other workflow runs).
4
-
5
- Two modes:
6
- - uuid mode: {"uuid": "<execution-uuid>"} → single execution with FULL detail including nodeExecutions, state, errorInfo. NotFound if the uuid doesn't exist.
7
- - filter mode: {"status": "completed"|"running"|"failed"|"cancelled", "projectUuid": "...", "page", "pageSize"} → paginated summaries.
8
-
9
- Response shape: {filter, pageInfo, executions[]}. Summary items have outcome/status/durationMs/timestamps; uuid-mode items additionally have nodeExecutions + state + errorInfo.`;
10
- export function buildSearchExecutionsTool() {
11
- return {
12
- name: 'search_executions',
13
- title: 'Search Workflow Executions',
14
- description: DESCRIPTION,
15
- inputSchema: {
16
- type: 'object',
17
- properties: {
18
- uuid: { type: 'string', description: 'Execution UUID. Returns single execution with full detail. Mutually exclusive with projectUuid/status filters.' },
19
- projectUuid: { type: 'string', description: 'Filter by project UUID.' },
20
- status: { type: 'string', description: 'Filter by status: completed | running | failed | cancelled | pending.' },
21
- page: { type: 'number', description: 'Page number (1-indexed).' },
22
- pageSize: { type: 'number', description: 'Page size (1..200). Default 20.' },
23
- },
24
- additionalProperties: false,
25
- },
26
- };
27
- }
28
- export function buildValidatedSearchExecutionsTool() {
29
- const tool = buildSearchExecutionsTool();
30
- return { ...tool, inputSchema: SearchExecutionsInputSchema, handler: searchExecutionsHandler };
31
- }
@@ -1,30 +0,0 @@
1
- import { SearchProjectsInputSchema } from '../types/index.js';
2
- import { searchProjectsHandler } from '../handlers/searchProjectsHandler.js';
3
- const DESCRIPTION = `Search or look up projects.
4
-
5
- Two modes:
6
- - uuid mode: pass {"uuid": "<project-uuid>"} → returns that project with the curated detail view (uuid, name, slug, platform, repoName, description, status, language, framework, timestamp, lastMod), or isError:true NotFound.
7
- - filter mode: omit uuid, optionally pass {"q": "<keyword>", "page": 1, "pageSize": 20} → returns a paginated list of summaries (uuid, name, slug, repoName).
8
-
9
- Response shape is always {filter, pageInfo, projects[]}. uuid mode returns exactly one project; filter mode returns summaries.`;
10
- export function buildSearchProjectsTool() {
11
- return {
12
- name: 'search_projects',
13
- title: 'Search Projects',
14
- description: DESCRIPTION,
15
- inputSchema: {
16
- type: 'object',
17
- properties: {
18
- uuid: { type: 'string', description: 'Project UUID. When provided, returns exactly that project with full detail. Mutually exclusive with q.' },
19
- q: { type: 'string', description: 'Free-text search (backend-side). Mutually exclusive with uuid.' },
20
- page: { type: 'number', description: 'Page number (1-indexed). Default 1.' },
21
- pageSize: { type: 'number', description: 'Page size (1..200). Default 20.' },
22
- },
23
- additionalProperties: false,
24
- },
25
- };
26
- }
27
- export function buildValidatedSearchProjectsTool() {
28
- const tool = buildSearchProjectsTool();
29
- return { ...tool, inputSchema: SearchProjectsInputSchema, handler: searchProjectsHandler };
30
- }
@@ -1,183 +0,0 @@
1
- import { CreateTestSuiteInputSchema, SearchTestSuitesInputSchema, DeleteTestSuiteInputSchema, CreateTestCaseInputSchema, UpdateTestCaseInputSchema, DeleteTestCaseInputSchema, RunTestSuiteInputSchema, GetTestSuiteResultsInputSchema, } from '../types/index.js';
2
- import { createTestSuiteHandler } from '../handlers/createTestSuiteHandler.js';
3
- import { searchTestSuitesHandler } from '../handlers/searchTestSuitesHandler.js';
4
- import { deleteTestSuiteHandler } from '../handlers/deleteTestSuiteHandler.js';
5
- import { createTestCaseHandler } from '../handlers/createTestCaseHandler.js';
6
- import { updateTestCaseHandler } from '../handlers/updateTestCaseHandler.js';
7
- import { deleteTestCaseHandler } from '../handlers/deleteTestCaseHandler.js';
8
- import { runTestSuiteHandler } from '../handlers/runTestSuiteHandler.js';
9
- import { getTestSuiteResultsHandler } from '../handlers/getTestSuiteResultsHandler.js';
10
- const PROJECT_PROPS = {
11
- projectUuid: { type: 'string', description: 'Project UUID. Provide projectUuid OR projectName.' },
12
- projectName: { type: 'string', description: 'Project name (case-insensitive exact match). Provide projectUuid OR projectName.' },
13
- };
14
- const SUITE_PROPS = {
15
- suiteUuid: { type: 'string', description: 'Test suite UUID. Provide suiteUuid OR (suiteName + project identifier).' },
16
- suiteName: { type: 'string', description: 'Test suite name (case-insensitive exact match). Requires projectUuid or projectName.' },
17
- };
18
- // ── create_test_suite ─────────────────────────────────────────────────────────
19
- export function buildCreateTestSuiteTool() {
20
- return {
21
- name: 'create_test_suite',
22
- title: 'Create Test Suite',
23
- description: 'Create a named test suite for a project. A test suite is a collection of test cases that can be run together. Requires name, description, and a project identifier (projectUuid or projectName). Returns {uuid, name, description, runStatus, testsCount}.',
24
- inputSchema: {
25
- type: 'object',
26
- properties: {
27
- name: { type: 'string', description: 'Suite name. Required.', minLength: 1 },
28
- description: { type: 'string', description: 'Suite description. Required.', minLength: 1 },
29
- ...PROJECT_PROPS,
30
- },
31
- required: ['name', 'description'],
32
- additionalProperties: false,
33
- },
34
- };
35
- }
36
- export function buildValidatedCreateTestSuiteTool() {
37
- return { ...buildCreateTestSuiteTool(), inputSchema: CreateTestSuiteInputSchema, handler: createTestSuiteHandler };
38
- }
39
- // ── search_test_suites ────────────────────────────────────────────────────────
40
- export function buildSearchTestSuitesTool() {
41
- return {
42
- name: 'search_test_suites',
43
- title: 'Search Test Suites',
44
- description: 'List and search test suites for a project. Returns paginated results with suite status, test counts, pass rates, and last run timestamps. Requires a project identifier (projectUuid or projectName). Optional: search text filter, page, pageSize (1-100, default 20).',
45
- inputSchema: {
46
- type: 'object',
47
- properties: {
48
- ...PROJECT_PROPS,
49
- search: { type: 'string', description: 'Optional text filter applied to suite name and description.' },
50
- page: { type: 'number', description: 'Page number (default 1).', minimum: 1 },
51
- pageSize: { type: 'number', description: 'Results per page (default 20, max 100).', minimum: 1, maximum: 100 },
52
- },
53
- additionalProperties: false,
54
- },
55
- };
56
- }
57
- export function buildValidatedSearchTestSuitesTool() {
58
- return { ...buildSearchTestSuitesTool(), inputSchema: SearchTestSuitesInputSchema, handler: searchTestSuitesHandler };
59
- }
60
- // ── delete_test_suite ─────────────────────────────────────────────────────────
61
- export function buildDeleteTestSuiteTool() {
62
- return {
63
- name: 'delete_test_suite',
64
- title: 'Delete Test Suite',
65
- description: 'Disable (soft-delete) a test suite. The suite and its tests are hidden from default list queries but not permanently removed. Accepts suiteUuid directly, or suiteName + project identifier for name-based lookup. Returns {deleted: true, suiteUuid}.',
66
- inputSchema: {
67
- type: 'object',
68
- properties: {
69
- ...SUITE_PROPS,
70
- ...PROJECT_PROPS,
71
- },
72
- additionalProperties: false,
73
- },
74
- };
75
- }
76
- export function buildValidatedDeleteTestSuiteTool() {
77
- return { ...buildDeleteTestSuiteTool(), inputSchema: DeleteTestSuiteInputSchema, handler: deleteTestSuiteHandler };
78
- }
79
- // ── create_test_case ──────────────────────────────────────────────────────────
80
- export function buildCreateTestCaseTool() {
81
- return {
82
- name: 'create_test_case',
83
- title: 'Create Test Case',
84
- description: 'Create an individual test case and assign it to a test suite. The test is NOT automatically executed. Requires name, description, agentTaskDescription (the AI agent\'s goal), and suite + project identifiers. Optional: relativeUrl (must start with "/") and maxSteps (1-100). Returns {uuid, name, description, agentTaskDescription, suite, project, runCount}.',
85
- inputSchema: {
86
- type: 'object',
87
- properties: {
88
- name: { type: 'string', description: 'Test case name. Required.', minLength: 1 },
89
- description: { type: 'string', description: 'Test case description. Required.', minLength: 1 },
90
- agentTaskDescription: { type: 'string', description: 'Natural language description of what the AI agent should do and verify. Required.', minLength: 1 },
91
- ...SUITE_PROPS,
92
- ...PROJECT_PROPS,
93
- relativeUrl: { type: 'string', description: 'Optional starting URL path relative to the app root, e.g. "/login". Must start with "/".' },
94
- maxSteps: { type: 'number', description: 'Maximum agent steps (1-100).', minimum: 1, maximum: 100 },
95
- },
96
- required: ['name', 'description', 'agentTaskDescription'],
97
- additionalProperties: false,
98
- },
99
- };
100
- }
101
- export function buildValidatedCreateTestCaseTool() {
102
- return { ...buildCreateTestCaseTool(), inputSchema: CreateTestCaseInputSchema, handler: createTestCaseHandler };
103
- }
104
- // ── update_test_case ──────────────────────────────────────────────────────────
105
- export function buildUpdateTestCaseTool() {
106
- return {
107
- name: 'update_test_case',
108
- title: 'Update Test Case',
109
- description: 'Update a test case\'s name, description, or agentTaskDescription. Requires testUuid. At least one of name, description, or agentTaskDescription must be provided. Returns the updated test case.',
110
- inputSchema: {
111
- type: 'object',
112
- properties: {
113
- testUuid: { type: 'string', description: 'UUID of the test case to update. Required.' },
114
- name: { type: 'string', description: 'New name for the test case.', minLength: 1 },
115
- description: { type: 'string', description: 'New description.', minLength: 1 },
116
- agentTaskDescription: { type: 'string', description: 'New agent task description.', minLength: 1 },
117
- },
118
- required: ['testUuid'],
119
- additionalProperties: false,
120
- },
121
- };
122
- }
123
- export function buildValidatedUpdateTestCaseTool() {
124
- return { ...buildUpdateTestCaseTool(), inputSchema: UpdateTestCaseInputSchema, handler: updateTestCaseHandler };
125
- }
126
- // ── delete_test_case ──────────────────────────────────────────────────────────
127
- export function buildDeleteTestCaseTool() {
128
- return {
129
- name: 'delete_test_case',
130
- title: 'Delete Test Case',
131
- description: 'Disable (soft-delete) a test case. The test is hidden from default list queries but not permanently removed. Requires testUuid. Returns {deleted: true, testUuid}.',
132
- inputSchema: {
133
- type: 'object',
134
- properties: {
135
- testUuid: { type: 'string', description: 'UUID of the test case to delete. Required.' },
136
- },
137
- required: ['testUuid'],
138
- additionalProperties: false,
139
- },
140
- };
141
- }
142
- export function buildValidatedDeleteTestCaseTool() {
143
- return { ...buildDeleteTestCaseTool(), inputSchema: DeleteTestCaseInputSchema, handler: deleteTestCaseHandler };
144
- }
145
- // ── run_test_suite ────────────────────────────────────────────────────────────
146
- export function buildRunTestSuiteTool() {
147
- return {
148
- name: 'run_test_suite',
149
- title: 'Run Test Suite',
150
- description: 'Trigger all test cases in a suite to run asynchronously. Accepts suiteUuid directly, or suiteName + project identifier. Optional: targetUrl to override the default test target. Returns {suiteUuid, runStatus, testsTriggered, note}. Use get_test_suite_results to poll for results.',
151
- inputSchema: {
152
- type: 'object',
153
- properties: {
154
- ...SUITE_PROPS,
155
- ...PROJECT_PROPS,
156
- targetUrl: { type: 'string', description: 'Optional URL to run tests against (overrides default). Must be a full URL.' },
157
- },
158
- additionalProperties: false,
159
- },
160
- };
161
- }
162
- export function buildValidatedRunTestSuiteTool() {
163
- return { ...buildRunTestSuiteTool(), inputSchema: RunTestSuiteInputSchema, handler: runTestSuiteHandler };
164
- }
165
- // ── get_test_suite_results ────────────────────────────────────────────────────
166
- export function buildGetTestSuiteResultsTool() {
167
- return {
168
- name: 'get_test_suite_results',
169
- title: 'Get Test Suite Results',
170
- description: 'Fetch a test suite with full per-test results. Returns suite-level status (NEVER_RUN, PENDING, RUNNING, COMPLETED, ERROR), pass rate, last run timestamp, and per-test outcomes (PASS, FAIL, ERROR, TIMEOUT, etc.) with execution times. Accepts suiteUuid directly or suiteName + project identifier.',
171
- inputSchema: {
172
- type: 'object',
173
- properties: {
174
- ...SUITE_PROPS,
175
- ...PROJECT_PROPS,
176
- },
177
- additionalProperties: false,
178
- },
179
- };
180
- }
181
- export function buildValidatedGetTestSuiteResultsTool() {
182
- return { ...buildGetTestSuiteResultsTool(), inputSchema: GetTestSuiteResultsInputSchema, handler: getTestSuiteResultsHandler };
183
- }