Repository navigation
Releases: vmware-skills/VMware-Log-Insight
Release list
v2.4.1 — fixed against a real 8.18.7 appliance (VMware-AIops#45)
First run of every tool on real hardware (Aria Operations for Logs 8.18.7-25423541, ingesting ESXi and vCenter
syslog). Three of the seven tools failed on every call, because the tests mocked response shapes the appliance
never sends. The regression tests now replay responses captured from that appliance
(tests/fixtures/li_8_18_7.json).
get_log_alert_historyreturns real records. Captured from a temporary alert that fired on 8.18.7 (then
deleted): each record is{triggerTimestamp, acknowledged, messages, ...}. The code read keys a record does not
have, so every row would have been{timestamp_ms: null, info: ""}. Rows are now{timestamp_ms, acknowledged, message_count, messages}(at most 3 events per record). History is paged by the appliance (up to 50), so a full page now
saystruncated: truewithtotal: nullinstead of claiming to be the whole history.list_log_fields,list_log_alerts,get_log_alert_historywork. 8.18.7 answers these with a bare JSON
array; the code read it as an object and every call failed withAttributeError: operation failed.Field items are
now{name, internal_name, type}. For a content-pack fieldnameis the qualified
<pack namespace>:<display name>, the only form a query accepts (verified on 8.18.7). These fields also carry
display_nameandcontent_pack. This costs one extra read of the content-pack list.aggregate_logsbins have timestamps. Bins carryminTimestamp/maxTimestamp; everytimestamp_mswas
null, so spikes could not be placed in time. Bins are now{timestamp_ms, end_ms, value}, oldest first.aggregate_logstakesfield(CLI--field). UCOUNT, AVG, MIN, MAX, SUM, STDDEV and VARIANCE need one and
answered HTTP 400 without it; COUNT and SAMPLE refuse one. Both are now checked before the call. An unknown field
returns an empty series with anote. A SAMPLE bin's value (a raw event) is now sanitized.search_logsfillshostname,appnameandprocid. On 8.18.7 these carry a position in the event text
instead of a value, and always came back as"".search_logsreturns at most 200 events and says when it lowerslimit. The cap was 20000 and silent; 20000
events was a 16 MB result that took 31 s and was stored whole in the audit database.- Windows longer than ~24.8 days work. The appliance reads
LASTas a 32-bit millisecond count and answered
HTTP 400; such a window is now sent as an absolute start time. begin_msafterend_ms, and a value that looks like epoch seconds, are refused with the fix.- An alert id containing
/,?, spaces or..is refused (../versionused to reach/version). An unknown id
(HTTP 404, or 500 for a non-UUID) now says "copy an exact id", not "retry". - Errors say "run doctor" only when doctor can help (config, credentials, reachability). An unexpected error says it
is not an input problem. doctornow readsGET /fieldsthrough the same code the tools use, so a broken query API no longer passes.- Roles: a target-limited role can use this skill. The role resolver read a
namefield that this skill's
target config does not have, so it raised on every call. Every call by a token whose role lists targets was
refused as "cannot be resolved", and the audit row could not name the default target. - A refused argument now logs one warning line; real failures keep their traceback.
- Spikes are measured against quiet, not only against bursts. The appliance omits bins with no events. For
COUNT and UCOUNT those bins are now added back as 0 (filled_empty_bins: true), so a burst after a quiet stretch
is reported as a spike. - A content-pack field whose pack is missing from the pack list is marked
unresolvedwith a note. Fields the
appliance lists twice are listed once. A non-JSON answer (e.g. a proxy's login page) is reported as the
appliance or proxy answering wrongly, not as an argument error. - An empty text search says why.
textmatches whole words: on 8.18.7agt-w2returned 0 events
(complete: true) whileagt-w2*returned 50+. When a text search without*matches nothing,search_logsand
aggregate_logsnow add anotesuggesting the wildcard, instead of an empty result that reads as "no such logs". - Docs:
search_logshas no field-filter parameter (the docs said it had);textmatches whole words, not
substrings, and*is a wildcard; corrected token estimates.
v2.4.0 — roles for network callers (HLD §18)
- Declares the role metadata vmware-policy ≥ 1.24.0 needs to apply roles to this server over HTTPS
(HLD §18): how a missing target resolves. With an older vmware-policy nothing changes. Once an operator runs
vmware-audit roles enable, a token's role decides which tools and targets it may use here; a refused call is
audited asdenied. See vmware-policy's README, "Roles for network callers". - A target-limited role is checked against the same config the connection uses: the copy the server's connection manager loaded on its first connection. Edit
config.yamland restart for both to see the change. - README and setup guide: one endpoint for several people or roles.
v2.3.0 — calls on one target run side by side over HTTPS
When vmware-log-insight mcp serves the network (--transport https or http), calls no longer queue behind each other.
Calls that name the same target, or name no target, now run side by side instead of one at a time (HLD §16
D-13(2), via vmware-policy 1.22.0):
- Each call gets its own session to the Log Insight for as long as it runs, so two calls never share a login.
- Each target keeps up to
VMWARE_MCP_POOL_SIZEsessions (default 2). Each session is a login on the target, so
raise it only as far as the account's session limit allows. - A call that finds every session busy waits at most 300 s for one, then fails with a message saying so.
- A session that has expired or stopped answering is closed and replaced the next time a call needs it.
stdio and the CLI are unchanged: one session per target, reused.
v2.2.0 — serve MCP over HTTPS
vmware-log-insight mcp can now serve the network as well as stdio (HLD §16, via vmware-policy 1.21.0):
vmware-audit token create alice # once per caller; shown once
vmware-log-insight mcp --transport https --host 0.0.0.0 --port 8443 \
--cert server.pem --key server-key.pem --public-name mcp.example.comConnect a client and manage callers (Claude Code shown; any MCP client with streamable HTTP and custom headers works):
claude mcp add --transport http vmware-log-insight https://mcp.example.com:8443/mcp \
--header "Authorization: Bearer $VMWARE_MCP_TOKEN"
vmware-audit token list # names only
vmware-audit token revoke alice # refused from its next request, no restart- Every request needs
Authorization: Bearer <token>; the server will not start without a token, refuses Host or
Origin values outside--public-name, and checks itself (unauthenticated requests must get 401) before it
binds.--transport httpbinds loopback only, for a TLS-terminating reverse proxy. - Audit rows record the caller as
token:<name>and the surface asmcp-https. Calls naming different targets
run in parallel; calls naming no target run one at a time. - stdio remains the default; the
vmware-log-insight-mcpconsole script andpython -mare unchanged. - Everyone who can reach the endpoint acts with this server's account; run one endpoint per privilege level.
- Requires
vmware-policy>=1.21.0andmcp>=1.28.
v2.1.0 — `mcp --expose` options; the account is the boundary
vmware-log-insight mcp accepts the family's new --expose / --allow-write options (vmware-policy 1.20.0). This server has
no write tools, so --expose read offers the same tools as the default; the options exist so every server in the
family can be started the same way. references/setup-guide.md now says why the service account must itself be
read-only: an agent with a shell can reach the platform without this skill. Requires vmware-policy>=1.20.0.
v2.0.0 — MCP tools renamed verb-first
Breaking for MCP clients: all 7 tool names changed. The family now uses one naming convention, verb
first (list_…, get_…, search_…), so an agent that learns one skill can guess the next. Where two skills
would otherwise ship the same name, a domain word is added: vmware-aria already has get_alert /
list_alerts, so this skill's alert tools are log_ alerts.
| Old name (≤ 1.9.1) | New name (2.0.0) |
|---|---|
log_search |
search_logs |
log_aggregate |
aggregate_logs |
log_fields |
list_log_fields |
log_version |
get_log_insight_version |
alert_list |
list_log_alerts |
alert_get |
get_log_alert |
alert_history |
get_log_alert_history |
- Parameters, results and behaviour are unchanged (including the v1.9.1 query-constraint fix for issue #3).
The audit log records the new names; CLI reads record their MCP twin's new name. - CLI commands are unchanged (
vmware-log-insight search,aggregate,fields,alert list|get|history). - Deny rules in
~/.vmware/rules.yamlwritten with old names keep working with vmware-policy ≥ 1.18.0 (it
matches both names);vmware-audit policylists any rule that now matches more.
v1.9.1 — searches no longer fail with HTTP 400 (#3)
Every log_search / log_aggregate call (and vmware-log-insight search) was rejected by a real appliance with
HTTP 400, while doctor passed. The query builder sent each constraint as three path segments —
timestamp/LAST/3600000, text/CONTAINS/error — but Log Insight reads a constraint as a field followed by ONE
segment holding the operator and value separated by a space: timestamp/LAST%203600000, text/CONTAINS%20error.
doctor never builds a query, which is why it stayed green. Reported in
#3; the reporter's hand-written curl had the right
form.
- All constraints now use
field/<OPERATOR value>: relative window, absolute>/<window,text CONTAINS, and
every(field, operator, value)filter.EXISTSis sent asfield/EXISTS(it previously got a trailing
empty segment). - The operator+value segment is percent-encoded as a whole, so a value containing
/or spaces still stays one
segment. - New regression
test_constraint_wire_grammar.pychecks the path that actually leaves the HTTP client (not just
the builder's string), including the exact request from the issue. Reverting the fix turns all three tests red.
Dependencies. uv.lock moves pyjwt 2.13.0 → 2.15.0 (supersedes Dependabot
#2; 2.15.0 carries an upstream fix for deeply nested
token payloads). pyjwt reaches this skill only transitively through mcp; the published wheel's requirements are
unchanged, so this only moves the locked dev/test environment.
v1.9.0 — MCP instructions name the configured targets
initialize hands the client this server's instructions, and for a skill whose every tool takes a target that
text is the only place the client learns which targets exist. Until now this one shipped a static description that never named them. The model then
called tools with no target, got whatever the default was, and answered confidently about the wrong system —
measured in vmware-monitor on 2026-09-15, where a standalone ESXi host was the default, so "how many VMs does the
vCenter have" was answered from that host.
The instructions now carry, built from the loaded config on every server start:
Configured targets:— each configured target with its host, and which one answers when none is named.Choosing a target:— how to pick one from what the user asked, and to ask rather than guess when the
request does not say and the targets would answer differently.
A config that cannot be read is said out loud, not dropped. On a machine that has not run init yet — every
new install — this server now reports Configured targets: could not be read (FileNotFoundError) — run vmware-log-insight doctor. instead of falling silent. A client shown no listing cannot
tell "this skill has no targets worth naming" from "this skill could not read them", and only the first reading
produces a confident answer about a system nobody chose. Only the error's type is included; its text quotes the
config path.
No tool, parameter or result shape changed. A new family gate (mcp_instructions_name_targets.py, in
family_smoke) probes every server under an empty HOME, so the check runs in the state a new user is in rather
than the one the developer happens to be in.
v1.8.17 — CLI reads are audited
No CLI read wrote ~/.vmware/audit.db — only MCP calls and CLI writes (@guarded) did. A live
vmware-aria resource list left the audit row count unchanged while the same read over MCP added a row.
Every CLI command that reaches Log Insight now passes the same guard() as its MCP twin and writes one
audit row under that tool's name (7 commands, @audited). Commands that reach nothing remote say so, with a reason (2, @cli_local).
A family gate now fails any CLI command that declares none of @guarded / @audited / @cli_local.
Behaviour change: a deny rule in ~/.vmware/rules.yaml whose operations name a read tool now stops
that CLI command too, as it already stopped the MCP call.
Requires vmware-policy>=1.15.0.
v1.8.16 — OpenClaw can load the skill
OpenClaw could not show this skill to the model. metadata.openclaw.requires listed
config file paths under requires.config, which OpenClaw reads as openclaw.json keys that
must be truthy — so the skill was "needs setup / not visible to the model" whatever was on disk
(verified on OpenClaw 2026.6.35). requires.env named an optional override and requires.bins
demanded a CLI that a plugin install (uvx) never has. requires is now anyBins: [<cli>, "uvx"];
the variables are still declared, under optional.env.
Install commands in the skill pin this release. ClawHub reviews SKILL.md and references/,
not the package they install, so an unpinned uv tool install vouched for code nobody reviewed.
Every install command for this package in the skill now names this version.
A config path written as ~/… now resolves. Every MCP example config and setup-guide snippet
sets VMWARE_LOG_INSIGHT_CONFIG to ~/.vmware-log-insight/config.yaml, but MCP clients pass env values verbatim and the
path was used unexpanded, so copying the snippet gave "Config file not found" for a file that was
there. ~ is now expanded in the variable and in --config.