qualys-mcp 2.1.1__tar.gz → 2.1.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qualys-mcp
3
- Version: 2.1.1
3
+ Version: 2.1.3
4
4
  Summary: MCP server for Qualys security APIs - natural language interaction with vulnerability, asset, and cloud security data
5
5
  Project-URL: Homepage, https://github.com/nelssec/qualys-mcp
6
6
  Project-URL: Repository, https://github.com/nelssec/qualys-mcp
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "qualys-mcp"
7
- version = "2.1.1"
7
+ version = "2.1.3"
8
8
  description = "MCP server for Qualys security APIs - natural language interaction with vulnerability, asset, and cloud security data"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -112,6 +112,49 @@ def get_assets(limit=100, qql=None):
112
112
  return []
113
113
 
114
114
 
115
+ def get_eol_assets(stage_filter="EOL,EOL/EOS", limit=500):
116
+ url = f"{GATEWAY_URL}/rest/2.0/search/am/asset?pageSize={limit}"
117
+ token = get_bearer_token()
118
+
119
+ filter_body = json.dumps({
120
+ "filters": [
121
+ {"field": "operatingSystem.lifecycle.stage", "operator": "IN", "value": stage_filter}
122
+ ]
123
+ })
124
+
125
+ req = Request(url, data=filter_body.encode(), method='POST')
126
+ req.add_header('Authorization', f'Bearer {token}' if token else f'Basic {BASIC_AUTH}')
127
+ req.add_header('Content-Type', 'application/json')
128
+ req.add_header('X-Requested-With', 'qualys-mcp')
129
+
130
+ try:
131
+ with urlopen(req, timeout=60) as resp:
132
+ data = json.loads(resp.read())
133
+ assets = []
134
+ for a in data.get('assetListData', {}).get('asset', []):
135
+ os_info = a.get('operatingSystem', {}) or {}
136
+ lifecycle = os_info.get('lifecycle', {}) or {}
137
+ os_name = os_info.get('osName', '') or os_info.get('fullName', '') or ''
138
+ if os_info.get('version'):
139
+ os_name = f"{os_name} {os_info.get('version', '')}".strip()
140
+ assets.append({
141
+ 'assetId': a.get('assetId'),
142
+ 'address': a.get('address', ''),
143
+ 'dnsName': a.get('dnsHostName', ''),
144
+ 'operatingSystem': {
145
+ 'osName': os_name,
146
+ 'lifecycle': {
147
+ 'stage': lifecycle.get('stage', ''),
148
+ 'eolDate': lifecycle.get('eolDate', ''),
149
+ 'eosDate': lifecycle.get('eosDate', '')
150
+ }
151
+ }
152
+ })
153
+ return assets
154
+ except Exception as e:
155
+ return []
156
+
157
+
115
158
  def get_images(limit=100, severity=None):
116
159
  url = f"{GATEWAY_URL}/csapi/v1.3/images?pageSize={limit}"
117
160
  if severity:
@@ -484,7 +527,7 @@ def get_tech_debt(days_until_eol: int = 0) -> dict:
484
527
  'byOS': []
485
528
  }
486
529
 
487
- assets = get_assets(500)
530
+ assets = get_eol_assets("EOL,EOL/EOS,EOS", 500)
488
531
  result['stats']['total'] = len(assets)
489
532
 
490
533
  today = datetime.utcnow().date()
@@ -523,7 +566,7 @@ def get_tech_debt(days_until_eol: int = 0) -> dict:
523
566
  os_data[os_name]['eol'] += 1
524
567
  if len(result['currentEOL']) < 20:
525
568
  result['currentEOL'].append(asset_info)
526
- elif stage == 'EOS':
569
+ elif stage in ('EOS', 'EOL/EOS'):
527
570
  result['stats']['currentEOS'] += 1
528
571
  os_data[os_name]['eos'] += 1
529
572
  if len(result['currentEOS']) < 20:
File without changes
File without changes
File without changes