@debugg-ai/debugg-ai-mcp 1.0.59 → 1.0.61

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.
@@ -7,17 +7,21 @@ const logger = new Logger({ module: 'listExecutionsHandler' });
7
7
  export async function listExecutionsHandler(input, _context) {
8
8
  const start = Date.now();
9
9
  const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
10
- logger.toolStart('list_executions', { status: input.status, ...pagination });
10
+ logger.toolStart('list_executions', { status: input.status, projectUuid: input.projectUuid, ...pagination });
11
11
  try {
12
12
  const client = new DebuggAIServerClient(config.api.key);
13
13
  await client.init();
14
14
  const { pageInfo, executions } = await client.workflows.listExecutions({
15
15
  status: input.status,
16
+ projectId: input.projectUuid,
16
17
  page: pagination.page,
17
18
  pageSize: pagination.pageSize,
18
19
  });
19
20
  const payload = {
20
- filter: { status: input.status ?? null },
21
+ filter: {
22
+ status: input.status ?? null,
23
+ projectUuid: input.projectUuid ?? null,
24
+ },
21
25
  pageInfo,
22
26
  executions,
23
27
  };
@@ -10,7 +10,6 @@ import { fetchImageAsBase64, imageContentBlock } from '../utils/imageUtils.js';
10
10
  import { DebuggAIServerClient } from '../services/index.js';
11
11
  import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
12
12
  import { detectRepoName } from '../utils/gitContext.js';
13
- import { tunnelManager } from '../services/ngrok/tunnelManager.js';
14
13
  const logger = new Logger({ module: 'testPageChangesHandler' });
15
14
  // Cache the template UUID and project UUIDs within a server session to avoid re-fetching
16
15
  let cachedTemplateUuid = null;
@@ -376,11 +375,11 @@ async function testPageChangesHandlerInner(input, context, progressCallback) {
376
375
  }
377
376
  finally {
378
377
  process.stdin.removeListener('close', onStdinClose);
379
- // Always tear down the tunnel when the request completes.
380
- if (ctx.tunnelId) {
381
- tunnelManager.stopTunnel(ctx.tunnelId).catch(err => logger.warn(`Failed to stop tunnel ${ctx.tunnelId}: ${err}`));
382
- }
383
- else if (keyId) {
378
+ // Tunnel is intentionally NOT torn down here tunnelManager reuses it on
379
+ // subsequent calls to the same port and auto-shutoffs after 55 min idle.
380
+ // Process-exit cleanup happens via stopAllTunnels() in the SIGINT/SIGTERM
381
+ // handlers in index.ts.
382
+ if (!ctx.tunnelId && keyId) {
384
383
  // Provisioned a key but tunnel creation failed — revoke the orphaned key.
385
384
  client.revokeNgrokKey(keyId).catch(err => logger.warn(`Failed to revoke unused ngrok key ${keyId}: ${err}`));
386
385
  }
package/dist/index.js CHANGED
@@ -191,16 +191,15 @@ function safeLog(level, message, meta) {
191
191
  /**
192
192
  * Handle graceful shutdown
193
193
  */
194
- process.on('SIGINT', async () => {
195
- safeLog('info', 'Received SIGINT, shutting down gracefully');
194
+ async function gracefulShutdown(signal) {
195
+ safeLog('info', `Received ${signal}, shutting down gracefully`);
196
+ const { tunnelManager } = await import('./services/ngrok/tunnelManager.js');
197
+ await tunnelManager.stopAllTunnels().catch((err) => safeLog('warn', 'stopAllTunnels failed during shutdown', { error: String(err) }));
196
198
  await Telemetry.shutdown();
197
199
  process.exit(0);
198
- });
199
- process.on('SIGTERM', async () => {
200
- safeLog('info', 'Received SIGTERM, shutting down gracefully');
201
- await Telemetry.shutdown();
202
- process.exit(0);
203
- });
200
+ }
201
+ process.on('SIGINT', () => { gracefulShutdown('SIGINT'); });
202
+ process.on('SIGTERM', () => { gracefulShutdown('SIGTERM'); });
204
203
  process.on('unhandledRejection', (reason) => {
205
204
  safeLog('error', 'Unhandled promise rejection', {
206
205
  error: reason instanceof Error ? reason.message : String(reason),
@@ -297,18 +297,20 @@ export class DebuggAIServerClient {
297
297
  }
298
298
  /**
299
299
  * List credentials for a specific environment. Unpaginated (fetches up to
300
- * backend max pageSize). q filters label/username client-side (backend
301
- * ?search= is inconsistent on this endpoint); role filters server-side.
302
- * Used internally by list_credentials when iterating across envs.
300
+ * backend max pageSize). q filters label/username server-side via ?search=;
301
+ * role filters server-side. Used internally by list_credentials when
302
+ * iterating across envs.
303
303
  */
304
304
  async listCredentialsForEnvironment(projectUuid, envUuid, q, role) {
305
305
  if (!this.tx)
306
306
  throw new Error('Client not initialized — call init() first');
307
307
  const params = { pageSize: 200 };
308
+ if (q)
309
+ params.search = q;
308
310
  if (role)
309
311
  params.role = role;
310
312
  const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/${envUuid}/credentials/`, params);
311
- let creds = (response?.results ?? [])
313
+ return (response?.results ?? [])
312
314
  .filter((c) => c.isActive)
313
315
  .map((c) => ({
314
316
  uuid: c.uuid,
@@ -317,24 +319,18 @@ export class DebuggAIServerClient {
317
319
  role: c.role,
318
320
  environmentUuid: envUuid,
319
321
  }));
320
- if (q) {
321
- const needle = q.toLowerCase();
322
- creds = creds.filter(c => c.label.toLowerCase().includes(needle) ||
323
- c.username.toLowerCase().includes(needle));
324
- }
325
- return creds;
326
322
  }
327
323
  async listCredentialsPaginated(projectUuid, envUuid, pagination, q, role) {
328
324
  if (!this.tx)
329
325
  throw new Error('Client not initialized — call init() first');
330
326
  const { makePageInfo } = await import('../utils/pagination.js');
331
327
  const params = { page: pagination.page, pageSize: pagination.pageSize };
332
- // Backend ?role= filter is currently ignored (bead hpo) — pass it anyway for future fix-forward,
333
- // but re-apply the filter client-side so behavior is correct today.
328
+ if (q)
329
+ params.search = q;
334
330
  if (role)
335
331
  params.role = role;
336
332
  const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/${envUuid}/credentials/`, params);
337
- let creds = (response?.results ?? [])
333
+ const creds = (response?.results ?? [])
338
334
  .filter((c) => c.isActive)
339
335
  .map((c) => ({
340
336
  uuid: c.uuid,
@@ -343,14 +339,6 @@ export class DebuggAIServerClient {
343
339
  role: c.role,
344
340
  environmentUuid: envUuid,
345
341
  }));
346
- if (q) {
347
- const needle = q.toLowerCase();
348
- creds = creds.filter(c => c.label.toLowerCase().includes(needle) ||
349
- c.username.toLowerCase().includes(needle));
350
- }
351
- if (role) {
352
- creds = creds.filter(c => c.role === role);
353
- }
354
342
  return {
355
343
  pageInfo: makePageInfo(pagination.page, pagination.pageSize, response?.count ?? 0, response?.next),
356
344
  credentials: creds,
@@ -52,6 +52,8 @@ export const createWorkflowsService = (tx) => {
52
52
  const params = { page: filters.page, pageSize: filters.pageSize };
53
53
  if (filters.status)
54
54
  params.status = filters.status;
55
+ if (filters.projectId)
56
+ params.projectId = filters.projectId;
55
57
  const response = await tx.get('api/v1/workflows/executions/', params);
56
58
  return {
57
59
  pageInfo: makePageInfo(filters.page, filters.pageSize, response?.count ?? 0, response?.next),
@@ -1,6 +1,6 @@
1
1
  import { ListExecutionsInputSchema } from '../types/index.js';
2
2
  import { listExecutionsHandler } from '../handlers/listExecutionsHandler.js';
3
- const DESCRIPTION = `List workflow execution history. Paginated — every response includes pageInfo {page, pageSize, totalCount, totalPages, hasMore}; default pageSize 20, max 200. Optional status filter (e.g. "completed", "running", "failed", "cancelled") passed to backend ?status=. Returns summary shape; use get_execution for full detail on a single uuid.`;
3
+ const DESCRIPTION = `List workflow execution history. Paginated — every response includes pageInfo {page, pageSize, totalCount, totalPages, hasMore}; default pageSize 20, max 200. Optional status filter (e.g. "completed", "running", "failed", "cancelled"). Optional projectUuid scopes to a single project. Returns summary shape; use get_execution for full detail on a single uuid.`;
4
4
  export function buildListExecutionsTool() {
5
5
  return {
6
6
  name: 'list_executions',
@@ -10,6 +10,7 @@ export function buildListExecutionsTool() {
10
10
  type: 'object',
11
11
  properties: {
12
12
  status: { type: 'string', description: 'Optional: filter by execution status.' },
13
+ projectUuid: { type: 'string', description: 'Optional: scope results to a specific project.' },
13
14
  page: { type: 'number', description: 'Optional: 1-indexed page number. Default 1.', minimum: 1 },
14
15
  pageSize: { type: 'number', description: 'Optional: items per page. Default 20, max 200.', minimum: 1, maximum: 200 },
15
16
  },
@@ -76,6 +76,7 @@ export const DeleteProjectInputSchema = z.object({
76
76
  }).strict();
77
77
  export const ListExecutionsInputSchema = z.object({
78
78
  status: z.string().min(1).optional(),
79
+ projectUuid: z.string().uuid().optional(),
79
80
  page: z.number().int().min(1).optional(),
80
81
  pageSize: z.number().int().min(1).optional(),
81
82
  }).strict();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {