vmware-debug 2.2.0__tar.gz → 2.3.1__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.
Files changed (99) hide show
  1. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/.claude-plugin/plugin.json +1 -1
  2. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/.mcp.json +1 -1
  3. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/PKG-INFO +73 -3
  4. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/README-CN.md +67 -0
  5. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/README.md +69 -0
  6. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/RELEASE_NOTES.md +27 -1
  7. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/openclaw.plugin.json +1 -1
  8. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/package.json +1 -1
  9. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/pyproject.toml +4 -3
  10. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/server.json +2 -2
  11. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/SKILL.md +3 -1
  12. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/setup-guide.md +38 -2
  13. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_audit_isolation.py +7 -2
  14. vmware_debug-2.3.1/tests/eval/regression/test_cli_policy_denial.py +23 -0
  15. vmware_debug-2.3.1/tests/test_policy_roles.py +32 -0
  16. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/uv.lock +16 -14
  17. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/__init__.py +1 -1
  18. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/cli.py +17 -1
  19. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/mcp_server/server.py +3 -0
  20. vmware_debug-2.3.1/vmware_debug/policy_roles.py +27 -0
  21. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/.clawhubignore +0 -0
  22. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/.gitignore +0 -0
  23. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/SECURITY.md +0 -0
  24. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/docs/plans/2026-08-28-investigation-layer-design.md +0 -0
  25. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/agent-guardrails.md +0 -0
  26. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/capabilities.md +0 -0
  27. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/cli-reference.md +0 -0
  28. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/event-envelope.md +0 -0
  29. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/skills/vmware-debug/references/routing.md +0 -0
  30. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/__init__.py +0 -0
  31. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/conftest.py +0 -0
  32. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/__init__.py +0 -0
  33. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/__init__.py +0 -0
  34. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/_family.py +0 -0
  35. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/_scores.json +0 -0
  36. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/_scoring.py +0 -0
  37. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/_skill.py +0 -0
  38. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/conftest.py +0 -0
  39. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/test_entity_reachability.py +0 -0
  40. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/test_error_actionability.py +0 -0
  41. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/test_tool_description_quality.py +0 -0
  42. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  43. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/__init__.py +0 -0
  44. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/_vsphere_event_vocabulary.py +0 -0
  45. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_capability_grader.py +0 -0
  46. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_case_id_non_ascii.py +0 -0
  47. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_case_plan_max_steps.py +0 -0
  48. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_classification_against_vsphere_vocabulary.py +0 -0
  49. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_debug_regressions.py +0 -0
  50. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_environment_resolver_isolation.py +0 -0
  51. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_every_tool_parameter_is_documented.py +0 -0
  52. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_evidence_payload_contract.py +0 -0
  53. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_host_lifecycle_classification.py +0 -0
  54. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_investigation_metrics.py +0 -0
  55. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_real_alert_titles_classify.py +0 -0
  56. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_result_envelope.py +0 -0
  57. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_skill_name_aliases.py +0 -0
  58. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_spike_resolution.py +0 -0
  59. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/eval/regression/test_tool_annotations.py +0 -0
  60. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_conclusion.py +0 -0
  61. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_evidence.py +0 -0
  62. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_grading.py +0 -0
  63. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_grading_per_hypothesis.py +0 -0
  64. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_hypotheses.py +0 -0
  65. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_ids.py +0 -0
  66. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_ledger_concurrency.py +0 -0
  67. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_plan.py +0 -0
  68. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_readiness.py +0 -0
  69. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_store.py +0 -0
  70. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_timeline_close.py +0 -0
  71. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_case_tools.py +0 -0
  72. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_knowledge.py +0 -0
  73. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_mcp_audit.py +0 -0
  74. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_safe_error_passthrough.py +0 -0
  75. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/tests/test_timeline.py +0 -0
  76. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/envelope.py +0 -0
  77. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/mcp/__init__.py +0 -0
  78. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/mcp/tools.py +0 -0
  79. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/mcp_server/__init__.py +0 -0
  80. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/__init__.py +0 -0
  81. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/__init__.py +0 -0
  82. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/api.py +0 -0
  83. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/conclusion.py +0 -0
  84. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/evidence.py +0 -0
  85. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/grading.py +0 -0
  86. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/hypotheses.py +0 -0
  87. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/ids.py +0 -0
  88. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/knowledge.py +0 -0
  89. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/model.py +0 -0
  90. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/payloads.py +0 -0
  91. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/plan.py +0 -0
  92. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/readiness.py +0 -0
  93. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/sources.py +0 -0
  94. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/store.py +0 -0
  95. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/cases/timeline.py +0 -0
  96. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/skill_names.py +0 -0
  97. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/ops/timeline.py +0 -0
  98. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/rules/evidence_sources.yaml +0 -0
  99. {vmware_debug-2.2.0 → vmware_debug-2.3.1}/vmware_debug/rules/grading_rules.yaml +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "vmware-debug",
4
4
  "displayName": "VMware Debug",
5
- "version": "2.2.0",
5
+ "version": "2.3.1",
6
6
  "description": "VMware incident timeline, root-cause routing, local case ledger; no vSphere access. 14 MCP tools.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-debug==2.2.0",
8
+ "vmware-debug==2.3.1",
9
9
  "vmware-debug-mcp"
10
10
  ]
11
11
  }
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vmware-debug
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: VMware diagnostic brain — offline incident triage, log/event correlation, and root-cause routing across the VMware skill family
5
5
  Project-URL: Homepage, https://github.com/vmware-skills/VMware-Debug
