@debugg-ai/debugg-ai-mcp 1.0.56 → 1.0.58

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.
package/README.md CHANGED
@@ -86,6 +86,20 @@ Runs an AI browser agent against your app. The agent navigates, interacts, and r
86
86
  | `get_execution` | Full detail for a single execution including node-level state. |
87
87
  | `cancel_execution` | Cancel an in-flight execution. |
88
88
 
89
+ ### Pagination
90
+
91
+ Every `list_*` tool is paginated by default. Response shape:
92
+
93
+ ```json
94
+ {
95
+ "filter": { "...echoed query params..." },
96
+ "pageInfo": { "page": 1, "pageSize": 20, "totalCount": 47, "totalPages": 3, "hasMore": true },
97
+ "<items>": [ ... ]
98
+ }
99
+ ```
100
+
101
+ Pass optional `page` (1-indexed, default 1) and `pageSize` (default 20, max 200; oversized values are clamped) to any list tool. No tool ever silently truncates results.
102
+
89
103
  ### Security invariants
90
104
 
91
105
  - Passwords are write-only. They never appear in any response body from any tool.
@@ -108,6 +122,18 @@ npm run test:e2e # real end-to-end evals against the backend
108
122
 
109
123
  The eval suite spawns the built MCP server as a subprocess, exercises every tool against a real backend, and writes per-flow artifacts to `scripts/evals/artifacts/<timestamp>/`. See `scripts/evals/flows/` for the individual scenarios.
110
124
 
125
+ ### MCP registration: `debugg-ai-local` vs `debugg-ai`
126
+
127
+ This repo ships a `.mcp.json` that registers a **project-scoped** server named `debugg-ai-local` pointing at `node dist/index.js` — the freshly-built local code. It only activates when Claude Code's working directory is this repo.
128
+
129
+ Your other projects should use the **user-scoped** `debugg-ai` registration that pulls from the published npm package:
130
+
131
+ ```bash
132
+ npm run mcp:global # registers debugg-ai in ~/.claude.json to npx -y @debugg-ai/debugg-ai-mcp
133
+ ```
134
+
135
+ After editing code here, run `npm run mcp:local` (which just rebuilds) so the next invocation of `debugg-ai-local` picks up your changes.
136
+
111
137
  ## Links
112
138
 
