@debugg-ai/debugg-ai-mcp 1.0.63 → 1.0.64

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 (46) hide show
  1. package/dist/config/index.js +4 -1
  2. package/dist/handlers/createEnvironmentHandler.js +33 -0
  3. package/dist/handlers/createProjectHandler.js +62 -10
  4. package/dist/handlers/index.js +4 -14
  5. package/dist/handlers/searchEnvironmentsHandler.js +122 -0
  6. package/dist/handlers/searchExecutionsHandler.js +71 -0
  7. package/dist/handlers/searchProjectsHandler.js +72 -0
  8. package/dist/handlers/testPageChangesHandler.js +46 -5
  9. package/dist/handlers/triggerCrawlHandler.js +37 -7
  10. package/dist/handlers/updateEnvironmentHandler.js +94 -15
  11. package/dist/index.js +15 -2
  12. package/dist/tools/createEnvironment.js +5 -1
  13. package/dist/tools/index.js +9 -42
  14. package/dist/tools/searchEnvironments.js +35 -0
  15. package/dist/tools/searchExecutions.js +31 -0
  16. package/dist/tools/searchProjects.js +30 -0
  17. package/dist/types/index.js +52 -71
  18. package/package.json +4 -1
  19. package/dist/handlers/cancelExecutionHandler.js +0 -41
  20. package/dist/handlers/createCredentialHandler.js +0 -60
  21. package/dist/handlers/deleteCredentialHandler.js +0 -51
  22. package/dist/handlers/getCredentialHandler.js +0 -49
  23. package/dist/handlers/getEnvironmentHandler.js +0 -49
  24. package/dist/handlers/getExecutionHandler.js +0 -37
  25. package/dist/handlers/getProjectHandler.js +0 -37
  26. package/dist/handlers/listCredentialsHandler.js +0 -93
  27. package/dist/handlers/listEnvironmentsHandler.js +0 -63
  28. package/dist/handlers/listExecutionsHandler.js +0 -35
  29. package/dist/handlers/listProjectsHandler.js +0 -32
  30. package/dist/handlers/listReposHandler.js +0 -27
  31. package/dist/handlers/listTeamsHandler.js +0 -27
  32. package/dist/handlers/updateCredentialHandler.js +0 -70
  33. package/dist/tools/cancelExecution.js +0 -22
  34. package/dist/tools/createCredential.js +0 -52
  35. package/dist/tools/deleteCredential.js +0 -24
  36. package/dist/tools/getCredential.js +0 -24
  37. package/dist/tools/getEnvironment.js +0 -23
  38. package/dist/tools/getExecution.js +0 -22
  39. package/dist/tools/getProject.js +0 -22
  40. package/dist/tools/listCredentials.js +0 -30
  41. package/dist/tools/listEnvironments.js +0 -28
  42. package/dist/tools/listExecutions.js +0 -24
  43. package/dist/tools/listProjects.js +0 -27
  44. package/dist/tools/listRepos.js +0 -23
  45. package/dist/tools/listTeams.js +0 -23
  46. package/dist/tools/updateCredential.js +0 -28
@@ -14,11 +14,23 @@ function notFound(uuid, context) {
14
14
  isError: true,
15
15
  };
16
16
  }
17
+ function stripPassword(c) {
18
+ return {
19
+ uuid: c.uuid,
20
+ label: c.label,
21
+ username: c.username,
22
+ role: c.role ?? null,
23
+ environmentUuid: c.environmentUuid,
24
+ };
25
+ }
17
26
  export async function updateEnvironmentHandler(input, _context) {
18
27
  const start = Date.now();
19
28
  logger.toolStart('update_environment', {
20
29
  uuid: input.uuid,
21
- patchKeys: Object.keys(input).filter(k => k !== 'uuid' && k !== 'projectUuid'),
30
+ hasEnvPatch: !!(input.name || input.url || input.description),
31
+ addCount: input.addCredentials?.length ?? 0,
32
+ updateCount: input.updateCredentials?.length ?? 0,
33
+ removeCount: input.removeCredentialIds?.length ?? 0,
22
34
  projectUuid: input.projectUuid,
23
35
  });
24
36
  try {
@@ -34,23 +46,90 @@ export async function updateEnvironmentHandler(input, _context) {
34
46
  return notFound(input.uuid, `no project found for repo "${repoName}"`);
35
47
  projectUuid = project.uuid;
36
48
  }
37
- try {
38
- const environment = await client.updateEnvironment(projectUuid, input.uuid, {
39
- name: input.name,
40
- url: input.url,
41
- description: input.description,
42
- });
43
- logger.toolComplete('update_environment', Date.now() - start);
44
- return {
45
- content: [{ type: 'text', text: JSON.stringify({ updated: true, environment }, null, 2) }],
46
- };
49
+ // ── Env field patch (only if any env field is present) ──────────────────
50
+ const hasEnvPatch = input.name !== undefined || input.url !== undefined || input.description !== undefined;
51
+ let environment = null;
52
+ if (hasEnvPatch) {
53
+ try {
54
+ environment = await client.updateEnvironment(projectUuid, input.uuid, {
55
+ name: input.name, url: input.url, description: input.description,
56
+ });
57
+ }
58
+ catch (err) {
59
+ if (err?.statusCode === 404 || err?.response?.status === 404) {
60
+ return notFound(input.uuid, `backend returned 404 for project ${projectUuid}`);
61
+ }
62
+ throw err;
63
+ }
64
+ }
65
+ else {
66
+ // Echo the uuid so the response shape stays consistent. Include projectUuid
67
+ // for downstream tooling. Only populate `environment` if we patched.
68
+ environment = { uuid: input.uuid };
69
+ }
70
+ // ── Cred sub-actions (remove → update → add) ────────────────────────────
71
+ const warnings = [];
72
+ const addedCredentials = [];
73
+ const updatedCredentials = [];
74
+ const removedCredentialIds = [];
75
+ if (input.removeCredentialIds) {
76
+ for (const credUuid of input.removeCredentialIds) {
77
+ try {
78
+ await client.deleteCredential(projectUuid, input.uuid, credUuid);
79
+ removedCredentialIds.push(credUuid);
80
+ }
81
+ catch (err) {
82
+ warnings.push({ op: 'remove', uuid: credUuid, error: err?.message ?? String(err) });
83
+ }
84
+ }
85
+ }
86
+ if (input.updateCredentials) {
87
+ for (const patch of input.updateCredentials) {
88
+ try {
89
+ const updated = await client.updateCredential(projectUuid, input.uuid, patch.uuid, {
90
+ label: patch.label,
91
+ username: patch.username,
92
+ password: patch.password,
93
+ role: patch.role,
94
+ });
95
+ updatedCredentials.push(stripPassword(updated));
96
+ }
97
+ catch (err) {
98
+ warnings.push({ op: 'update', uuid: patch.uuid, error: err?.message ?? String(err) });
99
+ }
100
+ }
47
101
  }
48
- catch (err) {
49
- if (err?.statusCode === 404 || err?.response?.status === 404) {
50
- return notFound(input.uuid, `backend returned 404 for project ${projectUuid}`);
102
+ if (input.addCredentials) {
103
+ for (const seed of input.addCredentials) {
104
+ try {
105
+ const cred = await client.createCredential(projectUuid, input.uuid, {
106
+ label: seed.label,
107
+ username: seed.username,
108
+ password: seed.password,
109
+ role: seed.role,
110
+ });
111
+ addedCredentials.push(stripPassword(cred));
112
+ }
113
+ catch (err) {
114
+ warnings.push({ op: 'add', label: seed.label, error: err?.message ?? String(err) });
115
+ }
51
116
  }
52
- throw err;
53
117
  }
118
+ // ── Build response ──────────────────────────────────────────────────────
119
+ const payload = {
120
+ updated: hasEnvPatch,
121
+ environment,
122
+ };
123
+ if (addedCredentials.length > 0)
124
+ payload.addedCredentials = addedCredentials;
125
+ if (updatedCredentials.length > 0)
126
+ payload.updatedCredentials = updatedCredentials;
127
+ if (removedCredentialIds.length > 0)
128
+ payload.removedCredentialIds = removedCredentialIds;
129
+ if (warnings.length > 0)
130
+ payload.credentialWarnings = warnings;
131
+ logger.toolComplete('update_environment', Date.now() - start);
132
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
54
133
  }
55
134
  catch (error) {
56
135
  logger.toolError('update_environment', error, Date.now() - start);
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
22
22
  import { config } from "./config/index.js";
23
23
  import { initTools, getTools, getTool } from "./tools/index.js";
24
24
  import { Logger, validateInput, createErrorResponse, toMCPError, handleConfigurationError, Telemetry, TelemetryEvents, } from "./utils/index.js";
25
+ import { MCPErrorCode, MCPError, } from "./types/index.js";
25
26
  // Logger and server are initialized lazily in main() to avoid triggering
26
27
  // config loading at module load time. If config validation fails (bad env vars),
27
28
  // the error is caught by main()'s try-catch instead of crashing before any
@@ -91,6 +92,16 @@ function registerHandlers() {
91
92
  requestLogger.warn(`Tool not found: ${name}`);
92
93
  throw new Error(`Unknown tool: ${name}`);
93
94
  }
95
+ // Deferred config validation (bead cma): if DEBUGGAI_API_KEY is missing,
96
+ // return a structured error MCP clients can surface in their UI — instead
97
+ // of letting the backend return a cryptic 401.
98
+ if (!config.api.key) {
99
+ const mcpError = new MCPError(MCPErrorCode.CONFIGURATION_ERROR, 'DEBUGGAI_API_KEY is not set. ' +
100
+ 'Configure it in your MCP server registration (e.g. `claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=<your-key> -- npx -y @debugg-ai/debugg-ai-mcp`). ' +
101
+ 'Get a key at https://debugg.ai.', { missingEnvVars: ['DEBUGGAI_API_KEY'] });
102
+ requestLogger.warn('Tool call blocked — DEBUGGAI_API_KEY missing', { tool: name });
103
+ return createErrorResponse(mcpError, name);
104
+ }
94
105
  try {
95
106
  const validatedInput = validateInput(tool.inputSchema, args, name);
96
107
  const context = {
@@ -141,9 +152,11 @@ async function main() {
141
152
  architecture: process.arch,
142
153
  pid: process.pid
143
154
  });
144
- // Validate required environment variables
155
+ // NOTE: DEBUGGAI_API_KEY validation is deferred to the first tool call so
156
+ // MCP clients see a proper initialize response + a structured tool error,
157
+ // rather than the subprocess dying with "Failed to reconnect" (bead cma).
145
158
  if (!config.api.key) {
146
- throw new Error('Missing required environment variable: DEBUGGAI_API_KEY');
159
+ logger.warn('DEBUGGAI_API_KEY is not set. Server will boot but every tool call will return a ConfigurationError until the env var is configured.');
147
160
  }
148
161
  // Initialize telemetry (PostHog when key is set, Noop otherwise)
149
162
  Telemetry.setDistinctId(config.api.key);
@@ -1,6 +1,10 @@
1
1
  import { CreateEnvironmentInputSchema } from '../types/index.js';
2
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 list_projects). Returns the created environment's uuid so you can reference it when running check_app_in_browser or creating credentials.`;
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.`;
4
8
  export function buildCreateEnvironmentTool() {
5
9
  return {
6
10
  name: 'create_environment',
@@ -1,24 +1,13 @@
1
1
  import { buildTestPageChangesTool, buildValidatedTestPageChangesTool } from './testPageChanges.js';
2
2
  import { buildTriggerCrawlTool, buildValidatedTriggerCrawlTool } from './triggerCrawl.js';
3
- import { buildListEnvironmentsTool, buildValidatedListEnvironmentsTool } from './listEnvironments.js';
4
- import { buildListCredentialsTool, buildValidatedListCredentialsTool } from './listCredentials.js';
5
- import { buildListProjectsTool, buildValidatedListProjectsTool } from './listProjects.js';
3
+ import { buildSearchProjectsTool, buildValidatedSearchProjectsTool } from './searchProjects.js';
4
+ import { buildSearchEnvironmentsTool, buildValidatedSearchEnvironmentsTool } from './searchEnvironments.js';
5
+ import { buildSearchExecutionsTool, buildValidatedSearchExecutionsTool } from './searchExecutions.js';
6
6
  import { buildCreateEnvironmentTool, buildValidatedCreateEnvironmentTool } from './createEnvironment.js';
7
- import { buildCreateCredentialTool, buildValidatedCreateCredentialTool } from './createCredential.js';
8
- import { buildGetEnvironmentTool, buildValidatedGetEnvironmentTool } from './getEnvironment.js';
9
7
  import { buildUpdateEnvironmentTool, buildValidatedUpdateEnvironmentTool } from './updateEnvironment.js';
10
8
  import { buildDeleteEnvironmentTool, buildValidatedDeleteEnvironmentTool } from './deleteEnvironment.js';
11
- import { buildGetCredentialTool, buildValidatedGetCredentialTool } from './getCredential.js';
12
- import { buildUpdateCredentialTool, buildValidatedUpdateCredentialTool } from './updateCredential.js';
13
- import { buildDeleteCredentialTool, buildValidatedDeleteCredentialTool } from './deleteCredential.js';
14
- import { buildGetProjectTool, buildValidatedGetProjectTool } from './getProject.js';
15
9
  import { buildUpdateProjectTool, buildValidatedUpdateProjectTool } from './updateProject.js';
16
10
  import { buildDeleteProjectTool, buildValidatedDeleteProjectTool } from './deleteProject.js';
17
- import { buildListExecutionsTool, buildValidatedListExecutionsTool } from './listExecutions.js';
18
- import { buildGetExecutionTool, buildValidatedGetExecutionTool } from './getExecution.js';
19
- import { buildCancelExecutionTool, buildValidatedCancelExecutionTool } from './cancelExecution.js';
20
- import { buildListTeamsTool, buildValidatedListTeamsTool } from './listTeams.js';
21
- import { buildListReposTool, buildValidatedListReposTool } from './listRepos.js';
22
11
  import { buildCreateProjectTool, buildValidatedCreateProjectTool } from './createProject.js';
23
12
  let _tools = null;
24
13
  let _validatedTools = null;
@@ -30,49 +19,27 @@ export function initTools(ctx) {
30
19
  const tools = [
31
20
  buildTestPageChangesTool(ctx),
32
21
  buildTriggerCrawlTool(ctx),
33
- buildListProjectsTool(),
34
- buildListEnvironmentsTool(),
35
- buildListCredentialsTool(),
22
+ buildSearchProjectsTool(),
23
+ buildSearchEnvironmentsTool(),
36
24
  buildCreateEnvironmentTool(),
37
- buildCreateCredentialTool(),
38
- buildGetEnvironmentTool(),
39
25
  buildUpdateEnvironmentTool(),
40
26
  buildDeleteEnvironmentTool(),
41
- buildGetCredentialTool(),
42
- buildUpdateCredentialTool(),
43
- buildDeleteCredentialTool(),
44
- buildGetProjectTool(),
45
27
  buildUpdateProjectTool(),
46
28
  buildDeleteProjectTool(),
47
- buildListExecutionsTool(),
48
- buildGetExecutionTool(),
49
- buildCancelExecutionTool(),
50
- buildListTeamsTool(),
51
- buildListReposTool(),
29
+ buildSearchExecutionsTool(),
52
30
  buildCreateProjectTool(),
53
31
  ];
54
32
  const validated = [
55
33
  buildValidatedTestPageChangesTool(ctx),
56
34
  buildValidatedTriggerCrawlTool(ctx),
57
- buildValidatedListProjectsTool(),
58
- buildValidatedListEnvironmentsTool(),
59
- buildValidatedListCredentialsTool(),
35
+ buildValidatedSearchProjectsTool(),
36
+ buildValidatedSearchEnvironmentsTool(),
60
37
  buildValidatedCreateEnvironmentTool(),
61
- buildValidatedCreateCredentialTool(),
62
- buildValidatedGetEnvironmentTool(),
63
38
  buildValidatedUpdateEnvironmentTool(),
64
39
  buildValidatedDeleteEnvironmentTool(),
65
- buildValidatedGetCredentialTool(),
66
- buildValidatedUpdateCredentialTool(),
67
- buildValidatedDeleteCredentialTool(),
68
- buildValidatedGetProjectTool(),
69
40
  buildValidatedUpdateProjectTool(),
70
41
  buildValidatedDeleteProjectTool(),
71
- buildValidatedListExecutionsTool(),
72
- buildValidatedGetExecutionTool(),
73
- buildValidatedCancelExecutionTool(),
74
- buildValidatedListTeamsTool(),
75
- buildValidatedListReposTool(),
42
+ buildValidatedSearchExecutionsTool(),
76
43
  buildValidatedCreateProjectTool(),
77
44
  ];
78
45
  _tools = tools;
@@ -0,0 +1,35 @@
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
+ }
@@ -0,0 +1,31 @@
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
+ }
@@ -0,0 +1,30 @@
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
+ }
@@ -29,21 +29,50 @@ export const TriggerCrawlInputSchema = z.object({
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();
32
- export const ListEnvironmentsInputSchema = z.object({
32
+ // ── New consolidated search schemas (bead ddq) ─────────────────────────────
33
+ // uuid and filter params are mutually exclusive: either look up one thing by
34
+ // uuid, or filter the collection. Mixing them is ambiguous.
35
+ export const SearchProjectsInputSchema = z.object({
36
+ uuid: z.string().uuid().optional(),
37
+ q: z.string().min(1).optional(),
38
+ page: z.number().int().min(1).optional(),
39
+ pageSize: z.number().int().min(1).optional(),
40
+ }).strict().refine((v) => !(v.uuid && (v.q !== undefined)), { message: 'Cannot combine uuid with filter params (q). Pass one or the other.' });
41
+ // projectUuid is a LOCATOR (required by the backend URL path for envs/creds), not a
42
+ // filter — so it's compatible with uuid mode. Only q and uuid are mutually exclusive.
43
+ export const SearchEnvironmentsInputSchema = z.object({
44
+ uuid: z.string().uuid().optional(),
33
45
  projectUuid: z.string().uuid().optional(),
34
46
  q: z.string().min(1).optional(),
35
47
  page: z.number().int().min(1).optional(),
36
48
  pageSize: z.number().int().min(1).optional(),
49
+ }).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).' });
50
+ export const SearchExecutionsInputSchema = z.object({
51
+ uuid: z.string().uuid().optional(),
52
+ projectUuid: z.string().uuid().optional(),
53
+ status: z.string().min(1).optional(),
54
+ page: z.number().int().min(1).optional(),
55
+ pageSize: z.number().int().min(1).optional(),
56
+ }).strict().refine((v) => !(v.uuid && (v.projectUuid || v.status)), { message: 'Cannot combine uuid with filter params (projectUuid, status).' });
57
+ const CredentialSeedSchema = z.object({
58
+ label: z.string().min(1, 'label is required'),
59
+ username: z.string().min(1, 'username is required'),
60
+ password: z.string().min(1, 'password is required'),
61
+ role: z.string().min(1).optional(),
37
62
  }).strict();
38
63
  export const CreateEnvironmentInputSchema = z.object({
39
64
  name: z.string().min(1, 'name is required'),
40
65
  url: z.string().url('url is required for standard environments'),
41
66
  description: z.string().optional(),
42
67
  projectUuid: z.string().uuid().optional(),
68
+ credentials: z.array(CredentialSeedSchema).optional(),
43
69
  }).strict();
44
- export const GetEnvironmentInputSchema = z.object({
70
+ const CredentialUpdateSchema = z.object({
45
71
  uuid: z.string().uuid(),
46
- projectUuid: z.string().uuid().optional(),
72
+ label: z.string().min(1).optional(),
73
+ username: z.string().min(1).optional(),
74
+ password: z.string().min(1).optional(),
75
+ role: z.string().min(1).optional(),
47
76
  }).strict();
48
77
  export const UpdateEnvironmentInputSchema = z.object({
49
78
  uuid: z.string().uuid(),
@@ -51,33 +80,14 @@ export const UpdateEnvironmentInputSchema = z.object({
51
80
  url: z.string().url().optional(),
52
81
  description: z.string().optional(),
53
82
  projectUuid: z.string().uuid().optional(),
83
+ addCredentials: z.array(CredentialSeedSchema).optional(),
84
+ updateCredentials: z.array(CredentialUpdateSchema).optional(),
85
+ removeCredentialIds: z.array(z.string().uuid()).optional(),
54
86
  }).strict();
55
87
  export const DeleteEnvironmentInputSchema = z.object({
56
88
  uuid: z.string().uuid(),
57
89
  projectUuid: z.string().uuid().optional(),
58
90
  }).strict();
59
- export const GetCredentialInputSchema = z.object({
60
- uuid: z.string().uuid(),
61
- environmentId: z.string().uuid(),
62
- projectUuid: z.string().uuid().optional(),
63
- }).strict();
64
- export const UpdateCredentialInputSchema = z.object({
65
- uuid: z.string().uuid(),
66
- environmentId: z.string().uuid(),
67
- label: z.string().min(1).optional(),
68
- username: z.string().min(1).optional(),
69
- password: z.string().min(1).optional(),
70
- role: z.string().min(1).optional(),
71
- projectUuid: z.string().uuid().optional(),
72
- }).strict();
73
- export const DeleteCredentialInputSchema = z.object({
74
- uuid: z.string().uuid(),
75
- environmentId: z.string().uuid(),
76
- projectUuid: z.string().uuid().optional(),
77
- }).strict();
78
- export const GetProjectInputSchema = z.object({
79
- uuid: z.string().uuid(),
80
- }).strict();
81
91
  export const UpdateProjectInputSchema = z.object({
82
92
  uuid: z.string().uuid(),
83
93
  name: z.string().min(1).optional(),
@@ -86,55 +96,26 @@ export const UpdateProjectInputSchema = z.object({
86
96
  export const DeleteProjectInputSchema = z.object({
87
97
  uuid: z.string().uuid(),
88
98
  }).strict();
89
- export const ListExecutionsInputSchema = z.object({
90
- status: z.string().min(1).optional(),
91
- projectUuid: z.string().uuid().optional(),
92
- page: z.number().int().min(1).optional(),
93
- pageSize: z.number().int().min(1).optional(),
94
- }).strict();
95
- export const GetExecutionInputSchema = z.object({
96
- uuid: z.string().uuid(),
97
- }).strict();
98
- export const CancelExecutionInputSchema = z.object({
99
- uuid: z.string().uuid(),
100
- }).strict();
101
- export const ListCredentialsInputSchema = z.object({
102
- environmentId: z.string().uuid().optional(),
103
- projectUuid: z.string().uuid().optional(),
104
- q: z.string().min(1).optional(),
105
- role: z.string().min(1).optional(),
106
- page: z.number().int().min(1).optional(),
107
- pageSize: z.number().int().min(1).optional(),
108
- }).strict();
109
- export const CreateCredentialInputSchema = z.object({
110
- environmentId: z.string().uuid(),
111
- label: z.string().min(1, 'label is required'),
112
- username: z.string().min(1, 'username is required'),
113
- password: z.string().min(1, 'password is required'),
114
- role: z.string().min(1).optional(),
115
- projectUuid: z.string().uuid().optional(),
116
- }).strict();
117
- export const ListProjectsInputSchema = z.object({
118
- q: z.string().min(1).optional(),
119
- page: z.number().int().min(1).optional(),
120
- pageSize: z.number().int().min(1).optional(),
121
- }).strict();
122
- export const ListTeamsInputSchema = z.object({
123
- q: z.string().min(1).optional(),
124
- page: z.number().int().min(1).optional(),
125
- pageSize: z.number().int().min(1).optional(),
126
- }).strict();
127
- export const ListReposInputSchema = z.object({
128
- q: z.string().min(1).optional(),
129
- page: z.number().int().min(1).optional(),
130
- pageSize: z.number().int().min(1).optional(),
131
- }).strict();
132
99
  export const CreateProjectInputSchema = z.object({
133
100
  name: z.string().min(1),
134
101
  platform: z.string().min(1),
135
- teamUuid: z.string().uuid(),
136
- repoUuid: z.string().uuid(),
137
- }).strict();
102
+ teamUuid: z.string().uuid().optional(),
103
+ teamName: z.string().min(1).optional(),
104
+ repoUuid: z.string().uuid().optional(),
105
+ repoName: z.string().min(1).optional(),
106
+ }).strict()
107
+ .refine((v) => !(v.teamUuid && v.teamName), {
108
+ message: 'Provide teamUuid OR teamName, not both.',
109
+ })
110
+ .refine((v) => !(v.repoUuid && v.repoName), {
111
+ message: 'Provide repoUuid OR repoName, not both.',
112
+ })
113
+ .refine((v) => v.teamUuid || v.teamName, {
114
+ message: 'Must provide teamUuid or teamName.',
115
+ })
116
+ .refine((v) => v.repoUuid || v.repoName, {
117
+ message: 'Must provide repoUuid or repoName.',
118
+ });
138
119
  /**
139
120
  * Error types
140
121
  */
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "1.0.63",
3
+ "version": "1.0.64",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "debugg-ai-mcp": "dist/index.js"
8
8
  },
9
+ "engines": {
10
+ "node": ">=20.20.0"
11
+ },
9
12
  "files": [
10
13
  "dist"
11
14
  ],
@@ -1,41 +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: 'cancelExecutionHandler' });
6
- function errorResponse(error, message, uuid) {
7
- return {
8
- content: [{ type: 'text', text: JSON.stringify({ error, message, uuid }, null, 2) }],
9
- isError: true,
10
- };
11
- }
12
- export async function cancelExecutionHandler(input, _context) {
13
- const start = Date.now();
14
- logger.toolStart('cancel_execution', { uuid: input.uuid });
15
- try {
16
- const client = new DebuggAIServerClient(config.api.key);
17
- await client.init();
18
- try {
19
- await client.workflows.cancelExecution(input.uuid);
20
- logger.toolComplete('cancel_execution', Date.now() - start);
21
- return {
22
- content: [{ type: 'text', text: JSON.stringify({ cancelled: true, uuid: input.uuid }, null, 2) }],
23
- };
24
- }
25
- catch (err) {
26
- const status = err?.statusCode ?? err?.response?.status;
27
- const detail = err?.responseData?.error ?? err?.message ?? '';
28
- if (status === 404) {
29
- return errorResponse('NotFound', `Execution ${input.uuid} not found.`, input.uuid);
30
- }
31
- if (status === 409 || /already|completed|cannot.?cancel/i.test(detail)) {
32
- return errorResponse('AlreadyCompleted', detail || `Execution ${input.uuid} cannot be cancelled.`, input.uuid);
33
- }
34
- throw err;
35
- }
36
- }
37
- catch (error) {
38
- logger.toolError('cancel_execution', error, Date.now() - start);
39
- throw handleExternalServiceError(error, 'DebuggAI', 'cancel_execution');
40
- }
41
- }
@@ -1,60 +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
- import { detectRepoName } from '../utils/gitContext.js';
6
- const logger = new Logger({ module: 'createCredentialHandler' });
7
- export async function createCredentialHandler(input, _context) {
8
- const start = Date.now();
9
- logger.toolStart('create_credential', {
10
- environmentId: input.environmentId,
11
- label: input.label,
12
- hasRole: !!input.role,
13
- projectUuid: input.projectUuid,
14
- });
15
- try {
16
- const client = new DebuggAIServerClient(config.api.key);
17
- await client.init();
18
- let projectUuid = input.projectUuid;
19
- if (!projectUuid) {
20
- const repoName = detectRepoName();
21
- if (!repoName) {
22
- return {
23
- content: [{ type: 'text', text: JSON.stringify({
24
- error: 'NoProjectResolved',
25
- message: 'No git repo detected and no projectUuid provided. Pass projectUuid (get it from list_projects) or invoke from a directory with a git origin.',
26
- }, null, 2) }],
27
- isError: true,
28
- };
29
- }
30
- const project = await client.findProjectByRepoName(repoName);
31
- if (!project) {
32
- return {
33
- content: [{ type: 'text', text: JSON.stringify({
34
- error: 'NoProjectResolved',
35
- message: `No DebuggAI project found for repo "${repoName}". Pass projectUuid explicitly.`,
36
- }, null, 2) }],
37
- isError: true,
38
- };
39
- }
40
- projectUuid = project.uuid;
41
- }
42
- const cred = await client.createCredential(projectUuid, input.environmentId, {
43
- label: input.label,
44
- username: input.username,
45
- password: input.password,
46
- role: input.role,
47
- });
48
- const payload = {
49
- created: true,
50
- projectUuid,
51
- credential: cred,
52
- };
53
- logger.toolComplete('create_credential', Date.now() - start);
54
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
55
- }
56
- catch (error) {
57
- logger.toolError('create_credential', error, Date.now() - start);
58
- throw handleExternalServiceError(error, 'DebuggAI', 'create_credential');
59
- }
60
- }