vmware-pilot 2.1.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.1.0 → vmware_pilot-2.2.0}/.claude-plugin/plugin.json +1 -1
  2. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/.mcp.json +1 -1
  3. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/PKG-INFO +3 -3
  4. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/RELEASE_NOTES.md +21 -0
  5. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/openclaw.plugin.json +1 -1
  6. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/package.json +1 -1
  7. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/pyproject.toml +3 -3
  8. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/server.json +2 -2
  9. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/SKILL.md +2 -2
  10. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/cli-reference.md +2 -2
  11. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/setup-guide.md +5 -5
  12. vmware_pilot-2.2.0/tests/test_approver_is_caller.py +67 -0
  13. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/uv.lock +6 -6
  14. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/__init__.py +1 -1
  15. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/cli.py +24 -3
  16. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/server.py +18 -4
  17. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/lifecycle.py +18 -1
  18. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/.clawhubignore +0 -0
  19. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/.gitignore +0 -0
  20. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/Dockerfile +0 -0
  21. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/README-CN.md +0 -0
  22. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/README.md +0 -0
  23. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/SECURITY.md +0 -0
  24. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/docs/architecture-audit-2026-04-30.md +0 -0
  25. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/docs/aria-pilot-boundary.md +0 -0
  26. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/examples/mcp-configs/claude-code.json +0 -0
  27. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/evals/evals.json +0 -0
  28. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/agent-guardrails.md +0 -0
  29. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/capabilities.md +0 -0
  30. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/integration-patterns.md +0 -0
  31. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/templates.md +0 -0
  32. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/workflow-design.md +0 -0
  33. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/list_available_tools.py +0 -0
  34. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/validate_workflow.py +0 -0
  35. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/smithery.yaml +0 -0
  36. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/__init__.py +0 -0
  37. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/conftest.py +0 -0
  38. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/__init__.py +0 -0
  39. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/__init__.py +0 -0
  40. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/_family.py +0 -0
  41. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scores.json +0 -0
  42. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scoring.py +0 -0
  43. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/_skill.py +0 -0
  44. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/conftest.py +0 -0
  45. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_entity_reachability.py +0 -0
  46. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_error_actionability.py +0 -0
  47. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
  48. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  49. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/__init__.py +0 -0
  50. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/_family_write_surface.py +0 -0
  51. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_approval_gate_coverage.py +0 -0
  52. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_capability_grader.py +0 -0
  53. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_catalog_covers_the_family.py +0 -0
  54. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
  55. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_environment_resolver_isolation.py +0 -0
  56. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_template_steps_name_real_tools.py +0 -0
  57. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/fixtures/companion_tools.json +0 -0
  58. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/fixtures/snapshot_companion_tools.py +0 -0
  59. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_confirm_needs_approval.py +0 -0
  60. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_custom_approval_gate.py +0 -0
  61. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_executor.py +0 -0
  62. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_executor_preview_is_not_success.py +0 -0
  63. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_final_review_fixes.py +0 -0
  64. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_models.py +0 -0
  65. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_no_destructive_ops.py +0 -0
  66. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_old_tool_names_translated.py +0 -0
  67. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_own_tools_gated.py +0 -0
  68. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_review.py +0 -0
  69. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_safe_error_passthrough.py +0 -0
  70. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_server.py +0 -0
  71. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_templates.py +0 -0
  72. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/tests/test_templates_pass_confirm.py +0 -0
  73. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/approval_gate.py +0 -0
  74. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/custom_loader.py +0 -0
  75. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/executor.py +0 -0
  76. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/lifecycle_gate.py +0 -0
  77. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__init__.py +0 -0
  78. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__main__.py +0 -0
  79. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_catalog.py +0 -0
  80. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_shared.py +0 -0
  81. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/__init__.py +0 -0
  82. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/authoring.py +0 -0
  83. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/query.py +0 -0
  84. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/models.py +0 -0
  85. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/renames.py +0 -0
  86. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/review.py +0 -0
  87. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/__init__.py +0 -0
  88. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_aiops_params.py +0 -0
  89. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_common.py +0 -0
  90. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_gated.py +0 -0
  91. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/baseline.py +0 -0
  92. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/k8s.py +0 -0
  93. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/monitoring.py +0 -0
  94. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/network.py +0 -0
  95. {vmware_pilot-2.1.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/storage.py +0 -0
  96. {vmware_pilot-2.1.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.1.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.1.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.1.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.20.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,24 @@
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
+
1
22
  ## v2.1.0 — offer only read tools, plus chosen write tools
2
23
 
3
24
  `vmware-pilot mcp` can now offer a subset of this server's tools (HLD §17 D-16, via vmware-policy 1.20.0):
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-pilot==2.1.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.1.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.1.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.20.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.1.0",
10
+ "version": "2.2.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-pilot",
15
- "version": "2.1.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.1.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.1.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.1.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.1.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.1.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.1.0
20
+ uv tool install vmware-pilot==2.2.0
21
21
 
22
22
  # China mainland mirror
23
- pip install vmware-pilot==2.1.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.1.0", "vmware-pilot-mcp"]
87
+ "args": ["--from", "vmware-pilot==2.2.0", "vmware-pilot-mcp"]
88
88
  }
89
89
  }
90
90
  }
@@ -100,7 +100,7 @@ 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.1.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
  ---
@@ -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.1.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.20.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.20.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/66/f3/cae0e18d269bd4764d5f9b34a838174e9668f931e44d41effab620aeecf4/vmware_policy-1.20.0.tar.gz", hash = "sha256:eb9875d3e94435a18d1dd6cf2c71905c46e2e812808556db3fb741ef322bee99", size = 220048, upload-time = "2026-10-15T06:33:35.174Z" }
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/b2/cf/6c87f6b5439407c1c3b83b84a5ef53def7544b687efd6f16a6cb187acf78/vmware_policy-1.20.0-py3-none-any.whl", hash = "sha256:cea9451c70be96e3d7adc420a2ac001cf431c29b61350dd58c3771378058737a", size = 86684, upload-time = "2026-10-15T06:33:33.413Z" },
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.1.0"
3
+ __version__ = "2.2.0"
@@ -82,13 +82,31 @@ def mcp_cmd(
82
82
  "--allow-write",
83
83
  help="With --expose read: comma-separated write tools to offer as well.",
84
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
+ ),
85
103
  ) -> None:
86
- """Start the MCP server (stdio transport).
104
+ """Start the MCP server (stdio by default; --transport https serves the network).
87
105
 
88
106
  Single-command entry point for MCP clients:
89
107
  vmware-pilot mcp
90
108
 
91
- 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).
92
110
  """
93
111
  import sys
94
112
 
@@ -109,7 +127,10 @@ def mcp_cmd(
109
127
 
110
128
  from vmware_pilot.mcp_server.server import main as _mcp_main
111
129
 
112
- _mcp_main(expose=expose, allow_write=allow_write)
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
+ )
113
134
 
114
135
 
115
136
  if __name__ == "__main__": # pragma: no cover
@@ -148,12 +148,26 @@ __all__ = [
148
148
  ]
149
149
 
150
150
 
151
- def main(expose: Optional[str] = None, allow_write: Optional[str] = None) -> 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:
152
162
  """Run the MCP server over stdio."""
153
163
  logging.basicConfig(level=logging.INFO)
154
- from vmware_policy.exposure import apply_or_exit
155
- apply_or_exit(mcp, skill="pilot", expose=expose, allow_write=allow_write)
156
- 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
+ )
157
171
 
158
172
  # The docstrings above are the schema. `describe_tool_parameters` copies each
159
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