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.
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.claude-plugin/plugin.json +1 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.mcp.json +1 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/PKG-INFO +3 -3
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/RELEASE_NOTES.md +38 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/openclaw.plugin.json +1 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/package.json +1 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/pyproject.toml +3 -3
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/server.json +2 -2
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/SKILL.md +2 -2
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/cli-reference.md +2 -2
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/setup-guide.md +32 -5
- vmware_pilot-2.2.0/tests/test_approver_is_caller.py +67 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/uv.lock +6 -6
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/__init__.py +1 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/cli.py +38 -4
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/server.py +19 -2
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/lifecycle.py +18 -1
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.clawhubignore +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/.gitignore +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/Dockerfile +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/README-CN.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/README.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/SECURITY.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/docs/architecture-audit-2026-04-30.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/docs/aria-pilot-boundary.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/examples/mcp-configs/claude-code.json +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/evals/evals.json +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/agent-guardrails.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/capabilities.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/integration-patterns.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/templates.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/workflow-design.md +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/list_available_tools.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/validate_workflow.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/smithery.yaml +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/conftest.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_family.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scores.json +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_scoring.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/_skill.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/conftest.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_entity_reachability.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_error_actionability.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/_family_write_surface.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_approval_gate_coverage.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_capability_grader.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_catalog_covers_the_family.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_environment_resolver_isolation.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_template_steps_name_real_tools.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/fixtures/companion_tools.json +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/fixtures/snapshot_companion_tools.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_confirm_needs_approval.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_custom_approval_gate.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_executor.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_executor_preview_is_not_success.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_final_review_fixes.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_models.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_no_destructive_ops.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_old_tool_names_translated.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_own_tools_gated.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_review.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_safe_error_passthrough.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_server.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_templates.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/test_templates_pass_confirm.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/approval_gate.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/custom_loader.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/executor.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/lifecycle_gate.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/__main__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_catalog.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/_shared.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/authoring.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/mcp_server/tools/query.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/models.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/renames.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/review.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/__init__.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_aiops_params.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_common.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/_gated.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/baseline.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/k8s.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/monitoring.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/network.py +0 -0
- {vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/vmware_pilot/templates/storage.py +0 -0
- {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.
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vmware-pilot
|
|
3
|
-
Version: 2.
|
|
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.
|
|
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.
|
|
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`.
|
|
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vmware-pilot"
|
|
7
|
-
version = "2.
|
|
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.
|
|
15
|
-
"mcp[cli]>=1.
|
|
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.
|
|
10
|
+
"version": "2.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "vmware-pilot",
|
|
15
|
-
"version": "2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
20
|
+
uv tool install vmware-pilot==2.2.0
|
|
21
21
|
|
|
22
22
|
# China mainland mirror
|
|
23
|
-
pip install vmware-pilot==2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
]
|
|
@@ -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(
|
|
73
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/agent-guardrails.md
RENAMED
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/references/integration-patterns.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/skills/vmware-pilot/scripts/list_available_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_description_quality.py
RENAMED
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/capability/test_tool_manifest_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_approval_gate_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
{vmware_pilot-2.0.0 → vmware_pilot-2.2.0}/tests/eval/regression/test_catalog_covers_the_family.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|