UNPKG

@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
/** * 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(), ]);