vmware-avi 2.2.0__tar.gz → 2.3.0__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.
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.claude-plugin/plugin.json +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.mcp.json +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/PKG-INFO +61 -2
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/README-CN.md +58 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/README.md +59 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/RELEASE_NOTES.md +26 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/openclaw.plugin.json +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/package.json +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/pyproject.toml +2 -2
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/server.json +2 -2
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/SKILL.md +2 -2
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/capabilities.md +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/setup-guide.md +33 -4
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/conftest.py +19 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_avi_specific.py +6 -4
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_fix_pack.py +6 -6
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_mcp_error_paths.py +8 -4
- vmware_avi-2.3.0/tests/test_session_pool.py +516 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/uv.lock +5 -5
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/__init__.py +1 -1
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/connection.py +176 -39
- vmware_avi-2.3.0/vmware_avi/k8s_connection.py +131 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/server.py +41 -10
- vmware_avi-2.2.0/vmware_avi/k8s_connection.py +0 -61
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.clawhubignore +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.env.example +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.gitattributes +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.gitignore +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/Dockerfile +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/SECURITY.md +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/config.example.yaml +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/evals/evals.json +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/examples/mcp-configs/claude-code.json +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/examples/mcp-configs/cursor.json +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/agent-guardrails.md +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/cli-reference.md +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/smithery.yaml +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_family.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_scores.json +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_scoring.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_skill.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/conftest.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_entity_reachability.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_error_actionability.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_ako_diff_predicts_upgrade.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_capability_grader.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_config_path_resolution_is_shared.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_env_secret_obfuscation.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_every_tool_parameter_is_documented.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_external_text_sanitized.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_helm_output_display_is_redacted.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_helm_values_redaction.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_init_and_onboarding.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_instructions_name_targets.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_list_scaling.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_release_blockers.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_text_io_declares_encoding.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_write_command_teaching.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_pod.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_sync.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_write_gate.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_analytics.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_audit.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_config.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_error_logs.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_lb_write_gate.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_mcp_tools.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_no_destructive_ops.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_pool_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_safe_error_passthrough.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_se_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_vs_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/_errors.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/_safety.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/cli.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/config.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/doctor.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/init_wizard.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/__main__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/notify/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/notify/audit.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/__init__.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_config.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_gate.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_ingress.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_multi_cluster.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_pod.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_sync.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/analytics.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/lb_gate.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/pool_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/se_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ssl_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/vs_mgmt.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/write_gate.py +0 -0
- {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/policy_environment.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "vmware-avi",
|
|
4
4
|
"displayName": "VMware AVI",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.3.0",
|
|
6
6
|
"description": "VMware AVI (NSX ALB) load balancer plus AKO Kubernetes ops — 28 MCP tools.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Wei Zhou",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vmware-avi
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: AVI (NSX Advanced Load Balancer) management and AKO Kubernetes operations tool
|
|
5
5
|
Project-URL: Homepage, https://github.com/vmware-skills/VMware-AVI
|
|
6
6
|
Project-URL: Repository, https://github.com/vmware-skills/VMware-AVI
|
|
@@ -17,7 +17,7 @@ Requires-Dist: pyaml<27.0,>=24.0
|
|
|
17
17
|
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
18
18
|
Requires-Dist: rich<15.0,>=13.0
|
|
19
19
|
Requires-Dist: typer<1.0,>=0.12
|
|
20
|
-
Requires-Dist: vmware-policy<2.0,>=1.
|
|
20
|
+
Requires-Dist: vmware-policy<2.0,>=1.22.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
<!-- mcp-name: io.github.vmware-skills/vmware-avi -->
|
|
@@ -573,6 +573,65 @@ Force resync triggers AKO to re-reconcile all K8s objects. If the drift persists
|
|
|
573
573
|
|
|
574
574
|
If you encounter any errors or issues, please send the error message, logs, or screenshots to **zhouwei008@gmail.com**. Contributions are welcome -- feel free to join us in maintaining and improving this project!
|
|
575
575
|
|
|
576
|
+
<!-- usage: remote-and-exposure -->
|
|
577
|
+
## Remote access (HTTPS) and limiting tools
|
|
578
|
+
|
|
579
|
+
By default `vmware-avi mcp` speaks stdio to one local client and offers every tool. Two options change that
|
|
580
|
+
(vmware-policy ≥ 1.21.0).
|
|
581
|
+
|
|
582
|
+
**Serve over HTTPS** so clients on other machines can connect. Every caller needs a bearer token:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
vmware-audit token create alice # prints the token once; give it to alice
|
|
586
|
+
vmware-audit token list # names only
|
|
587
|
+
vmware-audit token revoke alice # refused from its next request, no restart
|
|
588
|
+
vmware-avi mcp --transport https --host 0.0.0.0 --port 8443 \
|
|
589
|
+
--cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
|
|
590
|
+
--public-name mcp.example.com
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Connect a client (Claude Code shown; any MCP client that supports streamable HTTP and custom headers works):
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
claude mcp add --transport http vmware-avi https://mcp.example.com:8443/mcp \
|
|
597
|
+
--header "Authorization: Bearer $VMWARE_MCP_TOKEN"
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
```json
|
|
601
|
+
{
|
|
602
|
+
"mcpServers": {
|
|
603
|
+
"vmware-avi": {
|
|
604
|
+
"type": "http",
|
|
605
|
+
"url": "https://mcp.example.com:8443/mcp",
|
|
606
|
+
"headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
- The server will not start without a token, refuses Host/Origin values outside `--public-name`, and checks
|
|
613
|
+
before it binds that unauthenticated requests get 401. Use a certificate your clients trust for those names.
|
|
614
|
+
- Behind a TLS-terminating reverse proxy on the same machine use `--transport http --host 127.0.0.1
|
|
615
|
+
--public-name mcp.example.com` and pass the `Authorization` header through.
|
|
616
|
+
- The audit log records each caller as `token:<name>`. Calls run side by side: each holds its own session to a
|
|
617
|
+
Controller (or Kubernetes context), up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target. Each Controller
|
|
618
|
+
session is a login on the Controller; a call waits at most 300 s for a free one.
|
|
619
|
+
- **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
|
|
620
|
+
nothing on the platform. Run one endpoint per privilege level.
|
|
621
|
+
|
|
622
|
+
**Offer only read tools, plus the writes you choose:**
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
vmware-avi mcp --expose read --allow-write disable_lb_pool_member
|
|
626
|
+
# or: VMWARE_AVI_MCP_EXPOSE=read VMWARE_AVI_MCP_ALLOW_WRITE=disable_lb_pool_member vmware-avi mcp
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Tools not offered do not appear in the client's tool list. A bad value or an unknown write tool stops the server
|
|
630
|
+
(exit 2). This limits what clients are offered; it is not authorization, so the account itself should be
|
|
631
|
+
least-privilege.
|
|
632
|
+
|
|
633
|
+
Details: `skills/vmware-avi/references/setup-guide.md` ("Serving MCP over HTTPS", "Limiting which tools are offered").
|
|
634
|
+
|
|
576
635
|
## License
|
|
577
636
|
|
|
578
637
|
MIT
|
|
@@ -531,6 +531,64 @@ vmware-avi-mcp
|
|
|
531
531
|
|
|
532
532
|
如果遇到任何错误或问题,请将错误信息、日志或截图发送至 **zhouwei008@gmail.com**。欢迎参与贡献,一起维护和改进本项目!
|
|
533
533
|
|
|
534
|
+
<!-- usage: remote-and-exposure -->
|
|
535
|
+
## 远程访问(HTTPS)与限制工具
|
|
536
|
+
|
|
537
|
+
默认情况下 `vmware-avi mcp` 通过 stdio 服务一个本地客户端,并提供全部工具。以下两个选项可以改变这一点
|
|
538
|
+
(需 vmware-policy ≥ 1.21.0)。
|
|
539
|
+
|
|
540
|
+
**通过 HTTPS 提供服务**,让其他机器上的客户端连接。每个调用者都需要一个 bearer token:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
vmware-audit token create alice # 只显示一次,交给 alice
|
|
544
|
+
vmware-audit token list # 只列名字
|
|
545
|
+
vmware-audit token revoke alice # 下一个请求起即拒绝,无需重启
|
|
546
|
+
vmware-avi mcp --transport https --host 0.0.0.0 --port 8443 \
|
|
547
|
+
--cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
|
|
548
|
+
--public-name mcp.example.com
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
连接客户端(以 Claude Code 为例;任何支持 streamable HTTP 和自定义请求头的 MCP 客户端都可以):
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
claude mcp add --transport http vmware-avi https://mcp.example.com:8443/mcp \
|
|
555
|
+
--header "Authorization: Bearer $VMWARE_MCP_TOKEN"
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
```json
|
|
559
|
+
{
|
|
560
|
+
"mcpServers": {
|
|
561
|
+
"vmware-avi": {
|
|
562
|
+
"type": "http",
|
|
563
|
+
"url": "https://mcp.example.com:8443/mcp",
|
|
564
|
+
"headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
- 没有 token 时服务不会启动;`--public-name` 之外的 Host/Origin 一律拒绝;绑定端口前会自检,确认未认证请求得到 401。
|
|
571
|
+
证书要用客户端信任、且覆盖这些名字的证书。
|
|
572
|
+
- 在同一台机器上由反向代理终结 TLS 时,用 `--transport http --host 127.0.0.1 --public-name mcp.example.com`,
|
|
573
|
+
并让代理透传 `Authorization` 头。
|
|
574
|
+
- 审计日志把每个调用者记为 `token:<名字>`。调用并行执行:每个调用独占一个到 Controller(或 Kubernetes context)的
|
|
575
|
+
会话,每个 target 最多 `VMWARE_MCP_POOL_SIZE`(默认 2)个会话。每个 Controller 会话都是在 Controller 上的一次登录;
|
|
576
|
+
调用最多等待 300 秒拿到空闲会话。
|
|
577
|
+
- **能连到这个端点的人,都以该 server 配置的账号操作。** token 只用来识别调用者,不在平台上授予任何权限。
|
|
578
|
+
每个权限级别单独起一个端点。
|
|
579
|
+
|
|
580
|
+
**只提供读工具,加上你选定的写工具:**
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
vmware-avi mcp --expose read --allow-write disable_lb_pool_member
|
|
584
|
+
# 或:VMWARE_AVI_MCP_EXPOSE=read VMWARE_AVI_MCP_ALLOW_WRITE=disable_lb_pool_member vmware-avi mcp
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
未提供的工具不会出现在客户端的工具列表里。取值错误或写工具名不存在时服务直接退出(exit 2)。
|
|
588
|
+
这只限制提供给客户端的工具,不是授权;账号本身仍应遵循最小权限。
|
|
589
|
+
|
|
590
|
+
详见 `skills/vmware-avi/references/setup-guide.md`("Serving MCP over HTTPS"、"Limiting which tools are offered")。
|
|
591
|
+
|
|
534
592
|
## 许可证
|
|
535
593
|
|
|
536
594
|
MIT
|
|
@@ -551,6 +551,65 @@ Force resync triggers AKO to re-reconcile all K8s objects. If the drift persists
|
|
|
551
551
|
|
|
552
552
|
If you encounter any errors or issues, please send the error message, logs, or screenshots to **zhouwei008@gmail.com**. Contributions are welcome -- feel free to join us in maintaining and improving this project!
|
|
553
553
|
|
|
554
|
+
<!-- usage: remote-and-exposure -->
|
|
555
|
+
## Remote access (HTTPS) and limiting tools
|
|
556
|
+
|
|
557
|
+
By default `vmware-avi mcp` speaks stdio to one local client and offers every tool. Two options change that
|
|
558
|
+
(vmware-policy ≥ 1.21.0).
|
|
559
|
+
|
|
560
|
+
**Serve over HTTPS** so clients on other machines can connect. Every caller needs a bearer token:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
vmware-audit token create alice # prints the token once; give it to alice
|
|
564
|
+
vmware-audit token list # names only
|
|
565
|
+
vmware-audit token revoke alice # refused from its next request, no restart
|
|
566
|
+
vmware-avi mcp --transport https --host 0.0.0.0 --port 8443 \
|
|
567
|
+
--cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
|
|
568
|
+
--public-name mcp.example.com
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Connect a client (Claude Code shown; any MCP client that supports streamable HTTP and custom headers works):
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
claude mcp add --transport http vmware-avi https://mcp.example.com:8443/mcp \
|
|
575
|
+
--header "Authorization: Bearer $VMWARE_MCP_TOKEN"
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"mcpServers": {
|
|
581
|
+
"vmware-avi": {
|
|
582
|
+
"type": "http",
|
|
583
|
+
"url": "https://mcp.example.com:8443/mcp",
|
|
584
|
+
"headers": { "Authorization": "Bearer ${VMWARE_MCP_TOKEN}" }
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
- The server will not start without a token, refuses Host/Origin values outside `--public-name`, and checks
|
|
591
|
+
before it binds that unauthenticated requests get 401. Use a certificate your clients trust for those names.
|
|
592
|
+
- Behind a TLS-terminating reverse proxy on the same machine use `--transport http --host 127.0.0.1
|
|
593
|
+
--public-name mcp.example.com` and pass the `Authorization` header through.
|
|
594
|
+
- The audit log records each caller as `token:<name>`. Calls run side by side: each holds its own session to a
|
|
595
|
+
Controller (or Kubernetes context), up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target. Each Controller
|
|
596
|
+
session is a login on the Controller; a call waits at most 300 s for a free one.
|
|
597
|
+
- **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
|
|
598
|
+
nothing on the platform. Run one endpoint per privilege level.
|
|
599
|
+
|
|
600
|
+
**Offer only read tools, plus the writes you choose:**
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
vmware-avi mcp --expose read --allow-write disable_lb_pool_member
|
|
604
|
+
# or: VMWARE_AVI_MCP_EXPOSE=read VMWARE_AVI_MCP_ALLOW_WRITE=disable_lb_pool_member vmware-avi mcp
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Tools not offered do not appear in the client's tool list. A bad value or an unknown write tool stops the server
|
|
608
|
+
(exit 2). This limits what clients are offered; it is not authorization, so the account itself should be
|
|
609
|
+
least-privilege.
|
|
610
|
+
|
|
611
|
+
Details: `skills/vmware-avi/references/setup-guide.md` ("Serving MCP over HTTPS", "Limiting which tools are offered").
|
|
612
|
+
|
|
554
613
|
## License
|
|
555
614
|
|
|
556
615
|
MIT
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## v2.3.0 — calls run side by side
|
|
2
|
+
|
|
3
|
+
**Calls no longer wait for each other** (HLD §16 D-13(2), via vmware-policy 1.22.0). When one server serves several
|
|
4
|
+
callers (`--transport https` / `http`), each call now holds its own Controller session — and its own Kubernetes client
|
|
5
|
+
for the AKO tools — for as long as it runs, up to `VMWARE_MCP_POOL_SIZE` (default 2) per Controller or context. Each
|
|
6
|
+
Controller session is a separate login: avisdk's `ApiSession.get_session` handed every caller the one session it caches
|
|
7
|
+
per controller and user, so concurrent calls used to share, and re-authenticate over, a single login. A call that finds
|
|
8
|
+
every session busy waits at most 300 s, then fails naming the target. The CLI and stdio keep one session per
|
|
9
|
+
Controller, as before.
|
|
10
|
+
|
|
11
|
+
* Tool output no longer crosses between callers. Each tool captured its text by swapping the ops module's console for
|
|
12
|
+
the length of the call; two calls at once could each receive the other's lines. Capture is now per call.
|
|
13
|
+
* AKO tools build each Kubernetes client from the kubeconfig directly instead of through `load_kube_config`, which
|
|
14
|
+
rewrote the process-wide default configuration — two calls on different contexts could reach each other's cluster.
|
|
15
|
+
A client is rebuilt when the kubeconfig file changes.
|
|
16
|
+
* Requires `vmware-policy>=1.22.0`.
|
|
17
|
+
|
|
1
18
|
## v2.2.0 — serve MCP over HTTPS
|
|
2
19
|
|
|
3
20
|
`vmware-avi mcp` can now serve the network as well as stdio (HLD §16, via vmware-policy 1.21.0):
|
|
@@ -8,6 +25,15 @@ vmware-avi 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-avi 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.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vmware-avi"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
description = "AVI (NSX Advanced Load Balancer) management and AKO Kubernetes operations tool"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
"httpx>=0.27,<1.0",
|
|
20
20
|
"python-dotenv>=1.0,<2.0",
|
|
21
21
|
"mcp[cli]>=1.28,<2.0",
|
|
22
|
-
"vmware-policy>=1.
|
|
22
|
+
"vmware-policy>=1.22.0,<2.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/vmware-skills/VMware-AVI",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.3.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "vmware-avi",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.3.0",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"packageArguments": [
|
|
18
18
|
{
|
|
@@ -53,7 +53,7 @@ AVI (NSX Advanced Load Balancer) application delivery and AKO Kubernetes operati
|
|
|
53
53
|
## Quick Install
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
uv tool install vmware-avi==2.
|
|
56
|
+
uv tool install vmware-avi==2.3.0
|
|
57
57
|
vmware-avi doctor # checks Controller connectivity + kubeconfig + avisdk
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -235,7 +235,7 @@ Force resync triggers AKO to re-reconcile all K8s objects. If the drift persists
|
|
|
235
235
|
## Setup
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
|
-
uv tool install vmware-avi==2.
|
|
238
|
+
uv tool install vmware-avi==2.3.0
|
|
239
239
|
mkdir -p ~/.vmware-avi
|
|
240
240
|
vmware-avi init # generates config.yaml and .env templates
|
|
241
241
|
chmod 600 ~/.vmware-avi/.env
|
|
@@ -37,7 +37,7 @@ All 28 MCP tools exposed by `vmware-avi mcp` (v1.5.15+; legacy entry point: `vmw
|
|
|
37
37
|
| `vmware-avi mcp` (CLI subcommand, stdio) | ✅ Full | ✅ v1.5.15+ default — no PyPI re-resolve, works behind corporate TLS proxies. |
|
|
38
38
|
| `vmware-avi-mcp` (legacy console script, stdio) | ✅ Full | Kept for backward compatibility with pre-1.5.15 configs. |
|
|
39
39
|
| `python -m vmware_avi.mcp_server` (stdio, via `__main__.py`) | ✅ Full | Docker image `CMD` only — not for end-user CLI install, and no longer used by `smithery.yaml` (which now calls the `vmware-avi mcp` entry point). Added v1.5.22. |
|
|
40
|
-
| `uvx --from vmware-avi==2.
|
|
40
|
+
| `uvx --from vmware-avi==2.3.0 vmware-avi-mcp` | ⚠ Fallback | Re-resolves PyPI on each launch; fails behind corporate TLS proxies (踩坑 #25). Use `UV_NATIVE_TLS=true` workaround. |
|
|
41
41
|
|
|
42
42
|
## Automation Level Reference
|
|
43
43
|
|
|
@@ -21,7 +21,7 @@ Complete installation, configuration, and AI platform integration guide for the
|
|
|
21
21
|
### Standard Install (recommended)
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
uv tool install vmware-avi==2.
|
|
24
|
+
uv tool install vmware-avi==2.3.0
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
This installs the CLI (`vmware-avi`, with `vmware-avi mcp` subcommand for the MCP server in v1.5.15+), the legacy `vmware-avi-mcp` entry point (for backward compatibility), and all Python dependencies in an isolated environment.
|
|
@@ -29,7 +29,7 @@ This installs the CLI (`vmware-avi`, with `vmware-avi mcp` subcommand for the MC
|
|
|
29
29
|
### Development Install
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
git clone --branch v2.
|
|
32
|
+
git clone --branch v2.3.0 https://github.com/vmware-skills/VMware-AVI.git
|
|
33
33
|
cd VMware-AVI
|
|
34
34
|
uv pip install -e ".[dev]"
|
|
35
35
|
```
|
|
@@ -43,7 +43,7 @@ For platforms that prefer containerized MCP servers (e.g., Smithery registry, Ku
|
|
|
43
43
|
Build and run the MCP server in a container. The image uses `python:3.12-slim` with `uv` for dependency installation and runs `python -m vmware_avi.mcp_server` on stdio (no port exposed — MCP uses stdin/stdout).
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
git clone --branch v2.
|
|
46
|
+
git clone --branch v2.3.0 https://github.com/vmware-skills/VMware-AVI.git
|
|
47
47
|
cd VMware-AVI
|
|
48
48
|
|
|
49
49
|
# Build
|
|
@@ -72,7 +72,7 @@ Users can install via the Smithery UI or CLI without managing Python environment
|
|
|
72
72
|
|
|
73
73
|
| Deployment | Best For |
|
|
74
74
|
|------------|----------|
|
|
75
|
-
| `uv tool install vmware-avi==2.
|
|
75
|
+
| `uv tool install vmware-avi==2.3.0` + `vmware-avi mcp` | Local developer workstation, single-user CLI + MCP |
|
|
76
76
|
| Docker image | Self-hosted agents, CI runners, isolated environments, multi-user servers |
|
|
77
77
|
| Smithery | Zero-install agent integration, registry-managed discovery, hosted-MCP workflows |
|
|
78
78
|
|
|
@@ -239,6 +239,35 @@ vmware-avi mcp --expose read --allow-write disable_lb_pool_member
|
|
|
239
239
|
- An agent that can run shell commands can also reach the platform without MCP (SSH, curl, govc, PowerCLI). So
|
|
240
240
|
the service account itself must be least-privilege: exposure and deny rules are guardrails, not the boundary.
|
|
241
241
|
|
|
242
|
+
## Serving MCP over HTTPS
|
|
243
|
+
|
|
244
|
+
By default the MCP server speaks stdio to one local client. To let clients on other machines reach it, serve it over
|
|
245
|
+
HTTPS. Every caller then needs a bearer token.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
vmware-audit token create alice # prints the token once; give it to alice
|
|
249
|
+
vmware-avi mcp --transport https --host 0.0.0.0 --port 8443 \
|
|
250
|
+
--cert /etc/vmware-mcp/server.pem --key /etc/vmware-mcp/server-key.pem \
|
|
251
|
+
--public-name mcp.example.com
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- Clients connect to `https://mcp.example.com:8443/mcp` and send `Authorization: Bearer <token>`.
|
|
255
|
+
- `--public-name` lists every host name clients use (comma-separated); requests for any other Host or Origin are
|
|
256
|
+
refused. Use a certificate your clients trust for those names.
|
|
257
|
+
- Behind a reverse proxy that terminates TLS (Nginx, HAProxy) on the same machine, use
|
|
258
|
+
`--transport http --host 127.0.0.1 --public-name mcp.example.com` (the names the proxy forwards in `Host`), and
|
|
259
|
+
have the proxy pass the `Authorization` header through.
|
|
260
|
+
- `vmware-audit token list` / `token revoke NAME` manage callers; a revoked token is refused from its next request.
|
|
261
|
+
Replace a leaked token under a new name.
|
|
262
|
+
- The server will not start without at least one token, and checks before it binds that unauthenticated requests
|
|
263
|
+
are refused.
|
|
264
|
+
- Each audit row records the caller as `token:<name>`. Calls run side by side: each holds its own session to a
|
|
265
|
+
Controller (or Kubernetes context), up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target. Each Controller
|
|
266
|
+
session is a login on the Controller; a call waits at most 300 s for a free one.
|
|
267
|
+
- **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
|
|
268
|
+
nothing on the platform. Run one endpoint per privilege level (for example a read-only account behind
|
|
269
|
+
`--expose read`, and a separate endpoint for operators).
|
|
270
|
+
|
|
242
271
|
## Security
|
|
243
272
|
|
|
244
273
|
> **Disclaimer**: This is a community-maintained open-source project and is **not affiliated with, endorsed by, or sponsored by VMware, Inc. or Broadcom Inc.** "VMware", "NSX", and "AVI" are trademarks of Broadcom.
|
|
@@ -171,3 +171,22 @@ def _patch_k8s_connect(
|
|
|
171
171
|
),
|
|
172
172
|
):
|
|
173
173
|
yield
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Process-wide session pools (HLD §16 D-13(2))
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@pytest.fixture(autouse=True)
|
|
182
|
+
def _fresh_session_pools(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
183
|
+
"""Each test starts with empty pools: the managers share theirs process-wide."""
|
|
184
|
+
from vmware_avi.connection import AviConnectionManager
|
|
185
|
+
from vmware_avi.k8s_connection import K8sConnectionManager
|
|
186
|
+
|
|
187
|
+
for cls, registry in (
|
|
188
|
+
(AviConnectionManager, "_built_from"),
|
|
189
|
+
(K8sConnectionManager, "_kubeconfig_for"),
|
|
190
|
+
):
|
|
191
|
+
monkeypatch.setattr(cls, "_pool", None)
|
|
192
|
+
monkeypatch.setattr(cls, registry, {})
|
|
@@ -339,7 +339,8 @@ class TestLogoutIsPost:
|
|
|
339
339
|
|
|
340
340
|
mgr = AviConnectionManager(sample_config)
|
|
341
341
|
session = MagicMock()
|
|
342
|
-
|
|
342
|
+
with patch.object(AviConnectionManager, "_create_session", return_value=session):
|
|
343
|
+
assert mgr.connect("lab") is session
|
|
343
344
|
|
|
344
345
|
mgr.disconnect("lab")
|
|
345
346
|
|
|
@@ -349,7 +350,7 @@ class TestLogoutIsPost:
|
|
|
349
350
|
assert call.args[:1] != ("logout",), (
|
|
350
351
|
"logout must not be sent as DELETE"
|
|
351
352
|
)
|
|
352
|
-
assert "lab" not in mgr.
|
|
353
|
+
assert "lab" not in mgr.list_connected()
|
|
353
354
|
|
|
354
355
|
def test_logout_failure_does_not_raise(self, sample_config) -> None:
|
|
355
356
|
from vmware_avi.connection import AviConnectionManager
|
|
@@ -357,10 +358,11 @@ class TestLogoutIsPost:
|
|
|
357
358
|
mgr = AviConnectionManager(sample_config)
|
|
358
359
|
session = MagicMock()
|
|
359
360
|
session.post.side_effect = RuntimeError("controller gone")
|
|
360
|
-
|
|
361
|
+
with patch.object(AviConnectionManager, "_create_session", return_value=session):
|
|
362
|
+
mgr.connect("lab")
|
|
361
363
|
|
|
362
364
|
mgr.disconnect("lab") # must not raise
|
|
363
|
-
assert "lab" not in mgr.
|
|
365
|
+
assert "lab" not in mgr.list_connected()
|
|
364
366
|
|
|
365
367
|
|
|
366
368
|
# ── Finding 7 — error-log filter must be ge(response_code,400) ───────────────
|
|
@@ -19,7 +19,7 @@ Each test pins one production bug:
|
|
|
19
19
|
7. list_pools vs_filter missed pools because '#name' / '?...' fragments were
|
|
20
20
|
left on refs.
|
|
21
21
|
9. CLI write commands never called the audit module.
|
|
22
|
-
10. FQDN→IP resolution + verify_ssl kwarg must reach ApiSession.
|
|
22
|
+
10. FQDN→IP resolution + verify_ssl kwarg must reach the ApiSession it builds.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
@@ -525,24 +525,24 @@ class TestConnectionSessionKwargs:
|
|
|
525
525
|
assert resolved == "10.0.0.1"
|
|
526
526
|
mock_gai.assert_not_called()
|
|
527
527
|
|
|
528
|
-
def
|
|
528
|
+
def test_verify_ssl_kwarg_reaches_the_api_session(self) -> None:
|
|
529
529
|
ctrl = ControllerConfig(name="lab", host="10.0.0.1", verify_ssl=False)
|
|
530
530
|
with (
|
|
531
|
-
patch("avi.sdk.avi_api.ApiSession
|
|
531
|
+
patch("avi.sdk.avi_api.ApiSession") as mock_api_session,
|
|
532
532
|
patch.dict("os.environ", {"LAB_PASSWORD": "pw"}),
|
|
533
533
|
):
|
|
534
534
|
AviConnectionManager._create_session(ctrl)
|
|
535
535
|
|
|
536
|
-
kwargs =
|
|
536
|
+
kwargs = mock_api_session.call_args.kwargs
|
|
537
537
|
assert kwargs["verify"] is False
|
|
538
538
|
assert kwargs["controller_ip"] == "10.0.0.1"
|
|
539
539
|
|
|
540
540
|
def test_verify_ssl_default_true(self) -> None:
|
|
541
541
|
ctrl = ControllerConfig(name="lab", host="10.0.0.1")
|
|
542
542
|
with (
|
|
543
|
-
patch("avi.sdk.avi_api.ApiSession
|
|
543
|
+
patch("avi.sdk.avi_api.ApiSession") as mock_api_session,
|
|
544
544
|
patch.dict("os.environ", {"LAB_PASSWORD": "pw"}),
|
|
545
545
|
):
|
|
546
546
|
AviConnectionManager._create_session(ctrl)
|
|
547
547
|
|
|
548
|
-
assert
|
|
548
|
+
assert mock_api_session.call_args.kwargs["verify"] is True
|
|
@@ -210,15 +210,19 @@ def test_a_successful_call_is_still_audited_as_ok(monkeypatch, audit_rows):
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
def test_console_is_restored_after_a_failure(op):
|
|
213
|
-
"""
|
|
214
|
-
|
|
213
|
+
"""Leaking the capture would silently redirect every later CLI call in the
|
|
214
|
+
same process into a dead buffer: after a failed capture, printing outside a
|
|
215
|
+
capture must reach the module's own console again."""
|
|
216
|
+
import io
|
|
215
217
|
import sys
|
|
216
218
|
|
|
217
219
|
mod = sys.modules[__name__]
|
|
218
|
-
|
|
220
|
+
own = io.StringIO()
|
|
221
|
+
mod.console = Console(file=own, force_terminal=False)
|
|
219
222
|
|
|
220
223
|
def failing():
|
|
221
224
|
raise SystemExit(1)
|
|
222
225
|
|
|
223
226
|
srv._capture_output(op(failing))
|
|
224
|
-
|
|
227
|
+
mod.console.print("after the capture")
|
|
228
|
+
assert "after the capture" in own.getvalue()
|