6
6
  Project-URL: Repository, https://github.com/vmware-skills/VMware-Debug
7
7
  Project-URL: Issues, https://github.com/vmware-skills/VMware-Debug/issues
8
8
  Project-URL: Changelog, https://github.com/vmware-skills/VMware-Debug/blob/main/RELEASE_NOTES.md
9
- Author-email: Wei Zhou <wei-wz.zhou@broadcom.com>
9
+ Author-email: Wei Zhou <zhouwei008@gmail.com>
10
10
  License-Expression: MIT
11
11
  Keywords: ai-ops,debug,diagnostics,mcp,rca,troubleshooting,vmware,vsphere
12
12
  Classifier: Development Status :: 4 - Beta
@@ -14,7 +14,8 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Topic :: System :: Monitoring
16
16
  Requires-Python: >=3.10
17
- Requires-Dist: mcp[cli]<2.0,>=1.28
17
+ Requires-Dist: certifi>=2026.10.19
18
+ Requires-Dist: mcp[cli]<2.0,>=1.30
18
19
  Requires-Dist: pyyaml<7.0,>=6.0
19
20
  Requires-Dist: rich<15.0,>=13.0
20
21
  Requires-Dist: typer<1.0,>=0.12
@@ -25,6 +26,8 @@ Description-Content-Type: text/markdown
25
26
 
26
27
  # VMware Debug
27
28
 
29
+ For a read-only check of this skill with your model and agent host, use the shared [model-fit self-test](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md) in the marketplace root.
30
+
28
31
  > **Disclaimer**: Community-maintained open-source project, **not affiliated with,
29
32
  > endorsed by, or sponsored by VMware, Inc. or Broadcom Inc.** "VMware" and
30
33
  > "vSphere" are trademarks of Broadcom. Source is publicly auditable under the MIT
@@ -105,6 +108,8 @@ event actually is.
105
108
 
106
109
  ## Security
107
110
 
111
+ CLI policy refusals print the reason and rule, exit with code 1, and retain one `denied` audit row.
112
+
108
113
  Offline and credential-free by construction: it connects to no vCenter/NSX/Aria, has no destructive surface on
109
114
  VMware, and holds no secret to leak. The only writes are the local case ledger: evidence, gaps, hypotheses and
110
115
  grade history are append-only; `timeline.md` is regenerated from the evidence, and `case.json` holds the current
@@ -145,6 +150,71 @@ pip wheel . -w dist # → dist/*.whl (or: uv build, for just this packa
145
150
  pip install --no-index --find-links dist vmware-debug
