vmware-pilot 2.0.0__tar.gz → 2.2.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 (96) hide show
  1. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.claude-plugin/plugin.json +1 -1
  2. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.mcp.json +1 -1
  3. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/PKG-INFO +3 -3
  4. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/RELEASE_NOTES.md +38 -0
  5. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/openclaw.plugin.json +1 -1
  6. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/package.json +1 -1
  7. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/pyproject.toml +3 -3
  8. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/server.json +2 -2
  9. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/SKILL.md +2 -2
  10. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/cli-reference.md +2 -2
  11. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/setup-guide.md +32 -5
  12. vmware_pilot-2.2.0/tests/test_approver_is_caller.py +67 -0
  13. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/uv.lock +6 -6
  14. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/__init__.py +1 -1
  15. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/cli.py +38 -4
  16. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/server.py +19 -2
  17. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/lifecycle.py +18 -1
  18. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.clawhubignore +0 -0
  19. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.gitignore +0 -0
  20. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/Dockerfile +0 -0
  21. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/README-CN.md +0 -0
  22. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/README.md +0 -0
  23. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/SECURITY.md +0 -0
  24. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/docs/architecture-audit-2026-04-30.md +0 -0
  25. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/docs/aria-pilot-boundary.md +0 -0
  26. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/examples/mcp-configs/claude-code.json +0 -0
  27. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/evals/evals.json +0 -0
  28. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/agent-guardrails.md +0 -0
  29. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/capabilities.md +0 -0
  30. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/integration-patterns.md +0 -0
  31. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/templates.md +0 -0
  32. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/workflow-design.md +0 -0
  33. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/list_available_tools.py +0 -0
  34. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/validate_workflow.py +0 -0
  35. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/smithery.yaml +0 -0
  36. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/__init__.py +0 -0
  37. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/conftest.py +0 -0
  38. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/__init__.py +0 -0
  39. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/__init__.py +0 -0
  40. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_family.py +0 -0
  41. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scores.json +0 -0
  42. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scoring.py +0 -0
  43. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_skill.py +0 -0
  44. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/conftest.py +0 -0
  45. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_entity_reachability.py +0 -0
  46. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_error_actionability.py +0 -0
  47. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
  48. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  49. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/__init__.py +0 -0
  50. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/_family_write_surface.py +0 -0
  51. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_approval_gate_coverage.py +0 -0
  52. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_capability_grader.py +0 -0
  53. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_catalog_covers_the_family.py +0 -0
  54. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
  55. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_environment_resolver_isolation.py +0 -0
  56. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_template_steps_name_real_tools.py +0 -0
  57. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/fixtures/companion_tools.json +0 -0
  58. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/fixtures/snapshot_companion_tools.py +0 -0
  59. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_confirm_needs_approval.py +0 -0
  60. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_custom_approval_gate.py +0 -0
  61. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_executor.py +0 -0
  62. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_executor_preview_is_not_success.py +0 -0
  63. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_final_review_fixes.py +0 -0
  64. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_models.py +0 -0
  65. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_no_destructive_ops.py +0 -0
  66. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_old_tool_names_translated.py +0 -0
  67. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_own_tools_gated.py +0 -0
  68. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_review.py +0 -0
  69. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_safe_error_passthrough.py +0 -0
  70. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_server.py +0 -0
  71. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_templates.py +0 -0
  72. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_templates_pass_confirm.py +0 -0
  73. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/approval_gate.py +0 -0
  74. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/custom_loader.py +0 -0
  75. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/executor.py +0 -0
  76. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/lifecycle_gate.py +0 -0
  77. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__init__.py +0 -0
  78. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__main__.py +0 -0
  79. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_catalog.py +0 -0
  80. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_shared.py +0 -0
  81. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/__init__.py +0 -0
  82. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/authoring.py +0 -0
  83. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/query.py +0 -0
  84. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/models.py +0 -0
  85. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/renames.py +0 -0
  86. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/review.py +0 -0
  87. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/__init__.py +0 -0
  88. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_aiops_params.py +0 -0
  89. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_common.py +0 -0
  90. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_gated.py +0 -0
  91. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/baseline.py +0 -0
  92. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/k8s.py +0 -0
  93. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/monitoring.py +0 -0
  94. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/network.py +0 -0
  95. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/storage.py +0 -0
  96. {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/vm.py +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "vmware-pilot",
4
4
  "displayName": "VMware Pilot",
5
- "version": "2.0.0",
5
+ "version": "2.2.0",
6
6
  "description": "Multi-step workflow orchestration for VMware MCP skills with approval gates and rollback.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-pilot==2.0.0",
8
+ "vmware-pilot==2.2.0",
9
9
  "vmware-pilot-mcp"
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vmware-pilot
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: VMware workflow orchestration — multi-step state machine with approval gates
5
5
  Project-URL: Homepage, https://github.com/vmware-skills/VMware-Pilot
6
6
  Project-URL: Repository, https://github.com/vmware-skills/VMware-Pilot
@@ -9,10 +9,10 @@ Project-URL: Changelog, https://github.com/vmware-skills/VMware-Pilot/blob/main/
9
9
  Author-email: Wei Zhou <wei-wz.zhou@broadcom.com>
10
10
  License-Expression: MIT
11
11
  Requires-Python: >=3.10
12
- Requires-Dist: mcp[cli]<2.0,>=1.0
12
+ Requires-Dist: mcp[cli]<2.0,>=1.28
13
13
  Requires-Dist: pyyaml<7.0,>=6.0
14
14
  Requires-Dist: typer<1.0,>=0.12
15
- Requires-Dist: vmware-policy<2.0,>=1.19.0
15
+ Requires-Dist: vmware-policy<2.0,>=1.21.0
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  <!-- mcp-name: io.github.vmware-skills/vmware-pilot -->
@@ -1,3 +1,41 @@
1
+ ## v2.2.0 — serve MCP over HTTPS
2
+
3
+ `vmware-pilot mcp` can now serve the network as well as stdio (HLD §16, via vmware-policy 1.21.0):
4
+
5
+ ```bash
6
+ vmware-audit token create alice # once per caller; shown once
7
+ vmware-pilot mcp --transport https --host 0.0.0.0 --port 8443 \
8
+ --cert server.pem --key server-key.pem --public-name mcp.example.com
9
+ ```
10
+
11
+ * Every request needs `Authorization: Bearer <token>`; the server will not start without a token, refuses Host or
12
+ Origin values outside `--public-name`, and checks itself (unauthenticated requests must get 401) before it
13
+ binds. `--transport http` binds loopback only, for a TLS-terminating reverse proxy.
14
+ * Audit rows record the caller as `token:<name>` and the surface as `mcp-https`. Calls naming different targets
15
+ run in parallel; calls naming no target run one at a time.
16
+ * Over HTTPS, `approve` records the authenticated caller as the approver and refuses a different free-text
17
+ name (HLD §16 D-12). stdio and the CLI are unchanged.
18
+ * stdio remains the default; the `vmware-pilot-mcp` console script and `python -m` are unchanged.
19
+ * Everyone who can reach the endpoint acts with this server's account; run one endpoint per privilege level.
20
+ * Requires `vmware-policy>=1.21.0` and `mcp>=1.28`.
21
+
22
+ ## v2.1.0 — offer only read tools, plus chosen write tools
23
+
24
+ `vmware-pilot mcp` can now offer a subset of this server's tools (HLD §17 D-16, via vmware-policy 1.20.0):
25
+
26
+ ```bash
27
+ vmware-pilot mcp --expose read --allow-write cancel_workflow
28
+ ```
29
+
30
+ * `--expose read` (or `VMWARE_PILOT_MCP_EXPOSE=read`) keeps only tools annotated `readOnlyHint: true`; `--allow-write a,b`
31
+ (or `VMWARE_PILOT_MCP_ALLOW_WRITE`) keeps chosen write tools on top. The default is unchanged: every tool.
32
+ * A bad value, or a name that is not a write tool of this server, stops the server before it serves (exit 2).
33
+ Flags and environment variables are not mixed; a blank flag counts as not given. Pre-2.0 names are accepted.
34
+ * This limits what clients are offered; it is not authorization. See "Limiting which tools are offered" in
35
+ `references/setup-guide.md`, which also notes that an agent with a shell can bypass MCP, so the account itself
36
+ must be least-privilege.
37
+ * Requires `vmware-policy>=1.20.0`.
38
+
1
39
  ## v2.0.0 — create_workflow_from_template, and VKS kubeconfig tools at their new names
2
40
 
3
41
  **Breaking: one of Pilot's own MCP tools is renamed.** `plan_workflow` → `create_workflow_from_template`.
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-pilot==2.0.0",
14
+ "vmware-pilot==2.2.0",
15
15
  "vmware-pilot-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/vmware-pilot",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Multi-step workflow orchestration for VMware MCP skills with approval gates and rollback.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vmware-pilot"
7
- version = "2.0.0"
7
+ version = "2.2.0"
8
8
  description = "VMware workflow orchestration — multi-step state machine with approval gates"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.10"
12
12
  authors = [{ name = "Wei Zhou", email = "wei-wz.zhou@broadcom.com" }]
13
13
  dependencies = [
14
- "vmware-policy>=1.19.0,<2.0",
15
- "mcp[cli]>=1.0,<2.0",
14
+ "vmware-policy>=1.21.0,<2.0",
15
+ "mcp[cli]>=1.28,<2.0",
16
16
  "pyyaml>=6.0,<7.0",
17
17
  "typer>=0.12,<1.0",
18
18
  ]
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/vmware-skills/VMware-Pilot",
8
8
  "source": "github"
9
9
  },
