@debugg-ai/debugg-ai-mcp
Version:
Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.
277 lines (276 loc) • 15.1 kB
JavaScript
/**
* Comprehensive type definitions for DebuggAI MCP Server
*/
import { z } from 'zod';
import { normalizeUrl } from '../utils/urlParser.js';
/**
* Tool input validation schemas
*/
export const TestPageChangesInputSchema = z.object({
description: z.string().min(1, 'Description is required'),
url: z.preprocess(normalizeUrl, z.string().url('Invalid URL. Pass a full URL like "http://localhost:3000" or "https://example.com". Localhost URLs are auto-tunneled to the remote browser — no extra setup needed.')),
// Credential/environment resolution
environmentId: z.string().uuid().optional(),
credentialId: z.string().uuid().optional(),
credentialRole: z.string().optional(),
username: z.string().optional(),
password: z.string().optional(),
repoName: z.string().optional(),
});
export const TriggerCrawlInputSchema = z.object({
url: z.preprocess(normalizeUrl, z.string().url('Invalid URL. Pass a full URL like "http://localhost:3000" or "https://example.com". Localhost URLs are auto-tunneled to the remote browser.')),
projectUuid: z.string().uuid().optional(),
environmentId: z.string().uuid().optional(),
credentialId: z.string().uuid().optional(),
credentialRole: z.string().optional(),
username: z.string().optional(),
password: z.string().optional(),
// headless is intentionally NOT accepted — the MCP always runs headless (D7).
timeoutSeconds: z.number().int().positive().max(1800, 'timeoutSeconds cannot exceed 1800 (30 min)').optional(),
repoName: z.string().optional(),
}).strict();
// ── New consolidated search schemas (bead ddq) ─────────────────────────────
// uuid and filter params are mutually exclusive: either look up one thing by
// uuid, or filter the collection. Mixing them is ambiguous.
export const SearchProjectsInputSchema = z.object({
uuid: z.string().uuid().optional(),
q: z.string().min(1).optional(),
page: z.number().int().min(1).optional(),
pageSize: z.number().int().min(1).optional(),
}).strict().refine((v) => !(v.uuid && (v.q !== undefined)), { message: 'Cannot combine uuid with filter params (q). Pass one or the other.' });
// projectUuid is a LOCATOR (required by the backend URL path for envs/creds), not a
// filter — so it's compatible with uuid mode. Only q and uuid are mutually exclusive.
export const SearchEnvironmentsInputSchema = z.object({
uuid: z.string().uuid().optional(),
projectUuid: z.string().uuid().optional(),
q: z.string().min(1).optional(),
page: z.number().int().min(1).optional(),
pageSize: z.number().int().min(1).optional(),
}).strict().refine((v) => !(v.uuid && v.q !== undefined), { message: 'Cannot combine uuid with q (they are mutually exclusive — uuid mode returns one env; q filters a list).' });
export const SearchExecutionsInputSchema = z.object({
uuid: z.string().uuid().optional(),
projectUuid: z.string().uuid().optional(),
status: z.string().min(1).optional(),
page: z.number().int().min(1).optional(),
pageSize: z.number().int().min(1).optional(),
}).strict().refine((v) => !(v.uuid && (v.projectUuid || v.status)), { message: 'Cannot combine uuid with filter params (projectUuid, status).' });
const CredentialSeedSchema = z.object({
label: z.string().min(1, 'label is required'),
username: z.string().min(1, 'username is required'),
password: z.string().min(1, 'password is required'),
role: z.string().min(1).optional(),
}).strict();
export const CreateEnvironmentInputSchema = z.object({
name: z.string().min(1, 'name is required'),
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();
const CredentialUpdateSchema = z.object({
uuid: z.string().uuid(),
label: z.string().min(1).optional(),
username: z.string().min(1).optional(),
password: z.string().min(1).optional(),
role: z.string().min(1).optional(),
}).strict();
export const UpdateEnvironmentInputSchema = z.object({
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();
export const DeleteEnvironmentInputSchema = z.object({
uuid: z.string().uuid(),
projectUuid: z.string().uuid().optional(),
}).strict();
export const UpdateProjectInputSchema = z.object({
uuid: z.string().uuid(),
name: z.string().min(1).optional(),
description: z.string().optional(),
}).strict();
export const DeleteProjectInputSchema = z.object({
uuid: z.string().uuid(),
}).strict();
export const CreateProjectInputSchema = z.object({
name: z.string().min(1),
platform: z.string().min(1),
teamUuid: z.string().uuid().optional(),
teamName: z.string().min(1).optional(),
repoUuid: z.string().uuid().optional(),
repoName: z.string().min(1).optional(),
}).strict()
.refine((v) => !(v.teamUuid && v.teamName), {
message: 'Provide teamUuid OR teamName, not both.',
})
.refine((v) => !(v.repoUuid && v.repoName), {
message: 'Provide repoUuid OR repoName, not both.',
})
.refine((v) => v.teamUuid || v.teamName, {
message: 'Must provide teamUuid or teamName.',
})
.refine((v) => v.repoUuid || v.repoName, {
message: 'Must provide repoUuid or repoName.',
});
/**
* Error types
*/
export var MCPErrorCode;
(function (MCPErrorCode) {
MCPErrorCode[MCPErrorCode["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
MCPErrorCode[MCPErrorCode["METHOD_NOT_FOUND"] = -32601] = "METHOD_NOT_FOUND";
MCPErrorCode[MCPErrorCode["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
MCPErrorCode[MCPErrorCode["INTERNAL_ERROR"] = -32603] = "INTERNAL_ERROR";
MCPErrorCode[MCPErrorCode["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
// Custom error codes
MCPErrorCode[MCPErrorCode["VALIDATION_ERROR"] = -32000] = "VALIDATION_ERROR";
MCPErrorCode[MCPErrorCode["CONFIGURATION_ERROR"] = -32001] = "CONFIGURATION_ERROR";
MCPErrorCode[MCPErrorCode["AUTHENTICATION_ERROR"] = -32002] = "AUTHENTICATION_ERROR";
MCPErrorCode[MCPErrorCode["EXTERNAL_SERVICE_ERROR"] = -32003] = "EXTERNAL_SERVICE_ERROR";
})(MCPErrorCode || (MCPErrorCode = {}));
export class MCPError extends Error {
code;
data;
constructor(code, message, data) {
super(message);
this.code = code;
this.data = data;
this.name = 'MCPError';
}
}
/**
* Logging types
*/
export var LogLevel;
(function (LogLevel) {
LogLevel["ERROR"] = "error";
LogLevel["WARN"] = "warn";
LogLevel["INFO"] = "info";
LogLevel["DEBUG"] = "debug";
})(LogLevel || (LogLevel = {}));
// ── probe-page ────────────────────────────────────────────────────────────
// Lightweight no-LLM page-probe tool. Each target gets its own wait config;
// targets[] is the batch — one workflow execution covers up to 20 URLs sharing
// browser session + tunnel. Strict schema: forbidden agent fields like
// `description` and `credentialId` reject (zero-LLM contract).
export const ProbePageTargetSchema = z.object({
url: z.preprocess(normalizeUrl, z.string().url('Invalid URL. Pass a full URL like "http://localhost:3000" or "https://example.com". Localhost URLs are auto-tunneled to the remote browser.')),
waitForSelector: z.string().optional(),
waitForLoadState: z.enum(['load', 'domcontentloaded', 'networkidle']).default('load'),
timeoutMs: z.number().int().min(1000, 'timeoutMs minimum is 1000 (1s)').max(30000, 'timeoutMs maximum is 30000 (30s) — longer probes should use check_app_in_browser').default(10000),
}).strict();
export const ProbePageInputSchema = z.object({
targets: z.array(ProbePageTargetSchema).min(1, 'targets must have at least one URL').max(20, 'targets capped at 20 per call — split larger sweeps across multiple calls'),
includeHtml: z.boolean().default(false),
captureScreenshots: z.boolean().default(true),
repoName: z.string().optional(),
}).strict();
// ── E2E Suite Management ──────────────────────────────────────────────────────
const projectIdentifier = {
projectUuid: z.string().uuid().optional(),
projectName: z.string().min(1).optional(),
};
const suiteIdentifier = {
suiteUuid: z.string().uuid().optional(),
suiteName: z.string().min(1).optional(),
};
export const CreateTestSuiteInputSchema = z.object({
name: z.string().min(1),
description: z.string().min(1),
...projectIdentifier,
}).strict();
export const SearchTestSuitesInputSchema = z.object({
...projectIdentifier,
search: z.string().optional(),
page: z.number().int().min(1).optional(),
pageSize: z.number().int().min(1).max(100).optional(),
}).strict();
export const DeleteTestSuiteInputSchema = z.object({
...suiteIdentifier,
...projectIdentifier,
}).strict();
export const CreateTestCaseInputSchema = z.object({
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();
export const UpdateTestCaseInputSchema = z.object({
testUuid: z.string().uuid(),
name: z.string().min(1).optional(),
description: z.string().min(1).optional(),
agentTaskDescription: z.string().min(1).optional(),
}).strict();
export const DeleteTestCaseInputSchema = z.object({
testUuid: z.string().uuid(),
}).strict();
export const RunTestSuiteInputSchema = z.object({
...suiteIdentifier,
...projectIdentifier,
targetUrl: z.string().url().optional(),
}).strict();
export const GetTestSuiteResultsInputSchema = z.object({
...suiteIdentifier,
...projectIdentifier,
}).strict();
// ── Consolidated action-based tool schemas (epic yg7o6: 20 → 8 tools) ─────────
// Each entity tool takes a required `action` discriminator; params validate
// per-action. Delete branches carry an optional `confirm` (guarded by D2).
const _page = z.number().int().min(1).optional();
const _pageSize = z.number().int().min(1).optional();
export const ProjectInputSchema = z.discriminatedUnion('action', [
z.object({ action: z.literal('get'), uuid: z.string().uuid() }).strict(),
z.object({ action: z.literal('list'), q: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
z.object({
action: z.literal('create'),
name: z.string().min(1),
platform: z.string().min(1),
teamUuid: z.string().uuid().optional(),
teamName: z.string().min(1).optional(),
repoUuid: z.string().uuid().optional(),
repoName: z.string().min(1).optional(),
}).strict(),
]).superRefine((v, ctx) => {
if (v.action === 'create') {
if (v.teamUuid && v.teamName)
ctx.addIssue({ code: 'custom', message: 'Provide teamUuid OR teamName, not both.' });
if (v.repoUuid && v.repoName)
ctx.addIssue({ code: 'custom', message: 'Provide repoUuid OR repoName, not both.' });
if (!v.teamUuid && !v.teamName)
ctx.addIssue({ code: 'custom', message: 'Must provide teamUuid or teamName.' });
if (!v.repoUuid && !v.repoName)
ctx.addIssue({ code: 'custom', message: 'Must provide repoUuid or repoName.' });
}
});
export const EnvironmentInputSchema = z.discriminatedUnion('action', [
z.object({ action: z.literal('get'), uuid: z.string().uuid(), projectUuid: z.string().uuid().optional() }).strict(),
z.object({ action: z.literal('list'), projectUuid: z.string().uuid().optional(), q: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
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(),
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(),
z.object({ action: z.literal('delete'), uuid: z.string().uuid(), projectUuid: z.string().uuid().optional(), confirm: z.boolean().optional() }).strict(),
]);
export const TestSuiteInputSchema = z.discriminatedUnion('action', [
z.object({ action: z.literal('list'), ...projectIdentifier, search: z.string().optional(), page: _page, pageSize: z.number().int().min(1).max(100).optional() }).strict(),
z.object({ action: z.literal('create'), name: z.string().min(1), description: z.string().min(1), ...projectIdentifier }).strict(),
z.object({ action: z.literal('run'), ...suiteIdentifier, ...projectIdentifier, targetUrl: z.string().url().optional() }).strict(),
z.object({ action: z.literal('results'), ...suiteIdentifier, ...projectIdentifier }).strict(),
z.object({ action: z.literal('delete'), ...suiteIdentifier, ...projectIdentifier, confirm: z.boolean().optional() }).strict(),
]);
export const TestCaseInputSchema = z.discriminatedUnion('action', [
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(),
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(),
z.object({ action: z.literal('delete'), testUuid: z.string().uuid(), confirm: z.boolean().optional() }).strict(),
]);
// NOTE: D6 (recency sort) deferred — the backend listExecutions has no ordering
// param; threading real sort needs a backend change. Tracked, not shipped here.
export const ExecutionsInputSchema = z.discriminatedUnion('action', [
z.object({ action: z.literal('get'), uuid: z.string().uuid() }).strict(),
z.object({ action: z.literal('list'), projectUuid: z.string().uuid().optional(), status: z.string().min(1).optional(), page: _page, pageSize: _pageSize }).strict(),
]);