qualys-cli-mcp 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: qualys-cli-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that wraps qualys-cli — gives any LLM access to the Qualys Cloud Platform
5
+ Project-URL: Homepage, https://github.com/qualys/qualys-cli-mcp
6
+ Project-URL: Documentation, https://github.com/qualys/qualys-cli-mcp#readme
7
+ Project-URL: Issues, https://github.com/qualys/qualys-cli-mcp/issues
8
+ Project-URL: Changelog, https://github.com/qualys/qualys-cli-mcp/blob/main/CHANGELOG.md
9
+ Author: qualys-cli-mcp contributors
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: llm,mcp,model-context-protocol,qualys,security,vulnerability-management
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Operating System :: POSIX
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Security
27
+ Classifier: Topic :: System :: Systems Administration
28
+ Classifier: Topic :: Utilities
29
+ Requires-Python: >=3.11
30
+ Requires-Dist: fastmcp>=2.0
31
+ Requires-Dist: qualys-cli>=0.1.1
32
+ Description-Content-Type: text/markdown
33
+
34
+ # qualys-cli-mcp
35
+
36
+ MCP server that gives any LLM access to the Qualys Cloud Security Platform via [qualys-cli](https://stash.intranet.qualys.com/projects/AET/repos/qualys-cli).
37
+
38
+ ## Architecture
39
+
40
+ ```
41
+ LLM (Claude, GPT, Llama, etc.)
42
+ │ MCP protocol (stdio or SSE)
43
+
44
+ qualys-cli-mcp (this server — 2 tools)
45
+ │ subprocess
46
+
47
+ qualys-cli (auth, retry, pagination, 273 API endpoints)
48
+ │ HTTPS
49
+
50
+ Qualys Cloud Platform
51
+ ```
52
+
53
+ The MCP server is a thin pass-through. It executes `qualys-cli` commands as subprocesses and returns parsed JSON. All authentication, retry logic, pagination, and error handling is done by `qualys-cli`.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install qualys-cli-mcp
59
+ ```
60
+
61
+ This automatically pulls `qualys-cli` as a dependency.
62
+
63
+ Or install from source:
64
+
65
+ ```bash
66
+ pip install git+https://stash.intranet.qualys.com/scm/aet/qualys-cli-mcp.git
67
+ ```
68
+
69
+ ## Configure Credentials
70
+
71
+ Credentials are stored securely in the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager). No environment variables or plain-text config files needed.
72
+
73
+ ```bash
74
+ qualys-cli configure
75
+ ```
76
+
77
+ This prompts for your username and password, auto-detects your Qualys platform, and stores everything securely. One-time setup.
78
+
79
+ ## Usage with Claude Desktop
80
+
81
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "qualys": {
87
+ "command": "qualys-mcp"
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ No credentials in the config — they're pulled from the OS keyring at runtime.
94
+
95
+ ## Usage with VS Code / Cursor
96
+
97
+ Add to your MCP settings:
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "qualys": {
103
+ "command": "qualys-mcp"
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ## Tools
110
+
111
+ The server exposes 2 tools:
112
+
113
+ ### `qualys_cli(command)`
114
+
115
+ Execute any qualys-cli command and get JSON back.
116
+
117
+ ```
118
+ qualys_cli("vm scan list --state Running --format json")
119
+ qualys_cli("vm host detection --ips 10.0.0.1 --severities 4,5 --format json")
120
+ qualys_cli("csam asset list --filter asset.riskScore=GREATER=900 --format json --limit 10")
121
+ qualys_cli("pm job list --format json")
122
+ qualys_cli("was finding list --severity 5 --format json")
123
+ ```
124
+
125
+ ### `qualys_cli_help()`
126
+
127
+ Get the full command reference — all modules, resources, actions, common patterns, and gotchas.
128
+
129
+ ## Supported Modules
130
+
131
+ | Module | Coverage | Auth |
132
+ |--------|----------|------|
133
+ | VM (Vulnerability Management) | Scans, hosts, detections, KB, appliances, reports | Basic |
134
+ | PC (Policy Compliance) | Scans, policies, posture | Basic |
135
+ | PM (Patch Management) | Jobs, patches, mitigations, reports | JWT |
136
+ | ETM (Enterprise TruRisk) | Reports, findings | JWT |
137
+ | WAS (Web App Scanning) | Webapps, scans, findings, schedules, reports | Basic |
138
+ | TC (TotalCloud / CSPM) | Connectors, evaluations, rules, CDR | JWT |
139
+ | CA (Cloud Agent) | Agents, activation keys, config profiles | Basic/JWT |
140
+ | CS (Container Security) | Images, containers, registries, sensors | JWT |
141
+ | CSAM (Asset Management) | Assets, EASM, vulnerabilities, domains | JWT |
142
+ | Asset | IPs, networks, groups | Basic |
143
+ | ScanAuth | Auth records, vaults | Basic |
144
+ | User | User management | Basic |
145
+ | Sub | Subscription config | Basic |
146
+
147
+ ## Configuration
148
+
149
+ All optional — the server works with zero configuration after `qualys-cli configure`.
150
+
151
+ | Variable | Default | Description |
152
+ |----------|---------|-------------|
153
+ | `QUALYS_PROFILE` | `default` | qualys-cli profile name |
154
+ | `QUALYS_CLI_PATH` | auto-detected | Path to qualys-cli binary |
155
+ | `QUALYS_MCP_TIMEOUT` | `120` | Command timeout (seconds) |
156
+ | `QUALYS_MCP_MAX_RESPONSE` | `800000` | Max response bytes before truncation |
157
+ | `QUALYS_MCP_MAX_RETRIES` | `2` | Retries on transient failures (rate limit, timeout, 5xx) |
158
+ | `QUALYS_MCP_ALLOWED_MODULES` | all | Comma-separated module whitelist |
159
+ | `QUALYS_MCP_DENY_WRITE` | `false` | `1` to block destructive commands |
160
+ | `QUALYS_MCP_AUDIT_LOG` | `~/.config/qualys-cli/mcp-audit.jsonl` | Audit log path (`off` to disable) |
161
+
162
+ ## Security Controls
163
+
164
+ ### Command Injection Prevention
165
+
166
+ All commands are validated before execution:
167
+ - Shell metacharacters (`;`, `|`, `` ` ``, `$`, `{}`, etc.) are blocked
168
+ - Only known qualys-cli modules are accepted
169
+ - Commands executed via explicit argv list — never through a shell
170
+
171
+ ### Credential Security
172
+
173
+ - Credentials stored in OS keyring only — never in env vars, config files, or MCP client config
174
+ - Error messages scrubbed before returning to LLM (passwords, tokens, Bearer values redacted)
175
+ - ANSI escape codes stripped from all error output
176
+
177
+ ### Module Whitelist
178
+
179
+ Restrict which Qualys modules are accessible:
180
+
181
+ ```bash
182
+ export QUALYS_MCP_ALLOWED_MODULES="vm,csam,ca"
183
+ ```
184
+
185
+ ### Read-Only Mode
186
+
187
+ Block all write/destructive operations:
188
+
189
+ ```bash
190
+ export QUALYS_MCP_DENY_WRITE=1
191
+ ```
192
+
193
+ Blocked actions: `launch`, `create`, `delete`, `cancel`, `update`, `import`,
194
+ `activate`, `deactivate`, `uninstall`, `purge`, `enable`, `disable`, etc.
195
+
196
+ ### Response Truncation
197
+
198
+ Responses exceeding 800KB are automatically truncated to stay under the MCP 1MB transport limit. Key fields (QID, TITLE, SEVERITY, CVE_LIST) are always preserved. Verbose fields (SOLUTION, DIAGNOSIS, CONSEQUENCE) are stripped first.
199
+
200
+ ### Resilience
201
+
202
+ - Automatic retry on transient failures (rate limit, timeout, server errors) with exponential backoff
203
+ - Configurable command timeout (default 120s) with process kill on expiry
204
+ - Append-only audit log of every tool call
205
+
206
+ ### Inherited from qualys-cli
207
+
208
+ - Exponential backoff with jitter on 429/5xx
209
+ - JWT auto-refresh on 401
210
+ - HMAC-signed audit log with tamper detection
211
+ - OS keyring credential storage
212
+ - Secret redaction in all logs
213
+ - Correlation IDs on every request
214
+ - Body size guards (250MB cap)
215
+ - Idempotent-only retries (POST/PUT/PATCH never auto-retry)
216
+
217
+ ## License
218
+
219
+ MIT
@@ -0,0 +1,11 @@
1
+ qualys_mcp/__init__.py,sha256=jHDLhC0M6NrrtEMfV9UaUxrBr8CibJnU4yA9aYYdjwE,78
2
+ qualys_mcp/__main__.py,sha256=cd4U3rpWb0QpoErwLWN-KaQPrquhPCPPleff7bIhQPE,78
3
+ qualys_mcp/auth.py,sha256=scgnrxE22wybv3RtEjiXa88rD7qB55s0XNx7Fu29Tx8,3573
4
+ qualys_mcp/server.py,sha256=UlIBP6syqv0mWVLCuf0GYvK_AzIK8qhpxwUawgI_3vw,42719
5
+ qualys_mcp/tenant.py,sha256=MHlK_4jW3HvaIyi_I0bWKubfwEvTjkR4Eop_EKLIvOQ,2292
6
+ qualys_mcp/tools.py,sha256=PG41pze1JhY4F_DrroNzr3nCvwiAcyT2o54IgvupPy4,8005
7
+ qualys_cli_mcp-0.1.0.dist-info/METADATA,sha256=GjiW6Z0mtK2emhut94VdI0bu8_efkXkeglFPsjaGQmw,7215
8
+ qualys_cli_mcp-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ qualys_cli_mcp-0.1.0.dist-info/entry_points.txt,sha256=_VuZDuFoPSK9XFCZ8yVVrop4Fbr7f-2DK4bSeZQnWsY,54
10
+ qualys_cli_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=AZcPTOxR2q5jykiRf6MuuzZQy-yMO-wKTJYtSNN4HmQ,1084
11
+ qualys_cli_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ qualys-mcp = qualys_mcp.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qualys-cli-mcp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
qualys_mcp/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """qualys-mcp — MCP server that wraps qualys-cli as a pass-through tool."""
qualys_mcp/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Allow running as: python -m qualys_mcp"""
2
+ from .server import main
3
+
4
+ main()
qualys_mcp/auth.py ADDED
@@ -0,0 +1,110 @@
1
+ # qualys_mcp/auth.py
2
+ """Authentication providers for multi-tenant qualys-mcp.
3
+
4
+ Two modes:
5
+ 1. API keys (StaticTokenVerifier) - simple, good for server-to-server
6
+ 2. OAuth 2.1 (JWTVerifier) - validates JWT Bearer tokens from an external IdP
7
+
8
+ Both use FastMCP's built-in auth infrastructure. Inside tool functions,
9
+ call `get_access_token()` to get the authenticated client's identity.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from typing import Any
16
+
17
+ log = logging.getLogger("qualys-mcp")
18
+
19
+
20
+ def create_api_key_verifier(
21
+ keys: dict[str, dict[str, Any]],
22
+ ) -> Any:
23
+ """Create a StaticTokenVerifier for API key authentication.
24
+
25
+ Note: StaticTokenVerifier stores keys in plain text in memory. The security
26
+ boundary is the config file itself (which should be file-permission restricted).
27
+ For deployments requiring hashed key storage, replace with a custom TokenVerifier.
28
+
29
+ Args:
30
+ keys: mapping of API key string to {"client_id": str, "scopes": list[str]}
31
+
32
+ Returns:
33
+ A FastMCP StaticTokenVerifier that validates Bearer tokens against the key set.
34
+ """
35
+ from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
36
+
37
+ token_map: dict[str, dict[str, Any]] = {}
38
+ for key_value, meta in keys.items():
39
+ token_map[key_value] = {
40
+ "client_id": meta["client_id"],
41
+ "scopes": meta.get("scopes", []),
42
+ }
43
+
44
+ verifier = StaticTokenVerifier(tokens=token_map)
45
+ log.info("API key auth: %d keys configured", len(token_map))
46
+ return verifier
47
+
48
+
49
+ def create_oauth_provider(
50
+ issuer_url: str,
51
+ audience: str,
52
+ algorithm: str | None = None,
53
+ ) -> Any:
54
+ """Create a JWTVerifier for OAuth 2.1 token validation.
55
+
56
+ Validates Bearer tokens (JWTs) issued by the configured authorization server.
57
+ Uses JWKS discovery from the issuer's .well-known endpoint.
58
+
59
+ Args:
60
+ issuer_url: OAuth issuer URL (e.g. "https://auth.cisco.com")
61
+ audience: Expected audience claim in tokens
62
+ algorithm: Allowed signing algorithm (default: "RS256")
63
+
64
+ Returns:
65
+ A FastMCP JWTVerifier that validates JWTs against the issuer's JWKS.
66
+ """
67
+ from fastmcp.server.auth.providers.jwt import JWTVerifier
68
+
69
+ verifier = JWTVerifier(
70
+ jwks_uri=f"{issuer_url.rstrip('/')}/.well-known/jwks.json",
71
+ issuer=issuer_url,
72
+ audience=audience,
73
+ algorithm=algorithm or "RS256",
74
+ )
75
+ log.info("OAuth auth: issuer=%s audience=%s", issuer_url, audience)
76
+ return verifier
77
+
78
+
79
+ def load_api_keys_from_tenant_config(config_path: str) -> dict[str, dict[str, Any]]:
80
+ """Load API keys from the tenant config file.
81
+
82
+ Reads [api_keys] section:
83
+ [api_keys.<name>]
84
+ key = "bearer-token-value"
85
+ client_id = "tenant-client-id"
86
+ scopes = ["qualys:read"]
87
+ """
88
+ import tomllib
89
+ from pathlib import Path
90
+
91
+ path = Path(config_path)
92
+ if not path.is_file():
93
+ return {}
94
+
95
+ with open(path, "rb") as f:
96
+ data = tomllib.load(f)
97
+
98
+ keys: dict[str, dict[str, Any]] = {}
99
+ for name, cfg in data.get("api_keys", {}).items():
100
+ for required in ("key", "client_id"):
101
+ if required not in cfg:
102
+ raise ValueError(f"api_keys.{name} missing required field {required!r}")
103
+ key_value = cfg["key"]
104
+ if key_value in keys:
105
+ log.warning("duplicate API key in config (api_keys.%s overwrites previous)", name)
106
+ keys[key_value] = {
107
+ "client_id": cfg["client_id"],
108
+ "scopes": cfg.get("scopes", ["qualys:read"]),
109
+ }
110
+ return keys