146
151
  ```
147
152
 
153
+ <!-- usage: remote-and-exposure -->
154
+ ## Remote access (HTTPS) and limiting tools
155
+
156
+ By default `vmware-debug mcp` speaks stdio to one local client and offers every tool. Two options change that
157
+ (vmware-policy ≥ 1.21.0).
158
+
159
+ **Serve over HTTPS** so clients on other machines can connect. Every caller needs a bearer token:
160
+
161
+ ```bash
162
+ vmware-audit token create alice # prints the token once; give it to alice
163
+ vmware-audit token list # names only
164
+ vmware-audit token revoke alice # refused from its next request, no restart
165
+ vmware-debug mcp --transport https --host 0.0.0.0 --port 8443 \
166
+ --cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
167
+ --public-name mcp.example.com
168
+ ```
169
+
170
+ Connect a client (Claude Code shown; any MCP client that supports streamable HTTP and custom headers works):
171
+
172
+ ```bash
173
+ claude mcp add --transport http vmware-debug https://mcp.example.com:8443/mcp \
174
+ --header "Authorization: Bearer $VMWARE_MCP_TOKEN"
175
+ ```
176
+
177
+ ```json
178
+ {
179
+ "mcpServers": {
180
+ "vmware-debug": {
181
+ "type": "http",
182
+ "url": "https://mcp.example.com:8443/mcp",
183
+ "headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ - The server will not start without a token, refuses Host/Origin values outside `--public-name`, and checks
190
+ before it binds that unauthenticated requests get 401. Use a certificate your clients trust for those names.
191
+ - Behind a TLS-terminating reverse proxy on the same machine use `--transport http --host 127.0.0.1
192
+ --public-name mcp.example.com` and pass the `Authorization` header through.
193
+ - Behind a load balancer or gateway on another machine, keep `--transport https` (a self-signed certificate is
194
+ fine), list in `--public-name` the host name the balancer sends as `Host`, and have it forward `Authorization`.
195
+ Its health probe can use `GET /healthz`, which needs no token and returns only `{"status": "ok"}`
196
+ (vmware-policy ≥ 1.23.0).
197
+ - Send `traceparent` or `X-Request-ID` with each request to have it stored on the audit row;
198
+ `vmware-audit log --correlation-id ID` then finds the calls behind one platform request.
199
+ - The audit log records each caller as `token:<name>`. Calls naming different targets run in parallel; calls that
200
+ name no target run one at a time, so name the target when several people share a server.
201
+ - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
202
+ nothing on the platform. Run one endpoint per privilege level.
203
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
204
+
205
+ **Offer only read tools, plus the writes you choose:**
206
+
207
+ ```bash
208
+ vmware-debug mcp --expose read --allow-write close_case
209
+ # or: VMWARE_DEBUG_MCP_EXPOSE=read VMWARE_DEBUG_MCP_ALLOW_WRITE=close_case vmware-debug mcp
210
+ ```
211
+
212
+ Tools not offered do not appear in the client's tool list. A bad value or an unknown write tool stops the server
213
+ (exit 2). This limits what clients are offered; it is not authorization, so the account itself should be
214
+ least-privilege.
215
+
216
+ Details: `skills/vmware-debug/references/setup-guide.md` ("Serving MCP over HTTPS", "Limiting which tools are offered").
217
+
148
218
  ## License
149
219
 
150
220
  MIT.
@@ -2,6 +2,8 @@
2
2
 
3
3
  # VMware Debug(中文)
4
4
 
5
+ 要用自己的模型和 Agent 平台进行只读自测,请看 marketplace 根目录的[共享模型适配自测任务](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md)。
6
+
5
7
  > **声明**:本项目为社区维护的开源项目,**与 VMware, Inc. 或 Broadcom Inc. 无任何隶属、
6
8
  > 背书或赞助关系。** "VMware"、"vSphere" 为 Broadcom 商标。源码以 MIT 许可证公开可审计。
7
9
 
@@ -68,6 +70,8 @@ debug;debug 因此与其它包零运行时依赖。**请把每条事件的 `ev
68
70
 
69
71
  ## 安全
70
72
 
73
+ CLI 被策略拒绝时会显示原因和规则,以退出码 1 结束,并保留一条 `denied` 审计记录。
74
+
71
75
  结构上离线、无凭据:不连任何 vCenter/NSX/Aria,对 VMware 没有可破坏面,也没有秘密可泄露。
72
76
  唯一的写入是本地案卷:证据、缺口、假设和等级历史只增不改;`timeline.md` 由证据重新生成,
73
77
  `case.json` 保存当前等级和状态。多个写入者同样成立:每次写案卷都要先拿该案卷的锁文件(`.ledger.lock`,
@@ -102,6 +106,69 @@ pip wheel . -w dist # → dist/*.whl (或:uv build,只构建本
102
106
  pip install --no-index --find-links dist vmware-debug
103
107
  ```
104
108
 
109
+ <!-- usage: remote-and-exposure -->
110
+ ## 远程访问(HTTPS)与限制工具
111
+
112
+ 默认情况下 `vmware-debug mcp` 通过 stdio 服务一个本地客户端,并提供全部工具。以下两个选项可以改变这一点
113
+ (需 vmware-policy ≥ 1.21.0)。
114
+
115
+ **通过 HTTPS 提供服务**,让其他机器上的客户端连接。每个调用者都需要一个 bearer token:
116
+
117
+ ```bash
118
+ vmware-audit token create alice # 只显示一次,交给 alice
119
+ vmware-audit token list # 只列名字
120
+ vmware-audit token revoke alice # 下一个请求起即拒绝,无需重启
121
+ vmware-debug mcp --transport https --host 0.0.0.0 --port 8443 \
122
+ --cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
123
+ --public-name mcp.example.com
124
+ ```
125
+
126
+ 连接客户端(以 Claude Code 为例;任何支持 streamable HTTP 和自定义请求头的 MCP 客户端都可以):
127
+
128
+ ```bash
129
+ claude mcp add --transport http vmware-debug https://mcp.example.com:8443/mcp \
130
+ --header "Authorization: Bearer $VMWARE_MCP_TOKEN"
131
+ ```
132
+
133
+ ```json
134
+ {
135
+ "mcpServers": {
136
+ "vmware-debug": {
137
+ "type": "http",
138
+ "url": "https://mcp.example.com:8443/mcp",
139
+ "headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ - 没有 token 时服务不会启动;`--public-name` 之外的 Host/Origin 一律拒绝;绑定端口前会自检,确认未认证请求得到 401。
146
+ 证书要用客户端信任、且覆盖这些名字的证书。
147
+ - 在同一台机器上由反向代理终结 TLS 时,用 `--transport http --host 127.0.0.1 --public-name mcp.example.com`,
148
+ 并让代理透传 `Authorization` 头。
149
+ - 负载均衡或网关在另一台机器上时,保持 `--transport https`(自签证书也可以),在 `--public-name` 里写上负载均衡
150
+ 转发时带的 `Host` 名,并让它透传 `Authorization`。健康检查可以用 `GET /healthz`:不需要 token,只返回
151
+ `{"status": "ok"}`(需 vmware-policy ≥ 1.23.0)。
152
+ - 每个请求带上 `traceparent` 或 `X-Request-ID`,它会记进审计记录;用 `vmware-audit log --correlation-id ID`
153
+ 就能查到平台上某一次请求对应的调用。
154
+ - 审计日志把每个调用者记为 `token:<名字>`。指定不同 target 的调用并行执行;不指定 target 的调用逐个执行,
155
+ 所以多人共用一个 server 时请写明 target。
156
+ - **能连到这个端点的人,都以该 server 配置的账号操作。** token 只用来识别调用者,不在平台上授予任何权限。
157
+ 每个权限级别单独起一个端点。
158
+ - **一个端点给多人或多种角色用:** 先运行一次 `vmware-audit roles enable`,再给每个 token 指定角色(`vmware-audit token create 名字 --role reader`,或在 `roles.yaml` 里定义可用的工具和 target;需 vmware-policy ≥ 1.24.0)。角色只能限定这个 token 在本端点上能调用什么,不能超出账号本身的权限。详见 vmware-policy README 的「网络调用者的角色」一节。
159
+
160
+ **只提供读工具,加上你选定的写工具:**
161
+
162
+ ```bash
163
+ vmware-debug mcp --expose read --allow-write close_case
164
+ # 或:VMWARE_DEBUG_MCP_EXPOSE=read VMWARE_DEBUG_MCP_ALLOW_WRITE=close_case vmware-debug mcp
165
+ ```
166
+
167
+ 未提供的工具不会出现在客户端的工具列表里。取值错误或写工具名不存在时服务直接退出(exit 2)。
168
+ 这只限制提供给客户端的工具,不是授权;账号本身仍应遵循最小权限。
169
+
170
+ 详见 `skills/vmware-debug/references/setup-guide.md`("Serving MCP over HTTPS"、"Limiting which tools are offered")。
171
+
105
172
  ## 许可证
106
173
 
107
174
  MIT。
@@ -2,6 +2,8 @@
2
2
 
3
3
  # VMware Debug
4
4
 
5
+ For a read-only check of this skill with your model and agent host, use the shared [model-fit self-test](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md) in the marketplace root.
6
+
5
7
  > **Disclaimer**: Community-maintained open-source project, **not affiliated with,
6
8
  > endorsed by, or sponsored by VMware, Inc. or Broadcom Inc.** "VMware" and
7
9
  > "vSphere" are trademarks of Broadcom. Source is publicly auditable under the MIT
@@ -82,6 +84,8 @@ event actually is.
82
84
 
83
85
  ## Security
84
86
 
87
+ CLI policy refusals print the reason and rule, exit with code 1, and retain one `denied` audit row.
88
+
85
89
  Offline and credential-free by construction: it connects to no vCenter/NSX/Aria, has no destructive surface on
86
90
  VMware, and holds no secret to leak. The only writes are the local case ledger: evidence, gaps, hypotheses and
87
91
  grade history are append-only; `timeline.md` is regenerated from the evidence, and `case.json` holds the current
@@ -122,6 +126,71 @@ pip wheel . -w dist # → dist/*.whl (or: uv build, for just this packa
122
126
  pip install --no-index --find-links dist vmware-debug
123
127
  ```
124
128
 
129
+ <!-- usage: remote-and-exposure -->
130
+ ## Remote access (HTTPS) and limiting tools
131
+
132
+ By default `vmware-debug mcp` speaks stdio to one local client and offers every tool. Two options change that
133
+ (vmware-policy ≥ 1.21.0).
134
+
135
+ **Serve over HTTPS** so clients on other machines can connect. Every caller needs a bearer token:
136
+
137
+ ```bash
138
+ vmware-audit token create alice # prints the token once; give it to alice
139
+ vmware-audit token list # names only
140
+ vmware-audit token revoke alice # refused from its next request, no restart
141
+ vmware-debug mcp --transport https --host 0.0.0.0 --port 8443 \
142
+ --cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
143
+ --public-name mcp.example.com
144
+ ```
145
+
146
+ Connect a client (Claude Code shown; any MCP client that supports streamable HTTP and custom headers works):
147
+
148
+ ```bash
149
+ claude mcp add --transport http vmware-debug https://mcp.example.com:8443/mcp \
150
+ --header "Authorization: Bearer $VMWARE_MCP_TOKEN"
151
+ ```
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "vmware-debug": {
157
+ "type": "http",
158
+ "url": "https://mcp.example.com:8443/mcp",
159
+ "headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ - The server will not start without a token, refuses Host/Origin values outside `--public-name`, and checks
166
+ before it binds that unauthenticated requests get 401. Use a certificate your clients trust for those names.
167
+ - Behind a TLS-terminating reverse proxy on the same machine use `--transport http --host 127.0.0.1
168
+ --public-name mcp.example.com` and pass the `Authorization` header through.
169
+ - Behind a load balancer or gateway on another machine, keep `--transport https` (a self-signed certificate is
170
+ fine), list in `--public-name` the host name the balancer sends as `Host`, and have it forward `Authorization`.
171
+ Its health probe can use `GET /healthz`, which needs no token and returns only `{"status": "ok"}`
172
+ (vmware-policy ≥ 1.23.0).
173
+ - Send `traceparent` or `X-Request-ID` with each request to have it stored on the audit row;
174
+ `vmware-audit log --correlation-id ID` then finds the calls behind one platform request.
175
+ - The audit log records each caller as `token:<name>`. Calls naming different targets run in parallel; calls that
176
+ name no target run one at a time, so name the target when several people share a server.
177
+ - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
178
+ nothing on the platform. Run one endpoint per privilege level.
179
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
180
+
181
+ **Offer only read tools, plus the writes you choose:**
182
+
183
+ ```bash
184
+ vmware-debug mcp --expose read --allow-write close_case
185
+ # or: VMWARE_DEBUG_MCP_EXPOSE=read VMWARE_DEBUG_MCP_ALLOW_WRITE=close_case vmware-debug mcp
186
+ ```
187
+
188
+ Tools not offered do not appear in the client's tool list. A bad value or an unknown write tool stops the server
189
+ (exit 2). This limits what clients are offered; it is not authorization, so the account itself should be
190
+ least-privilege.
191
+
192
+ Details: `skills/vmware-debug/references/setup-guide.md` ("Serving MCP over HTTPS", "Limiting which tools are offered").
193
+
125
194
  ## License
126
195
 
127
196
  MIT.
@@ -1,3 +1,20 @@
1
+ ## v2.3.1 — readable Policy refusals and dependency security (2026-11-08)
2
+
3
+ * Update public author/project contact metadata to `zhouwei008@gmail.com`.
4
+
5
+ ### dependency security and sandbox test stability
6
+ * Require certifi 2026.10.19 or later for MCP's HTTP/TLS dependencies (GHSA-6q48-6c7w-9gw2); the lock resolves 2026.10.23.
7
+ * The audit-isolation regression still proves its write reached only the sandbox DB; it skips the production-DB comparison when the host sandbox denies even read-only access to that DB.
8
+ * CLI policy refusals now print their reason and rule without a traceback. The audited wrapper still records exactly one `denied` row before the CLI exits with code 1.
9
+
10
+ ## v2.3.0 — roles for network callers (HLD §18)
11
+
12
+ * Declares the role metadata vmware-policy ≥ 1.24.0 needs to apply **roles** to this server over HTTPS
13
+ (HLD §18): that no tool takes a target. With an older vmware-policy nothing changes. Once an operator runs
14
+ `vmware-audit roles enable`, a token's role decides which tools and targets it may use here; a refused call is
15
+ audited as `denied`. See vmware-policy's README, "Roles for network callers".
16
+ * README and setup guide: one endpoint for several people or roles.
17
+
1
18
  ## v2.2.0 — serve MCP over HTTPS
2
19
 
3
20
  `vmware-debug mcp` can now serve the network as well as stdio (HLD §16, via vmware-policy 1.21.0):
@@ -8,6 +25,15 @@ vmware-debug mcp --transport https --host 0.0.0.0 --port 8443 \
8
25
  --cert server.pem --key server-key.pem --public-name mcp.example.com
9
26
  ```
10
27
 
28
+ Connect a client and manage callers (Claude Code shown; any MCP client with streamable HTTP and custom headers works):
29
+
30
+ ```bash
31
+ claude mcp add --transport http vmware-debug https://mcp.example.com:8443/mcp \
32
+ --header "Authorization: Bearer $VMWARE_MCP_TOKEN"
33
+ vmware-audit token list # names only
34
+ vmware-audit token revoke alice # refused from its next request, no restart
35
+ ```
36
+
11
37
  * Every request needs `Authorization: Bearer <token>`; the server will not start without a token, refuses Host or
12
38
  Origin values outside `--public-name`, and checks itself (unauthenticated requests must get 401) before it
13
39
  binds. `--transport http` binds loopback only, for a TLS-terminating reverse proxy.
@@ -881,4 +907,4 @@ executes fixes (advisor/executor split, mirroring vmware-harden → vmware-pilot
881
907
  ### Notes
882
908
  - Read-only by construction; remediation is routed, never executed.
883
909
  - `parse_timestamp` rejects implausible/garbage timestamps loudly rather than
884
- silently landing at the epoch.
910
+ silently landing at the epoch.
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-debug==2.2.0",
14
+ "vmware-debug==2.3.1",
15
15
  "vmware-debug-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/vmware-debug",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "VMware incident timeline, root-cause routing, local case ledger; no vSphere access. 14 MCP tools.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vmware-debug"
7
- version = "2.2.0"
7
+ version = "2.3.1"
8
8
  description = "VMware diagnostic brain — offline incident triage, log/event correlation, and root-cause routing across the VMware skill family"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.10"
12
- authors = [{ name = "Wei Zhou", email = "wei-wz.zhou@broadcom.com" }]
12
+ authors = [{ name = "Wei Zhou", email = "zhouwei008@gmail.com" }]
13
13
  keywords = ["vmware", "vsphere", "debug", "troubleshooting", "diagnostics", "rca", "mcp", "ai-ops"]
14
14
  classifiers = [
15
15
  "Development Status :: 4 - Beta",
@@ -20,7 +20,8 @@ classifiers = [
20
20
  dependencies = [
21
21
  "typer>=0.12,<1.0",
22
22
  "rich>=13.0,<15.0",
23
- "mcp[cli]>=1.28,<2.0",
23
+ "mcp[cli]>=1.30,<2.0",
24
+ "certifi>=2026.10.19", # GHSA-6q48-6c7w-9gw2 via MCP/httpx
24
25
  "vmware-policy>=1.21.0,<2.0",
25
26
  # Imported directly by ops/cases/grading.py for the rules file. It has
26
27
  # always arrived transitively via vmware-policy; a package should
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/vmware-skills/VMware-Debug",
8
8
  "source": "github"
9
9
  },
10
- "version": "2.2.0",
10
+ "version": "2.3.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-debug",
15
- "version": "2.2.0",
15
+ "version": "2.3.1",
16
16
  "runtimeHint": "uvx",
17
17
  "packageArguments": [
18
18
  {
@@ -25,6 +25,8 @@ metadata: {"openclaw":{"requires":{"anyBins":["vmware-debug","uvx"]},"optional":
25
25
 
26
26
  # VMware Debug
27
27
 
28
+ > Self-test: [guide](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md).
29
+
28
30
  > **Disclaimer**: Community-maintained open-source project, **not affiliated with,
29
31
  > endorsed by, or sponsored by VMware, Inc. or Broadcom Inc.** "VMware" and "vSphere"
30
32
  > are trademarks of Broadcom. Source is publicly auditable under the MIT license.
@@ -51,7 +53,7 @@ write tools touch only the local case ledger.
51
53
  ## Quick Install
52
54
 
53
55
  ```bash
54
- uv tool install vmware-debug==2.2.0
56
+ uv tool install vmware-debug==2.3.1
55
57
  vmware-debug categories # see what it can diagnose
56
58
  ```
57
59
 
@@ -6,7 +6,7 @@ is a pure, offline correlation engine. There is no `config.yaml` and no `.env`.
6
6
  ## Install
7
7
 
8
8
  ```bash
9
- uv tool install vmware-debug==2.2.0
9
+ uv tool install vmware-debug==2.3.1
10
10
  vmware-debug categories # verify it runs
11
11
  ```
12
12
 
@@ -15,7 +15,7 @@ vmware-debug categories # verify it runs
15
15
  ```json
16
16
  {
17
17
  "command": "uvx",
18
- "args": ["--from", "vmware-debug==2.2.0", "vmware-debug-mcp"]
18
+ "args": ["--from", "vmware-debug==2.3.1", "vmware-debug-mcp"]
19
19
  }
20
20
  ```
21
21
 
@@ -51,6 +51,42 @@ vmware-debug mcp --expose read --allow-write close_case
51
51
  - This limits what clients are offered. It is **not** authorization. This server's write tools change case records on this machine;
52
52
  deny rules in `~/.vmware/rules.yaml` still apply to every exposed tool.
53
53
 
54
+ ## Serving MCP over HTTPS
55
+
56
+ By default the MCP server speaks stdio to one local client. To let clients on other machines reach it, serve it over
57
+ HTTPS. Every caller then needs a bearer token.
58
+
59
+ ```bash
60
+ vmware-audit token create alice # prints the token once; give it to alice
61
+ vmware-debug mcp --transport https --host 0.0.0.0 --port 8443 \
62
+ --cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
63
+ --public-name mcp.example.com
64
+ ```
65
+
66
+ - Clients connect to `https://mcp.example.com:8443/mcp` and send `Authorization: Bearer <token>`.
67
+ - `--public-name` lists every host name clients use (comma-separated); requests for any other Host or Origin are
68
+ refused. Use a certificate your clients trust for those names.
69
+ - Behind a reverse proxy that terminates TLS (Nginx, HAProxy) on the same machine, use
70
+ `--transport http --host 127.0.0.1 --public-name mcp.example.com` (the names the proxy forwards in `Host`), and
71
+ have the proxy pass the `Authorization` header through.
72
+ - Behind a load balancer or gateway on another machine, keep `--transport https` (a self-signed certificate is
73
+ fine: the balancer is the client that has to trust it), list in `--public-name` the host name the balancer sends
74
+ as `Host`, and have it forward `Authorization`. Point its health probe at `GET /healthz`: it needs no token, returns
75
+ only `{"status": "ok"}` and accepts any `Host` (vmware-policy ≥ 1.23.0). Every other path still needs the token.
76
+ - Correlation: each audit row stores the caller's `traceparent` trace-id, else its `X-Request-ID` /
77
+ `X-Correlation-ID`. Have the agent platform send one, and `vmware-audit log --correlation-id ID` lists the calls
78
+ behind one platform request.
79
+ - `vmware-audit token list` / `token revoke NAME` manage callers; a revoked token is refused from its next request.
80
+ Replace a leaked token under a new name.
81
+ - The server will not start without at least one token, and checks before it binds that unauthenticated requests
82
+ are refused.
83
+ - Each audit row records the caller as `token:<name>`. Calls naming different targets run in parallel; calls that
84
+ name no target run one at a time, so name the target when several people use one server.
85
+ - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
86
+ nothing on the platform. Run one endpoint per privilege level (for example a read-only account behind
87
+ `--expose read`, and a separate endpoint for operators).
88
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
89
+
54
90
  ## Security
55
91
 
56
92
  > **Disclaimer**: Community-maintained open-source project, **not affiliated with,
@@ -62,6 +62,11 @@ def test_an_audited_write_lands_in_the_sandbox_and_not_in_the_real_db() -> None:
62
62
  real_db = REAL_HOME / ".vmware" / "audit.db"
63
63
  if not real_db.exists():
64
64
  pytest.skip("no production audit database on this machine to check against")
65
- with sqlite3.connect(f"file:{real_db}?mode=ro", uri=True) as con:
66
- leaked = con.execute("SELECT count(*) FROM audit_log WHERE tool = ?", (marker,)).fetchone()[0]
65
+ try:
66
+ with sqlite3.connect(f"file:{real_db}?mode=ro", uri=True) as con:
67
+ leaked = con.execute("SELECT count(*) FROM audit_log WHERE tool = ?", (marker,)).fetchone()[0]
68
+ except sqlite3.OperationalError as exc:
69
+ if "unable to open database file" not in str(exc):
70
+ raise
71
+ pytest.skip("production audit DB is inaccessible from this sandbox")
67
72
  assert leaked == 0, f"{leaked} row(s) from this test reached {real_db}"
@@ -0,0 +1,23 @@
1
+ """CLI policy denial must remain a denial in audit and be readable by an operator."""
2
+
3
+ import pytest
4
+ from typer.testing import CliRunner
5
+
6
+ from vmware_debug.cli import app
7
+ from vmware_policy import cli_guard
8
+ from vmware_policy.policy import PolicyDenied, PolicyResult
9
+
10
+
11
+ @pytest.mark.parametrize("args", [["categories"], ["triage"]])
12
+ def test_denied_cli_command_prints_reason_and_records_one_denial(monkeypatch, args):
13
+ rows = []
14
+ def deny(*args, **kwargs):
15
+ raise PolicyDenied(PolicyResult(allowed=False, rule="ops-review", reason="offline checks denied"))
16
+ monkeypatch.setattr(cli_guard, "guard", deny)
17
+ monkeypatch.setattr(cli_guard, "audit_call", lambda *a, **kw: rows.append(kw))
18
+ result = CliRunner().invoke(app, args, input="[]")
19
+ assert result.exit_code == 1
20
+ assert "Denied by policy" in result.output
21
+ assert "offline checks denied" in result.output and "ops-review" in result.output
22
+ assert "Traceback" not in result.output
23
+ assert len(rows) == 1 and rows[0]["status"] == "denied"
@@ -0,0 +1,32 @@
1
+ """HLD §18: the role metadata this skill declares matches its real tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import pytest
8
+
9
+ from vmware_debug import policy_roles
10
+
11
+
12
+ def _registry() -> set[str]:
13
+ from vmware_debug.mcp_server.server import build_server
14
+
15
+ return {t.name for t in asyncio.run(build_server().list_tools())}
16
+
17
+
18
+ @pytest.mark.unit
19
+ def test_every_declared_name_is_a_real_tool():
20
+ named = set(policy_roles.ESCAPES) | set(getattr(policy_roles, "AKO_TOOLS", ()))
21
+ assert named - _registry() == set(), sorted(named - _registry())
22
+
23
+
24
+ @pytest.mark.unit
25
+ def test_metadata_is_declared_when_policy_has_roles():
26
+ roles = pytest.importorskip("vmware_policy.roles")
27
+ import vmware_debug.mcp_server.server # noqa: F401 (registers the metadata on import)
28
+
29
+ meta = roles.metadata("debug")
30
+ assert meta is not None and meta.version == 1
31
+ assert meta.escapes == policy_roles.ESCAPES
32
+ assert (meta.resolver is not None) is False
@@ -1,12 +1,12 @@
1
1
  version = 1
2
- revision = 3
2
+ revision = 5
3
3
  requires-python = ">=3.10"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.14' and sys_platform == 'win32'",
6
6
  "python_full_version >= '3.14' and sys_platform != 'win32'",
7
7
  "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
8
- "python_full_version < '3.11' and sys_platform == 'win32'",
9
8
  "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'win32'",
9
+ "python_full_version < '3.11' and sys_platform == 'win32'",
10
10
  "python_full_version < '3.11' and sys_platform != 'win32'",
11
11
  ]
12
12
 
@@ -53,11 +53,11 @@ wheels = [
53
53
 
54
54
  [[package]]
55
55
  name = "certifi"
56
- version = "2026.6.17"
56
+ version = "2026.10.23"
57
57
  source = { registry = "https://pypi.org/simple" }
58
- sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
58
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
59
59
  wheels = [
60
- { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
60
+ { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
61
61
  ]
62
62
 
63
63
  [[package]]
@@ -356,7 +356,7 @@ name = "exceptiongroup"
356
356
  version = "1.3.1"
357
357
  source = { registry = "https://pypi.org/simple" }
358
358
  dependencies = [
359
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
359
+ { name = "typing-extensions" },
360
360
  ]
361
361
  sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
362
362
  wheels = [
@@ -469,7 +469,7 @@ wheels = [
469
469
 
470
470
  [[package]]
471
471
  name = "mcp"
472
- version = "1.28.1"
472
+ version = "1.30.0"
473
473
  source = { registry = "https://pypi.org/simple" }
474
474
  dependencies = [
475
475
  { name = "anyio" },
@@ -487,9 +487,9 @@ dependencies = [
487
487
  { name = "typing-inspection" },
488
488
  { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
489
489
  ]
490
- sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" }
490
+ sdist = { url = "https://files.pythonhosted.org/packages/ba/93/0142dc84a666daf8ad51a34268f34c12fd6fda4f3810c4be2504eecc8212/mcp-1.30.0.tar.gz", hash = "sha256:445414625fce5c295faa505bb11bacece661ab6f4028d57c935db57820b7a3e4", size = 680511, upload-time = "2026-09-07T14:34:15.845Z" }
491
491
  wheels = [
492
- { url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" },
492
+ { url = "https://files.pythonhosted.org/packages/f5/f4/e58bc33317c92a0203664daaf00bf6f41166cc0149e5d6870a03f7cd004a/mcp-1.30.0-py3-none-any.whl", hash = "sha256:666edb5009503e1047c9d60346a756f94b261f05cc2625f23d41c728ffc484d0", size = 234581, upload-time = "2026-09-07T14:34:14.266Z" },
493
493
  ]
494
494
 
495
495
  [package.optional-dependencies]
@@ -1293,9 +1293,10 @@ wheels = [
1293
1293
 
1294
1294
  [[package]]
1295
1295
  name = "vmware-debug"
1296
- version = "2.2.0"
1296
+ version = "2.3.1"
1297
1297
  source = { editable = "." }
1298
1298
  dependencies = [
1299
+ { name = "certifi" },
1299
1300
  { name = "mcp", extra = ["cli"] },
1300
1301
  { name = "pyyaml" },
1301
1302
  { name = "rich" },
@@ -1312,7 +1313,8 @@ dev = [
1312
1313
 
1313
1314
  [package.metadata]
1314
1315
  requires-dist = [
1315
- { name = "mcp", extras = ["cli"], specifier = ">=1.28,<2.0" },
1316
+ { name = "certifi", specifier = ">=2026.10.19" },
1317
+ { name = "mcp", extras = ["cli"], specifier = ">=1.30,<2.0" },
1316
1318
  { name = "pyyaml", specifier = ">=6.0,<7.0" },
1317
1319
  { name = "rich", specifier = ">=13.0,<15.0" },
1318
1320
  { name = "typer", specifier = ">=0.12,<1.0" },
@@ -1328,14 +1330,14 @@ dev = [
1328
1330
 
1329
1331
  [[package]]
1330
1332
  name = "vmware-policy"
1331
- version = "1.21.0"
1333
+ version = "1.24.2"
1332
1334
  source = { registry = "https://pypi.org/simple" }
1333
1335
  dependencies = [
1334
1336
  { name = "pyyaml" },
1335
1337
  { name = "rich" },
1336
1338
  { name = "typer" },
1337
1339
  ]
1338
- sdist = { url = "https://files.pythonhosted.org/packages/87/5d/4221ea4a344ec33d6b54903682ce8ae2013bb0a2d6137c94ac769690ea8b/vmware_policy-1.21.0.tar.gz", hash = "sha256:f42b794ea69e52e1fa13aff8b5bc07631d82683b1c12872f2f2ec744e3853549", size = 303862, upload-time = "2026-10-15T10:13:33.719Z" }
1340
+ sdist = { url = "https://files.pythonhosted.org/packages/15/8d/b9867ea1c0112caa481841c290c3fe718c0dbd738757db7877836f46267a/vmware_policy-1.24.2.tar.gz", hash = "sha256:6c816174fcd0b490bcd1f9ebaaa18b80fcb26b0eccb614e938938bb2e8452a40", size = 369315, upload-time = "2026-10-18T03:43:52.753Z" }
1339
1341
  wheels = [
1340
- { url = "https://files.pythonhosted.org/packages/b3/b4/feb7015ae5f7870e8058ad713050f7fe3ddefa57aa7bdd4a83f2e3b9abb4/vmware_policy-1.21.0-py3-none-any.whl", hash = "sha256:a10c26816be09f9a2823cc58bc4eacb9b5eff8b6df49dfc5b504b3d76cb3b7ec", size = 99818, upload-time = "2026-10-15T10:13:31.67Z" },
1342
+ { url = "https://files.pythonhosted.org/packages/20/01/bc5f984a420d22514d8c3a627f332891f2008213d455c2f682f311e355f1/vmware_policy-1.24.2-py3-none-any.whl", hash = "sha256:2734ab49a76474fd74beb221a2d6535caab1f91a477668484c145dc2802ae072", size = 134565, upload-time = "2026-10-18T03:43:50.856Z" },
1341
1343
  ]
@@ -6,4 +6,4 @@ remediation to vmware-aiops / vmware-pilot. Never writes to any VMware system
6
6
  and never executes fixes; its only writes go to the local case ledger.
7
7
  """
8
8
 
9
- __version__ = "2.2.0"
9
+ __version__ = "2.3.1"
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import json
13
13
  import sys
14
+ from functools import wraps
14
15
  from pathlib import Path
15
16
  from typing import Optional
16
17
 
@@ -20,7 +21,7 @@ from rich.table import Table
20
21
 
21
22
  from vmware_debug import __version__
22
23
  from vmware_debug.mcp.tools import build_incident_timeline, list_symptom_categories
23
- from vmware_policy import audited, cli_local
24
+ from vmware_policy import PolicyDenied, audited, cli_local, sanitize
24
25
 
25
26
 
26
27
  def _harden_console_encoding() -> None:
@@ -53,6 +54,19 @@ app = typer.Typer(
53
54
  console = Console()
54
55
 
55
56
 
57
+ def handle_cli_errors(func):
58
+ """Render policy refusals after the audited wrapper has recorded the denial."""
59
+ @wraps(func)
60
+ def wrapper(*args, **kwargs):
61
+ try:
62
+ return func(*args, **kwargs)
63
+ except PolicyDenied as exc:
64
+ rule = f" (rule: {sanitize(exc.result.rule, 100)})" if exc.result.rule else ""
65
+ typer.echo(f"Denied by policy: {sanitize(exc.result.reason, 500)}{rule}", err=True)
66
+ raise typer.Exit(1) from None
67
+ return wrapper
68
+
69
+
56
70
  @app.command()
57
71
  @cli_local("prints the installed version")
58
72
  def version() -> None:
@@ -61,6 +75,7 @@ def version() -> None:
61
75
 
62
76
 
63
77
  @app.command()
78
+ @handle_cli_errors
64
79
  @audited(tool="list_symptom_categories")
65
80
  def categories() -> None:
66
81
  """List the symptom categories debug recognises and what to check for each."""
@@ -74,6 +89,7 @@ def categories() -> None:
74
89
 
75
90
 
76
91
  @app.command()
92
+ @handle_cli_errors
77
93
  @audited(tool="build_incident_timeline")
78
94
  def triage(
79
95
  events_file: Optional[Path] = typer.Option(
@@ -898,5 +898,8 @@ def main(
898
898
  )
899
899
 
900
900
 
901
+ # Role metadata for network callers (HLD §18).
902
+ import vmware_debug.policy_roles # noqa: F401
903
+
901
904
  if __name__ == "__main__":
902
905
  main()
@@ -0,0 +1,27 @@
1
+ """What vmware-policy needs to apply roles to this skill (HLD §18 D-21/D-22).
2
+
3
+ Policy cannot see which tools escape a role or how a missing target resolves; this
4
+ module declares them. Imported by the MCP server (roles apply only to a network
5
+ MCP server). With a vmware-policy that has no roles module (< 1.24) nothing is
6
+ declared.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from vmware_policy import skill_name
12
+
13
+ #: Tools that give their holder more than a role. Host-path tools are added by
14
+ #: policy while a file root is set.
15
+ ESCAPES = frozenset()
16
+
17
+
18
+ try:
19
+ from vmware_policy.roles import declare_role_metadata
20
+ except ImportError: # vmware-policy without roles: nothing to declare
21
+ declare_role_metadata = None
22
+
23
+ if declare_role_metadata is not None:
24
+ declare_role_metadata(
25
+ skill_name(__name__),
26
+ escapes=ESCAPES,
27
+ )
File without changes
File without changes