10
- "version": "2.0.0",
10
+ "version": "2.2.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-pilot",
15
- "version": "2.0.0",
15
+ "version": "2.2.0",
16
16
  "runtimeHint": "uvx",
17
17
  "packageArguments": [
18
18
  {
@@ -42,7 +42,7 @@ Multi-step workflow orchestration for VMware MCP skills — design, approve, exe
42
42
  ## Quick Install
43
43
 
44
44
  ```bash
45
- uv tool install vmware-pilot==2.0.0
45
+ uv tool install vmware-pilot==2.2.0
46
46
  vmware-pilot mcp # start the MCP server (stdio)
47
47
  ```
48
48
 
@@ -303,7 +303,7 @@ No vCenter credentials needed — pilot orchestrates other skills that handle co
303
303
  }
304
304
  ```
305
305
 
306
- > Fallback: `{"command": "uvx", "args": ["--from", "vmware-pilot==2.0.0", "vmware-pilot-mcp"]}` also
306
+ > Fallback: `{"command": "uvx", "args": ["--from", "vmware-pilot==2.2.0", "vmware-pilot-mcp"]}` also
307
307
  > works, but `uvx` re-resolves the package against PyPI on every start and fails behind a
308
308
  > TLS-inspecting corporate proxy (`invalid peer certificate: UnknownIssuer`). The installed
309
309
  > entry point above touches the network zero times; set `UV_NATIVE_TLS=true` if you must use `uvx`.
@@ -19,12 +19,12 @@ client.
19
19
 
20
20
  ```bash
21
21
  # Recommended: installed entry point, never touches the network
22
- uv tool install vmware-pilot==2.0.0
22
+ uv tool install vmware-pilot==2.2.0
23
23
  vmware-pilot mcp
24
24
 
25
25
  # Fallback: uvx re-resolves against PyPI each start and fails behind a
26
26
  # TLS-inspecting corporate proxy — set UV_NATIVE_TLS=true if you must use it
27
- uvx --from vmware-pilot==2.0.0 vmware-pilot-mcp
27
+ uvx --from vmware-pilot==2.2.0 vmware-pilot-mcp
28
28
  ```
29
29
 
30
30
  The server runs on **stdio** transport and exposes 13 MCP tools (4 read, 9 write).
@@ -11,16 +11,16 @@
11
11
  ### Via uv (Recommended)
12
12
 
13
13
  ```bash
14
- uv tool install vmware-pilot==2.0.0
14
+ uv tool install vmware-pilot==2.2.0
15
15
  ```
16
16
 
17
17
  ### Via pip
18
18
 
19
19
  ```bash
20
- uv tool install vmware-pilot==2.0.0
20
+ uv tool install vmware-pilot==2.2.0
21
21
 
22
22
  # China mainland mirror
23
- pip install vmware-pilot==2.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
23
+ pip install vmware-pilot==2.2.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
24
24
  ```
25
25
 
26
26
  ### Verify Installation
@@ -84,7 +84,7 @@ The `uvx` form still works and needs no prior install:
84
84
  "mcpServers": {
85
85
  "vmware-pilot": {
86
86
  "command": "uvx",
87
- "args": ["--from", "vmware-pilot==2.0.0", "vmware-pilot-mcp"]
87
+ "args": ["--from", "vmware-pilot==2.2.0", "vmware-pilot-mcp"]
88
88
  }
89
89
  }
90
90
  }
@@ -100,11 +100,38 @@ invalid peer certificate: UnknownIssuer
100
100
 
101
101
  `uv` ships its own certificate bundle and ignores the system trust store, which is what breaks.
102
102
  Either set `UV_NATIVE_TLS=true` so it honours your corporate CA, or install the tool once
103
- (`uv tool install vmware-pilot==2.0.0`) and launch `vmware-pilot mcp`, which touches the network
103
+ (`uv tool install vmware-pilot==2.2.0`) and launch `vmware-pilot mcp`, which touches the network
104
104
  zero times.
105
105
 
106
106
  ---
107
107
 
108
+ ## Limiting which tools are offered
109
+
110
+ By default the MCP server offers every tool. To offer only read tools, start it with `--expose read`, or set
111
+ `VMWARE_PILOT_MCP_EXPOSE=read` in the MCP client's `env` block (useful when the client launches `vmware-pilot mcp` itself). To offer
112
+ chosen write tools as well, add `--allow-write` with a comma-separated list (or `VMWARE_PILOT_MCP_ALLOW_WRITE`):
113
+
114
+ ```bash
115
+ vmware-pilot mcp --expose read --allow-write cancel_workflow
116
+ ```
117
+
118
+ - A read tool is one annotated `readOnlyHint: true`; every other tool counts as a write.
119
+ - An unknown setting, or a name that is not a write tool of this server, stops the server before it serves (exit
120
+ code 2), so it never offers more than you asked for. Pre-2.0 tool names are accepted.
121
+ - With `--expose read`, the server prints one line to stderr saying what it exposes.
122
+ - With `--expose read`, the server's instructions still describe the workflow tools it hides (`create_workflow`,
123
+ `create_workflow_from_template`, `run_workflow`, `approve`, `rollback`); a call to a hidden tool is answered `Unknown tool`.
124
+ - Flags and environment variables are not mixed: if either flag is given, both environment variables are
125
+ ignored, so a leftover `_ALLOW_WRITE` in the shell cannot widen `--expose read`. A blank flag value counts as
126
+ not given.
127
+ - A tool that runs other steps (a plan, workflow or batch tool) carries every write it can execute, including
128
+ writes this setting hides. Expose such a tool only if you would expose those writes too.
129
+ - A launcher that imports the server object can call
130
+ `vmware_policy.exposure.restrict_tools(mcp, skill="pilot", expose="read", allow_write=[...])` before running it,
131
+ instead of editing the SDK's tool registry.
132
+ - This limits what clients are offered. It is **not** authorization. This server's write tools change workflow state on this machine; the steps it runs act through the other skills' accounts;
133
+ deny rules in `~/.vmware/rules.yaml` still apply to every exposed tool.
134
+
108
135
  ## Configuration
109
136
 
110
137
  Pilot itself requires **no vCenter/NSX/AVI credentials**. It is a pure orchestrator that delegates all infrastructure calls to companion skills.
@@ -0,0 +1,67 @@
1
+ """Over HTTPS the approver is the authenticated caller (HLD §16 D-12).
2
+
3
+ Token "bob" calling approve(approver="alice (CISO)") used to record an approval
4
+ by "alice (CISO)". The audit row said token:bob; the workflow said otherwise.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pytest
10
+
11
+ from vmware_pilot.mcp_server.tools import lifecycle
12
+
13
+
14
+ class _Store:
15
+ def load(self, workflow_id):
16
+ return object()
17
+
18
+
19
+ class _Executor:
20
+ def __init__(self):
21
+ self.approvers = []
22
+
23
+ def resume_after_approval(self, wf, approver):
24
+ self.approvers.append(approver)
25
+ return {"state": "running"}
26
+
27
+
28
+ @pytest.fixture
29
+ def executor(monkeypatch):
30
+ ex = _Executor()
31
+ monkeypatch.setattr(lifecycle, "_get_store", lambda: _Store())
32
+ monkeypatch.setattr(lifecycle, "_get_executor", lambda: ex)
33
+ return ex
34
+
35
+
36
+ def _as(monkeypatch, who):
37
+ import vmware_policy.caller as caller_mod
38
+
39
+ monkeypatch.setattr(caller_mod, "current_caller", lambda: who)
40
+
41
+
42
+ def _approve(**kw):
43
+ fn = getattr(lifecycle.approve, "__wrapped__", lifecycle.approve)
44
+ return fn(**kw)
45
+
46
+
47
+ @pytest.mark.unit
48
+ def test_network_approval_is_recorded_as_the_caller(executor, monkeypatch):
49
+ _as(monkeypatch, "token:bob")
50
+ result = _approve(workflow_id="wf1")
51
+ assert "error" not in result
52
+ assert executor.approvers == ["token:bob"]
53
+
54
+
55
+ @pytest.mark.unit
56
+ def test_a_different_approver_name_over_the_network_is_refused(executor, monkeypatch):
57
+ _as(monkeypatch, "token:bob")
58
+ result = _approve(workflow_id="wf1", approver="alice (CISO)")
59
+ assert "error" in result and "token:bob" in result["error"]
60
+ assert executor.approvers == []
61
+
62
+
63
+ @pytest.mark.unit
64
+ def test_stdio_keeps_the_free_text_approver(executor, monkeypatch):
65
+ _as(monkeypatch, None)
66
+ _approve(workflow_id="wf1", approver="alice")
67
+ assert executor.approvers == ["alice"]
@@ -1142,7 +1142,7 @@ wheels = [
1142
1142
 
1143
1143
  [[package]]
1144
1144
  name = "vmware-pilot"
1145
- version = "2.0.0"
1145
+ version = "2.2.0"
1146
1146
  source = { editable = "." }
1147
1147
  dependencies = [
1148
1148
  { name = "mcp", extra = ["cli"] },
@@ -1160,10 +1160,10 @@ dev = [
1160
1160
 
1161
1161
  [package.metadata]
1162
1162
  requires-dist = [
1163
- { name = "mcp", extras = ["cli"], specifier = ">=1.0,<2.0" },
1163
+ { name = "mcp", extras = ["cli"], specifier = ">=1.28,<2.0" },
1164
1164
  { name = "pyyaml", specifier = ">=6.0,<7.0" },
1165
1165
  { name = "typer", specifier = ">=0.12,<1.0" },
1166
- { name = "vmware-policy", specifier = ">=1.19.0,<2.0" },
1166
+ { name = "vmware-policy", specifier = ">=1.21.0,<2.0" },
1167
1167
  ]
1168
1168
 
1169
1169
  [package.metadata.requires-dev]
@@ -1175,14 +1175,14 @@ dev = [
1175
1175
 
1176
1176
  [[package]]
1177
1177
  name = "vmware-policy"
1178
- version = "1.19.0"
1178
+ version = "1.21.0"
1179
1179
  source = { registry = "https://pypi.org/simple" }
1180
1180
  dependencies = [
1181
1181
  { name = "pyyaml" },
1182
1182
  { name = "rich" },
1183
1183
  { name = "typer" },
1184
1184
  ]
1185
- sdist = { url = "https://files.pythonhosted.org/packages/91/ef/e8c02d7c6fe2efd2d4bced230372348c91f492505a8d6a540573fcac3c02/vmware_policy-1.19.0.tar.gz", hash = "sha256:ea89498ca642907491081d9e75e8b2fbe5457a121e29dd5c42cc1d978145ca22", size = 213283, upload-time = "2026-10-15T02:21:28.595Z" }
1185
+ 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" }
1186
1186
  wheels = [
1187
- { url = "https://files.pythonhosted.org/packages/23/90/14744a439862439d3e8cf122e997d3bd9eee22421fc2760bbc55992710a8/vmware_policy-1.19.0-py3-none-any.whl", hash = "sha256:125d6e7e97a77ad3a150f307cbd6138329d54e8bd695d8ef1c51b16d0e72f931", size = 82881, upload-time = "2026-10-15T02:21:26.952Z" },
1187
+ { 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" },
1188
1188
  ]
@@ -1,3 +1,3 @@
1
1
  """VMware Pilot — multi-step workflow orchestration with approval gates."""
2
2
 
3
- __version__ = "2.0.0"
3
+ __version__ = "2.2.0"
@@ -13,6 +13,7 @@ convention — every sibling skill exposes the same ``<skill> mcp`` subcommand.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
+ from typing import Optional
16
17
  import typer
17
18
 
18
19
  from vmware_pilot import __version__
@@ -69,13 +70,43 @@ def version_cmd() -> None:
69
70
 
70
71
  @app.command("mcp")
71
72
  @cli_local("starts the MCP server; its tools audit themselves")
72
- def mcp_cmd() -> None:
73
- """Start the MCP server (stdio transport).
73
+ def mcp_cmd(
74
+ expose: Optional[str] = typer.Option(
75
+ None,
76
+ "--expose",
77
+ help="Which tools to offer: all (default) or read. Limits what clients see; "
78
+ "not authorization: the account's permissions still decide.",
79
+ ),
80
+ allow_write: Optional[str] = typer.Option(
81
+ None,
82
+ "--allow-write",
83
+ help="With --expose read: comma-separated write tools to offer as well.",
84
+ ),
85
+ transport: Optional[str] = typer.Option(
86
+ None,
87
+ "--transport",
88
+ help="stdio (default), https, or http (loopback only, behind a TLS reverse proxy). "
89
+ "https/http need a token: 'vmware-audit token create NAME'.",
90
+ ),
91
+ host: Optional[str] = typer.Option(None, "--host", help="Bind address (default 127.0.0.1)."),
92
+ port: Optional[int] = typer.Option(None, "--port", help="Port (default 8443)."),
93
+ cert: Optional[str] = typer.Option(None, "--cert", help="TLS certificate (PEM) for https."),
94
+ key: Optional[str] = typer.Option(None, "--key", help="TLS private key (PEM) for https."),
95
+ public_name: Optional[str] = typer.Option(
96
+ None,
97
+ "--public-name",
98
+ help="Host names clients use, comma-separated; other Host/Origin values are refused.",
99
+ ),
100
+ token_file: Optional[str] = typer.Option(
101
+ None, "--token-file", help="Token hash file (default $OPS_HOME/mcp-tokens)."
102
+ ),
103
+ ) -> None:
104
+ """Start the MCP server (stdio by default; --transport https serves the network).
74
105
 
75
106
  Single-command entry point for MCP clients:
76
107
  vmware-pilot mcp
77
108
 
78
- Equivalent to the legacy `vmware-pilot-mcp` console script.
109
+ Equivalent to the legacy `vmware-pilot-mcp` console script for stdio (the console script takes no flags).
79
110
  """
80
111
  import sys
81
112
 
@@ -96,7 +127,10 @@ def mcp_cmd() -> None:
96
127
 
97
128
  from vmware_pilot.mcp_server.server import main as _mcp_main
98
129
 
99
- _mcp_main()
130
+ _mcp_main(
131
+ expose=expose, allow_write=allow_write, transport=transport, host=host, port=port,
132
+ cert=cert, key=key, public_name=public_name, token_file=token_file,
133
+ )
100
134
 
101
135
 
102
136
  if __name__ == "__main__": # pragma: no cover
@@ -15,6 +15,7 @@ Exposes 13 tools for AI agents to manage multi-step VMware workflows:
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
+ from typing import Optional
18
19
  import logging
19
20
 
20
21
  from vmware_policy import describe_tool_parameters
@@ -147,10 +148,26 @@ __all__ = [
147
148
  ]
148
149
 
149
150
 
150
- def main() -> None:
151
+ def main(
152
+ expose: Optional[str] = None,
153
+ allow_write: Optional[str] = None,
154
+ transport: Optional[str] = None,
155
+ host: Optional[str] = None,
156
+ port: Optional[int] = None,
157
+ cert: Optional[str] = None,
158
+ key: Optional[str] = None,
159
+ public_name: Optional[str] = None,
160
+ token_file: Optional[str] = None,
161
+ ) -> None:
151
162
  """Run the MCP server over stdio."""
152
163
  logging.basicConfig(level=logging.INFO)
153
- mcp.run(transport="stdio")
164
+ from vmware_policy.serve import run_server
165
+ run_server(
166
+ mcp,
167
+ skill="pilot",
168
+ expose=expose, allow_write=allow_write, transport=transport, host=host, port=port,
169
+ cert=cert, key=key, public_name=public_name, token_file=token_file,
170
+ )
154
171
 
155
172
  # The docstrings above are the schema. `describe_tool_parameters` copies each
156
173
  # `Args:` entry into the JSON schema an agent actually reads, and closes the
@@ -291,7 +291,9 @@ def approve(workflow_id: str, approver: str = "") -> dict:
291
291
 
292
292
  Args:
293
293
  workflow_id: The workflow ID to approve.
294
- approver: Name of the person approving (for audit trail).
294
+ approver: Name of the person approving (for audit trail). Over an
295
+ authenticated network transport leave it empty: the approver is
296
+ the caller's token name, and a different name is refused.
295
297
 
296
298
  Note: this server has no dispatcher — after approval, remaining steps
297
299
  are recorded as 'not_executed' and the result carries
@@ -306,6 +308,21 @@ def approve(workflow_id: str, approver: str = "") -> dict:
306
308
  if not wf:
307
309
  return {"error": f"Workflow '{workflow_id}' not found"}
308
310
 
311
+ # Over HTTPS the approver is who authenticated, not a free-text claim
312
+ # (HLD §16 D-12): token "bob" must not record an approval as "alice".
313
+ from vmware_policy.caller import current_caller
314
+
315
+ caller = current_caller()
316
+ if caller is not None:
317
+ if approver and approver != caller:
318
+ return {
319
+ "error": f"approver {approver!r} differs from the authenticated caller "
320
+ f"{caller!r}; over a network transport the approval is recorded as the "
321
+ "caller.",
322
+ "hint": "Call approve again without 'approver', or have that person "
323
+ "approve with their own token.",
324
+ }
325
+ approver = caller
309
326
  # Non-empty approver enforcement lives in the executor so embedders
310
327
  # cannot bypass the audit-trail requirement.
311
328
  return _get_executor().resume_after_approval(wf, approver=approver)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes