@convisoappsec/mcp 0.3.1 → 0.5.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/README.md +19 -1
- package/package.json +8 -4
- package/src/conviso_mcp/feed_gateway.js +130 -12
- package/src/conviso_mcp/filters.js +54 -0
- package/src/conviso_mcp/graphql_client.js +527 -114
- package/src/conviso_mcp/mutations.js +117 -0
- package/src/conviso_mcp/mutations_catalog.json +3174 -0
- package/src/conviso_mcp/operation_allowlist.js +74 -0
- package/src/conviso_mcp/server.js +933 -21
|
@@ -16,7 +16,7 @@ console.error('[+] Starting Conviso MCP Server (MCP SDK)');
|
|
|
16
16
|
|
|
17
17
|
const server = new McpServer({
|
|
18
18
|
name: pkg.name || 'conviso-mcp',
|
|
19
|
-
version: pkg.version || '0.
|
|
19
|
+
version: pkg.version || '0.4.0',
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
function sanitizeError(err, message = 'Request failed') {
|
|
@@ -39,7 +39,13 @@ function sanitizeError(err, message = 'Request failed') {
|
|
|
39
39
|
status,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
const result = { error: message, status, error_id };
|
|
43
|
+
// GraphQL errors describe the caller's own request (e.g. a missing required field) — pass
|
|
44
|
+
// them through so the model can fix the input on the next attempt.
|
|
45
|
+
if (Array.isArray(err?.graphqlErrors) && err.graphqlErrors.length) {
|
|
46
|
+
result.details = err.graphqlErrors;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
function ok(data) {
|
|
@@ -57,14 +63,19 @@ function fail(err, msg) {
|
|
|
57
63
|
return ok(sanitizeError(err, msg));
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
// Annotation presets to keep tool definitions terse.
|
|
67
|
+
const READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
68
|
+
const WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
69
|
+
|
|
60
70
|
server.registerTool(
|
|
61
71
|
'get_companies',
|
|
62
72
|
{
|
|
63
|
-
description: 'Return a paginated list of companies accessible with the provided API key.
|
|
73
|
+
description: 'Return a paginated list of companies accessible with the provided API key. search = name contains; label_eq = exact name match.',
|
|
64
74
|
inputSchema: z.object({
|
|
65
75
|
page: z.number().optional(),
|
|
66
76
|
limit: z.number().optional(),
|
|
67
77
|
search: z.string().optional(),
|
|
78
|
+
label_eq: z.string().optional(),
|
|
68
79
|
}),
|
|
69
80
|
annotations: {
|
|
70
81
|
title: 'List Companies',
|
|
@@ -74,9 +85,9 @@ server.registerTool(
|
|
|
74
85
|
openWorldHint: true,
|
|
75
86
|
},
|
|
76
87
|
},
|
|
77
|
-
async ({ page = 1, limit = 10, search = '' }) => {
|
|
88
|
+
async ({ page = 1, limit = 10, search = '', label_eq = null }) => {
|
|
78
89
|
try {
|
|
79
|
-
return ok(await gateway.get_companies(page, limit, search));
|
|
90
|
+
return ok(await gateway.get_companies(page, limit, search, label_eq));
|
|
80
91
|
} catch (err) {
|
|
81
92
|
return fail(err, 'Failed to list companies');
|
|
82
93
|
}
|
|
@@ -133,26 +144,217 @@ server.registerTool(
|
|
|
133
144
|
server.registerTool(
|
|
134
145
|
'get_issues',
|
|
135
146
|
{
|
|
136
|
-
description:
|
|
147
|
+
description: `Get issues (vulnerabilities) for a company, with rich filtering and sorting.
|
|
148
|
+
|
|
149
|
+
Filters (all optional):
|
|
150
|
+
- search: substring match on issue title.
|
|
151
|
+
- severities: any of NOTIFICATION, LOW, MEDIUM, HIGH, CRITICAL.
|
|
152
|
+
- statuses: any of CREATED, DRAFT, IDENTIFIED, IN_PROGRESS, AWAITING_VALIDATION,
|
|
153
|
+
FIX_ACCEPTED, RISK_ACCEPTED, FALSE_POSITIVE, SUPPRESSED.
|
|
154
|
+
- sla_states: any of ON_TRACK, APPROACHING, BREACHED, RESOLVED, NOT_TRACKED, NOT_PARAMETERIZED.
|
|
155
|
+
- created_after / created_before: ISO8601 dates (YYYY-MM-DD). For relative ranges
|
|
156
|
+
("last 30 days") call get_today_date first and compute the bounds.
|
|
157
|
+
- assignee_emails: list of assignee emails.
|
|
158
|
+
- project_id: restrict to one project. asset filtering: use get_issues_by_asset_id.
|
|
159
|
+
- sort_by: one of RISK_SCORE, SEVERITY, ID, CREATED_AT, UPDATED_AT, SLA_DUE_AT. order: ASC or DESC.
|
|
160
|
+
- extra_filters: dict mapping directly to IssuesFiltersInput for advanced keys, e.g.
|
|
161
|
+
{"cves": [...], "categories": [...], "reachableBy": ["STATIC_ANALYSIS"],
|
|
162
|
+
"businessImpact": ["HIGH"], "exploitability": "INTERNET_FACING",
|
|
163
|
+
"compromisedEnvironment": true, "aiFpAnalyzed": true, "assetTags": [...]}.
|
|
164
|
+
extra_filters values are sent as-is — omit a key rather than passing an empty list.
|
|
165
|
+
|
|
166
|
+
Returns issue collection (id, title, severity, status, dates, sla, assignedUsers,
|
|
167
|
+
asset, project) plus metadata (totalCount, totalPages, currentPage) for pagination.`,
|
|
168
|
+
inputSchema: z.object({
|
|
169
|
+
company_id: z.number(),
|
|
170
|
+
page: z.number().optional(),
|
|
171
|
+
limit: z.number().optional(),
|
|
172
|
+
project_id: z.number().optional(),
|
|
173
|
+
search: z.string().optional(),
|
|
174
|
+
severities: z.array(z.string()).optional(),
|
|
175
|
+
statuses: z.array(z.string()).optional(),
|
|
176
|
+
sla_states: z.array(z.string()).optional(),
|
|
177
|
+
created_after: z.string().optional(),
|
|
178
|
+
created_before: z.string().optional(),
|
|
179
|
+
assignee_emails: z.array(z.string()).optional(),
|
|
180
|
+
sort_by: z.string().optional(),
|
|
181
|
+
order: z.string().optional(),
|
|
182
|
+
extra_filters: z.record(z.string(), z.any()).optional(),
|
|
183
|
+
}),
|
|
184
|
+
annotations: {
|
|
185
|
+
title: 'List Issues',
|
|
186
|
+
readOnlyHint: true,
|
|
187
|
+
destructiveHint: false,
|
|
188
|
+
idempotentHint: true,
|
|
189
|
+
openWorldHint: true,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
async ({
|
|
193
|
+
company_id, page = 1, limit = 10, project_id, search = '',
|
|
194
|
+
severities, statuses, sla_states, created_after, created_before,
|
|
195
|
+
assignee_emails, sort_by, order = 'DESC', extra_filters,
|
|
196
|
+
}) => {
|
|
197
|
+
try {
|
|
198
|
+
return ok(await gateway.getIssues(company_id, {
|
|
199
|
+
page,
|
|
200
|
+
limit,
|
|
201
|
+
projectId: project_id,
|
|
202
|
+
search,
|
|
203
|
+
severities,
|
|
204
|
+
statuses,
|
|
205
|
+
slaStates: sla_states,
|
|
206
|
+
createdAfter: created_after,
|
|
207
|
+
createdBefore: created_before,
|
|
208
|
+
assigneeEmails: assignee_emails,
|
|
209
|
+
sortBy: sort_by,
|
|
210
|
+
order,
|
|
211
|
+
extraFilters: extra_filters,
|
|
212
|
+
}));
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return fail(err, 'Failed to list issues');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
server.registerTool(
|
|
220
|
+
'get_issues_by_asset_id',
|
|
221
|
+
{
|
|
222
|
+
description: `List vulnerabilities for a company filtered by a single asset ID, with rich filtering and sorting.
|
|
223
|
+
|
|
224
|
+
Filters (all optional):
|
|
225
|
+
- search: substring match on issue title.
|
|
226
|
+
- severities: any of NOTIFICATION, LOW, MEDIUM, HIGH, CRITICAL.
|
|
227
|
+
- statuses: any of CREATED, DRAFT, IDENTIFIED, IN_PROGRESS, AWAITING_VALIDATION,
|
|
228
|
+
FIX_ACCEPTED, RISK_ACCEPTED, FALSE_POSITIVE, SUPPRESSED.
|
|
229
|
+
- sla_states: any of ON_TRACK, APPROACHING, BREACHED, RESOLVED, NOT_TRACKED, NOT_PARAMETERIZED.
|
|
230
|
+
- created_after / created_before: ISO8601 dates (YYYY-MM-DD). For relative ranges
|
|
231
|
+
("last 30 days") call get_today_date first and compute the bounds.
|
|
232
|
+
- assignee_emails: list of assignee emails.
|
|
233
|
+
- sort_by: one of RISK_SCORE, SEVERITY, ID, CREATED_AT, UPDATED_AT, SLA_DUE_AT. order: ASC or DESC.
|
|
234
|
+
- extra_filters: dict mapping directly to IssuesFiltersInput for advanced keys, e.g.
|
|
235
|
+
{"cves": [...], "categories": [...], "reachableBy": ["STATIC_ANALYSIS"],
|
|
236
|
+
"businessImpact": ["HIGH"], "exploitability": "INTERNET_FACING",
|
|
237
|
+
"compromisedEnvironment": true, "aiFpAnalyzed": true, "assetTags": [...]}.
|
|
238
|
+
|
|
239
|
+
Returns issue collection (id, title, severity, status, dates, sla, assignedUsers,
|
|
240
|
+
asset, project) plus metadata (totalCount, totalPages, currentPage) for pagination.`,
|
|
137
241
|
inputSchema: z.object({
|
|
138
242
|
company_id: z.number(),
|
|
243
|
+
asset_id: z.number(),
|
|
139
244
|
page: z.number().optional(),
|
|
140
245
|
limit: z.number().optional(),
|
|
141
|
-
|
|
246
|
+
search: z.string().optional(),
|
|
247
|
+
severities: z.array(z.string()).optional(),
|
|
248
|
+
statuses: z.array(z.string()).optional(),
|
|
249
|
+
sla_states: z.array(z.string()).optional(),
|
|
250
|
+
created_after: z.string().optional(),
|
|
251
|
+
created_before: z.string().optional(),
|
|
252
|
+
assignee_emails: z.array(z.string()).optional(),
|
|
253
|
+
sort_by: z.string().optional(),
|
|
254
|
+
order: z.string().optional(),
|
|
255
|
+
extra_filters: z.record(z.string(), z.any()).optional(),
|
|
142
256
|
}),
|
|
143
257
|
annotations: {
|
|
144
|
-
title: 'List Issues',
|
|
258
|
+
title: 'List Issues by Asset ID',
|
|
145
259
|
readOnlyHint: true,
|
|
146
260
|
destructiveHint: false,
|
|
147
261
|
idempotentHint: true,
|
|
148
262
|
openWorldHint: true,
|
|
149
263
|
},
|
|
150
264
|
},
|
|
151
|
-
async ({
|
|
265
|
+
async ({
|
|
266
|
+
company_id, asset_id, page = 1, limit = 10, search = '',
|
|
267
|
+
severities, statuses, sla_states, created_after, created_before,
|
|
268
|
+
assignee_emails, sort_by, order = 'DESC', extra_filters,
|
|
269
|
+
}) => {
|
|
152
270
|
try {
|
|
153
|
-
|
|
271
|
+
const asset_ids = Array.isArray(asset_id) ? asset_id : [asset_id];
|
|
272
|
+
return ok(await gateway.get_issues_by_asset_ids(company_id, page, limit, asset_ids, search, {
|
|
273
|
+
severities,
|
|
274
|
+
statuses,
|
|
275
|
+
slaStates: sla_states,
|
|
276
|
+
createdAfter: created_after,
|
|
277
|
+
createdBefore: created_before,
|
|
278
|
+
assigneeEmails: assignee_emails,
|
|
279
|
+
sortBy: sort_by,
|
|
280
|
+
order,
|
|
281
|
+
extraFilters: extra_filters,
|
|
282
|
+
}));
|
|
154
283
|
} catch (err) {
|
|
155
|
-
return fail(err, 'Failed to list issues');
|
|
284
|
+
return fail(err, 'Failed to list issues by asset id');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
server.registerTool(
|
|
290
|
+
'get_issues_by_project_id',
|
|
291
|
+
{
|
|
292
|
+
description: `List vulnerabilities for a company filtered by a project ID, with rich filtering and sorting.
|
|
293
|
+
|
|
294
|
+
Filters (all optional):
|
|
295
|
+
- search: substring match on issue title.
|
|
296
|
+
- severities: any of NOTIFICATION, LOW, MEDIUM, HIGH, CRITICAL.
|
|
297
|
+
- statuses: any of CREATED, DRAFT, IDENTIFIED, IN_PROGRESS, AWAITING_VALIDATION,
|
|
298
|
+
FIX_ACCEPTED, RISK_ACCEPTED, FALSE_POSITIVE, SUPPRESSED.
|
|
299
|
+
- sla_states: any of ON_TRACK, APPROACHING, BREACHED, RESOLVED, NOT_TRACKED, NOT_PARAMETERIZED.
|
|
300
|
+
- created_after / created_before: ISO8601 dates (YYYY-MM-DD). For relative ranges
|
|
301
|
+
("last 30 days") call get_today_date first and compute the bounds.
|
|
302
|
+
- assignee_emails: list of assignee emails.
|
|
303
|
+
- sort_by: one of RISK_SCORE, SEVERITY, ID, CREATED_AT, UPDATED_AT, SLA_DUE_AT. order: ASC or DESC.
|
|
304
|
+
- extra_filters: dict mapping directly to IssuesFiltersInput for advanced keys, e.g.
|
|
305
|
+
{"cves": [...], "categories": [...], "reachableBy": ["STATIC_ANALYSIS"],
|
|
306
|
+
"businessImpact": ["HIGH"], "exploitability": "INTERNET_FACING",
|
|
307
|
+
"compromisedEnvironment": true, "aiFpAnalyzed": true, "assetTags": [...]}.
|
|
308
|
+
|
|
309
|
+
Returns issue collection (id, title, severity, status, dates, sla, assignedUsers,
|
|
310
|
+
asset, project) plus metadata (totalCount, totalPages, currentPage) for pagination.`,
|
|
311
|
+
inputSchema: z.object({
|
|
312
|
+
company_id: z.number(),
|
|
313
|
+
project_id: z.number(),
|
|
314
|
+
page: z.number().optional(),
|
|
315
|
+
limit: z.number().optional(),
|
|
316
|
+
search: z.string().optional(),
|
|
317
|
+
severities: z.array(z.string()).optional(),
|
|
318
|
+
statuses: z.array(z.string()).optional(),
|
|
319
|
+
sla_states: z.array(z.string()).optional(),
|
|
320
|
+
created_after: z.string().optional(),
|
|
321
|
+
created_before: z.string().optional(),
|
|
322
|
+
assignee_emails: z.array(z.string()).optional(),
|
|
323
|
+
sort_by: z.string().optional(),
|
|
324
|
+
order: z.string().optional(),
|
|
325
|
+
extra_filters: z.record(z.string(), z.any()).optional(),
|
|
326
|
+
}),
|
|
327
|
+
annotations: {
|
|
328
|
+
title: 'List Issues by Project ID',
|
|
329
|
+
readOnlyHint: true,
|
|
330
|
+
destructiveHint: false,
|
|
331
|
+
idempotentHint: true,
|
|
332
|
+
openWorldHint: true,
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
async ({
|
|
336
|
+
company_id, project_id, page = 1, limit = 10, search = '',
|
|
337
|
+
severities, statuses, sla_states, created_after, created_before,
|
|
338
|
+
assignee_emails, sort_by, order = 'DESC', extra_filters,
|
|
339
|
+
}) => {
|
|
340
|
+
try {
|
|
341
|
+
return ok(await gateway.getIssues(company_id, {
|
|
342
|
+
page,
|
|
343
|
+
limit,
|
|
344
|
+
projectId: project_id,
|
|
345
|
+
search,
|
|
346
|
+
severities,
|
|
347
|
+
statuses,
|
|
348
|
+
slaStates: sla_states,
|
|
349
|
+
createdAfter: created_after,
|
|
350
|
+
createdBefore: created_before,
|
|
351
|
+
assigneeEmails: assignee_emails,
|
|
352
|
+
sortBy: sort_by,
|
|
353
|
+
order,
|
|
354
|
+
extraFilters: extra_filters,
|
|
355
|
+
}));
|
|
356
|
+
} catch (err) {
|
|
357
|
+
return fail(err, 'Failed to list issues by project id');
|
|
156
358
|
}
|
|
157
359
|
}
|
|
158
360
|
);
|
|
@@ -160,8 +362,21 @@ server.registerTool(
|
|
|
160
362
|
server.registerTool(
|
|
161
363
|
'get_top_vulnerabilities',
|
|
162
364
|
{
|
|
163
|
-
description: 'Return a summary of vulnerability counts grouped by severity for a given company (risk overview).'
|
|
164
|
-
|
|
365
|
+
description: 'Return a summary of vulnerability counts grouped by severity for a given company (risk overview). '
|
|
366
|
+
+ 'Optional filters (severities, statuses, asset_ids, asset_tags, created_after/created_before) narrow the '
|
|
367
|
+
+ 'overview; when none are set the response is identical to calling with no arguments at all. '
|
|
368
|
+
+ 'severities: NOTIFICATION, LOW, MEDIUM, HIGH, CRITICAL. statuses: CREATED, DRAFT, IDENTIFIED, IN_PROGRESS, '
|
|
369
|
+
+ 'AWAITING_VALIDATION, FIX_ACCEPTED, RISK_ACCEPTED, FALSE_POSITIVE, SUPPRESSED. created_after/created_before '
|
|
370
|
+
+ 'are ISO8601 dates (YYYY-MM-DD).',
|
|
371
|
+
inputSchema: z.object({
|
|
372
|
+
company_id: z.number(),
|
|
373
|
+
severities: z.array(z.string()).optional(),
|
|
374
|
+
statuses: z.array(z.string()).optional(),
|
|
375
|
+
asset_ids: z.array(z.number()).optional(),
|
|
376
|
+
asset_tags: z.array(z.string()).optional(),
|
|
377
|
+
created_after: z.string().optional(),
|
|
378
|
+
created_before: z.string().optional(),
|
|
379
|
+
}),
|
|
165
380
|
annotations: {
|
|
166
381
|
title: 'Top Vulnerabilities',
|
|
167
382
|
readOnlyHint: true,
|
|
@@ -170,9 +385,16 @@ server.registerTool(
|
|
|
170
385
|
openWorldHint: true,
|
|
171
386
|
},
|
|
172
387
|
},
|
|
173
|
-
async ({ company_id }) => {
|
|
388
|
+
async ({ company_id, severities, statuses, asset_ids, asset_tags, created_after, created_before }) => {
|
|
174
389
|
try {
|
|
175
|
-
return ok(await gateway.get_top_vulnerabilities(company_id
|
|
390
|
+
return ok(await gateway.get_top_vulnerabilities(company_id, {
|
|
391
|
+
severities,
|
|
392
|
+
statuses,
|
|
393
|
+
assetIds: asset_ids,
|
|
394
|
+
assetTags: asset_tags,
|
|
395
|
+
createdAfter: created_after,
|
|
396
|
+
createdBefore: created_before,
|
|
397
|
+
}));
|
|
176
398
|
} catch (err) {
|
|
177
399
|
return fail(err, 'Failed to get top vulnerabilities');
|
|
178
400
|
}
|
|
@@ -182,12 +404,29 @@ server.registerTool(
|
|
|
182
404
|
server.registerTool(
|
|
183
405
|
'get_projects',
|
|
184
406
|
{
|
|
185
|
-
description:
|
|
407
|
+
description: `Return a paginated list of security projects for a company, with filtering and sorting. Defaults to 25 results per page to conserve tokens.
|
|
408
|
+
|
|
409
|
+
Filters (all optional):
|
|
410
|
+
- search: substring match on project label.
|
|
411
|
+
- statuses: platform status labels (free text, e.g. "Fixing"), not an enum.
|
|
412
|
+
- project_types: platform project type labels (free text, e.g. "Pentest"), not an enum.
|
|
413
|
+
- created_after / created_before: ISO8601 dates (YYYY-MM-DD) bounding createdAt.
|
|
414
|
+
- tags: list of project tags.
|
|
415
|
+
- analyst_emails: list of allocated analyst emails.
|
|
416
|
+
- sort_by: field to sort by (default "createdAt"). descending: sort direction (default true).`,
|
|
186
417
|
inputSchema: z.object({
|
|
187
418
|
company_id: z.number(),
|
|
188
419
|
page: z.number().optional(),
|
|
189
420
|
limit: z.number().optional(),
|
|
190
421
|
search: z.string().optional(),
|
|
422
|
+
statuses: z.array(z.string()).optional(),
|
|
423
|
+
project_types: z.array(z.string()).optional(),
|
|
424
|
+
created_after: z.string().optional(),
|
|
425
|
+
created_before: z.string().optional(),
|
|
426
|
+
tags: z.array(z.string()).optional(),
|
|
427
|
+
analyst_emails: z.array(z.string()).optional(),
|
|
428
|
+
sort_by: z.string().optional(),
|
|
429
|
+
descending: z.boolean().optional(),
|
|
191
430
|
}),
|
|
192
431
|
annotations: {
|
|
193
432
|
title: 'List Projects',
|
|
@@ -197,9 +436,22 @@ server.registerTool(
|
|
|
197
436
|
openWorldHint: true,
|
|
198
437
|
},
|
|
199
438
|
},
|
|
200
|
-
async ({
|
|
439
|
+
async ({
|
|
440
|
+
company_id, page = 1, limit = 25, search = '', statuses, project_types,
|
|
441
|
+
created_after, created_before, tags, analyst_emails, sort_by = 'createdAt',
|
|
442
|
+
descending = true,
|
|
443
|
+
}) => {
|
|
201
444
|
try {
|
|
202
|
-
return ok(await gateway.get_projects(company_id, page, limit, search
|
|
445
|
+
return ok(await gateway.get_projects(company_id, page, limit, search, {
|
|
446
|
+
statuses,
|
|
447
|
+
projectTypes: project_types,
|
|
448
|
+
createdAfter: created_after,
|
|
449
|
+
createdBefore: created_before,
|
|
450
|
+
tags,
|
|
451
|
+
analystEmails: analyst_emails,
|
|
452
|
+
sortBy: sort_by,
|
|
453
|
+
descending,
|
|
454
|
+
}));
|
|
203
455
|
} catch (err) {
|
|
204
456
|
return fail(err, 'Failed to list projects');
|
|
205
457
|
}
|
|
@@ -253,11 +505,39 @@ server.registerTool(
|
|
|
253
505
|
server.registerTool(
|
|
254
506
|
'get_assets',
|
|
255
507
|
{
|
|
256
|
-
description:
|
|
508
|
+
description: `Return a paginated list of assets for a company, with rich filtering and sorting. Defaults to 25 results per page to reduce token usage.
|
|
509
|
+
|
|
510
|
+
Filters (all optional):
|
|
511
|
+
- name / search: substring match on asset name.
|
|
512
|
+
- tags: list of asset tags.
|
|
513
|
+
- technology: list of technologies.
|
|
514
|
+
- business_impact: any of LOW, MEDIUM, HIGH, NOT_DEFINED.
|
|
515
|
+
- exploitability: any of INTERNET_FACING, INTERNAL, NOT_DEFINED.
|
|
516
|
+
- asset_type: asset type filter.
|
|
517
|
+
- environment_compromised: boolean filter for compromised environment.
|
|
518
|
+
- covered_by_scan: boolean filter for scan coverage.
|
|
519
|
+
- sort_by: one of updated_at, name, business_impact, risk_score. order: ASC or DESC.
|
|
520
|
+
- extra_filters: object mapping directly to AssetsSearch for advanced keys.
|
|
521
|
+
extra_filters values are sent as-is — omit a key rather than passing an empty list.
|
|
522
|
+
|
|
523
|
+
Returns asset collection (id, name, assetType, environment, audience, dates, riskScore)
|
|
524
|
+
plus metadata (totalCount, totalPages, currentPage) for pagination.`,
|
|
257
525
|
inputSchema: z.object({
|
|
258
526
|
company_id: z.number(),
|
|
259
527
|
page: z.number().optional(),
|
|
260
528
|
limit: z.number().optional(),
|
|
529
|
+
name: z.string().optional(),
|
|
530
|
+
search: z.string().optional(),
|
|
531
|
+
tags: z.array(z.string()).optional(),
|
|
532
|
+
technology: z.array(z.string()).optional(),
|
|
533
|
+
business_impact: z.array(z.string()).optional(),
|
|
534
|
+
exploitability: z.array(z.string()).optional(),
|
|
535
|
+
asset_type: z.string().optional(),
|
|
536
|
+
environment_compromised: z.boolean().optional(),
|
|
537
|
+
covered_by_scan: z.boolean().optional(),
|
|
538
|
+
sort_by: z.string().optional(),
|
|
539
|
+
order: z.string().optional(),
|
|
540
|
+
extra_filters: z.record(z.string(), z.any()).optional(),
|
|
261
541
|
}),
|
|
262
542
|
annotations: {
|
|
263
543
|
title: 'List Assets',
|
|
@@ -267,9 +547,26 @@ server.registerTool(
|
|
|
267
547
|
openWorldHint: true,
|
|
268
548
|
},
|
|
269
549
|
},
|
|
270
|
-
async ({
|
|
550
|
+
async ({
|
|
551
|
+
company_id, page = 1, limit = 25, name, search, tags, technology,
|
|
552
|
+
business_impact, exploitability, asset_type, environment_compromised,
|
|
553
|
+
covered_by_scan, sort_by, order, extra_filters,
|
|
554
|
+
}) => {
|
|
271
555
|
try {
|
|
272
|
-
return ok(await gateway.get_assets(company_id, page, limit
|
|
556
|
+
return ok(await gateway.get_assets(company_id, page, limit, {
|
|
557
|
+
name,
|
|
558
|
+
search,
|
|
559
|
+
tags,
|
|
560
|
+
technology,
|
|
561
|
+
businessImpact: business_impact,
|
|
562
|
+
exploitability,
|
|
563
|
+
assetType: asset_type,
|
|
564
|
+
environmentCompromised: environment_compromised,
|
|
565
|
+
coveredByScan: covered_by_scan,
|
|
566
|
+
sortBy: sort_by,
|
|
567
|
+
order,
|
|
568
|
+
extraFilters: extra_filters,
|
|
569
|
+
}));
|
|
273
570
|
} catch (err) {
|
|
274
571
|
return fail(err, 'Failed to list assets');
|
|
275
572
|
}
|
|
@@ -411,6 +708,621 @@ server.registerTool(
|
|
|
411
708
|
}
|
|
412
709
|
);
|
|
413
710
|
|
|
711
|
+
/**
|
|
712
|
+
* MUTATIONS — generic catalog-driven engine (covers the allowlisted platform mutations;
|
|
713
|
+
* see operation_allowlist.js)
|
|
714
|
+
*/
|
|
715
|
+
|
|
716
|
+
server.registerTool(
|
|
717
|
+
'list_mutations',
|
|
718
|
+
{
|
|
719
|
+
description: `Discover available Conviso Platform mutations (write operations). Returns name, description, category and a destructive flag. This is step 1 of the write workflow: list_mutations (discover) -> describe_mutation (get the input schema) -> execute_mutation (run it).
|
|
720
|
+
|
|
721
|
+
Filters (all optional):
|
|
722
|
+
- search: case-insensitive substring matched against mutation name and description.
|
|
723
|
+
- category: one of issue, ticket, project, asset, requirement, pentest, application, threat_model.
|
|
724
|
+
- limit: max rows to return (default 50).`,
|
|
725
|
+
inputSchema: z.object({
|
|
726
|
+
search: z.string().optional(),
|
|
727
|
+
category: z.string().optional(),
|
|
728
|
+
limit: z.number().optional(),
|
|
729
|
+
}),
|
|
730
|
+
annotations: {
|
|
731
|
+
title: 'List Mutations',
|
|
732
|
+
readOnlyHint: true,
|
|
733
|
+
destructiveHint: false,
|
|
734
|
+
idempotentHint: true,
|
|
735
|
+
openWorldHint: false,
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
async ({ search, category, limit }) => {
|
|
739
|
+
try {
|
|
740
|
+
return ok(gateway.list_mutations({ search, category, limit }));
|
|
741
|
+
} catch (err) {
|
|
742
|
+
return fail(err, 'Failed to list mutations');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
server.registerTool(
|
|
748
|
+
'describe_mutation',
|
|
749
|
+
{
|
|
750
|
+
description: `Return the full input schema for a single mutation: argument list, every input field (with type, whether it is required, descriptions, allowed enum values, and nested input objects expanded), plus the default fields returned by execute_mutation. Use this before execute_mutation to build a valid 'variables.input' object.`,
|
|
751
|
+
inputSchema: z.object({
|
|
752
|
+
name: z.string(),
|
|
753
|
+
}),
|
|
754
|
+
annotations: {
|
|
755
|
+
title: 'Describe Mutation',
|
|
756
|
+
readOnlyHint: true,
|
|
757
|
+
destructiveHint: false,
|
|
758
|
+
idempotentHint: true,
|
|
759
|
+
openWorldHint: false,
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
async ({ name }) => {
|
|
763
|
+
try {
|
|
764
|
+
return ok(gateway.describe_mutation(name));
|
|
765
|
+
} catch (err) {
|
|
766
|
+
return fail(err, 'Failed to describe mutation');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
server.registerTool(
|
|
772
|
+
'execute_mutation',
|
|
773
|
+
{
|
|
774
|
+
description: `Execute any allowlisted Conviso Platform mutation by name (use list_mutations to see the full set). Covers writes for issues/vulnerabilities, assets (+ DAST), tickets, projects, requirements, AI-pentest, applications and threat modeling.
|
|
775
|
+
|
|
776
|
+
Usage: call describe_mutation(name) first to learn the input shape, then pass it here. Every mutation takes a single input object, so variables must be { "input": { ...fields... } }.
|
|
777
|
+
|
|
778
|
+
- name: the mutation name (e.g. "changeIssueStatus", "createProject", "deleteAsset").
|
|
779
|
+
- variables: GraphQL variables, normally { input: { ... } }.
|
|
780
|
+
- return_fields: optional raw GraphQL selection set to override the default returned fields.
|
|
781
|
+
|
|
782
|
+
WARNING: this performs writes and can be destructive (the catalog marks delete/bulk/remove/cancel/revoke operations as destructive). Confirm intent before running delete or bulk mutations.`,
|
|
783
|
+
inputSchema: z.object({
|
|
784
|
+
name: z.string(),
|
|
785
|
+
variables: z.record(z.string(), z.any()).optional(),
|
|
786
|
+
return_fields: z.string().optional(),
|
|
787
|
+
}),
|
|
788
|
+
annotations: {
|
|
789
|
+
title: 'Execute Mutation',
|
|
790
|
+
readOnlyHint: false,
|
|
791
|
+
destructiveHint: true,
|
|
792
|
+
idempotentHint: false,
|
|
793
|
+
openWorldHint: true,
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
async ({ name, variables = {}, return_fields = null }) => {
|
|
797
|
+
try {
|
|
798
|
+
return ok(await gateway.execute_mutation(name, variables, return_fields));
|
|
799
|
+
} catch (err) {
|
|
800
|
+
return fail(err, 'Failed to execute mutation');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* MUTATIONS — curated typed shortcuts for the most common writes
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
server.registerTool(
|
|
810
|
+
'change_issue_status',
|
|
811
|
+
{
|
|
812
|
+
description: `Change the status of an issue/vulnerability. status must be one of CREATED, DRAFT, IDENTIFIED, IN_PROGRESS, AWAITING_VALIDATION, FIX_ACCEPTED, RISK_ACCEPTED, FALSE_POSITIVE, SUPPRESSED. Pass 'extra' for advanced ChangeIssueStatusInput fields (e.g. riskAcceptedUntil, externalAuthorIdentifier).`,
|
|
813
|
+
inputSchema: z.object({
|
|
814
|
+
issue_id: z.number(),
|
|
815
|
+
status: z.string(),
|
|
816
|
+
reason: z.string().optional(),
|
|
817
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
818
|
+
}),
|
|
819
|
+
annotations: {
|
|
820
|
+
title: 'Change Issue Status',
|
|
821
|
+
readOnlyHint: false,
|
|
822
|
+
destructiveHint: false,
|
|
823
|
+
idempotentHint: false,
|
|
824
|
+
openWorldHint: true,
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
async ({ issue_id, status, reason, extra }) => {
|
|
828
|
+
try {
|
|
829
|
+
return ok(await gateway.change_issue_status({ issue_id, status, reason, extra }));
|
|
830
|
+
} catch (err) {
|
|
831
|
+
return fail(err, 'Failed to change issue status');
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
server.registerTool(
|
|
837
|
+
'create_source_code_vulnerability',
|
|
838
|
+
{
|
|
839
|
+
description: `Create a source-code (SAST-style) vulnerability/issue on an asset. severity is one of NOTIFICATION, LOW, MEDIUM, HIGH, CRITICAL; impact_level and probability_level are LOW, MEDIUM or HIGH (default MEDIUM); status defaults to DRAFT. Pass 'extra' for any other CreateSourceCodeVulnerabilityInput field.`,
|
|
840
|
+
inputSchema: z.object({
|
|
841
|
+
asset_id: z.number(),
|
|
842
|
+
title: z.string(),
|
|
843
|
+
description: z.string(),
|
|
844
|
+
solution: z.string(),
|
|
845
|
+
severity: z.string(),
|
|
846
|
+
code_snippet: z.string(),
|
|
847
|
+
file_name: z.string(),
|
|
848
|
+
first_line: z.number(),
|
|
849
|
+
vulnerable_line: z.number(),
|
|
850
|
+
project_id: z.number().optional(),
|
|
851
|
+
impact_level: z.string().optional(),
|
|
852
|
+
probability_level: z.string().optional(),
|
|
853
|
+
status: z.string().optional(),
|
|
854
|
+
category: z.string().optional(),
|
|
855
|
+
reference: z.string().optional(),
|
|
856
|
+
summary: z.string().optional(),
|
|
857
|
+
impact_description: z.string().optional(),
|
|
858
|
+
steps_to_reproduce: z.string().optional(),
|
|
859
|
+
compromised_environment: z.boolean().optional(),
|
|
860
|
+
source: z.string().optional(),
|
|
861
|
+
sink: z.string().optional(),
|
|
862
|
+
patterns: z.array(z.string()).optional(),
|
|
863
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
864
|
+
}),
|
|
865
|
+
annotations: {
|
|
866
|
+
title: 'Create Source Code Vulnerability',
|
|
867
|
+
readOnlyHint: false,
|
|
868
|
+
destructiveHint: false,
|
|
869
|
+
idempotentHint: false,
|
|
870
|
+
openWorldHint: true,
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
async (args) => {
|
|
874
|
+
try {
|
|
875
|
+
return ok(await gateway.create_source_code_vulnerability(args));
|
|
876
|
+
} catch (err) {
|
|
877
|
+
return fail(err, 'Failed to create source code vulnerability');
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
server.registerTool(
|
|
883
|
+
'create_project',
|
|
884
|
+
{
|
|
885
|
+
description: `Create a project. Required: company_id, type_id (project type id), label, goal, scope, start_date (YYYY-MM-DD). Optional: end_date. Pass 'extra' for advanced CreateProjectInput fields (e.g. assetsIds, tags, allocatedPortalUserEmails, playbooksIds).`,
|
|
886
|
+
inputSchema: z.object({
|
|
887
|
+
company_id: z.number(),
|
|
888
|
+
type_id: z.number(),
|
|
889
|
+
label: z.string(),
|
|
890
|
+
goal: z.string(),
|
|
891
|
+
scope: z.string(),
|
|
892
|
+
start_date: z.string(),
|
|
893
|
+
end_date: z.string().optional(),
|
|
894
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
895
|
+
}),
|
|
896
|
+
annotations: {
|
|
897
|
+
title: 'Create Project',
|
|
898
|
+
readOnlyHint: false,
|
|
899
|
+
destructiveHint: false,
|
|
900
|
+
idempotentHint: false,
|
|
901
|
+
openWorldHint: true,
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
async (args) => {
|
|
905
|
+
try {
|
|
906
|
+
return ok(await gateway.create_project(args));
|
|
907
|
+
} catch (err) {
|
|
908
|
+
return fail(err, 'Failed to create project');
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
server.registerTool(
|
|
914
|
+
'create_asset',
|
|
915
|
+
{
|
|
916
|
+
description: `Create an asset. Required: company_id, name. Optional: asset_type, url, description, business_impact (LOW/MEDIUM/HIGH/NOT_DEFINED), exploitability (INTERNET_FACING/INTERNAL/NOT_DEFINED), tags (string list). Pass 'extra' for advanced CreateAssetInput fields.`,
|
|
917
|
+
inputSchema: z.object({
|
|
918
|
+
company_id: z.number(),
|
|
919
|
+
name: z.string(),
|
|
920
|
+
asset_type: z.string().optional(),
|
|
921
|
+
url: z.string().optional(),
|
|
922
|
+
description: z.string().optional(),
|
|
923
|
+
business_impact: z.string().optional(),
|
|
924
|
+
exploitability: z.string().optional(),
|
|
925
|
+
tags: z.array(z.string()).optional(),
|
|
926
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
927
|
+
}),
|
|
928
|
+
annotations: {
|
|
929
|
+
title: 'Create Asset',
|
|
930
|
+
readOnlyHint: false,
|
|
931
|
+
destructiveHint: false,
|
|
932
|
+
idempotentHint: false,
|
|
933
|
+
openWorldHint: true,
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
async (args) => {
|
|
937
|
+
try {
|
|
938
|
+
return ok(await gateway.create_asset(args));
|
|
939
|
+
} catch (err) {
|
|
940
|
+
return fail(err, 'Failed to create asset');
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
server.registerTool(
|
|
946
|
+
'create_ticket',
|
|
947
|
+
{
|
|
948
|
+
description: `Open a ticket. Required: company_id, type (BUG, FEATURE_REQUEST, PROJECT_REQUEST, SUPPORT_REQUEST), title, description. Optional: priority (P1/P2/P3), impact (LOW/MEDIUM/HIGH). Pass 'extra' for advanced CreateTicketInput fields.`,
|
|
949
|
+
inputSchema: z.object({
|
|
950
|
+
company_id: z.number(),
|
|
951
|
+
type: z.string(),
|
|
952
|
+
title: z.string(),
|
|
953
|
+
description: z.string(),
|
|
954
|
+
priority: z.string().optional(),
|
|
955
|
+
impact: z.string().optional(),
|
|
956
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
957
|
+
}),
|
|
958
|
+
annotations: {
|
|
959
|
+
title: 'Create Ticket',
|
|
960
|
+
readOnlyHint: false,
|
|
961
|
+
destructiveHint: false,
|
|
962
|
+
idempotentHint: false,
|
|
963
|
+
openWorldHint: true,
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
async (args) => {
|
|
967
|
+
try {
|
|
968
|
+
return ok(await gateway.create_ticket(args));
|
|
969
|
+
} catch (err) {
|
|
970
|
+
return fail(err, 'Failed to create ticket');
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* CURATED WRITES — DAST + AI-Pentest shortcuts
|
|
977
|
+
*/
|
|
978
|
+
|
|
979
|
+
server.registerTool(
|
|
980
|
+
'run_dast',
|
|
981
|
+
{
|
|
982
|
+
description: 'Start a Conviso DAST scan on an asset (startConvisoDast). Required: asset_id.',
|
|
983
|
+
inputSchema: z.object({ asset_id: z.number(), extra: z.record(z.string(), z.any()).optional() }),
|
|
984
|
+
annotations: { title: 'Run DAST', ...WRITE },
|
|
985
|
+
},
|
|
986
|
+
async ({ asset_id, extra }) => {
|
|
987
|
+
try {
|
|
988
|
+
return ok(await gateway.run_dast({ asset_id, extra }));
|
|
989
|
+
} catch (err) {
|
|
990
|
+
return fail(err, 'Failed to start DAST');
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
server.registerTool(
|
|
996
|
+
'trigger_pentest',
|
|
997
|
+
{
|
|
998
|
+
description: 'Trigger an AI-Pentest execution from an existing pentest artifact (createPentestExecution). Required: artifact_id.',
|
|
999
|
+
inputSchema: z.object({ artifact_id: z.number(), extra: z.record(z.string(), z.any()).optional() }),
|
|
1000
|
+
annotations: { title: 'Trigger AI-Pentest', ...WRITE },
|
|
1001
|
+
},
|
|
1002
|
+
async ({ artifact_id, extra }) => {
|
|
1003
|
+
try {
|
|
1004
|
+
return ok(await gateway.trigger_pentest({ artifact_id, extra }));
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
return fail(err, 'Failed to trigger pentest');
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
server.registerTool(
|
|
1012
|
+
'create_pentest_artifact',
|
|
1013
|
+
{
|
|
1014
|
+
description: 'Create an AI-Pentest artifact (the scope/config a pentest runs against). Required: company_id, application_id, label, pentest_type. Optional: description, scope_text, assignee_email, domains, in_scope, out_scope. Pass \'extra\' for advanced fields (scheduling, repositories, documentation, size/depth).',
|
|
1015
|
+
inputSchema: z.object({
|
|
1016
|
+
company_id: z.number(),
|
|
1017
|
+
application_id: z.number(),
|
|
1018
|
+
label: z.string(),
|
|
1019
|
+
pentest_type: z.string(),
|
|
1020
|
+
description: z.string().optional(),
|
|
1021
|
+
scope_text: z.string().optional(),
|
|
1022
|
+
assignee_email: z.string().optional(),
|
|
1023
|
+
domains: z.array(z.string()).optional(),
|
|
1024
|
+
in_scope: z.array(z.string()).optional(),
|
|
1025
|
+
out_scope: z.array(z.string()).optional(),
|
|
1026
|
+
extra: z.record(z.string(), z.any()).optional(),
|
|
1027
|
+
}),
|
|
1028
|
+
annotations: { title: 'Create Pentest Artifact', ...WRITE },
|
|
1029
|
+
},
|
|
1030
|
+
async (args) => {
|
|
1031
|
+
try {
|
|
1032
|
+
return ok(await gateway.create_pentest_artifact(args));
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
return fail(err, 'Failed to create pentest artifact');
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* READS — Tickets, Requirements, Applications, Scans, Supply chain, AI-Pentest, Threat Modeling
|
|
1041
|
+
*/
|
|
1042
|
+
|
|
1043
|
+
server.registerTool(
|
|
1044
|
+
'get_tickets',
|
|
1045
|
+
{
|
|
1046
|
+
description: 'List tickets for a company (paginated). Optional: search (title/description), sort_by, descending, and params (TicketSearch: types, statuses, priorities, impacts, tags, mineOnly...).',
|
|
1047
|
+
inputSchema: z.object({
|
|
1048
|
+
company_id: z.number(),
|
|
1049
|
+
page: z.number().optional(),
|
|
1050
|
+
limit: z.number().optional(),
|
|
1051
|
+
search: z.string().optional(),
|
|
1052
|
+
sort_by: z.string().optional(),
|
|
1053
|
+
descending: z.boolean().optional(),
|
|
1054
|
+
params: z.record(z.string(), z.any()).optional(),
|
|
1055
|
+
}),
|
|
1056
|
+
annotations: { title: 'List Tickets', ...READ },
|
|
1057
|
+
},
|
|
1058
|
+
async ({ company_id, page, limit, search, sort_by, descending, params }) => {
|
|
1059
|
+
try {
|
|
1060
|
+
return ok(await gateway.get_tickets(company_id, { page, limit, search, sort_by, descending, params }));
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
return fail(err, 'Failed to list tickets');
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
server.registerTool(
|
|
1068
|
+
'get_ticket',
|
|
1069
|
+
{
|
|
1070
|
+
description: 'Get a single ticket by id, including status, priority, impact and assignee.',
|
|
1071
|
+
inputSchema: z.object({ company_id: z.number(), ticket_id: z.number() }),
|
|
1072
|
+
annotations: { title: 'Ticket Details', ...READ },
|
|
1073
|
+
},
|
|
1074
|
+
async ({ company_id, ticket_id }) => {
|
|
1075
|
+
try {
|
|
1076
|
+
return ok(await gateway.get_ticket(company_id, ticket_id));
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
return fail(err, 'Failed to get ticket');
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
server.registerTool(
|
|
1084
|
+
'get_requirements',
|
|
1085
|
+
{
|
|
1086
|
+
description: 'List security requirements/checklists for a scope (company) id, paginated. Optional: filters (RequirementsFilterInput).',
|
|
1087
|
+
inputSchema: z.object({
|
|
1088
|
+
scope_id: z.number(),
|
|
1089
|
+
page: z.number().optional(),
|
|
1090
|
+
limit: z.number().optional(),
|
|
1091
|
+
filters: z.record(z.string(), z.any()).optional(),
|
|
1092
|
+
}),
|
|
1093
|
+
annotations: { title: 'List Requirements', ...READ },
|
|
1094
|
+
},
|
|
1095
|
+
async ({ scope_id, page, limit, filters }) => {
|
|
1096
|
+
try {
|
|
1097
|
+
return ok(await gateway.get_requirements(scope_id, { page, limit, filters }));
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
return fail(err, 'Failed to list requirements');
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
server.registerTool(
|
|
1105
|
+
'get_requirement',
|
|
1106
|
+
{
|
|
1107
|
+
description: 'Get a single requirement/checklist by id.',
|
|
1108
|
+
inputSchema: z.object({ company_id: z.number(), requirement_id: z.number() }),
|
|
1109
|
+
annotations: { title: 'Requirement Details', ...READ },
|
|
1110
|
+
},
|
|
1111
|
+
async ({ company_id, requirement_id }) => {
|
|
1112
|
+
try {
|
|
1113
|
+
return ok(await gateway.get_requirement(company_id, requirement_id));
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
return fail(err, 'Failed to get requirement');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
server.registerTool(
|
|
1121
|
+
'get_project_requirements',
|
|
1122
|
+
{
|
|
1123
|
+
description: 'List the requirements/checklists attached to a specific project.',
|
|
1124
|
+
inputSchema: z.object({ project_id: z.number() }),
|
|
1125
|
+
annotations: { title: 'Project Requirements', ...READ },
|
|
1126
|
+
},
|
|
1127
|
+
async ({ project_id }) => {
|
|
1128
|
+
try {
|
|
1129
|
+
return ok(await gateway.get_project_requirements(project_id));
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
return fail(err, 'Failed to list project requirements');
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
server.registerTool(
|
|
1137
|
+
'get_applications',
|
|
1138
|
+
{
|
|
1139
|
+
description: 'List applications for a company (id, name, url, riskScore, assetsCount). Optional: search by name.',
|
|
1140
|
+
inputSchema: z.object({ company_id: z.number(), search: z.string().optional() }),
|
|
1141
|
+
annotations: { title: 'List Applications', ...READ },
|
|
1142
|
+
},
|
|
1143
|
+
async ({ company_id, search }) => {
|
|
1144
|
+
try {
|
|
1145
|
+
return ok(await gateway.get_applications(company_id, search));
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
return fail(err, 'Failed to list applications');
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
server.registerTool(
|
|
1153
|
+
'get_application',
|
|
1154
|
+
{
|
|
1155
|
+
description: 'Get a single application by id, including its linked assets.',
|
|
1156
|
+
inputSchema: z.object({ company_id: z.number(), application_id: z.number() }),
|
|
1157
|
+
annotations: { title: 'Application Details', ...READ },
|
|
1158
|
+
},
|
|
1159
|
+
async ({ company_id, application_id }) => {
|
|
1160
|
+
try {
|
|
1161
|
+
return ok(await gateway.get_application(company_id, application_id));
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
return fail(err, 'Failed to get application');
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
server.registerTool(
|
|
1169
|
+
'get_scan_histories',
|
|
1170
|
+
{
|
|
1171
|
+
description: 'List scan execution histories for a company (status, integration, durations, vulnerability counts). Optional: asset_ids, page, limit, filters (ScansHistoriesFiltersInput).',
|
|
1172
|
+
inputSchema: z.object({
|
|
1173
|
+
company_id: z.number(),
|
|
1174
|
+
asset_ids: z.array(z.number()).optional(),
|
|
1175
|
+
page: z.number().optional(),
|
|
1176
|
+
limit: z.number().optional(),
|
|
1177
|
+
filters: z.record(z.string(), z.any()).optional(),
|
|
1178
|
+
}),
|
|
1179
|
+
annotations: { title: 'List Scan Histories', ...READ },
|
|
1180
|
+
},
|
|
1181
|
+
async ({ company_id, asset_ids, page, limit, filters }) => {
|
|
1182
|
+
try {
|
|
1183
|
+
return ok(await gateway.get_scan_histories(company_id, { assetIds: asset_ids, page, limit, filters }));
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
return fail(err, 'Failed to list scan histories');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
server.registerTool(
|
|
1191
|
+
'get_asset_scans_count',
|
|
1192
|
+
{
|
|
1193
|
+
description: 'Get scan coverage counts for a company (assets with scans, without scans, and which scans are considered).',
|
|
1194
|
+
inputSchema: z.object({ company_id: z.number() }),
|
|
1195
|
+
annotations: { title: 'Asset Scans Count', ...READ },
|
|
1196
|
+
},
|
|
1197
|
+
async ({ company_id }) => {
|
|
1198
|
+
try {
|
|
1199
|
+
return ok(await gateway.get_asset_scans_count(company_id));
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
return fail(err, 'Failed to get asset scans count');
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
|
|
1206
|
+
server.registerTool(
|
|
1207
|
+
'get_sbom_components',
|
|
1208
|
+
{
|
|
1209
|
+
description: 'List Software Bill of Materials (SBOM) / supply-chain components for a company (name, version, technology, package manager, license, issues by severity). Optional: search (SbomComponentSearchInput).',
|
|
1210
|
+
inputSchema: z.object({
|
|
1211
|
+
company_id: z.number(),
|
|
1212
|
+
page: z.number().optional(),
|
|
1213
|
+
limit: z.number().optional(),
|
|
1214
|
+
search: z.record(z.string(), z.any()).optional(),
|
|
1215
|
+
}),
|
|
1216
|
+
annotations: { title: 'List SBOM Components', ...READ },
|
|
1217
|
+
},
|
|
1218
|
+
async ({ company_id, page, limit, search }) => {
|
|
1219
|
+
try {
|
|
1220
|
+
return ok(await gateway.get_sbom_components(company_id, { page, limit, search }));
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
return fail(err, 'Failed to list SBOM components');
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
server.registerTool(
|
|
1228
|
+
'get_pentest_artifacts',
|
|
1229
|
+
{
|
|
1230
|
+
description: 'List AI-Pentest artifacts for a company (label, type, scheduling, latest execution). Optional: search, assignee_email, pentest_type, application_id.',
|
|
1231
|
+
inputSchema: z.object({
|
|
1232
|
+
company_id: z.number(),
|
|
1233
|
+
page: z.number().optional(),
|
|
1234
|
+
limit: z.number().optional(),
|
|
1235
|
+
search: z.string().optional(),
|
|
1236
|
+
assignee_email: z.string().optional(),
|
|
1237
|
+
pentest_type: z.string().optional(),
|
|
1238
|
+
application_id: z.number().optional(),
|
|
1239
|
+
}),
|
|
1240
|
+
annotations: { title: 'List Pentest Artifacts', ...READ },
|
|
1241
|
+
},
|
|
1242
|
+
async ({ company_id, page, limit, search, assignee_email, pentest_type, application_id }) => {
|
|
1243
|
+
try {
|
|
1244
|
+
return ok(await gateway.get_pentest_artifacts(company_id, {
|
|
1245
|
+
page, limit, search, assigneeEmail: assignee_email, pentestType: pentest_type, applicationId: application_id,
|
|
1246
|
+
}));
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
return fail(err, 'Failed to list pentest artifacts');
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
|
|
1253
|
+
server.registerTool(
|
|
1254
|
+
'get_pentest_artifact',
|
|
1255
|
+
{
|
|
1256
|
+
description: 'Get a single AI-Pentest artifact by id, including scope and its executions.',
|
|
1257
|
+
inputSchema: z.object({ artifact_id: z.number() }),
|
|
1258
|
+
annotations: { title: 'Pentest Artifact Details', ...READ },
|
|
1259
|
+
},
|
|
1260
|
+
async ({ artifact_id }) => {
|
|
1261
|
+
try {
|
|
1262
|
+
return ok(await gateway.get_pentest_artifact(artifact_id));
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
return fail(err, 'Failed to get pentest artifact');
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
server.registerTool(
|
|
1270
|
+
'get_pentest_execution',
|
|
1271
|
+
{
|
|
1272
|
+
description: 'Get the result/status of a single AI-Pentest execution by id (status, vulnerability count, severity breakdown, retest progress).',
|
|
1273
|
+
inputSchema: z.object({ execution_id: z.number() }),
|
|
1274
|
+
annotations: { title: 'Pentest Execution Result', ...READ },
|
|
1275
|
+
},
|
|
1276
|
+
async ({ execution_id }) => {
|
|
1277
|
+
try {
|
|
1278
|
+
return ok(await gateway.get_pentest_execution(execution_id));
|
|
1279
|
+
} catch (err) {
|
|
1280
|
+
return fail(err, 'Failed to get pentest execution');
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
server.registerTool(
|
|
1286
|
+
'get_threat_model_artifacts',
|
|
1287
|
+
{
|
|
1288
|
+
description: 'List Threat Modeling artifacts for a company (label, scope, latest version). Optional: search, assignee_email, has_version.',
|
|
1289
|
+
inputSchema: z.object({
|
|
1290
|
+
company_id: z.number(),
|
|
1291
|
+
page: z.number().optional(),
|
|
1292
|
+
limit: z.number().optional(),
|
|
1293
|
+
search: z.string().optional(),
|
|
1294
|
+
assignee_email: z.string().optional(),
|
|
1295
|
+
has_version: z.boolean().optional(),
|
|
1296
|
+
}),
|
|
1297
|
+
annotations: { title: 'List Threat Model Artifacts', ...READ },
|
|
1298
|
+
},
|
|
1299
|
+
async ({ company_id, page, limit, search, assignee_email, has_version }) => {
|
|
1300
|
+
try {
|
|
1301
|
+
return ok(await gateway.get_threat_model_artifacts(company_id, {
|
|
1302
|
+
page, limit, search, assigneeEmail: assignee_email, hasVersion: has_version,
|
|
1303
|
+
}));
|
|
1304
|
+
} catch (err) {
|
|
1305
|
+
return fail(err, 'Failed to list threat model artifacts');
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
server.registerTool(
|
|
1311
|
+
'get_threat_model_artifact',
|
|
1312
|
+
{
|
|
1313
|
+
description: 'Get a single Threat Modeling artifact by id, including its versions (diagrams, notes, scope).',
|
|
1314
|
+
inputSchema: z.object({ artifact_id: z.number() }),
|
|
1315
|
+
annotations: { title: 'Threat Model Artifact Details', ...READ },
|
|
1316
|
+
},
|
|
1317
|
+
async ({ artifact_id }) => {
|
|
1318
|
+
try {
|
|
1319
|
+
return ok(await gateway.get_threat_model_artifact(artifact_id));
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
return fail(err, 'Failed to get threat model artifact');
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
);
|
|
1325
|
+
|
|
414
1326
|
/**
|
|
415
1327
|
* START
|
|
416
1328
|
*/
|