113
139
  [Dashboard](https://app.debugg.ai) · [Docs](https://debugg.ai/docs) · [Issues](https://github.com/debugg-ai/debugg-ai-mcp/issues) · [Discord](https://debugg.ai/discord)
@@ -3,14 +3,17 @@ import { handleExternalServiceError } from '../utils/errors.js';
3
3
  import { DebuggAIServerClient } from '../services/index.js';
4
4
  import { config } from '../config/index.js';
5
5
  import { detectRepoName } from '../utils/gitContext.js';
6
+ import { toPaginationParams, makePageInfo } from '../utils/pagination.js';
6
7
  const logger = new Logger({ module: 'listCredentialsHandler' });
7
8
  export async function listCredentialsHandler(input, _context) {
8
9
  const start = Date.now();
10
+ const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
9
11
  logger.toolStart('list_credentials', {
10
12
  environmentId: input.environmentId,
11
13
  projectUuid: input.projectUuid,
12
14
  q: input.q,
13
15
  role: input.role,
16
+ ...pagination,
14
17
  });
15
18
  try {
16
19
  const client = new DebuggAIServerClient(config.api.key);
@@ -22,6 +25,7 @@ export async function listCredentialsHandler(input, _context) {
22
25
  const payload = {
23
26
  error: 'NoProjectResolved',
24
27
  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.',
28
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null),
25
29
  credentials: [],
26
30
  };
27
31
  logger.toolComplete('list_credentials', Date.now() - start);
@@ -32,6 +36,7 @@ export async function listCredentialsHandler(input, _context) {
32
36
  const payload = {
33
37
  error: 'NoProjectResolved',
34
38
  message: `No DebuggAI project found for repo "${repoName}". Pass projectUuid explicitly.`,
39
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null),
35
40
  credentials: [],
36
41
  };
37
42
  logger.toolComplete('list_credentials', Date.now() - start);
@@ -39,17 +44,34 @@ export async function listCredentialsHandler(input, _context) {
39
44
  }
40
45
  projectUuid = project.uuid;
41
46
  }
47
+ let pageInfo;
42
48
  let credentials = [];
43
49
  if (input.environmentId) {
44
- credentials = await client.listCredentialsForEnvironment(projectUuid, input.environmentId, input.q, input.role);
50
+ // Paginated path scoped to a single env.
51
+ const result = await client.listCredentialsPaginated(projectUuid, input.environmentId, pagination, input.q, input.role);
52
+ pageInfo = result.pageInfo;
53
+ credentials = result.credentials;
45
54
  }
46
55
  else {
47
- // No environment filter — iterate all envs for the project
56
+ // No env filter — iterate all envs and merge. Synthesize pageInfo from the full
57
+ // result (client-side paginate the merged list for consistent shape).
48
58
  const envs = await client.listEnvironmentsForProject(projectUuid);
59
+ const all = [];
49
60
  for (const env of envs) {
50
61
  const credsForEnv = await client.listCredentialsForEnvironment(projectUuid, env.uuid, input.q, input.role);
51
- credentials.push(...credsForEnv);
62
+ all.push(...credsForEnv);
52
63
  }
64
+ const offset = (pagination.page - 1) * pagination.pageSize;
65
+ credentials = all.slice(offset, offset + pagination.pageSize);
66
+ const totalCount = all.length;
67
+ const totalPages = totalCount === 0 ? 0 : Math.ceil(totalCount / pagination.pageSize);
68
+ pageInfo = {
69
+ page: pagination.page,
70
+ pageSize: pagination.pageSize,
71
+ totalCount,
72
+ totalPages,
73
+ hasMore: offset + credentials.length < totalCount,
74
+ };
53
75
  }
54
76
  const payload = {
55
77
  project: { uuid: projectUuid },
@@ -58,7 +80,7 @@ export async function listCredentialsHandler(input, _context) {
58
80
  q: input.q ?? null,
59
81
  role: input.role ?? null,
60
82
  },
61
- count: credentials.length,
83
+ pageInfo,
62
84
  credentials,
63
85
  };
64
86
  logger.toolComplete('list_credentials', Date.now() - start);
@@ -3,10 +3,12 @@ import { handleExternalServiceError } from '../utils/errors.js';
3
3
  import { DebuggAIServerClient } from '../services/index.js';
4
4
  import { config } from '../config/index.js';
5
5
  import { detectRepoName } from '../utils/gitContext.js';
6
+ import { toPaginationParams, makePageInfo } from '../utils/pagination.js';
6
7
  const logger = new Logger({ module: 'listEnvironmentsHandler' });
7
8
  export async function listEnvironmentsHandler(input, _context) {
8
9
  const start = Date.now();
9
- logger.toolStart('list_environments', { projectUuid: input.projectUuid, q: input.q });
10
+ const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
11
+ logger.toolStart('list_environments', { projectUuid: input.projectUuid, q: input.q, ...pagination });
10
12
  try {
11
13
  const client = new DebuggAIServerClient(config.api.key);
12
14
  await client.init();
@@ -19,6 +21,7 @@ export async function listEnvironmentsHandler(input, _context) {
19
21
  const payload = {
20
22
  error: 'NoProjectResolved',
21
23
  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.',
24
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null),
22
25
  environments: [],
23
26
  };
24
27
  logger.toolComplete('list_environments', Date.now() - start);
@@ -29,6 +32,7 @@ export async function listEnvironmentsHandler(input, _context) {
29
32
  const payload = {
30
33
  error: 'NoProjectResolved',
31
34
  message: `No DebuggAI project found for repo "${repoName}". Pass projectUuid explicitly or call list_projects to discover.`,
35
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null),
32
36
  environments: [],
33
37
  };
34
38
  logger.toolComplete('list_environments', Date.now() - start);
@@ -38,15 +42,15 @@ export async function listEnvironmentsHandler(input, _context) {
38
42
  projectName = project.name;
39
43
  projectRepoName = project.repo?.name ?? repoName;
40
44
  }
41
- const environments = await client.listEnvironmentsForProject(projectUuid, input.q);
45
+ const { pageInfo, environments } = await client.listEnvironmentsPaginated(projectUuid, pagination, input.q);
42
46
  const payload = {
43
47
  project: {
44
48
  uuid: projectUuid,
45
49
  name: projectName,
46
50
  repoName: projectRepoName,
47
51
  },
48
- query: input.q ?? null,
49
- count: environments.length,
52
+ filter: { q: input.q ?? null },
53
+ pageInfo,
50
54
  environments,
51
55
  };
52
56
  logger.toolComplete('list_environments', Date.now() - start);
@@ -2,23 +2,23 @@ import { Logger } from '../utils/logger.js';
2
2
  import { handleExternalServiceError } from '../utils/errors.js';
3
3
  import { DebuggAIServerClient } from '../services/index.js';
4
4
  import { config } from '../config/index.js';
5
+ import { toPaginationParams } from '../utils/pagination.js';
5
6
  const logger = new Logger({ module: 'listExecutionsHandler' });
6
7
  export async function listExecutionsHandler(input, _context) {
7
8
  const start = Date.now();
8
- logger.toolStart('list_executions', { status: input.status, limit: input.limit });
9
+ const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
10
+ logger.toolStart('list_executions', { status: input.status, ...pagination });
9
11
  try {
10
12
  const client = new DebuggAIServerClient(config.api.key);
11
13
  await client.init();
12
- const { count, executions } = await client.workflows.listExecutions({
14
+ const { pageInfo, executions } = await client.workflows.listExecutions({
13
15
  status: input.status,
14
- limit: input.limit,
16
+ page: pagination.page,
17
+ pageSize: pagination.pageSize,
15
18
  });
16
19
  const payload = {
17
- filter: {
18
- status: input.status ?? null,
19
- limit: input.limit ?? null,
20
- },
21
- count,
20
+ filter: { status: input.status ?? null },
21
+ pageInfo,
22
22
  executions,
23
23
  };
24
24
  logger.toolComplete('list_executions', Date.now() - start);
@@ -2,17 +2,19 @@ import { Logger } from '../utils/logger.js';
2
2
  import { handleExternalServiceError } from '../utils/errors.js';
3
3
  import { DebuggAIServerClient } from '../services/index.js';
4
4
  import { config } from '../config/index.js';
5
+ import { toPaginationParams } from '../utils/pagination.js';
5
6
  const logger = new Logger({ module: 'listProjectsHandler' });
6
7
  export async function listProjectsHandler(input, _context) {
7
8
  const start = Date.now();
8
- logger.toolStart('list_projects', { q: input.q });
9
+ const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
10
+ logger.toolStart('list_projects', { q: input.q, ...pagination });
9
11
  try {
10
12
  const client = new DebuggAIServerClient(config.api.key);
11
13
  await client.init();
12
- const projects = await client.listProjects(input.q);
14
+ const { pageInfo, projects } = await client.listProjects(pagination, input.q);
13
15
  const payload = {
14
- query: input.q ?? null,
15
- count: projects.length,
16
+ filter: { q: input.q ?? null },
17
+ pageInfo,
16
18
  projects: projects.map(p => ({
17
19
  uuid: p.uuid,
18
20
  name: p.name,
@@ -116,24 +116,34 @@ export class DebuggAIServerClient {
116
116
  await this.tx.delete(`api/v1/projects/${uuid}/`);
117
117
  }
118
118
  /**
119
- * List projects accessible to the current API key.
119
+ * List projects accessible to the current API key. Paginated.
120
120
  * Optional q filters by project name / repo name server-side (backend `?search=`).
121
- * Returns the first page only; pagination can be added if needed.
122
121
  */
123
- async listProjects(q) {
122
+ async listProjects(pagination, q) {
124
123
  if (!this.tx)
125
124
  throw new Error('Client not initialized — call init() first');
126
- const params = q ? { search: q } : undefined;
125
+ const { makePageInfo } = await import('../utils/pagination.js');
126
+ const params = { page: pagination.page, pageSize: pagination.pageSize };
127
+ if (q)
128
+ params.search = q;
127
129
  const response = await this.tx.get('api/v1/projects/', params);
128
- return response?.results ?? [];
130
+ return {
131
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, response?.count ?? 0, response?.next),
132
+ projects: response?.results ?? [],
133
+ };
129
134
  }
130
135
  /**
131
- * List environments for a project. Optional q filters by name via backend ?search=.
136
+ * List environments for a project. Paginated.
137
+ * Optional q filters by name via backend ?search=.
138
+ * The bare-array variant (no pagination) is still used internally by
139
+ * list_credentials when iterating across all envs.
132
140
  */
133
141
  async listEnvironmentsForProject(projectUuid, q) {
134
142
  if (!this.tx)
135
143
  throw new Error('Client not initialized — call init() first');
136
- const params = q ? { search: q } : undefined;
144
+ const params = { pageSize: 200 };
145
+ if (q)
146
+ params.search = q;
137
147
  const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/`, params);
138
148
  return (response?.results ?? []).map((e) => ({
139
149
  uuid: e.uuid,
@@ -142,6 +152,24 @@ export class DebuggAIServerClient {
142
152
  isActive: e.isActive,
143
153
  }));
144
154
  }
155
+ async listEnvironmentsPaginated(projectUuid, pagination, q) {
156
+ if (!this.tx)
157
+ throw new Error('Client not initialized — call init() first');
158
+ const { makePageInfo } = await import('../utils/pagination.js');
159
+ const params = { page: pagination.page, pageSize: pagination.pageSize };
160
+ if (q)
161
+ params.search = q;
162
+ const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/`, params);
163
+ return {
164
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, response?.count ?? 0, response?.next),
165
+ environments: (response?.results ?? []).map((e) => ({
166
+ uuid: e.uuid,
167
+ name: e.name,
168
+ url: e.url || e.activeUrl || '',
169
+ isActive: e.isActive,
170
+ })),
171
+ };
172
+ }
145
173
  /**
146
174
  * Create a new environment under a project.
147
175
  * Backend requires `name`. Other fields optional.
@@ -213,13 +241,44 @@ export class DebuggAIServerClient {
213
241
  };
214
242
  }
215
243
  /**
216
- * List credentials for a specific environment. q filters label/username client-side.
217
- * role filters for exact match.
244
+ * List credentials for a specific environment. Unpaginated (fetches up to
245
+ * backend max pageSize). q filters label/username client-side (backend
246
+ * ?search= is inconsistent on this endpoint); role filters server-side.
247
+ * Used internally by list_credentials when iterating across envs.
218
248
  */
219
249
  async listCredentialsForEnvironment(projectUuid, envUuid, q, role) {
220
250
  if (!this.tx)
221
251
  throw new Error('Client not initialized — call init() first');
222
- const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/${envUuid}/credentials/`);
252
+ const params = { pageSize: 200 };
253
+ if (role)
254
+ params.role = role;
255
+ const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/${envUuid}/credentials/`, params);
256
+ let creds = (response?.results ?? [])
257
+ .filter((c) => c.isActive)
258
+ .map((c) => ({
259
+ uuid: c.uuid,
260
+ label: c.label || c.username,
261
+ username: c.username,
262
+ role: c.role,
263
+ environmentUuid: envUuid,
264
+ }));
265
+ if (q) {
266
+ const needle = q.toLowerCase();
267
+ creds = creds.filter(c => c.label.toLowerCase().includes(needle) ||
268
+ c.username.toLowerCase().includes(needle));
269
+ }
270
+ return creds;
271
+ }
272
+ async listCredentialsPaginated(projectUuid, envUuid, pagination, q, role) {
273
+ if (!this.tx)
274
+ throw new Error('Client not initialized — call init() first');
275
+ const { makePageInfo } = await import('../utils/pagination.js');
276
+ const params = { page: pagination.page, pageSize: pagination.pageSize };
277
+ // Backend ?role= filter is currently ignored (bead hpo) — pass it anyway for future fix-forward,
278
+ // but re-apply the filter client-side so behavior is correct today.
279
+ if (role)
280
+ params.role = role;
281
+ const response = await this.tx.get(`api/v1/projects/${projectUuid}/environments/${envUuid}/credentials/`, params);
223
282
  let creds = (response?.results ?? [])
224
283
  .filter((c) => c.isActive)
225
284
  .map((c) => ({
@@ -237,7 +296,10 @@ export class DebuggAIServerClient {
237
296
  if (role) {
238
297
  creds = creds.filter(c => c.role === role);
239
298
  }
240
- return creds;
299
+ return {
300
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, response?.count ?? 0, response?.next),
301
+ credentials: creds,
302
+ };
241
303
  }
242
304
  /**
243
305
  * Create a credential on an environment. password is write-only — never echoed back.
@@ -48,14 +48,13 @@ export const createWorkflowsService = (tx) => {
48
48
  return response;
49
49
  },
50
50
  async listExecutions(filters) {
51
- const params = {};
51
+ const { makePageInfo } = await import('../utils/pagination.js');
52
+ const params = { page: filters.page, pageSize: filters.pageSize };
52
53
  if (filters.status)
53
54
  params.status = filters.status;
54
- if (filters.limit)
55
- params.pageSize = filters.limit; // backend uses page_size (snake_case via transport)
56
55
  const response = await tx.get('api/v1/workflows/executions/', params);
57
56
  return {
58
- count: response?.count ?? 0,
57
+ pageInfo: makePageInfo(filters.page, filters.pageSize, response?.count ?? 0, response?.next),
59
58
  executions: (response?.results ?? []).map((e) => ({
60
59
  uuid: e.uuid,
61
60
  workflow: e.workflow,
@@ -1,6 +1,6 @@
1
1
  import { ListCredentialsInputSchema } from '../types/index.js';
2
2
  import { listCredentialsHandler } from '../handlers/listCredentialsHandler.js';
3
- const DESCRIPTION = `List credentials for a DebuggAI project. By default targets the project resolved from the current git repo. Optional environmentId filters to a single environment. Optional q filters by label or username. Optional role filters for exact match (server-side). Optional projectUuid overrides the auto-detected project. Never returns passwords or secret values.`;
3
+ const DESCRIPTION = `List credentials for a DebuggAI project. Paginated when scoped to a single environment (pass environmentId); otherwise iterates all envs and returns everything with pageInfo reflecting the total. Default pageSize 20, max 200. Optional q filters label/username (client-side); role filters server-side. Never returns passwords.`;
4
4
  export function buildListCredentialsTool() {
5
5
  return {
6
6
  name: 'list_credentials',
@@ -9,22 +9,12 @@ export function buildListCredentialsTool() {
9
9
  inputSchema: {
10
10
  type: 'object',
11
11
  properties: {
12
- environmentId: {
13
- type: 'string',
14
- description: 'Optional: filter credentials to a single environment UUID.',
15
- },
16
- projectUuid: {
17
- type: 'string',
18
- description: 'Optional: UUID of the target project. Defaults to the project resolved from the current git repo.',
19
- },
20
- q: {
21
- type: 'string',
22
- description: 'Optional: filter by label or username (case-insensitive substring).',
23
- },
24
- role: {
25
- type: 'string',
26
- description: 'Optional: filter by exact role match (e.g. "admin", "guest").',
27
- },
12
+ environmentId: { type: 'string', description: 'Optional: filter to a single environment. Required for true pagination.' },
13
+ projectUuid: { type: 'string', description: 'Optional: UUID of the target project. Defaults to git-auto-detect.' },
14
+ q: { type: 'string', description: 'Optional: filter by label or username.' },
15
+ role: { type: 'string', description: 'Optional: filter by exact role match.' },
16
+ page: { type: 'number', description: 'Optional: 1-indexed page number. Default 1.', minimum: 1 },
17
+ pageSize: { type: 'number', description: 'Optional: items per page. Default 20, max 200.', minimum: 1, maximum: 200 },
28
18
  },
29
19
  additionalProperties: false,
30
20
  },
@@ -1,6 +1,6 @@
1
1
  import { ListEnvironmentsInputSchema } from '../types/index.js';
2
2
  import { listEnvironmentsHandler } from '../handlers/listEnvironmentsHandler.js';
3
- const DESCRIPTION = `List environments for a DebuggAI project. By default targets the project resolved from the current git repo; pass projectUuid to target a different project (get UUIDs via list_projects). Optional q filters by environment name via the backend search. Returns each environment's uuid, name, url, and isActive flag.`;
3
+ const DESCRIPTION = `List environments for a DebuggAI project. Paginated — every response includes pageInfo {page, pageSize, totalCount, totalPages, hasMore}; default pageSize 20, max 200. By default targets the project resolved from the current git repo; pass projectUuid to target a different project. Optional q filters by environment name via backend search.`;
4
4
  export function buildListEnvironmentsTool() {
5
5
  return {
6
6
  name: 'list_environments',
@@ -9,14 +9,10 @@ export function buildListEnvironmentsTool() {
9
9
  inputSchema: {
10
10
  type: 'object',
11
11
  properties: {
12
- projectUuid: {
13
- type: 'string',
14
- description: 'Optional: UUID of the project to query. Defaults to the project resolved from the current git repo.',
15
- },
16
- q: {
17
- type: 'string',
18
- description: 'Optional: filter environments by name (server-side search).',
19
- },
12
+ projectUuid: { type: 'string', description: 'Optional: UUID of the project to query. Defaults to git-auto-detect.' },
13
+ q: { type: 'string', description: 'Optional: filter by environment name.' },
14
+ page: { type: 'number', description: 'Optional: 1-indexed page number. Default 1.', minimum: 1 },
15
+ pageSize: { type: 'number', description: 'Optional: items per page. Default 20, max 200.', minimum: 1, maximum: 200 },
20
16
  },
21
17
  additionalProperties: false,
22
18
  },
@@ -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. Optional status filter (e.g. "completed", "running", "failed", "cancelled") passed to backend ?status=. Optional limit caps the page size (default 10). Returns {count, executions:[{uuid,workflow,status,mode,source,outcome,startedAt,completedAt,durationMs,timestamp}]} — summary shape only. 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") passed to backend ?status=. 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,7 +10,8 @@ export function buildListExecutionsTool() {
10
10
  type: 'object',
11
11
  properties: {
12
12
  status: { type: 'string', description: 'Optional: filter by execution status.' },
13
- limit: { type: 'number', description: 'Optional: page size cap.', minimum: 1, maximum: 200 },
13
+ page: { type: 'number', description: 'Optional: 1-indexed page number. Default 1.', minimum: 1 },
14
+ pageSize: { type: 'number', description: 'Optional: items per page. Default 20, max 200.', minimum: 1, maximum: 200 },
14
15
  },
15
16
  additionalProperties: false,
16
17
  },
@@ -1,6 +1,6 @@
1
1
  import { ListProjectsInputSchema } from '../types/index.js';
2
2
  import { listProjectsHandler } from '../handlers/listProjectsHandler.js';
3
- const DESCRIPTION = `List DebuggAI projects accessible to the current API key. Optional "q" input filters by project name or repo name (passed through to the backend search). Returns uuid, name, slug, and repo for each project. Use this when you don't know which project to target or when the current git repo doesn't resolve to a DebuggAI project.`;
3
+ const DESCRIPTION = `List DebuggAI projects accessible to the current API key. Paginated — every response includes pageInfo {page, pageSize, totalCount, totalPages, hasMore}; default pageSize 20, max 200. Optional "q" input filters by project name or repo name via backend search. Use this when you don't know which project to target or when the current git repo doesn't resolve to a DebuggAI project.`;
4
4
  export function buildListProjectsTool() {
5
5
  return {
6
6
  name: 'list_projects',
@@ -9,10 +9,9 @@ export function buildListProjectsTool() {
9
9
  inputSchema: {
10
10
  type: 'object',
11
11
  properties: {
12
- q: {
13
- type: 'string',
14
- description: 'Optional search query to filter projects by name or repo name.',
15
- },
12
+ q: { type: 'string', description: 'Optional: search by name or repo name.' },
13
+ page: { type: 'number', description: 'Optional: 1-indexed page number. Default 1.', minimum: 1 },
14
+ pageSize: { type: 'number', description: 'Optional: items per page. Default 20, max 200.', minimum: 1, maximum: 200 },
16
15
  },
17
16
  additionalProperties: false,
18
17
  },
@@ -20,6 +20,8 @@ export const TestPageChangesInputSchema = z.object({
20
20
  export const ListEnvironmentsInputSchema = z.object({
21
21
  projectUuid: z.string().uuid().optional(),
22
22
  q: z.string().min(1).optional(),
23
+ page: z.number().int().min(1).optional(),
24
+ pageSize: z.number().int().min(1).optional(),
23
25
  }).strict();
24
26
  export const CreateEnvironmentInputSchema = z.object({
25
27
  name: z.string().min(1, 'name is required'),
@@ -74,7 +76,8 @@ export const DeleteProjectInputSchema = z.object({
74
76
  }).strict();
75
77
  export const ListExecutionsInputSchema = z.object({
76
78
  status: z.string().min(1).optional(),
77
- limit: z.number().int().min(1).max(200).optional(),
79
+ page: z.number().int().min(1).optional(),
80
+ pageSize: z.number().int().min(1).optional(),
78
81
  }).strict();
79
82
  export const GetExecutionInputSchema = z.object({
80
83
  uuid: z.string().uuid(),
@@ -87,6 +90,8 @@ export const ListCredentialsInputSchema = z.object({
87
90
  projectUuid: z.string().uuid().optional(),
88
91
  q: z.string().min(1).optional(),
89
92
  role: z.string().min(1).optional(),
93
+ page: z.number().int().min(1).optional(),
94
+ pageSize: z.number().int().min(1).optional(),
90
95
  }).strict();
91
96
  export const CreateCredentialInputSchema = z.object({
92
97
  environmentId: z.string().uuid(),
@@ -98,6 +103,8 @@ export const CreateCredentialInputSchema = z.object({
98
103
  }).strict();
99
104
  export const ListProjectsInputSchema = z.object({
100
105
  q: z.string().min(1).optional(),
106
+ page: z.number().int().min(1).optional(),
107
+ pageSize: z.number().int().min(1).optional(),
101
108
  }).strict();
102
109
  /**
103
110
  * Error types
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared pagination helpers for list_* tools. Every list tool must expose
3
+ * {page?, pageSize?} inputs and return a consistent pageInfo object so
4
+ * callers never silently get truncated results.
5
+ */
6
+ export const DEFAULT_PAGE_SIZE = 20;
7
+ export const MAX_PAGE_SIZE = 200;
8
+ /**
9
+ * Translate caller inputs into safe backend query params.
10
+ * Defaults page=1, pageSize=20. Clamps pageSize to [1, MAX_PAGE_SIZE=200].
11
+ * Returns snake_case keys matching the backend's `?page=&page_size=` convention.
12
+ */
13
+ export function toPaginationParams(input) {
14
+ const page = Math.max(1, Math.floor(input.page ?? 1));
15
+ const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, Math.floor(input.pageSize ?? DEFAULT_PAGE_SIZE)));
16
+ return { page, pageSize };
17
+ }
18
+ /**
19
+ * Build the pageInfo block for the MCP response from the backend's
20
+ * DRF-style list envelope (count + next + previous).
21
+ */
22
+ export function makePageInfo(page, pageSize, count, next) {
23
+ const totalCount = count ?? 0;
24
+ const totalPages = totalCount === 0 ? 0 : Math.ceil(totalCount / pageSize);
25
+ return {
26
+ page,
27
+ pageSize,
28
+ totalCount,
29
+ totalPages,
30
+ hasMore: !!next,
31
+ };
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,8 +26,8 @@
26
26
  "version:major": "npm version major --no-git-tag-version",
27
27
  "publish:check": "npm pack --dry-run",
28
28
  "prepublishOnly": "npm test && npm run build",
29
- "mcp:local": "npm run build && claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -e POSTHOG_API_KEY=phc_4h2Yov2P0Vc9UMqfKf3dYKSQ6THOs7N6LZR0VKYopZN -- node /Users/qosha/Repos/debugg-ai/debugg-ai-mcp/dist/index.js",
30
- "mcp:npm": "claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -e POSTHOG_API_KEY=phc_4h2Yov2P0Vc9UMqfKf3dYKSQ6THOs7N6LZR0VKYopZN -- npx -y @debugg-ai/debugg-ai-mcp"
29
+ "mcp:local": "npm run build && echo 'Local MCP built. Claude Code auto-registers it as debugg-ai-local from .mcp.json when cwd is this repo.'",
30
+ "mcp:global": "claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -e POSTHOG_API_KEY=phc_4h2Yov2P0Vc9UMqfKf3dYKSQ6THOs7N6LZR0VKYopZN -- npx -y @debugg-ai/debugg-ai-mcp"
31
31
  },
32
32
  "keywords": [
33
33
  "debugg",