qualys-mcp 2.1.1__tar.gz → 2.1.2__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.2
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.2"
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,46 @@ 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', {})
136
+ lifecycle = os_info.get('lifecycle', {})
137
+ assets.append({
138
+ 'assetId': a.get('assetId'),
139
+ 'address': a.get('address', ''),
140
+ 'dnsName': a.get('dnsHostName', ''),
141
+ 'operatingSystem': {
142
+ 'osName': f"{os_info.get('name', '')} {os_info.get('version', '')}".strip(),
143
+ 'lifecycle': {
144
+ 'stage': lifecycle.get('stage', ''),
145
+ 'eolDate': lifecycle.get('eolDate', ''),
146
+ 'eosDate': lifecycle.get('eosDate', '')
147
+ }
148
+ }
149
+ })
150
+ return assets
151
+ except Exception as e:
152
+ return []
153
+
154
+
115
155
  def get_images(limit=100, severity=None):
116
156
  url = f"{GATEWAY_URL}/csapi/v1.3/images?pageSize={limit}"
117
157
  if severity:
@@ -484,7 +524,7 @@ def get_tech_debt(days_until_eol: int = 0) -> dict:
484
524
  'byOS': []
485
525
  }
486
526
 
487
- assets = get_assets(500)
527
+ assets = get_eol_assets("EOL,EOL/EOS,EOS", 500)
488
528
  result['stats']['total'] = len(assets)
489
529
 
490
530
  today = datetime.utcnow().date()
@@ -523,7 +563,7 @@ def get_tech_debt(days_until_eol: int = 0) -> dict:
523
563
  os_data[os_name]['eol'] += 1
524
564
  if len(result['currentEOL']) < 20:
525
565
  result['currentEOL'].append(asset_info)
526
- elif stage == 'EOS':
566
+ elif stage in ('EOS', 'EOL/EOS'):
527
567
  result['stats']['currentEOS'] += 1
528
568
  os_data[os_name]['eos'] += 1
529
569
  if len(result['currentEOS']) < 20:
File without changes
File without changes
File without changes