@gaffer-sh/mcp 0.2.1 → 0.4.0
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/dist/index.js +1028 -183
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,12 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
|
|
7
7
|
// src/api-client.ts
|
|
8
8
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
9
|
+
var MAX_RETRIES = 3;
|
|
10
|
+
var INITIAL_RETRY_DELAY_MS = 1e3;
|
|
11
|
+
var RETRYABLE_STATUS_CODES = [401, 429, 500, 502, 503, 504];
|
|
12
|
+
function sleep(ms) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
9
15
|
function detectTokenType(token) {
|
|
10
16
|
if (token.startsWith("gaf_")) {
|
|
11
17
|
return "user";
|
|
@@ -42,7 +48,7 @@ var GafferApiClient = class _GafferApiClient {
|
|
|
42
48
|
return this.tokenType === "user";
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
|
-
* Make authenticated request to Gaffer API
|
|
51
|
+
* Make authenticated request to Gaffer API with retry logic
|
|
46
52
|
*/
|
|
47
53
|
async request(endpoint, params) {
|
|
48
54
|
const url = new URL(`/api/v1${endpoint}`, this.baseUrl);
|
|
@@ -53,32 +59,59 @@ var GafferApiClient = class _GafferApiClient {
|
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
let lastError = null;
|
|
63
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(url.toString(), {
|
|
68
|
+
method: "GET",
|
|
69
|
+
headers: {
|
|
70
|
+
"X-API-Key": this.apiKey,
|
|
71
|
+
"Accept": "application/json",
|
|
72
|
+
"User-Agent": "gaffer-mcp/0.2.1"
|
|
73
|
+
},
|
|
74
|
+
signal: controller.signal
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorData = await response.json().catch(() => ({}));
|
|
78
|
+
if (RETRYABLE_STATUS_CODES.includes(response.status) && attempt < MAX_RETRIES) {
|
|
79
|
+
let delayMs = INITIAL_RETRY_DELAY_MS * 2 ** attempt;
|
|
80
|
+
if (response.status === 429) {
|
|
81
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
82
|
+
if (retryAfter) {
|
|
83
|
+
delayMs = Math.max(delayMs, Number.parseInt(retryAfter, 10) * 1e3);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
lastError = new Error(errorData.error?.message || `API request failed: ${response.status}`);
|
|
87
|
+
await sleep(delayMs);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const errorMessage = errorData.error?.message || `API request failed: ${response.status}`;
|
|
91
|
+
throw new Error(errorMessage);
|
|
92
|
+
}
|
|
93
|
+
return response.json();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
97
|
+
lastError = new Error(`Request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
98
|
+
if (attempt < MAX_RETRIES) {
|
|
99
|
+
await sleep(INITIAL_RETRY_DELAY_MS * 2 ** attempt);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
throw lastError;
|
|
103
|
+
}
|
|
104
|
+
if (error instanceof TypeError && attempt < MAX_RETRIES) {
|
|
105
|
+
lastError = error;
|
|
106
|
+
await sleep(INITIAL_RETRY_DELAY_MS * 2 ** attempt);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
} finally {
|
|
111
|
+
clearTimeout(timeoutId);
|
|
77
112
|
}
|
|
78
|
-
throw error;
|
|
79
|
-
} finally {
|
|
80
|
-
clearTimeout(timeoutId);
|
|
81
113
|
}
|
|
114
|
+
throw lastError || new Error("Request failed after retries");
|
|
82
115
|
}
|
|
83
116
|
/**
|
|
84
117
|
* List all projects the user has access to
|
|
@@ -228,6 +261,7 @@ var GafferApiClient = class _GafferApiClient {
|
|
|
228
261
|
* @param options.days - Analysis period in days (default: 30)
|
|
229
262
|
* @param options.limit - Maximum number of results (default: 20)
|
|
230
263
|
* @param options.framework - Filter by test framework
|
|
264
|
+
* @param options.branch - Filter by git branch name
|
|
231
265
|
* @returns Slowest tests sorted by P95 duration
|
|
232
266
|
*/
|
|
233
267
|
async getSlowestTests(options) {
|
|
@@ -240,31 +274,481 @@ var GafferApiClient = class _GafferApiClient {
|
|
|
240
274
|
return this.request(`/user/projects/${options.projectId}/slowest-tests`, {
|
|
241
275
|
...options.days && { days: options.days },
|
|
242
276
|
...options.limit && { limit: options.limit },
|
|
243
|
-
...options.framework && { framework: options.framework }
|
|
277
|
+
...options.framework && { framework: options.framework },
|
|
278
|
+
...options.branch && { branch: options.branch }
|
|
244
279
|
});
|
|
245
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Get parsed test results for a specific test run
|
|
283
|
+
*
|
|
284
|
+
* @param options - Query options
|
|
285
|
+
* @param options.projectId - The project ID (required)
|
|
286
|
+
* @param options.testRunId - The test run ID (required)
|
|
287
|
+
* @param options.status - Filter by test status ('passed', 'failed', 'skipped')
|
|
288
|
+
* @param options.limit - Maximum number of results (default: 100)
|
|
289
|
+
* @param options.offset - Pagination offset (default: 0)
|
|
290
|
+
* @returns Parsed test cases with pagination
|
|
291
|
+
*/
|
|
292
|
+
async getTestRunDetails(options) {
|
|
293
|
+
if (!this.isUserToken()) {
|
|
294
|
+
throw new Error("getTestRunDetails requires a user API Key (gaf_).");
|
|
295
|
+
}
|
|
296
|
+
if (!options.projectId) {
|
|
297
|
+
throw new Error("projectId is required");
|
|
298
|
+
}
|
|
299
|
+
if (!options.testRunId) {
|
|
300
|
+
throw new Error("testRunId is required");
|
|
301
|
+
}
|
|
302
|
+
return this.request(
|
|
303
|
+
`/user/projects/${options.projectId}/test-runs/${options.testRunId}/details`,
|
|
304
|
+
{
|
|
305
|
+
...options.status && { status: options.status },
|
|
306
|
+
...options.limit && { limit: options.limit },
|
|
307
|
+
...options.offset && { offset: options.offset }
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Compare test metrics between two commits or test runs
|
|
313
|
+
*
|
|
314
|
+
* @param options - Query options
|
|
315
|
+
* @param options.projectId - The project ID (required)
|
|
316
|
+
* @param options.testName - The test name to compare (required)
|
|
317
|
+
* @param options.beforeCommit - Commit SHA for before (use with afterCommit)
|
|
318
|
+
* @param options.afterCommit - Commit SHA for after (use with beforeCommit)
|
|
319
|
+
* @param options.beforeRunId - Test run ID for before (use with afterRunId)
|
|
320
|
+
* @param options.afterRunId - Test run ID for after (use with beforeRunId)
|
|
321
|
+
* @returns Comparison of test metrics
|
|
322
|
+
*/
|
|
323
|
+
async compareTestMetrics(options) {
|
|
324
|
+
if (!this.isUserToken()) {
|
|
325
|
+
throw new Error("compareTestMetrics requires a user API Key (gaf_).");
|
|
326
|
+
}
|
|
327
|
+
if (!options.projectId) {
|
|
328
|
+
throw new Error("projectId is required");
|
|
329
|
+
}
|
|
330
|
+
if (!options.testName) {
|
|
331
|
+
throw new Error("testName is required");
|
|
332
|
+
}
|
|
333
|
+
return this.request(
|
|
334
|
+
`/user/projects/${options.projectId}/compare-test`,
|
|
335
|
+
{
|
|
336
|
+
testName: options.testName,
|
|
337
|
+
...options.beforeCommit && { beforeCommit: options.beforeCommit },
|
|
338
|
+
...options.afterCommit && { afterCommit: options.afterCommit },
|
|
339
|
+
...options.beforeRunId && { beforeRunId: options.beforeRunId },
|
|
340
|
+
...options.afterRunId && { afterRunId: options.afterRunId }
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get coverage summary for a project
|
|
346
|
+
*
|
|
347
|
+
* @param options - Query options
|
|
348
|
+
* @param options.projectId - The project ID (required)
|
|
349
|
+
* @param options.days - Analysis period in days (default: 30)
|
|
350
|
+
* @returns Coverage summary with trends and lowest coverage files
|
|
351
|
+
*/
|
|
352
|
+
async getCoverageSummary(options) {
|
|
353
|
+
if (!this.isUserToken()) {
|
|
354
|
+
throw new Error("getCoverageSummary requires a user API Key (gaf_).");
|
|
355
|
+
}
|
|
356
|
+
if (!options.projectId) {
|
|
357
|
+
throw new Error("projectId is required");
|
|
358
|
+
}
|
|
359
|
+
return this.request(
|
|
360
|
+
`/user/projects/${options.projectId}/coverage-summary`,
|
|
361
|
+
{
|
|
362
|
+
...options.days && { days: options.days }
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get coverage files for a project with filtering
|
|
368
|
+
*
|
|
369
|
+
* @param options - Query options
|
|
370
|
+
* @param options.projectId - The project ID (required)
|
|
371
|
+
* @param options.filePath - Filter to specific file path
|
|
372
|
+
* @param options.minCoverage - Minimum coverage percentage
|
|
373
|
+
* @param options.maxCoverage - Maximum coverage percentage
|
|
374
|
+
* @param options.limit - Maximum number of results
|
|
375
|
+
* @param options.offset - Pagination offset
|
|
376
|
+
* @param options.sortBy - Sort by 'path' or 'coverage'
|
|
377
|
+
* @param options.sortOrder - Sort order 'asc' or 'desc'
|
|
378
|
+
* @returns List of files with coverage data
|
|
379
|
+
*/
|
|
380
|
+
async getCoverageFiles(options) {
|
|
381
|
+
if (!this.isUserToken()) {
|
|
382
|
+
throw new Error("getCoverageFiles requires a user API Key (gaf_).");
|
|
383
|
+
}
|
|
384
|
+
if (!options.projectId) {
|
|
385
|
+
throw new Error("projectId is required");
|
|
386
|
+
}
|
|
387
|
+
return this.request(
|
|
388
|
+
`/user/projects/${options.projectId}/coverage/files`,
|
|
389
|
+
{
|
|
390
|
+
...options.filePath && { filePath: options.filePath },
|
|
391
|
+
...options.minCoverage !== void 0 && { minCoverage: options.minCoverage },
|
|
392
|
+
...options.maxCoverage !== void 0 && { maxCoverage: options.maxCoverage },
|
|
393
|
+
...options.limit && { limit: options.limit },
|
|
394
|
+
...options.offset && { offset: options.offset },
|
|
395
|
+
...options.sortBy && { sortBy: options.sortBy },
|
|
396
|
+
...options.sortOrder && { sortOrder: options.sortOrder }
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get risk areas (files with low coverage AND test failures)
|
|
402
|
+
*
|
|
403
|
+
* @param options - Query options
|
|
404
|
+
* @param options.projectId - The project ID (required)
|
|
405
|
+
* @param options.days - Analysis period in days (default: 30)
|
|
406
|
+
* @param options.coverageThreshold - Include files below this coverage (default: 80)
|
|
407
|
+
* @returns List of risk areas sorted by risk score
|
|
408
|
+
*/
|
|
409
|
+
async getCoverageRiskAreas(options) {
|
|
410
|
+
if (!this.isUserToken()) {
|
|
411
|
+
throw new Error("getCoverageRiskAreas requires a user API Key (gaf_).");
|
|
412
|
+
}
|
|
413
|
+
if (!options.projectId) {
|
|
414
|
+
throw new Error("projectId is required");
|
|
415
|
+
}
|
|
416
|
+
return this.request(
|
|
417
|
+
`/user/projects/${options.projectId}/coverage/risk-areas`,
|
|
418
|
+
{
|
|
419
|
+
...options.days && { days: options.days },
|
|
420
|
+
...options.coverageThreshold !== void 0 && { coverageThreshold: options.coverageThreshold }
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get a browser-navigable URL for viewing a test report
|
|
426
|
+
*
|
|
427
|
+
* @param options - Query options
|
|
428
|
+
* @param options.projectId - The project ID (required)
|
|
429
|
+
* @param options.testRunId - The test run ID (required)
|
|
430
|
+
* @param options.filename - Specific file to open (default: index.html)
|
|
431
|
+
* @returns URL with signed token for browser access
|
|
432
|
+
*/
|
|
433
|
+
async getReportBrowserUrl(options) {
|
|
434
|
+
if (!this.isUserToken()) {
|
|
435
|
+
throw new Error("getReportBrowserUrl requires a user API Key (gaf_).");
|
|
436
|
+
}
|
|
437
|
+
if (!options.projectId) {
|
|
438
|
+
throw new Error("projectId is required");
|
|
439
|
+
}
|
|
440
|
+
if (!options.testRunId) {
|
|
441
|
+
throw new Error("testRunId is required");
|
|
442
|
+
}
|
|
443
|
+
return this.request(
|
|
444
|
+
`/user/projects/${options.projectId}/reports/${options.testRunId}/browser-url`,
|
|
445
|
+
{
|
|
446
|
+
...options.filename && { filename: options.filename }
|
|
447
|
+
}
|
|
448
|
+
);
|
|
449
|
+
}
|
|
246
450
|
};
|
|
247
451
|
|
|
248
|
-
// src/tools/
|
|
452
|
+
// src/tools/compare-test-metrics.ts
|
|
249
453
|
import { z } from "zod";
|
|
454
|
+
var compareTestMetricsInputSchema = {
|
|
455
|
+
projectId: z.string().describe("Project ID. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
456
|
+
testName: z.string().describe("The test name to compare. Can be the short name or full name including describe blocks."),
|
|
457
|
+
beforeCommit: z.string().optional().describe('Commit SHA for the "before" measurement. Use with afterCommit.'),
|
|
458
|
+
afterCommit: z.string().optional().describe('Commit SHA for the "after" measurement. Use with beforeCommit.'),
|
|
459
|
+
beforeRunId: z.string().optional().describe('Test run ID for the "before" measurement. Use with afterRunId.'),
|
|
460
|
+
afterRunId: z.string().optional().describe('Test run ID for the "after" measurement. Use with beforeRunId.')
|
|
461
|
+
};
|
|
462
|
+
var compareTestMetricsOutputSchema = {
|
|
463
|
+
testName: z.string(),
|
|
464
|
+
before: z.object({
|
|
465
|
+
testRunId: z.string(),
|
|
466
|
+
commit: z.string().nullable(),
|
|
467
|
+
branch: z.string().nullable(),
|
|
468
|
+
status: z.enum(["passed", "failed", "skipped"]),
|
|
469
|
+
durationMs: z.number().nullable(),
|
|
470
|
+
createdAt: z.string()
|
|
471
|
+
}),
|
|
472
|
+
after: z.object({
|
|
473
|
+
testRunId: z.string(),
|
|
474
|
+
commit: z.string().nullable(),
|
|
475
|
+
branch: z.string().nullable(),
|
|
476
|
+
status: z.enum(["passed", "failed", "skipped"]),
|
|
477
|
+
durationMs: z.number().nullable(),
|
|
478
|
+
createdAt: z.string()
|
|
479
|
+
}),
|
|
480
|
+
change: z.object({
|
|
481
|
+
durationMs: z.number().nullable(),
|
|
482
|
+
percentChange: z.number().nullable(),
|
|
483
|
+
statusChanged: z.boolean()
|
|
484
|
+
})
|
|
485
|
+
};
|
|
486
|
+
async function executeCompareTestMetrics(client, input) {
|
|
487
|
+
const hasCommits = input.beforeCommit && input.afterCommit;
|
|
488
|
+
const hasRunIds = input.beforeRunId && input.afterRunId;
|
|
489
|
+
if (!hasCommits && !hasRunIds) {
|
|
490
|
+
throw new Error("Must provide either (beforeCommit + afterCommit) or (beforeRunId + afterRunId)");
|
|
491
|
+
}
|
|
492
|
+
if (hasCommits) {
|
|
493
|
+
if (input.beforeCommit.trim().length === 0 || input.afterCommit.trim().length === 0) {
|
|
494
|
+
throw new Error("beforeCommit and afterCommit must not be empty strings");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (hasRunIds) {
|
|
498
|
+
if (input.beforeRunId.trim().length === 0 || input.afterRunId.trim().length === 0) {
|
|
499
|
+
throw new Error("beforeRunId and afterRunId must not be empty strings");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const response = await client.compareTestMetrics({
|
|
503
|
+
projectId: input.projectId,
|
|
504
|
+
testName: input.testName,
|
|
505
|
+
beforeCommit: input.beforeCommit,
|
|
506
|
+
afterCommit: input.afterCommit,
|
|
507
|
+
beforeRunId: input.beforeRunId,
|
|
508
|
+
afterRunId: input.afterRunId
|
|
509
|
+
});
|
|
510
|
+
return response;
|
|
511
|
+
}
|
|
512
|
+
var compareTestMetricsMetadata = {
|
|
513
|
+
name: "compare_test_metrics",
|
|
514
|
+
title: "Compare Test Metrics",
|
|
515
|
+
description: `Compare test metrics between two commits or test runs.
|
|
516
|
+
|
|
517
|
+
Useful for measuring the impact of code changes on test performance or reliability.
|
|
518
|
+
|
|
519
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
520
|
+
Use list_projects to find available project IDs.
|
|
521
|
+
|
|
522
|
+
Parameters:
|
|
523
|
+
- projectId (required): Project ID
|
|
524
|
+
- testName (required): The test name to compare (short name or full name)
|
|
525
|
+
- Option 1 - Compare by commit:
|
|
526
|
+
- beforeCommit: Commit SHA for "before" measurement
|
|
527
|
+
- afterCommit: Commit SHA for "after" measurement
|
|
528
|
+
- Option 2 - Compare by test run:
|
|
529
|
+
- beforeRunId: Test run ID for "before" measurement
|
|
530
|
+
- afterRunId: Test run ID for "after" measurement
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
- testName: The test that was compared
|
|
534
|
+
- before: Metrics from the before commit/run
|
|
535
|
+
- testRunId, commit, branch, status, durationMs, createdAt
|
|
536
|
+
- after: Metrics from the after commit/run
|
|
537
|
+
- testRunId, commit, branch, status, durationMs, createdAt
|
|
538
|
+
- change: Calculated changes
|
|
539
|
+
- durationMs: Duration difference (negative = faster)
|
|
540
|
+
- percentChange: Percentage change (negative = improvement)
|
|
541
|
+
- statusChanged: Whether pass/fail status changed
|
|
542
|
+
|
|
543
|
+
Use cases:
|
|
544
|
+
- "Did my fix make this test faster?"
|
|
545
|
+
- "Compare test performance between these two commits"
|
|
546
|
+
- "Did this test start failing after my changes?"
|
|
547
|
+
- "Show me the before/after for the slow test I optimized"
|
|
548
|
+
|
|
549
|
+
Tip: Use get_test_history first to find the commit SHAs or test run IDs you want to compare.`
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/tools/find-uncovered-failure-areas.ts
|
|
553
|
+
import { z as z2 } from "zod";
|
|
554
|
+
var findUncoveredFailureAreasInputSchema = {
|
|
555
|
+
projectId: z2.string().describe("Project ID to analyze. Required. Use list_projects to find project IDs."),
|
|
556
|
+
days: z2.number().int().min(1).max(365).optional().describe("Number of days to analyze for test failures (default: 30)"),
|
|
557
|
+
coverageThreshold: z2.number().min(0).max(100).optional().describe("Include files with coverage below this percentage (default: 80)")
|
|
558
|
+
};
|
|
559
|
+
var findUncoveredFailureAreasOutputSchema = {
|
|
560
|
+
hasCoverage: z2.boolean(),
|
|
561
|
+
hasTestResults: z2.boolean(),
|
|
562
|
+
riskAreas: z2.array(z2.object({
|
|
563
|
+
filePath: z2.string(),
|
|
564
|
+
coverage: z2.number(),
|
|
565
|
+
failureCount: z2.number(),
|
|
566
|
+
riskScore: z2.number(),
|
|
567
|
+
testNames: z2.array(z2.string())
|
|
568
|
+
})),
|
|
569
|
+
message: z2.string().optional()
|
|
570
|
+
};
|
|
571
|
+
async function executeFindUncoveredFailureAreas(client, input) {
|
|
572
|
+
const response = await client.getCoverageRiskAreas({
|
|
573
|
+
projectId: input.projectId,
|
|
574
|
+
days: input.days,
|
|
575
|
+
coverageThreshold: input.coverageThreshold
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
hasCoverage: response.hasCoverage,
|
|
579
|
+
hasTestResults: response.hasTestResults,
|
|
580
|
+
riskAreas: response.riskAreas,
|
|
581
|
+
message: response.message
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
var findUncoveredFailureAreasMetadata = {
|
|
585
|
+
name: "find_uncovered_failure_areas",
|
|
586
|
+
title: "Find Uncovered Failure Areas",
|
|
587
|
+
description: `Find areas of code that have both low coverage AND test failures.
|
|
588
|
+
|
|
589
|
+
This cross-references test failures with coverage data to identify high-risk
|
|
590
|
+
areas in your codebase that need attention. Files are ranked by a "risk score"
|
|
591
|
+
calculated as: (100 - coverage%) \xD7 failureCount.
|
|
592
|
+
|
|
593
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
594
|
+
Use list_projects first to find available project IDs.
|
|
595
|
+
|
|
596
|
+
Parameters:
|
|
597
|
+
- projectId: The project to analyze (required)
|
|
598
|
+
- days: Analysis period for test failures (default: 30)
|
|
599
|
+
- coverageThreshold: Include files below this coverage % (default: 80)
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
- List of risk areas sorted by risk score (highest risk first)
|
|
603
|
+
- Each area includes: file path, coverage %, failure count, risk score, test names
|
|
604
|
+
|
|
605
|
+
Use this to prioritize which parts of your codebase need better test coverage.`
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// src/tools/get-coverage-for-file.ts
|
|
609
|
+
import { z as z3 } from "zod";
|
|
610
|
+
var getCoverageForFileInputSchema = {
|
|
611
|
+
projectId: z3.string().describe("Project ID to get coverage for. Required. Use list_projects to find project IDs."),
|
|
612
|
+
filePath: z3.string().describe("File path to get coverage for. Can be exact path or partial match.")
|
|
613
|
+
};
|
|
614
|
+
var getCoverageForFileOutputSchema = {
|
|
615
|
+
hasCoverage: z3.boolean(),
|
|
616
|
+
files: z3.array(z3.object({
|
|
617
|
+
path: z3.string(),
|
|
618
|
+
lines: z3.object({
|
|
619
|
+
covered: z3.number(),
|
|
620
|
+
total: z3.number(),
|
|
621
|
+
percentage: z3.number()
|
|
622
|
+
}),
|
|
623
|
+
branches: z3.object({
|
|
624
|
+
covered: z3.number(),
|
|
625
|
+
total: z3.number(),
|
|
626
|
+
percentage: z3.number()
|
|
627
|
+
}),
|
|
628
|
+
functions: z3.object({
|
|
629
|
+
covered: z3.number(),
|
|
630
|
+
total: z3.number(),
|
|
631
|
+
percentage: z3.number()
|
|
632
|
+
})
|
|
633
|
+
})),
|
|
634
|
+
message: z3.string().optional()
|
|
635
|
+
};
|
|
636
|
+
async function executeGetCoverageForFile(client, input) {
|
|
637
|
+
const response = await client.getCoverageFiles({
|
|
638
|
+
projectId: input.projectId,
|
|
639
|
+
filePath: input.filePath,
|
|
640
|
+
limit: 10
|
|
641
|
+
// Return up to 10 matching files
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
hasCoverage: response.hasCoverage,
|
|
645
|
+
files: response.files.map((f) => ({
|
|
646
|
+
path: f.path,
|
|
647
|
+
lines: f.lines,
|
|
648
|
+
branches: f.branches,
|
|
649
|
+
functions: f.functions
|
|
650
|
+
})),
|
|
651
|
+
message: response.message
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
var getCoverageForFileMetadata = {
|
|
655
|
+
name: "get_coverage_for_file",
|
|
656
|
+
title: "Get Coverage for File",
|
|
657
|
+
description: `Get coverage metrics for a specific file or files matching a path pattern.
|
|
658
|
+
|
|
659
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
660
|
+
Use list_projects first to find available project IDs.
|
|
661
|
+
|
|
662
|
+
Parameters:
|
|
663
|
+
- projectId: The project to query (required)
|
|
664
|
+
- filePath: File path to search for (exact or partial match)
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
- Line coverage (covered/total/percentage)
|
|
668
|
+
- Branch coverage (covered/total/percentage)
|
|
669
|
+
- Function coverage (covered/total/percentage)
|
|
670
|
+
|
|
671
|
+
Use this to check coverage for specific files or find files by path pattern.`
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// src/tools/get-coverage-summary.ts
|
|
675
|
+
import { z as z4 } from "zod";
|
|
676
|
+
var getCoverageSummaryInputSchema = {
|
|
677
|
+
projectId: z4.string().describe("Project ID to get coverage for. Required. Use list_projects to find project IDs."),
|
|
678
|
+
days: z4.number().int().min(1).max(365).optional().describe("Number of days to analyze for trends (default: 30)")
|
|
679
|
+
};
|
|
680
|
+
var getCoverageSummaryOutputSchema = {
|
|
681
|
+
hasCoverage: z4.boolean(),
|
|
682
|
+
current: z4.object({
|
|
683
|
+
lines: z4.number(),
|
|
684
|
+
branches: z4.number(),
|
|
685
|
+
functions: z4.number()
|
|
686
|
+
}).optional(),
|
|
687
|
+
trend: z4.object({
|
|
688
|
+
direction: z4.enum(["up", "down", "stable"]),
|
|
689
|
+
change: z4.number()
|
|
690
|
+
}).optional(),
|
|
691
|
+
totalReports: z4.number(),
|
|
692
|
+
latestReportDate: z4.string().nullable().optional(),
|
|
693
|
+
lowestCoverageFiles: z4.array(z4.object({
|
|
694
|
+
path: z4.string(),
|
|
695
|
+
coverage: z4.number()
|
|
696
|
+
})).optional(),
|
|
697
|
+
message: z4.string().optional()
|
|
698
|
+
};
|
|
699
|
+
async function executeGetCoverageSummary(client, input) {
|
|
700
|
+
const response = await client.getCoverageSummary({
|
|
701
|
+
projectId: input.projectId,
|
|
702
|
+
days: input.days
|
|
703
|
+
});
|
|
704
|
+
return {
|
|
705
|
+
hasCoverage: response.hasCoverage,
|
|
706
|
+
current: response.current,
|
|
707
|
+
trend: response.trend,
|
|
708
|
+
totalReports: response.totalReports,
|
|
709
|
+
latestReportDate: response.latestReportDate,
|
|
710
|
+
lowestCoverageFiles: response.lowestCoverageFiles,
|
|
711
|
+
message: response.message
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
var getCoverageSummaryMetadata = {
|
|
715
|
+
name: "get_coverage_summary",
|
|
716
|
+
title: "Get Coverage Summary",
|
|
717
|
+
description: `Get the coverage metrics summary for a project.
|
|
718
|
+
|
|
719
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
720
|
+
Use list_projects first to find available project IDs.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
- Current coverage percentages (lines, branches, functions)
|
|
724
|
+
- Trend direction (up, down, stable) and change amount
|
|
725
|
+
- Total number of coverage reports
|
|
726
|
+
- Latest report date
|
|
727
|
+
- Top 5 files with lowest coverage
|
|
728
|
+
|
|
729
|
+
Use this to understand your project's overall test coverage health.`
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/tools/get-flaky-tests.ts
|
|
733
|
+
import { z as z5 } from "zod";
|
|
250
734
|
var getFlakyTestsInputSchema = {
|
|
251
|
-
projectId:
|
|
252
|
-
threshold:
|
|
253
|
-
limit:
|
|
254
|
-
days:
|
|
735
|
+
projectId: z5.string().optional().describe("Project ID to get flaky tests for. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
736
|
+
threshold: z5.number().min(0).max(1).optional().describe("Minimum flip rate to be considered flaky (0-1, default: 0.1 = 10%)"),
|
|
737
|
+
limit: z5.number().int().min(1).max(100).optional().describe("Maximum number of flaky tests to return (default: 50)"),
|
|
738
|
+
days: z5.number().int().min(1).max(365).optional().describe("Analysis period in days (default: 30)")
|
|
255
739
|
};
|
|
256
740
|
var getFlakyTestsOutputSchema = {
|
|
257
|
-
flakyTests:
|
|
258
|
-
name:
|
|
259
|
-
flipRate:
|
|
260
|
-
flipCount:
|
|
261
|
-
totalRuns:
|
|
262
|
-
lastSeen:
|
|
741
|
+
flakyTests: z5.array(z5.object({
|
|
742
|
+
name: z5.string(),
|
|
743
|
+
flipRate: z5.number(),
|
|
744
|
+
flipCount: z5.number(),
|
|
745
|
+
totalRuns: z5.number(),
|
|
746
|
+
lastSeen: z5.string()
|
|
263
747
|
})),
|
|
264
|
-
summary:
|
|
265
|
-
threshold:
|
|
266
|
-
totalFlaky:
|
|
267
|
-
period:
|
|
748
|
+
summary: z5.object({
|
|
749
|
+
threshold: z5.number(),
|
|
750
|
+
totalFlaky: z5.number(),
|
|
751
|
+
period: z5.number()
|
|
268
752
|
})
|
|
269
753
|
};
|
|
270
754
|
async function executeGetFlakyTests(client, input) {
|
|
@@ -304,22 +788,22 @@ specific tests are flaky and need investigation.`
|
|
|
304
788
|
};
|
|
305
789
|
|
|
306
790
|
// src/tools/get-project-health.ts
|
|
307
|
-
import { z as
|
|
791
|
+
import { z as z6 } from "zod";
|
|
308
792
|
var getProjectHealthInputSchema = {
|
|
309
|
-
projectId:
|
|
310
|
-
days:
|
|
793
|
+
projectId: z6.string().optional().describe("Project ID to get health for. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
794
|
+
days: z6.number().int().min(1).max(365).optional().describe("Number of days to analyze (default: 30)")
|
|
311
795
|
};
|
|
312
796
|
var getProjectHealthOutputSchema = {
|
|
313
|
-
projectName:
|
|
314
|
-
healthScore:
|
|
315
|
-
passRate:
|
|
316
|
-
testRunCount:
|
|
317
|
-
flakyTestCount:
|
|
318
|
-
trend:
|
|
319
|
-
period:
|
|
320
|
-
days:
|
|
321
|
-
start:
|
|
322
|
-
end:
|
|
797
|
+
projectName: z6.string(),
|
|
798
|
+
healthScore: z6.number(),
|
|
799
|
+
passRate: z6.number().nullable(),
|
|
800
|
+
testRunCount: z6.number(),
|
|
801
|
+
flakyTestCount: z6.number(),
|
|
802
|
+
trend: z6.enum(["up", "down", "stable"]),
|
|
803
|
+
period: z6.object({
|
|
804
|
+
days: z6.number(),
|
|
805
|
+
start: z6.string(),
|
|
806
|
+
end: z6.string()
|
|
323
807
|
})
|
|
324
808
|
};
|
|
325
809
|
async function executeGetProjectHealth(client, input) {
|
|
@@ -355,23 +839,77 @@ Returns:
|
|
|
355
839
|
Use this to understand the current state of your test suite.`
|
|
356
840
|
};
|
|
357
841
|
|
|
842
|
+
// src/tools/get-report-browser-url.ts
|
|
843
|
+
import { z as z7 } from "zod";
|
|
844
|
+
var getReportBrowserUrlInputSchema = {
|
|
845
|
+
projectId: z7.string().describe("Project ID the test run belongs to. Required. Use list_projects to find project IDs."),
|
|
846
|
+
testRunId: z7.string().describe("The test run ID to get the report URL for. Use list_test_runs to find test run IDs."),
|
|
847
|
+
filename: z7.string().optional().describe("Specific file to open (default: index.html or first HTML file)")
|
|
848
|
+
};
|
|
849
|
+
var getReportBrowserUrlOutputSchema = {
|
|
850
|
+
url: z7.string(),
|
|
851
|
+
filename: z7.string(),
|
|
852
|
+
testRunId: z7.string(),
|
|
853
|
+
expiresAt: z7.string(),
|
|
854
|
+
expiresInSeconds: z7.number()
|
|
855
|
+
};
|
|
856
|
+
async function executeGetReportBrowserUrl(client, input) {
|
|
857
|
+
const response = await client.getReportBrowserUrl({
|
|
858
|
+
projectId: input.projectId,
|
|
859
|
+
testRunId: input.testRunId,
|
|
860
|
+
filename: input.filename
|
|
861
|
+
});
|
|
862
|
+
return {
|
|
863
|
+
url: response.url,
|
|
864
|
+
filename: response.filename,
|
|
865
|
+
testRunId: response.testRunId,
|
|
866
|
+
expiresAt: response.expiresAt,
|
|
867
|
+
expiresInSeconds: response.expiresInSeconds
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
var getReportBrowserUrlMetadata = {
|
|
871
|
+
name: "get_report_browser_url",
|
|
872
|
+
title: "Get Report Browser URL",
|
|
873
|
+
description: `Get a browser-navigable URL for viewing a test report (Playwright, Vitest, etc.).
|
|
874
|
+
|
|
875
|
+
Returns a signed URL that can be opened directly in a browser without requiring
|
|
876
|
+
the user to log in. The URL expires after 30 minutes for security.
|
|
877
|
+
|
|
878
|
+
When using a user API Key (gaf_), you must provide both projectId and testRunId.
|
|
879
|
+
Use list_projects and list_test_runs to find available IDs.
|
|
880
|
+
|
|
881
|
+
Parameters:
|
|
882
|
+
- projectId: The project the test run belongs to (required)
|
|
883
|
+
- testRunId: The test run to view (required)
|
|
884
|
+
- filename: Specific file to open (optional, defaults to index.html)
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
- url: Browser-navigable URL with signed token
|
|
888
|
+
- filename: The file being accessed
|
|
889
|
+
- expiresAt: ISO timestamp when the URL expires
|
|
890
|
+
- expiresInSeconds: Time until expiration
|
|
891
|
+
|
|
892
|
+
The returned URL can be shared with users who need to view the report.
|
|
893
|
+
Note: URLs expire after 30 minutes for security.`
|
|
894
|
+
};
|
|
895
|
+
|
|
358
896
|
// src/tools/get-report.ts
|
|
359
|
-
import { z as
|
|
897
|
+
import { z as z8 } from "zod";
|
|
360
898
|
var getReportInputSchema = {
|
|
361
|
-
testRunId:
|
|
899
|
+
testRunId: z8.string().describe("The test run ID to get report files for. Use list_test_runs to find test run IDs.")
|
|
362
900
|
};
|
|
363
901
|
var getReportOutputSchema = {
|
|
364
|
-
testRunId:
|
|
365
|
-
projectId:
|
|
366
|
-
projectName:
|
|
367
|
-
resultSchema:
|
|
368
|
-
files:
|
|
369
|
-
filename:
|
|
370
|
-
size:
|
|
371
|
-
contentType:
|
|
372
|
-
downloadUrl:
|
|
902
|
+
testRunId: z8.string(),
|
|
903
|
+
projectId: z8.string(),
|
|
904
|
+
projectName: z8.string(),
|
|
905
|
+
resultSchema: z8.string().optional(),
|
|
906
|
+
files: z8.array(z8.object({
|
|
907
|
+
filename: z8.string(),
|
|
908
|
+
size: z8.number(),
|
|
909
|
+
contentType: z8.string(),
|
|
910
|
+
downloadUrl: z8.string()
|
|
373
911
|
})),
|
|
374
|
-
urlExpiresInSeconds:
|
|
912
|
+
urlExpiresInSeconds: z8.number().optional()
|
|
375
913
|
};
|
|
376
914
|
async function executeGetReport(client, input) {
|
|
377
915
|
const response = await client.getReport(input.testRunId);
|
|
@@ -392,52 +930,66 @@ async function executeGetReport(client, input) {
|
|
|
392
930
|
var getReportMetadata = {
|
|
393
931
|
name: "get_report",
|
|
394
932
|
title: "Get Report Files",
|
|
395
|
-
description: `Get
|
|
933
|
+
description: `Get URLs for report files uploaded with a test run.
|
|
396
934
|
|
|
397
|
-
|
|
398
|
-
|
|
935
|
+
IMPORTANT: This tool returns download URLs, not file content. You must fetch the URLs separately.
|
|
936
|
+
|
|
937
|
+
Returns for each file:
|
|
938
|
+
- filename: The file name (e.g., "report.html", "results.json", "junit.xml")
|
|
399
939
|
- size: File size in bytes
|
|
400
|
-
- contentType: MIME type (e.g., "text/html", "application/json")
|
|
401
|
-
- downloadUrl: Presigned URL to download the file
|
|
402
|
-
- urlExpiresInSeconds: How long the download URLs are valid (typically 5 minutes)
|
|
940
|
+
- contentType: MIME type (e.g., "text/html", "application/json", "application/xml")
|
|
941
|
+
- downloadUrl: Presigned URL to download the file (valid for ~5 minutes)
|
|
403
942
|
|
|
404
|
-
|
|
405
|
-
- HTML reports (Playwright, pytest-html, Vitest UI)
|
|
406
|
-
- JSON results (Jest, Vitest)
|
|
407
|
-
- JUnit XML files
|
|
408
|
-
- Coverage reports
|
|
943
|
+
How to use the returned URLs:
|
|
409
944
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
945
|
+
1. **JSON files** (results.json, coverage.json):
|
|
946
|
+
Use WebFetch with the downloadUrl to retrieve and parse the JSON content.
|
|
947
|
+
Example: WebFetch(url=downloadUrl, prompt="Extract test results from this JSON")
|
|
948
|
+
|
|
949
|
+
2. **XML files** (junit.xml, xunit.xml):
|
|
950
|
+
Use WebFetch with the downloadUrl to retrieve and parse the XML content.
|
|
951
|
+
Example: WebFetch(url=downloadUrl, prompt="Parse the test results from this JUnit XML")
|
|
414
952
|
|
|
415
|
-
|
|
953
|
+
3. **HTML reports** (Playwright, pytest-html, Vitest):
|
|
954
|
+
These are typically bundled React/JavaScript applications that require a browser.
|
|
955
|
+
They cannot be meaningfully parsed by WebFetch.
|
|
956
|
+
For programmatic analysis, use get_test_run_details instead.
|
|
957
|
+
|
|
958
|
+
Recommendations:
|
|
959
|
+
- For analyzing test results programmatically: Use get_test_run_details (returns parsed test data)
|
|
960
|
+
- For JSON/XML files: Use this tool + WebFetch on the downloadUrl
|
|
961
|
+
- For HTML reports: Direct users to view in browser, or use get_test_run_details
|
|
962
|
+
|
|
963
|
+
Use cases:
|
|
964
|
+
- "What files are in this test run?" (list available reports)
|
|
965
|
+
- "Get the coverage data from this run" (then WebFetch the JSON URL)
|
|
966
|
+
- "Parse the JUnit XML results" (then WebFetch the XML URL)`
|
|
416
967
|
};
|
|
417
968
|
|
|
418
969
|
// src/tools/get-slowest-tests.ts
|
|
419
|
-
import { z as
|
|
970
|
+
import { z as z9 } from "zod";
|
|
420
971
|
var getSlowestTestsInputSchema = {
|
|
421
|
-
projectId:
|
|
422
|
-
days:
|
|
423
|
-
limit:
|
|
424
|
-
framework:
|
|
972
|
+
projectId: z9.string().describe("Project ID to get slowest tests for. Required. Use list_projects to find project IDs."),
|
|
973
|
+
days: z9.number().int().min(1).max(365).optional().describe("Analysis period in days (default: 30)"),
|
|
974
|
+
limit: z9.number().int().min(1).max(100).optional().describe("Maximum number of tests to return (default: 20)"),
|
|
975
|
+
framework: z9.string().optional().describe('Filter by test framework (e.g., "playwright", "vitest", "jest")'),
|
|
976
|
+
branch: z9.string().optional().describe('Filter by git branch name (e.g., "main", "develop")')
|
|
425
977
|
};
|
|
426
978
|
var getSlowestTestsOutputSchema = {
|
|
427
|
-
slowestTests:
|
|
428
|
-
name:
|
|
429
|
-
fullName:
|
|
430
|
-
filePath:
|
|
431
|
-
framework:
|
|
432
|
-
avgDurationMs:
|
|
433
|
-
p95DurationMs:
|
|
434
|
-
runCount:
|
|
979
|
+
slowestTests: z9.array(z9.object({
|
|
980
|
+
name: z9.string(),
|
|
981
|
+
fullName: z9.string(),
|
|
982
|
+
filePath: z9.string().optional(),
|
|
983
|
+
framework: z9.string().optional(),
|
|
984
|
+
avgDurationMs: z9.number(),
|
|
985
|
+
p95DurationMs: z9.number(),
|
|
986
|
+
runCount: z9.number()
|
|
435
987
|
})),
|
|
436
|
-
summary:
|
|
437
|
-
projectId:
|
|
438
|
-
projectName:
|
|
439
|
-
period:
|
|
440
|
-
totalReturned:
|
|
988
|
+
summary: z9.object({
|
|
989
|
+
projectId: z9.string(),
|
|
990
|
+
projectName: z9.string(),
|
|
991
|
+
period: z9.number(),
|
|
992
|
+
totalReturned: z9.number()
|
|
441
993
|
})
|
|
442
994
|
};
|
|
443
995
|
async function executeGetSlowestTests(client, input) {
|
|
@@ -445,7 +997,8 @@ async function executeGetSlowestTests(client, input) {
|
|
|
445
997
|
projectId: input.projectId,
|
|
446
998
|
days: input.days,
|
|
447
999
|
limit: input.limit,
|
|
448
|
-
framework: input.framework
|
|
1000
|
+
framework: input.framework,
|
|
1001
|
+
branch: input.branch
|
|
449
1002
|
});
|
|
450
1003
|
return {
|
|
451
1004
|
slowestTests: response.slowestTests.map((test) => ({
|
|
@@ -473,6 +1026,7 @@ Parameters:
|
|
|
473
1026
|
- days (optional): Analysis period in days (default: 30, max: 365)
|
|
474
1027
|
- limit (optional): Max tests to return (default: 20, max: 100)
|
|
475
1028
|
- framework (optional): Filter by framework (e.g., "playwright", "vitest")
|
|
1029
|
+
- branch (optional): Filter by git branch (e.g., "main", "develop")
|
|
476
1030
|
|
|
477
1031
|
Returns:
|
|
478
1032
|
- List of slowest tests with:
|
|
@@ -488,32 +1042,33 @@ Returns:
|
|
|
488
1042
|
Use cases:
|
|
489
1043
|
- "Which tests are slowing down my CI pipeline?"
|
|
490
1044
|
- "Find the slowest Playwright tests to optimize"
|
|
491
|
-
- "Show me e2e tests taking over 30 seconds"
|
|
1045
|
+
- "Show me e2e tests taking over 30 seconds"
|
|
1046
|
+
- "What are the slowest tests on the main branch?"`
|
|
492
1047
|
};
|
|
493
1048
|
|
|
494
1049
|
// src/tools/get-test-history.ts
|
|
495
|
-
import { z as
|
|
1050
|
+
import { z as z10 } from "zod";
|
|
496
1051
|
var getTestHistoryInputSchema = {
|
|
497
|
-
projectId:
|
|
498
|
-
testName:
|
|
499
|
-
filePath:
|
|
500
|
-
limit:
|
|
1052
|
+
projectId: z10.string().optional().describe("Project ID to get test history for. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
1053
|
+
testName: z10.string().optional().describe("Exact test name to search for"),
|
|
1054
|
+
filePath: z10.string().optional().describe("File path containing the test"),
|
|
1055
|
+
limit: z10.number().int().min(1).max(100).optional().describe("Maximum number of results (default: 20)")
|
|
501
1056
|
};
|
|
502
1057
|
var getTestHistoryOutputSchema = {
|
|
503
|
-
history:
|
|
504
|
-
testRunId:
|
|
505
|
-
createdAt:
|
|
506
|
-
branch:
|
|
507
|
-
commitSha:
|
|
508
|
-
status:
|
|
509
|
-
durationMs:
|
|
510
|
-
message:
|
|
1058
|
+
history: z10.array(z10.object({
|
|
1059
|
+
testRunId: z10.string(),
|
|
1060
|
+
createdAt: z10.string(),
|
|
1061
|
+
branch: z10.string().optional(),
|
|
1062
|
+
commitSha: z10.string().optional(),
|
|
1063
|
+
status: z10.enum(["passed", "failed", "skipped", "pending"]),
|
|
1064
|
+
durationMs: z10.number(),
|
|
1065
|
+
message: z10.string().optional()
|
|
511
1066
|
})),
|
|
512
|
-
summary:
|
|
513
|
-
totalRuns:
|
|
514
|
-
passedRuns:
|
|
515
|
-
failedRuns:
|
|
516
|
-
passRate:
|
|
1067
|
+
summary: z10.object({
|
|
1068
|
+
totalRuns: z10.number(),
|
|
1069
|
+
passedRuns: z10.number(),
|
|
1070
|
+
failedRuns: z10.number(),
|
|
1071
|
+
passRate: z10.number().nullable()
|
|
517
1072
|
})
|
|
518
1073
|
};
|
|
519
1074
|
async function executeGetTestHistory(client, input) {
|
|
@@ -567,24 +1122,185 @@ Returns:
|
|
|
567
1122
|
Use this to investigate flaky tests or understand test stability.`
|
|
568
1123
|
};
|
|
569
1124
|
|
|
1125
|
+
// src/tools/get-test-run-details.ts
|
|
1126
|
+
import { z as z11 } from "zod";
|
|
1127
|
+
var getTestRunDetailsInputSchema = {
|
|
1128
|
+
testRunId: z11.string().describe("The test run ID to get details for. Use list_test_runs to find test run IDs."),
|
|
1129
|
+
projectId: z11.string().describe("Project ID the test run belongs to. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
1130
|
+
status: z11.enum(["passed", "failed", "skipped"]).optional().describe("Filter tests by status. Returns only tests matching this status."),
|
|
1131
|
+
limit: z11.number().int().min(1).max(500).optional().describe("Maximum number of tests to return (default: 100, max: 500)"),
|
|
1132
|
+
offset: z11.number().int().min(0).optional().describe("Number of tests to skip for pagination (default: 0)")
|
|
1133
|
+
};
|
|
1134
|
+
var getTestRunDetailsOutputSchema = {
|
|
1135
|
+
testRunId: z11.string(),
|
|
1136
|
+
summary: z11.object({
|
|
1137
|
+
passed: z11.number(),
|
|
1138
|
+
failed: z11.number(),
|
|
1139
|
+
skipped: z11.number(),
|
|
1140
|
+
total: z11.number()
|
|
1141
|
+
}),
|
|
1142
|
+
tests: z11.array(z11.object({
|
|
1143
|
+
name: z11.string(),
|
|
1144
|
+
fullName: z11.string(),
|
|
1145
|
+
status: z11.enum(["passed", "failed", "skipped"]),
|
|
1146
|
+
durationMs: z11.number().nullable(),
|
|
1147
|
+
filePath: z11.string().nullable(),
|
|
1148
|
+
error: z11.string().nullable()
|
|
1149
|
+
})),
|
|
1150
|
+
pagination: z11.object({
|
|
1151
|
+
total: z11.number(),
|
|
1152
|
+
limit: z11.number(),
|
|
1153
|
+
offset: z11.number(),
|
|
1154
|
+
hasMore: z11.boolean()
|
|
1155
|
+
})
|
|
1156
|
+
};
|
|
1157
|
+
async function executeGetTestRunDetails(client, input) {
|
|
1158
|
+
const response = await client.getTestRunDetails({
|
|
1159
|
+
projectId: input.projectId,
|
|
1160
|
+
testRunId: input.testRunId,
|
|
1161
|
+
status: input.status,
|
|
1162
|
+
limit: input.limit,
|
|
1163
|
+
offset: input.offset
|
|
1164
|
+
});
|
|
1165
|
+
return {
|
|
1166
|
+
testRunId: response.testRunId,
|
|
1167
|
+
summary: response.summary,
|
|
1168
|
+
tests: response.tests,
|
|
1169
|
+
pagination: response.pagination
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
var getTestRunDetailsMetadata = {
|
|
1173
|
+
name: "get_test_run_details",
|
|
1174
|
+
title: "Get Test Run Details",
|
|
1175
|
+
description: `Get parsed test results for a specific test run.
|
|
1176
|
+
|
|
1177
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
1178
|
+
Use list_projects to find available project IDs, and list_test_runs to find test run IDs.
|
|
1179
|
+
|
|
1180
|
+
Parameters:
|
|
1181
|
+
- testRunId (required): The test run ID to get details for
|
|
1182
|
+
- projectId (required): Project ID the test run belongs to
|
|
1183
|
+
- status (optional): Filter by test status: "passed", "failed", or "skipped"
|
|
1184
|
+
- limit (optional): Max tests to return (default: 100, max: 500)
|
|
1185
|
+
- offset (optional): Pagination offset (default: 0)
|
|
1186
|
+
|
|
1187
|
+
Returns:
|
|
1188
|
+
- testRunId: The test run ID
|
|
1189
|
+
- summary: Overall counts (passed, failed, skipped, total)
|
|
1190
|
+
- tests: Array of individual test results with:
|
|
1191
|
+
- name: Short test name
|
|
1192
|
+
- fullName: Full test name including describe blocks
|
|
1193
|
+
- status: Test status (passed, failed, skipped)
|
|
1194
|
+
- durationMs: Test duration in milliseconds (null if not recorded)
|
|
1195
|
+
- filePath: Test file path (null if not recorded)
|
|
1196
|
+
- error: Error message for failed tests (null otherwise)
|
|
1197
|
+
- pagination: Pagination info (total, limit, offset, hasMore)
|
|
1198
|
+
|
|
1199
|
+
Use cases:
|
|
1200
|
+
- "Show me all failed tests from this test run"
|
|
1201
|
+
- "Get the test results from commit abc123"
|
|
1202
|
+
- "List tests that took the longest in this run"
|
|
1203
|
+
- "Find tests with errors in the auth module"
|
|
1204
|
+
|
|
1205
|
+
Note: For aggregate analytics like flaky test detection or duration trends,
|
|
1206
|
+
use get_test_history, get_flaky_tests, or get_slowest_tests instead.`
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/tools/get-untested-files.ts
|
|
1210
|
+
import { z as z12 } from "zod";
|
|
1211
|
+
var getUntestedFilesInputSchema = {
|
|
1212
|
+
projectId: z12.string().describe("Project ID to analyze. Required. Use list_projects to find project IDs."),
|
|
1213
|
+
maxCoverage: z12.number().min(0).max(100).optional().describe('Maximum coverage percentage to include (default: 10 for "untested")'),
|
|
1214
|
+
limit: z12.number().int().min(1).max(100).optional().describe("Maximum number of files to return (default: 20)")
|
|
1215
|
+
};
|
|
1216
|
+
var getUntestedFilesOutputSchema = {
|
|
1217
|
+
hasCoverage: z12.boolean(),
|
|
1218
|
+
files: z12.array(z12.object({
|
|
1219
|
+
path: z12.string(),
|
|
1220
|
+
lines: z12.object({
|
|
1221
|
+
covered: z12.number(),
|
|
1222
|
+
total: z12.number(),
|
|
1223
|
+
percentage: z12.number()
|
|
1224
|
+
}),
|
|
1225
|
+
branches: z12.object({
|
|
1226
|
+
covered: z12.number(),
|
|
1227
|
+
total: z12.number(),
|
|
1228
|
+
percentage: z12.number()
|
|
1229
|
+
}),
|
|
1230
|
+
functions: z12.object({
|
|
1231
|
+
covered: z12.number(),
|
|
1232
|
+
total: z12.number(),
|
|
1233
|
+
percentage: z12.number()
|
|
1234
|
+
})
|
|
1235
|
+
})),
|
|
1236
|
+
totalCount: z12.number(),
|
|
1237
|
+
message: z12.string().optional()
|
|
1238
|
+
};
|
|
1239
|
+
async function executeGetUntestedFiles(client, input) {
|
|
1240
|
+
const maxCoverage = input.maxCoverage ?? 10;
|
|
1241
|
+
const limit = input.limit ?? 20;
|
|
1242
|
+
const response = await client.getCoverageFiles({
|
|
1243
|
+
projectId: input.projectId,
|
|
1244
|
+
maxCoverage,
|
|
1245
|
+
limit,
|
|
1246
|
+
sortBy: "coverage",
|
|
1247
|
+
sortOrder: "asc"
|
|
1248
|
+
// Lowest coverage first
|
|
1249
|
+
});
|
|
1250
|
+
return {
|
|
1251
|
+
hasCoverage: response.hasCoverage,
|
|
1252
|
+
files: response.files.map((f) => ({
|
|
1253
|
+
path: f.path,
|
|
1254
|
+
lines: f.lines,
|
|
1255
|
+
branches: f.branches,
|
|
1256
|
+
functions: f.functions
|
|
1257
|
+
})),
|
|
1258
|
+
totalCount: response.pagination.total,
|
|
1259
|
+
message: response.message
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
var getUntestedFilesMetadata = {
|
|
1263
|
+
name: "get_untested_files",
|
|
1264
|
+
title: "Get Untested Files",
|
|
1265
|
+
description: `Get files with little or no test coverage.
|
|
1266
|
+
|
|
1267
|
+
Returns files sorted by coverage percentage (lowest first), filtered
|
|
1268
|
+
to only include files below a coverage threshold.
|
|
1269
|
+
|
|
1270
|
+
When using a user API Key (gaf_), you must provide a projectId.
|
|
1271
|
+
Use list_projects first to find available project IDs.
|
|
1272
|
+
|
|
1273
|
+
Parameters:
|
|
1274
|
+
- projectId: The project to analyze (required)
|
|
1275
|
+
- maxCoverage: Include files with coverage at or below this % (default: 10)
|
|
1276
|
+
- limit: Maximum number of files to return (default: 20, max: 100)
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
- List of files sorted by coverage (lowest first)
|
|
1280
|
+
- Each file includes line/branch/function coverage metrics
|
|
1281
|
+
- Total count of files matching the criteria
|
|
1282
|
+
|
|
1283
|
+
Use this to find files that need tests added.`
|
|
1284
|
+
};
|
|
1285
|
+
|
|
570
1286
|
// src/tools/list-projects.ts
|
|
571
|
-
import { z as
|
|
1287
|
+
import { z as z13 } from "zod";
|
|
572
1288
|
var listProjectsInputSchema = {
|
|
573
|
-
organizationId:
|
|
574
|
-
limit:
|
|
1289
|
+
organizationId: z13.string().optional().describe("Filter by organization ID (optional)"),
|
|
1290
|
+
limit: z13.number().int().min(1).max(100).optional().describe("Maximum number of projects to return (default: 50)")
|
|
575
1291
|
};
|
|
576
1292
|
var listProjectsOutputSchema = {
|
|
577
|
-
projects:
|
|
578
|
-
id:
|
|
579
|
-
name:
|
|
580
|
-
description:
|
|
581
|
-
organization:
|
|
582
|
-
id:
|
|
583
|
-
name:
|
|
584
|
-
slug:
|
|
1293
|
+
projects: z13.array(z13.object({
|
|
1294
|
+
id: z13.string(),
|
|
1295
|
+
name: z13.string(),
|
|
1296
|
+
description: z13.string().nullable().optional(),
|
|
1297
|
+
organization: z13.object({
|
|
1298
|
+
id: z13.string(),
|
|
1299
|
+
name: z13.string(),
|
|
1300
|
+
slug: z13.string()
|
|
585
1301
|
})
|
|
586
1302
|
})),
|
|
587
|
-
total:
|
|
1303
|
+
total: z13.number()
|
|
588
1304
|
};
|
|
589
1305
|
async function executeListProjects(client, input) {
|
|
590
1306
|
const response = await client.listProjects({
|
|
@@ -613,28 +1329,28 @@ Requires a user API Key (gaf_). Get one from Account Settings in the Gaffer dash
|
|
|
613
1329
|
};
|
|
614
1330
|
|
|
615
1331
|
// src/tools/list-test-runs.ts
|
|
616
|
-
import { z as
|
|
1332
|
+
import { z as z14 } from "zod";
|
|
617
1333
|
var listTestRunsInputSchema = {
|
|
618
|
-
projectId:
|
|
619
|
-
commitSha:
|
|
620
|
-
branch:
|
|
621
|
-
status:
|
|
622
|
-
limit:
|
|
1334
|
+
projectId: z14.string().optional().describe("Project ID to list test runs for. Required when using a user API Key (gaf_). Use list_projects to find project IDs."),
|
|
1335
|
+
commitSha: z14.string().optional().describe("Filter by commit SHA (exact or prefix match)"),
|
|
1336
|
+
branch: z14.string().optional().describe("Filter by branch name"),
|
|
1337
|
+
status: z14.enum(["passed", "failed"]).optional().describe('Filter by status: "passed" (no failures) or "failed" (has failures)'),
|
|
1338
|
+
limit: z14.number().int().min(1).max(100).optional().describe("Maximum number of test runs to return (default: 20)")
|
|
623
1339
|
};
|
|
624
1340
|
var listTestRunsOutputSchema = {
|
|
625
|
-
testRuns:
|
|
626
|
-
id:
|
|
627
|
-
commitSha:
|
|
628
|
-
branch:
|
|
629
|
-
passedCount:
|
|
630
|
-
failedCount:
|
|
631
|
-
skippedCount:
|
|
632
|
-
totalCount:
|
|
633
|
-
createdAt:
|
|
1341
|
+
testRuns: z14.array(z14.object({
|
|
1342
|
+
id: z14.string(),
|
|
1343
|
+
commitSha: z14.string().optional(),
|
|
1344
|
+
branch: z14.string().optional(),
|
|
1345
|
+
passedCount: z14.number(),
|
|
1346
|
+
failedCount: z14.number(),
|
|
1347
|
+
skippedCount: z14.number(),
|
|
1348
|
+
totalCount: z14.number(),
|
|
1349
|
+
createdAt: z14.string()
|
|
634
1350
|
})),
|
|
635
|
-
pagination:
|
|
636
|
-
total:
|
|
637
|
-
hasMore:
|
|
1351
|
+
pagination: z14.object({
|
|
1352
|
+
total: z14.number(),
|
|
1353
|
+
hasMore: z14.boolean()
|
|
638
1354
|
})
|
|
639
1355
|
};
|
|
640
1356
|
async function executeListTestRuns(client, input) {
|
|
@@ -691,6 +1407,23 @@ Use cases:
|
|
|
691
1407
|
};
|
|
692
1408
|
|
|
693
1409
|
// src/index.ts
|
|
1410
|
+
function logError(toolName, error) {
|
|
1411
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1412
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1413
|
+
const stack = error instanceof Error ? error.stack : void 0;
|
|
1414
|
+
console.error(`[${timestamp}] [gaffer-mcp] ${toolName} failed: ${message}`);
|
|
1415
|
+
if (stack) {
|
|
1416
|
+
console.error(stack);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
function handleToolError(toolName, error) {
|
|
1420
|
+
logError(toolName, error);
|
|
1421
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1422
|
+
return {
|
|
1423
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1424
|
+
isError: true
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
694
1427
|
async function main() {
|
|
695
1428
|
const apiKey = process.env.GAFFER_API_KEY;
|
|
696
1429
|
if (!apiKey) {
|
|
@@ -733,11 +1466,7 @@ async function main() {
|
|
|
733
1466
|
structuredContent: output
|
|
734
1467
|
};
|
|
735
1468
|
} catch (error) {
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
739
|
-
isError: true
|
|
740
|
-
};
|
|
1469
|
+
return handleToolError(getProjectHealthMetadata.name, error);
|
|
741
1470
|
}
|
|
742
1471
|
}
|
|
743
1472
|
);
|
|
@@ -757,11 +1486,7 @@ async function main() {
|
|
|
757
1486
|
structuredContent: output
|
|
758
1487
|
};
|
|
759
1488
|
} catch (error) {
|
|
760
|
-
|
|
761
|
-
return {
|
|
762
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
763
|
-
isError: true
|
|
764
|
-
};
|
|
1489
|
+
return handleToolError(getTestHistoryMetadata.name, error);
|
|
765
1490
|
}
|
|
766
1491
|
}
|
|
767
1492
|
);
|
|
@@ -781,11 +1506,7 @@ async function main() {
|
|
|
781
1506
|
structuredContent: output
|
|
782
1507
|
};
|
|
783
1508
|
} catch (error) {
|
|
784
|
-
|
|
785
|
-
return {
|
|
786
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
787
|
-
isError: true
|
|
788
|
-
};
|
|
1509
|
+
return handleToolError(getFlakyTestsMetadata.name, error);
|
|
789
1510
|
}
|
|
790
1511
|
}
|
|
791
1512
|
);
|
|
@@ -805,11 +1526,7 @@ async function main() {
|
|
|
805
1526
|
structuredContent: output
|
|
806
1527
|
};
|
|
807
1528
|
} catch (error) {
|
|
808
|
-
|
|
809
|
-
return {
|
|
810
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
811
|
-
isError: true
|
|
812
|
-
};
|
|
1529
|
+
return handleToolError(listTestRunsMetadata.name, error);
|
|
813
1530
|
}
|
|
814
1531
|
}
|
|
815
1532
|
);
|
|
@@ -829,11 +1546,7 @@ async function main() {
|
|
|
829
1546
|
structuredContent: output
|
|
830
1547
|
};
|
|
831
1548
|
} catch (error) {
|
|
832
|
-
|
|
833
|
-
return {
|
|
834
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
835
|
-
isError: true
|
|
836
|
-
};
|
|
1549
|
+
return handleToolError(listProjectsMetadata.name, error);
|
|
837
1550
|
}
|
|
838
1551
|
}
|
|
839
1552
|
);
|
|
@@ -853,11 +1566,7 @@ async function main() {
|
|
|
853
1566
|
structuredContent: output
|
|
854
1567
|
};
|
|
855
1568
|
} catch (error) {
|
|
856
|
-
|
|
857
|
-
return {
|
|
858
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
859
|
-
isError: true
|
|
860
|
-
};
|
|
1569
|
+
return handleToolError(getReportMetadata.name, error);
|
|
861
1570
|
}
|
|
862
1571
|
}
|
|
863
1572
|
);
|
|
@@ -877,11 +1586,147 @@ async function main() {
|
|
|
877
1586
|
structuredContent: output
|
|
878
1587
|
};
|
|
879
1588
|
} catch (error) {
|
|
880
|
-
|
|
1589
|
+
return handleToolError(getSlowestTestsMetadata.name, error);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1593
|
+
server.registerTool(
|
|
1594
|
+
getTestRunDetailsMetadata.name,
|
|
1595
|
+
{
|
|
1596
|
+
title: getTestRunDetailsMetadata.title,
|
|
1597
|
+
description: getTestRunDetailsMetadata.description,
|
|
1598
|
+
inputSchema: getTestRunDetailsInputSchema,
|
|
1599
|
+
outputSchema: getTestRunDetailsOutputSchema
|
|
1600
|
+
},
|
|
1601
|
+
async (input) => {
|
|
1602
|
+
try {
|
|
1603
|
+
const output = await executeGetTestRunDetails(client, input);
|
|
1604
|
+
return {
|
|
1605
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1606
|
+
structuredContent: output
|
|
1607
|
+
};
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
return handleToolError(getTestRunDetailsMetadata.name, error);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
);
|
|
1613
|
+
server.registerTool(
|
|
1614
|
+
compareTestMetricsMetadata.name,
|
|
1615
|
+
{
|
|
1616
|
+
title: compareTestMetricsMetadata.title,
|
|
1617
|
+
description: compareTestMetricsMetadata.description,
|
|
1618
|
+
inputSchema: compareTestMetricsInputSchema,
|
|
1619
|
+
outputSchema: compareTestMetricsOutputSchema
|
|
1620
|
+
},
|
|
1621
|
+
async (input) => {
|
|
1622
|
+
try {
|
|
1623
|
+
const output = await executeCompareTestMetrics(client, input);
|
|
1624
|
+
return {
|
|
1625
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1626
|
+
structuredContent: output
|
|
1627
|
+
};
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
return handleToolError(compareTestMetricsMetadata.name, error);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
);
|
|
1633
|
+
server.registerTool(
|
|
1634
|
+
getCoverageSummaryMetadata.name,
|
|
1635
|
+
{
|
|
1636
|
+
title: getCoverageSummaryMetadata.title,
|
|
1637
|
+
description: getCoverageSummaryMetadata.description,
|
|
1638
|
+
inputSchema: getCoverageSummaryInputSchema,
|
|
1639
|
+
outputSchema: getCoverageSummaryOutputSchema
|
|
1640
|
+
},
|
|
1641
|
+
async (input) => {
|
|
1642
|
+
try {
|
|
1643
|
+
const output = await executeGetCoverageSummary(client, input);
|
|
1644
|
+
return {
|
|
1645
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1646
|
+
structuredContent: output
|
|
1647
|
+
};
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
return handleToolError(getCoverageSummaryMetadata.name, error);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
);
|
|
1653
|
+
server.registerTool(
|
|
1654
|
+
getCoverageForFileMetadata.name,
|
|
1655
|
+
{
|
|
1656
|
+
title: getCoverageForFileMetadata.title,
|
|
1657
|
+
description: getCoverageForFileMetadata.description,
|
|
1658
|
+
inputSchema: getCoverageForFileInputSchema,
|
|
1659
|
+
outputSchema: getCoverageForFileOutputSchema
|
|
1660
|
+
},
|
|
1661
|
+
async (input) => {
|
|
1662
|
+
try {
|
|
1663
|
+
const output = await executeGetCoverageForFile(client, input);
|
|
1664
|
+
return {
|
|
1665
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1666
|
+
structuredContent: output
|
|
1667
|
+
};
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
return handleToolError(getCoverageForFileMetadata.name, error);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
);
|
|
1673
|
+
server.registerTool(
|
|
1674
|
+
findUncoveredFailureAreasMetadata.name,
|
|
1675
|
+
{
|
|
1676
|
+
title: findUncoveredFailureAreasMetadata.title,
|
|
1677
|
+
description: findUncoveredFailureAreasMetadata.description,
|
|
1678
|
+
inputSchema: findUncoveredFailureAreasInputSchema,
|
|
1679
|
+
outputSchema: findUncoveredFailureAreasOutputSchema
|
|
1680
|
+
},
|
|
1681
|
+
async (input) => {
|
|
1682
|
+
try {
|
|
1683
|
+
const output = await executeFindUncoveredFailureAreas(client, input);
|
|
1684
|
+
return {
|
|
1685
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1686
|
+
structuredContent: output
|
|
1687
|
+
};
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
return handleToolError(findUncoveredFailureAreasMetadata.name, error);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
);
|
|
1693
|
+
server.registerTool(
|
|
1694
|
+
getUntestedFilesMetadata.name,
|
|
1695
|
+
{
|
|
1696
|
+
title: getUntestedFilesMetadata.title,
|
|
1697
|
+
description: getUntestedFilesMetadata.description,
|
|
1698
|
+
inputSchema: getUntestedFilesInputSchema,
|
|
1699
|
+
outputSchema: getUntestedFilesOutputSchema
|
|
1700
|
+
},
|
|
1701
|
+
async (input) => {
|
|
1702
|
+
try {
|
|
1703
|
+
const output = await executeGetUntestedFiles(client, input);
|
|
1704
|
+
return {
|
|
1705
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1706
|
+
structuredContent: output
|
|
1707
|
+
};
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
return handleToolError(getUntestedFilesMetadata.name, error);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
);
|
|
1713
|
+
server.registerTool(
|
|
1714
|
+
getReportBrowserUrlMetadata.name,
|
|
1715
|
+
{
|
|
1716
|
+
title: getReportBrowserUrlMetadata.title,
|
|
1717
|
+
description: getReportBrowserUrlMetadata.description,
|
|
1718
|
+
inputSchema: getReportBrowserUrlInputSchema,
|
|
1719
|
+
outputSchema: getReportBrowserUrlOutputSchema
|
|
1720
|
+
},
|
|
1721
|
+
async (input) => {
|
|
1722
|
+
try {
|
|
1723
|
+
const output = await executeGetReportBrowserUrl(client, input);
|
|
881
1724
|
return {
|
|
882
|
-
content: [{ type: "text", text:
|
|
883
|
-
|
|
1725
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1726
|
+
structuredContent: output
|
|
884
1727
|
};
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
return handleToolError(getReportBrowserUrlMetadata.name, error);
|
|
885
1730
|
}
|
|
886
1731
|
}
|
|
887
1732
|
);
|