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.
Files changed (104) hide show
  1. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.claude-plugin/plugin.json +1 -1
  2. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.mcp.json +1 -1
  3. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/PKG-INFO +61 -2
  4. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/README-CN.md +58 -0
  5. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/README.md +59 -0
  6. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/RELEASE_NOTES.md +26 -0
  7. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/openclaw.plugin.json +1 -1
  8. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/package.json +1 -1
  9. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/pyproject.toml +2 -2
  10. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/server.json +2 -2
  11. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/SKILL.md +2 -2
  12. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/capabilities.md +1 -1
  13. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/setup-guide.md +33 -4
  14. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/conftest.py +19 -0
  15. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_avi_specific.py +6 -4
  16. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_fix_pack.py +6 -6
  17. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_mcp_error_paths.py +8 -4
  18. vmware_avi-2.3.0/tests/test_session_pool.py +516 -0
  19. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/uv.lock +5 -5
  20. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/__init__.py +1 -1
  21. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/connection.py +176 -39
  22. vmware_avi-2.3.0/vmware_avi/k8s_connection.py +131 -0
  23. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/server.py +41 -10
  24. vmware_avi-2.2.0/vmware_avi/k8s_connection.py +0 -61
  25. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.clawhubignore +0 -0
  26. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.env.example +0 -0
  27. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.gitattributes +0 -0
  28. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/.gitignore +0 -0
  29. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/Dockerfile +0 -0
  30. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/SECURITY.md +0 -0
  31. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/config.example.yaml +0 -0
  32. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/evals/evals.json +0 -0
  33. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/examples/mcp-configs/claude-code.json +0 -0
  34. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/examples/mcp-configs/cursor.json +0 -0
  35. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/agent-guardrails.md +0 -0
  36. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/skills/vmware-avi/references/cli-reference.md +0 -0
  37. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/smithery.yaml +0 -0
  38. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/__init__.py +0 -0
  39. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/__init__.py +0 -0
  40. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/__init__.py +0 -0
  41. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_family.py +0 -0
  42. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_scores.json +0 -0
  43. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_scoring.py +0 -0
  44. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/_skill.py +0 -0
  45. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/conftest.py +0 -0
  46. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_entity_reachability.py +0 -0
  47. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_error_actionability.py +0 -0
  48. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
  49. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  50. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/__init__.py +0 -0
  51. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_ako_diff_predicts_upgrade.py +0 -0
  52. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_capability_grader.py +0 -0
  53. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
  54. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_config_path_resolution_is_shared.py +0 -0
  55. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_env_secret_obfuscation.py +0 -0
  56. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_every_tool_parameter_is_documented.py +0 -0
  57. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_external_text_sanitized.py +0 -0
  58. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_helm_output_display_is_redacted.py +0 -0
  59. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_helm_values_redaction.py +0 -0
  60. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_init_and_onboarding.py +0 -0
  61. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_instructions_name_targets.py +0 -0
  62. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_list_scaling.py +0 -0
  63. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_release_blockers.py +0 -0
  64. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_text_io_declares_encoding.py +0 -0
  65. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/eval/regression/test_write_command_teaching.py +0 -0
  66. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_pod.py +0 -0
  67. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_sync.py +0 -0
  68. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_ako_write_gate.py +0 -0
  69. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_analytics.py +0 -0
  70. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_audit.py +0 -0
  71. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_config.py +0 -0
  72. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_error_logs.py +0 -0
  73. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_lb_write_gate.py +0 -0
  74. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_mcp_tools.py +0 -0
  75. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_no_destructive_ops.py +0 -0
  76. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_pool_mgmt.py +0 -0
  77. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_safe_error_passthrough.py +0 -0
  78. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_se_mgmt.py +0 -0
  79. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/tests/test_vs_mgmt.py +0 -0
  80. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/_errors.py +0 -0
  81. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/_safety.py +0 -0
  82. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/cli.py +0 -0
  83. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/config.py +0 -0
  84. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/doctor.py +0 -0
  85. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/init_wizard.py +0 -0
  86. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/__init__.py +0 -0
  87. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/mcp_server/__main__.py +0 -0
  88. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/notify/__init__.py +0 -0
  89. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/notify/audit.py +0 -0
  90. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/__init__.py +0 -0
  91. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_config.py +0 -0
  92. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_gate.py +0 -0
  93. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_ingress.py +0 -0
  94. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_multi_cluster.py +0 -0
  95. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_pod.py +0 -0
  96. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ako_sync.py +0 -0
  97. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/analytics.py +0 -0
  98. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/lb_gate.py +0 -0
  99. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/pool_mgmt.py +0 -0
  100. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/se_mgmt.py +0 -0
  101. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/ssl_mgmt.py +0 -0
  102. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/vs_mgmt.py +0 -0
  103. {vmware_avi-2.2.0 → vmware_avi-2.3.0}/vmware_avi/ops/write_gate.py +0 -0
  104. {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.2.0",
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",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-avi==2.2.0",
8
+ "vmware-avi==2.3.0",
9
9
  "vmware-avi-mcp"
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vmware-avi
3
- Version: 2.2.0
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.21.0
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.
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-avi==2.2.0",
14
+ "vmware-avi==2.3.0",
15
15
  "vmware-avi-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/vmware-avi",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "VMware AVI (NSX ALB) load balancer plus AKO Kubernetes ops — 28 MCP tools.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vmware-avi"
7
- version = "2.2.0"
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.21.0,<2.0",
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.2.0",
10
+ "version": "2.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-avi",
15
- "version": "2.2.0",
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.2.0
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.2.0
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.2.0 vmware-avi-mcp` | ⚠ Fallback | Re-resolves PyPI on each launch; fails behind corporate TLS proxies (踩坑 #25). Use `UV_NATIVE_TLS=true` workaround. |
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.2.0
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.2.0 https://github.com/vmware-skills/VMware-AVI.git
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.2.0 https://github.com/vmware-skills/VMware-AVI.git
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.2.0` + `vmware-avi mcp` | Local developer workstation, single-user CLI + MCP |
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
- mgr._sessions["lab"] = session
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._sessions
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
- mgr._sessions["lab"] = session
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._sessions
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.get_session.
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 test_verify_ssl_kwarg_reaches_get_session(self) -> None:
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.get_session") as mock_get_session,
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 = mock_get_session.call_args.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.get_session") as mock_get_session,
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 mock_get_session.call_args.kwargs["verify"] is True
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
- """The swap is global to the ops module; leaking it would silently redirect
214
- every later CLI call in the same process into a dead buffer."""
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
- before = mod.console
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
- assert mod.console is before
227
+ mod.console.print("after the capture")
228
+ assert "after the capture" in own.getvalue()