@convisoappsec/mcp 0.3.0 → 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 CHANGED
@@ -15,6 +15,8 @@ The server exposes the following tools to the LLM (see `node/manifest.json` for
15
15
  | **Vulnerabilities** | `get_issues` | List vulnerabilities by company or project. |
16
16
  | **Vulnerabilities** | `get_issue` | Technical details, including **code snippets** and raw requests/responses. |
17
17
  | **Vulnerabilities** | `get_top_vulnerabilities` | Risk overview (vulnerability count by severity). |
18
+ | **Vulnerabilities** | `get_issues_by_asset_id` | List vulnerabilities for a company filtered by a single asset ID. |
19
+ | **Vulnerabilities** | `get_issues_by_project_id` | List vulnerabilities for a company filtered by a project ID. |
18
20
  | **Management** | `get_projects` | List active security projects. |
19
21
  | **Management** | `get_project` | Get specific project in Conviso Platform by project ID. |
20
22
  | **Assets** | `get_assets` | List assets mapped within the platform. |
@@ -24,12 +26,28 @@ The server exposes the following tools to the LLM (see `node/manifest.json` for
24
26
  | **Utilities** | `get_today_date` | Return current day/month/year (utility). |
25
27
  | **Metrics** | `get_mttr_over_time` | Get Mean Time To Resolution (MTTR) metrics over time for a company. Returns resolution times by severity level. |
26
28
  | **Metrics** | `get_overall_risk_score_history` | Get overall risk score history for a company, including current score and difference from last period. |
29
+ | **Tickets** | `get_tickets` / `get_ticket` | List or fetch support/bug tickets. |
30
+ | **Requirements** | `get_requirements` / `get_requirement` / `get_project_requirements` | Browse security requirements/checklists. |
31
+ | **Applications** | `get_applications` / `get_application` | List or fetch applications and their assets. |
32
+ | **Scans** | `get_scan_histories` / `get_asset_scans_count` | Scan execution history and coverage counts. |
33
+ | **Supply chain** | `get_sbom_components` | SBOM / dependency components per company. |
34
+ | **AI-Pentest** | `get_pentest_artifacts` / `get_pentest_artifact` / `get_pentest_execution` | Pentest artifacts, scope and execution results. |
35
+ | **Threat Modeling** | `get_threat_model_artifacts` / `get_threat_model_artifact` | Threat model artifacts and versions. |
36
+ | **Write engine** | `list_mutations` / `describe_mutation` / `execute_mutation` | Discover, describe and run any of the allowlisted mutations. |
37
+ | **Write (curated)** | `change_issue_status` | Change an issue/vulnerability status. |
38
+ | **Write (curated)** | `create_source_code_vulnerability` | Create a manual source-code vulnerability. |
39
+ | **Write (curated)** | `create_project` / `create_asset` / `create_ticket` | Create a project, asset or ticket. |
40
+ | **Write (curated)** | `run_dast` | Start a Conviso DAST scan on an asset. |
41
+ | **Write (curated)** | `trigger_pentest` / `create_pentest_artifact` | Trigger an AI-Pentest execution / create its artifact. |
42
+
43
+ > Write tools are **Node-only**. Mutations are restricted to an allowlist
44
+ > (`src/conviso_mcp/operation_allowlist.js`); see the root `README.md` for the write workflow.
27
45
 
28
46
  ## 🚀 Installation and Configuration (Node.js bundle)
29
47
 
30
48
  ### Prerequisites
31
49
 
32
- * Node.js 18 or later.
50
+ * Node.js 20.10 or later.
33
51
  * A Conviso Platform API Key (obtained from your profile settings).
34
52
  * An MCP-compatible client (e.g., Claude Desktop, Cursor, etc.).
35
53
 
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@convisoappsec/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "MCP Server for Conviso Platform integration",
5
5
  "type": "module",
6
6
  "main": "src/conviso_mcp/server.js",
7
7
  "scripts": {
8
8
  "start": "node src/conviso_mcp/server.js",
9
9
  "dev": "node src/conviso_mcp/server.js",
10
- "test": "echo \"Error: no test specified\" && exit 1"
10
+ "gen:mutations": "node scripts/generate_mutation_catalog.mjs",
11
+ "test": "node --test"
11
12
  },
12
13
  "bin": {
13
14
  "conviso-mcp": "src/conviso_mcp/server.js"
@@ -16,7 +17,7 @@
16
17
  "src"
17
18
  ],
18
19
  "engines": {
19
- "node": ">=18"
20
+ "node": ">=20.10.0"
20
21
  },
21
22
  "publishConfig": {
22
23
  "access": "public"
@@ -43,8 +44,11 @@
43
44
  "dependencies": {
44
45
  "@hono/node-server": "^1.19.11",
45
46
  "@modelcontextprotocol/sdk": "^1.28.0",
46
- "axios": "^1.13.5",
47
+ "axios": "^1.16.1",
47
48
  "dotenv": "^16.6.1",
48
49
  "zod": "^4.3.6"
50
+ },
51
+ "devDependencies": {
52
+ "graphql": "^16.9.0"
49
53
  }
50
54
  }
@@ -1,5 +1,6 @@
1
1
  import 'dotenv/config';
2
2
  import { GraphQLClient } from './graphql_client.js';
3
+ import { listMutations, describeMutation } from './mutations.js';
3
4
 
4
5
  class FeedGateway {
5
6
  constructor(graphql_api_key = '') {
@@ -8,8 +9,9 @@ class FeedGateway {
8
9
  this.graphql = new GraphQLClient(`${this.base_url}/graphql`, apiKey);
9
10
  }
10
11
 
11
- async get_companies(page = 1, limit = 10, search = '') {
12
- return this.graphql.get_companies(page, limit, search);
12
+ // search = name contains; label_eq = exact name match.
13
+ async get_companies(page = 1, limit = 10, search = '', label_eq = null) {
14
+ return this.graphql.get_companies(page, limit, search, label_eq);
13
15
  }
14
16
 
15
17
  async get_company_by_id(company_id) {
@@ -20,36 +22,43 @@ class FeedGateway {
20
22
  return this.graphql.get_issue_by_id(issue_id, return_snippets);
21
23
  }
22
24
 
23
- async get_issues(company_id, search = '', page = 1, limit = 1, project_id = null) {
24
- return this.graphql.get_issues(company_id, search, page, limit, project_id);
25
+ // Single rich issues passthrough (parity with Python gateway.get_issues kwargs).
26
+ async getIssues(company_id, opts = {}) {
27
+ return this.graphql.getIssues(company_id, opts);
25
28
  }
26
29
 
27
30
  async get_issue_with_company_id(company_id, issue_id) {
28
31
  return this.graphql.get_issues(company_id, '', 1, 5, null, [issue_id]);
29
32
  }
30
33
 
31
- async get_issues_by_asset_ids(company_id, page = 1, limit = 1, asset_ids = []) {
32
- return this.graphql.get_issues(company_id, '', page, limit, null, [], asset_ids);
34
+ async get_issues_by_asset_ids(company_id, page = 1, limit = 10, asset_ids = [], search = '', opts = {}) {
35
+ return this.graphql.getIssues(company_id, {
36
+ ...opts,
37
+ page,
38
+ limit,
39
+ search,
40
+ assetIds: asset_ids,
41
+ });
33
42
  }
34
43
 
35
- async get_projects(company_id, page = 1, limit = 1000, search = '') {
36
- return this.graphql.get_projects(company_id, page, limit, search);
44
+ async get_projects(company_id, page = 1, limit = 1000, search = '', opts = {}) {
45
+ return this.graphql.get_projects(company_id, page, limit, search, opts);
37
46
  }
38
47
 
39
48
  async get_project_by_id(project_id) {
40
49
  return this.graphql.get_project_by_id(project_id);
41
50
  }
42
51
 
43
- async get_assets(company_id, page = 1, limit = 1000) {
44
- return this.graphql.get_assets_by_company(company_id, page, limit);
52
+ async get_assets(company_id, page = 1, limit = 1000, opts = {}) {
53
+ return this.graphql.get_assets_by_company(company_id, page, limit, opts);
45
54
  }
46
55
 
47
56
  async get_asset_by_id(asset_id) {
48
57
  return this.graphql.get_asset_by_id(asset_id);
49
58
  }
50
59
 
51
- async get_top_vulnerabilities(company_id) {
52
- return this.graphql.get_top_vulnerabilities(company_id);
60
+ async get_top_vulnerabilities(company_id, opts = {}) {
61
+ return this.graphql.get_top_vulnerabilities(company_id, opts);
53
62
  }
54
63
 
55
64
  async generate_project_report(project_id, language = 'en', vulnerability_criticity = null, vulnerability_statuses = null, requirements = true, evidences = true) {
@@ -75,6 +84,115 @@ class FeedGateway {
75
84
  async get_overall_risk_score_history(company_id) {
76
85
  return this.graphql.get_overall_risk_score_history(company_id);
77
86
  }
87
+
88
+ // --- Mutations -------------------------------------------------------------
89
+
90
+ // Discovery/introspection are pure (no network) — answered straight from the catalog.
91
+ list_mutations(opts = {}) {
92
+ return listMutations(opts);
93
+ }
94
+
95
+ describe_mutation(name) {
96
+ return describeMutation(name);
97
+ }
98
+
99
+ async execute_mutation(name, variables = {}, returnFields = null) {
100
+ return this.graphql.executeMutation(name, variables, returnFields);
101
+ }
102
+
103
+ async change_issue_status(a = {}) {
104
+ return this.graphql.change_issue_status(a);
105
+ }
106
+
107
+ async create_source_code_vulnerability(a = {}) {
108
+ return this.graphql.create_source_code_vulnerability(a);
109
+ }
110
+
111
+ async create_project(a = {}) {
112
+ return this.graphql.create_project(a);
113
+ }
114
+
115
+ async create_asset(a = {}) {
116
+ return this.graphql.create_asset(a);
117
+ }
118
+
119
+ async create_ticket(a = {}) {
120
+ return this.graphql.create_ticket(a);
121
+ }
122
+
123
+ async run_dast(a = {}) {
124
+ return this.graphql.run_dast(a);
125
+ }
126
+
127
+ async trigger_pentest(a = {}) {
128
+ return this.graphql.trigger_pentest(a);
129
+ }
130
+
131
+ async create_pentest_artifact(a = {}) {
132
+ return this.graphql.create_pentest_artifact(a);
133
+ }
134
+
135
+ // --- Reads (curated query tools) -------------------------------------------
136
+
137
+ async get_tickets(company_id, opts = {}) {
138
+ return this.graphql.get_tickets(company_id, opts);
139
+ }
140
+
141
+ async get_ticket(company_id, ticket_id) {
142
+ return this.graphql.get_ticket(company_id, ticket_id);
143
+ }
144
+
145
+ async get_requirements(scope_id, opts = {}) {
146
+ return this.graphql.get_requirements(scope_id, opts);
147
+ }
148
+
149
+ async get_requirement(company_id, requirement_id) {
150
+ return this.graphql.get_requirement(company_id, requirement_id);
151
+ }
152
+
153
+ async get_project_requirements(project_id) {
154
+ return this.graphql.get_project_requirements(project_id);
155
+ }
156
+
157
+ async get_applications(company_id, search = null) {
158
+ return this.graphql.get_applications(company_id, search);
159
+ }
160
+
161
+ async get_application(company_id, application_id) {
162
+ return this.graphql.get_application(company_id, application_id);
163
+ }
164
+
165
+ async get_scan_histories(company_id, opts = {}) {
166
+ return this.graphql.get_scan_histories(company_id, opts);
167
+ }
168
+
169
+ async get_asset_scans_count(company_id) {
170
+ return this.graphql.get_asset_scans_count(company_id);
171
+ }
172
+
173
+ async get_sbom_components(company_id, opts = {}) {
174
+ return this.graphql.get_sbom_components(company_id, opts);
175
+ }
176
+
177
+ async get_pentest_artifacts(company_id, opts = {}) {
178
+ return this.graphql.get_pentest_artifacts(company_id, opts);
179
+ }
180
+
181
+ async get_pentest_artifact(artifact_id) {
182
+ return this.graphql.get_pentest_artifact(artifact_id);
183
+ }
184
+
185
+ async get_pentest_execution(execution_id) {
186
+ return this.graphql.get_pentest_execution(execution_id);
187
+ }
188
+
189
+ async get_threat_model_artifacts(company_id, opts = {}) {
190
+ return this.graphql.get_threat_model_artifacts(company_id, opts);
191
+ }
192
+
193
+ async get_threat_model_artifact(artifact_id) {
194
+ return this.graphql.get_threat_model_artifact(artifact_id);
195
+ }
78
196
  }
79
197
 
80
198
  export { FeedGateway };
@@ -0,0 +1,54 @@
1
+ // node/src/conviso_mcp/filters.js
2
+ // Pure helpers for building GraphQL filter/sort variables. No network.
3
+
4
+ export const SEVERITIES = ['NOTIFICATION', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
5
+ export const ISSUE_STATUSES = ['CREATED', 'DRAFT', 'IDENTIFIED', 'IN_PROGRESS', 'AWAITING_VALIDATION', 'FIX_ACCEPTED', 'RISK_ACCEPTED', 'FALSE_POSITIVE', 'SUPPRESSED'];
6
+ export const SLA_STATES = ['ON_TRACK', 'APPROACHING', 'BREACHED', 'RESOLVED', 'NOT_TRACKED', 'NOT_PARAMETERIZED'];
7
+ export const BUSINESS_IMPACT = ['LOW', 'MEDIUM', 'HIGH', 'NOT_DEFINED'];
8
+ export const EXPLOITABILITY = ['INTERNET_FACING', 'INTERNAL', 'NOT_DEFINED'];
9
+ export const REACHABILITY = ['STATIC_ANALYSIS', 'DYNAMIC_ANALYSIS'];
10
+ export const ISSUE_SORT_BY = ['RISK_SCORE', 'SEVERITY', 'ID', 'CREATED_AT', 'UPDATED_AT', 'SLA_DUE_AT'];
11
+ export const ASSET_SORT_BY = ['updated_at', 'name', 'business_impact', 'risk_score'];
12
+ export const ORDER = ['ASC', 'DESC'];
13
+
14
+ export function normalizeEnum(value, allowed, upper = true) {
15
+ if (value === null || value === undefined) return null;
16
+ let s = String(value).trim();
17
+ s = upper ? s.toUpperCase() : s.toLowerCase();
18
+ return allowed.includes(s) ? s : null;
19
+ }
20
+
21
+ export function normalizeEnumList(values, allowed, upper = true) {
22
+ if (!values || values.length === 0) return [];
23
+ const out = [];
24
+ for (const v of values) {
25
+ const s = normalizeEnum(v, allowed, upper);
26
+ if (s !== null) out.push(s);
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export function buildDateRange(after, before) {
32
+ const rng = {};
33
+ if (after) rng.startDate = after;
34
+ if (before) rng.endDate = before;
35
+ return Object.keys(rng).length ? rng : null;
36
+ }
37
+
38
+ export function buildIssueSortOptions(sortBy, order) {
39
+ const sb = normalizeEnum(sortBy, ISSUE_SORT_BY);
40
+ if (sb === null) return [];
41
+ const ord = normalizeEnum(order, ORDER) || 'DESC';
42
+ return [{ sortBy: sb, order: ord }];
43
+ }
44
+
45
+ export function prune(obj) {
46
+ const out = {};
47
+ for (const [k, v] of Object.entries(obj)) {
48
+ if (v === null || v === undefined || v === '') continue;
49
+ if (Array.isArray(v) && v.length === 0) continue;
50
+ if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue;
51
+ out[k] = v;
52
+ }
53
+ return out;
54
+ }