@aristanetworks/eos-mcp-server 0.6.1-beta → 0.8.1-beta

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 (76) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +171 -44
  3. package/dist/config/diagnostics.d.ts +65 -0
  4. package/dist/config/diagnostics.js +38 -0
  5. package/dist/config/diagnostics.js.map +1 -0
  6. package/dist/config/loadConfig.js +1 -0
  7. package/dist/config/loadConfig.js.map +1 -1
  8. package/dist/config/schema.d.ts +182 -8
  9. package/dist/config/schema.js +3 -0
  10. package/dist/config/schema.js.map +1 -1
  11. package/dist/diagnostics/concurrency.d.ts +29 -0
  12. package/dist/diagnostics/concurrency.js +115 -0
  13. package/dist/diagnostics/concurrency.js.map +1 -0
  14. package/dist/diagnostics/eapiRunner.d.ts +10 -0
  15. package/dist/diagnostics/eapiRunner.js +52 -0
  16. package/dist/diagnostics/eapiRunner.js.map +1 -0
  17. package/dist/diagnostics/executionError.d.ts +7 -0
  18. package/dist/diagnostics/executionError.js +10 -0
  19. package/dist/diagnostics/executionError.js.map +1 -0
  20. package/dist/diagnostics/executor.d.ts +31 -0
  21. package/dist/diagnostics/executor.js +114 -0
  22. package/dist/diagnostics/executor.js.map +1 -0
  23. package/dist/diagnostics/ping.d.ts +7 -0
  24. package/dist/diagnostics/ping.js +131 -0
  25. package/dist/diagnostics/ping.js.map +1 -0
  26. package/dist/diagnostics/types.d.ts +81 -0
  27. package/dist/diagnostics/types.js +2 -0
  28. package/dist/diagnostics/types.js.map +1 -0
  29. package/dist/eapi/client.d.ts +3 -1
  30. package/dist/eapi/client.js +18 -6
  31. package/dist/eapi/client.js.map +1 -1
  32. package/dist/eapi/types.d.ts +9 -0
  33. package/dist/eapi/types.js.map +1 -1
  34. package/dist/inventory/buildModel.js +1 -0
  35. package/dist/inventory/buildModel.js.map +1 -1
  36. package/dist/inventory/listInventoryView.d.ts +1 -0
  37. package/dist/inventory/listInventoryView.js +1 -0
  38. package/dist/inventory/listInventoryView.js.map +1 -1
  39. package/dist/inventory/policy.d.ts +1 -0
  40. package/dist/inventory/policy.js +3 -1
  41. package/dist/inventory/policy.js.map +1 -1
  42. package/dist/inventory/types.d.ts +1 -0
  43. package/dist/inventory/varValidation.js +1 -0
  44. package/dist/inventory/varValidation.js.map +1 -1
  45. package/dist/mcp/createServer.js +39 -4
  46. package/dist/mcp/createServer.js.map +1 -1
  47. package/dist/mcp/tools/getServerInfo.d.ts +2 -1
  48. package/dist/mcp/tools/getServerInfo.js +2 -2
  49. package/dist/mcp/tools/getServerInfo.js.map +1 -1
  50. package/dist/mcp/tools/ping.d.ts +31 -0
  51. package/dist/mcp/tools/ping.js +27 -0
  52. package/dist/mcp/tools/ping.js.map +1 -0
  53. package/dist/operations/readExecution.d.ts +3 -1
  54. package/dist/operations/readExecution.js +24 -16
  55. package/dist/operations/readExecution.js.map +1 -1
  56. package/dist/serverInfo/buildServerInfo.d.ts +2 -1
  57. package/dist/serverInfo/buildServerInfo.js +17 -2
  58. package/dist/serverInfo/buildServerInfo.js.map +1 -1
  59. package/dist/serverInfo/types.d.ts +16 -0
  60. package/docs/CHECKPOINT.md +8 -1
  61. package/docs/CODE_WALKTHROUGH.md +25 -6
  62. package/docs/DESIGN.md +17 -4
  63. package/docs/DIAGNOSTICS.md +167 -0
  64. package/docs/IMPLEMENTATION_PLAN.md +4 -3
  65. package/docs/QUICKSTART.md +13 -13
  66. package/docs/adr/0001-non-configuring-diagnostics.md +5 -0
  67. package/docs/adr/0002-fail-closed-diagnostic-reservations.md +9 -0
  68. package/docs/adr/0003-durable-diagnostic-ledger.md +11 -0
  69. package/docs/adr/0004-offline-operator-attested-release.md +7 -0
  70. package/docs/adr/0005-multi-destination-ping.md +9 -0
  71. package/docs/adr/0006-in-memory-timed-diagnostic-capacity.md +21 -0
  72. package/docs/research/eos-diagnostic-lifecycle.md +86 -0
  73. package/package.json +1 -1
  74. package/dist/inventory/writePolicy.d.ts +0 -2
  75. package/dist/inventory/writePolicy.js +0 -36
  76. package/dist/inventory/writePolicy.js.map +0 -1
@@ -0,0 +1,167 @@
1
+ # Non-configuring network diagnostics
2
+
3
+ `eos_ping` executes bounded ping commands **on EOS**, not on the MCP host. It never changes configuration. It generates network traffic and is not a passive read. Traceroute and packet capture are future tools, not implemented capabilities.
4
+
5
+ ## Authorization and addressing
6
+
7
+ A **Target** is an inventory host or group executing the diagnostic. A **Destination** is a literal IPv4/IPv6 address being tested and need not appear in inventory. DNS names and inventory-reference shorthand are not accepted as destinations. One request may name several destinations; each is tested from every resolved host.
8
+
9
+ Diagnostic access defaults to denied. Opt in explicitly using inherited inventory variables:
10
+
11
+ ```yaml
12
+ vars:
13
+ ansible_network_os: eos
14
+ ansible_user: diagnostics-user
15
+ mcp_password_env: EOS_MCP_PASSWORD
16
+ mcp_diagnostics_allowed: true
17
+ hosts:
18
+ leaf1:
19
+ ansible_host: 192.0.2.11
20
+ ```
21
+
22
+ `mcp_read_allowed: false` also denies diagnostics, regardless of the opt-in. Every resolved host must be eligible and authorized; a mixed-permission group fails before any commands are dispatched. `eos_list_inventory` reports `diagnostics_allowed` per host. Group resolution deduplicates hosts and preserves the existing alphabetical result ordering.
23
+
24
+ Use EOS AAA to restrict the credential to the intended operational commands. Ping requires enable mode on the tested EOS version. There is no SSH, device-shell, or MCP-host-shell fallback. Explicit diagnostic opt-in authorizes probes to arbitrary supported destinations reachable from the selected routing context; it is not a destination allowlist. Use network policy/ACLs if additional destination restrictions are required.
25
+
26
+ Supported addresses are unicast IPv4/IPv6 literals, including loopback. Unspecified addresses, IPv4 0/8 and reserved/multicast ranges, limited broadcast, IPv6 multicast, and IPv6 link-local/scoped addresses are rejected. IPv4-mapped IPv6 cannot bypass IPv4 restrictions. A directed broadcast cannot reliably be identified from an address alone; no such detection is promised.
27
+
28
+ ## Tool inputs
29
+
30
+ ```json
31
+ {
32
+ "target": "leaf1",
33
+ "destinations": ["2001:db8::10", "2001:db8::20"],
34
+ "count": 3,
35
+ "vrf": "BLUE",
36
+ "source": "2001:db8::11",
37
+ "include_raw": false
38
+ }
39
+ ```
40
+
41
+ - `target` and `destinations` are required. A single ping is a one-element `destinations` array.
42
+ - `destinations` holds 1 to `maxDestinationsPerRequest` unique addresses. Duplicates are detected by canonical address, so different IPv6 spellings of one address (such as `::1` and `0:0:0:0:0:0:0:1`) count as duplicates, while an IPv4 address and its IPv4-mapped IPv6 form are distinct destinations. Every destination is validated before anything is dispatched, so one invalid or duplicate entry rejects the whole request. The former single `destination` field is rejected with guidance rather than ignored.
43
+ - `count`, `vrf`, and `source` apply to every destination.
44
+ - `count` defaults to the configured `defaultPingCount`; callers cannot exceed `maxPingCount`.
45
+ - `vrf` is optional, a safe single identifier of up to 64 characters. Omission uses the EOS default routing context.
46
+ - `source` is an optional IP literal in the same address family as every destination; mixing IPv4 and IPv6 destinations therefore requires omitting `source`. Source-interface selection is not supported.
47
+ - An invalid/unavailable VRF or source fails; it never falls back to another context.
48
+ - `include_raw` defaults to false and exposes original EOS text only when true, including unrecognized output returned as a device failure.
49
+ - Arbitrary CLI commands/options are not accepted. Pacing is explicitly fixed at one second; EOS packet `size` is fixed at 64. Flood/rapid modes, custom sizes, and DNS resolution are not exposed.
50
+
51
+ ## Settings and defaults
52
+
53
+ Add an optional `diagnostics` mapping to the existing settings YAML. Every omitted field retains its default:
54
+
55
+ ```yaml
56
+ diagnostics:
57
+ defaultPingCount: 3
58
+ maxPingCount: 10
59
+ maxProbesPerRequest: 100
60
+ maxDestinationsPerRequest: 16
61
+ deviceConcurrency: 5
62
+ pingReplyTimeoutMs: 1000
63
+ commandTimeoutMs: 30000
64
+ httpTimeoutMs: 35000
65
+ overallOperationTimeoutMs: 60000
66
+ nativeRequestTimeout: true
67
+ ```
68
+
69
+ | Setting | Meaning |
70
+ | --- | --- |
71
+ | `defaultPingCount` | Number of probes when the caller omits `count` |
72
+ | `maxPingCount` | Maximum probes per device and destination |
73
+ | `maxProbesPerRequest` | Maximum resolved host count × destination count × requested probes |
74
+ | `maxDestinationsPerRequest` | Maximum destinations in one `eos_ping` call |
75
+ | `deviceConcurrency` | Diagnostic capacity of one server process, shared by its active and uncertain executions across calls |
76
+ | `pingReplyTimeoutMs` | EOS per-reply timeout; a positive multiple of 1000 ms |
77
+ | `commandTimeoutMs` | Conservative device execution budget; see lifecycle below |
78
+ | `httpTimeoutMs` | Per-device diagnostic eAPI HTTP timeout, separate from `readTimeoutMs` |
79
+ | `overallOperationTimeoutMs` | Whole diagnostic call deadline, including waiting for global capacity |
80
+ | `nativeRequestTimeout` | Send EOS `runCmds.requestTimeout` set to `commandTimeoutMs`, so the device interrupts a ping that outlives its budget; set `false` for an EOS version that rejects the field |
81
+
82
+ Diagnostic capacity is held in memory by each server process; nothing is written to disk. Several MCP servers (for example two chat sessions) can run against the same inventory at once, each with its own `deviceConcurrency`. The former `diagnostics.stateFile` setting is rejected with guidance; delete it. Ledger files left under `$XDG_STATE_HOME/eos-mcp-server/` (or `~/.local/state/eos-mcp-server/`) by earlier versions are no longer read and can be deleted. See [ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md).
83
+
84
+ Existing `maxReadTargets` (default 50) and `maxResponseSizeBytes` (default 1,048,576) also apply. Diagnostic concurrency and timeouts are independent of the corresponding read settings. Effective diagnostic settings are reported by `eos_get_server_info` under `limits.diagnostics`.
85
+
86
+ Numeric settings must be finite positive integers. The default count cannot exceed the maximum count; the maximum count cannot exceed the aggregate probe budget. The command budget must cover:
87
+
88
+ ```text
89
+ maxPingCount * (pingReplyTimeoutMs + 1000) + 1000 ms
90
+ ```
91
+
92
+ The HTTP timeout must exceed the command budget by at least 1000 ms. Individual settings must fit the supported timer range (2,147,483,647 ms); there is no combined reservation-expiry timer. Unknown fields and contradictory settings fail config validation. Increasing count/reply-timeout ceilings may require increasing both command and HTTP budgets. The overall deadline may intentionally be shorter than device execution; that cancels the call, not necessarily the remote command.
93
+
94
+ ## Execution and lifecycle
95
+
96
+ Each host/destination execution is one text-mode eAPI request containing `enable` followed by the generated ping command. The reservation ID is used as the exact wire request ID. All eAPI responses, including read-tool responses, must use JSON-RPC 2.0, match the exact request ID, and contain exactly one `result` or `error` member. Missing/stale identity fails with `eapi_response_uncorrelated`; contradictory envelopes fail with `eapi_payload_invalid`. Neither is completion evidence, and an unrelated response is never exposed as the requested diagnostic's raw output. There are **no automatic ping retries**, including no JSON-to-text retry. Read-only auto fallback also requires a correlated error and uses a new ID for its subsequent request. Existing `eos_run_show` validation remains show-only.
97
+
98
+ On cEOS 4.34.3M, ping's `timeout` maps to a per-reply wait, not a whole-process deadline. The tool combines finite `repeat`, explicit one-second `interval`, and finite reply timeout; the conservative execution formula above must fit `commandTimeoutMs`. This budget is **not** a remote wall-clock kill timer. It assumes normal EOS command scheduling, not a stalled operating system or an indefinitely queued eAPI command. No remote process termination guarantee is made. The same budget is how long an uncertain slot is held (below).
99
+
100
+ One admission controller is shared by all diagnostic calls in a server process. A slot is reserved **before** the device runner can dispatch. Confirmed synchronous completion or a locally established rejection before dispatch frees it immediately. Unknown completion after transport loss, HTTP timeout, invalid eAPI payload (including correlated results with missing or non-text output), cancellation, or a post-dispatch RPC error becomes **uncertain**: the slot stays held for `commandTimeoutMs` from that moment, then frees itself. A late confirmed response frees it sooner. RPC interruption errors are not treated as proof of completion. Restarting the server forgets uncertain slots; under normal scheduling the original command has finished within `commandTimeoutMs` of dispatch anyway. If EOS ever queued a request for longer than that, replacement work could overlap it; [ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md) accepts that bounded risk for ping.
101
+
102
+ A request fans out into one execution per resolved host and destination. Executions run concurrently, up to `deviceConcurrency`, through the same admission controller as every other call; each takes and releases its own reservation exactly as a single ping does. Executions are scheduled host by host, in destination order. Queued executions wait for capacity inside the same overall deadline, so size multi-destination requests so that roughly `ceil(executions / available capacity)` successive waves of pings fit within `overallOperationTimeoutMs`; uncertain reservations reduce available capacity. The device can also limit overlap: on cEOS 4.34.3M, eAPI executed at most three commands concurrently per device, and further delivered requests waited on the device while holding their server reservations. A single-host request therefore gains at most that device's eAPI concurrency, even when more server capacity is free.
103
+
104
+ The overall deadline or MCP cancellation stops scheduling new executions, removes capacity waiters, and aborts in-flight HTTP work. Every in-flight execution of a cancelled multi-destination request becomes uncertain, so one cancellation can hold up to `deviceConcurrency` slots for up to `commandTimeoutMs`; queued executions of that request are never dispatched. An overall timeout is a request-level `operation_timeout`, not a partial-results response. Already completed findings are not returned on that path. Do not blindly retry an interrupted diagnostic.
105
+
106
+ ## Uncertain execution and capacity visibility
107
+
108
+ `eos_get_server_info` returns a fresh `diagnostics.capacity` snapshot for that server process with:
109
+
110
+ - `limit` and `available_count`.
111
+ - `active_count`, `uncertain_count`, and `queued_count`.
112
+ - `reservations`: ID (also the wire RPC ID), inventory hostname, diagnostic kind, state, reservation timestamp, and (when uncertain) uncertainty timestamp, `releases_at`, and machine-readable reason code. Destinations, endpoints, credentials, and original EOS output are not included.
113
+
114
+ Uncertain slots count against `deviceConcurrency` like active ones. New work queues behind them and is dispatched when a slot frees, within the overall deadline; if the deadline expires first the call fails with `operation_timeout`. If some capacity remains, diagnostics keep running without waiting for uncertain slots.
115
+
116
+ The standalone `print-server-info` CLI has no live executor and reports `diagnostics.capacity: null`, not fabricated available capacity. Inspect the running MCP server for actual reservation state.
117
+
118
+ ### Native EOS timeout qualification
119
+
120
+ The installed cEOS implementation exposes a `runCmds.requestTimeout` execution-stage interruption timer. It was accepted in a bounded read request, and a later loopback experiment observed an interruption response after 273 ms with a 250 ms native timer (RPC code 1001 / error 408). No ping process appeared in the subsequent samples. This single normal-load result does not qualify a dispatch-relative or process-exit deadline, nor establish the public support contract.
121
+
122
+ Ping requests now send `requestTimeout` equal to `commandTimeoutMs` (in seconds) by default, so EOS also stops a command that outlives its budget. Re-checked on the cEOS 4.34.3M lab: a 4-probe loopback ping with a 30 s timeout completed normally in about 3.1 s; with 0.25 s EOS returned RPC 1001 / error 408 after 264 ms and no ping process remained on the device. The timer starts only when EOS begins executing the command, so it does not cover time a request spends queued before that. A timeout-shaped RPC error is still treated as unknown completion and holds the slot like any other uncertain execution. Other EOS versions and physical platforms have not been checked; if one rejects the field, every ping fails with an RPC error, and `diagnostics.nativeRequestTimeout: false` turns it off. No durable token-bound status/cancel method was qualified. See [the primary-source investigation](research/eos-diagnostic-lifecycle.md) for evidence, citations, and limits.
123
+
124
+ ## Results
125
+
126
+ Results use the existing target metadata and envelope with one entry per host and destination, ordered by host and then by requested destination. `resolved_devices` lists hosts; `summary` counts host/destination executions, not devices. Each entry carries `inventory_hostname`, `resolved_endpoint`, `destination`, and `status`. `request` echoes normalized intent, including `destinations` and the effective count. Each successful entry's `finding` contains:
127
+
128
+ - `kind: "ping"`
129
+ - `reachability`: `reachable` (all replies), `partial` (some replies), or `unreachable` (no replies)
130
+ - `packets_sent`, `packets_received`, and calculated `packet_loss_percent`
131
+ - `rtt_ms: { min, avg, max }`, or null when unavailable
132
+
133
+ Execution `status: "success"` means ping executed and its output was understood. Zero replies are a successful execution with an `unreachable` finding, not a failed tool operation and not a diagnosis of the cause. Authentication/transport errors, invalid VRF/source, and malformed/inconsistent output are execution failures with machine-readable error codes, reported on that host/destination entry without failing the others. Unknown output never becomes an invented reachability or loss result.
134
+
135
+ ## Extension seam
136
+
137
+ `DiagnosticExecutor.execute` accepts typed diagnostic intent and owns authorization, aggregate budgets, fan-out, fail-closed global admission, cancellation, result envelopes, and response limits. `getCapacity()` exposes a detached snapshot; server-info obtains it at call time rather than caching startup state. `DiagnosticDeviceRunner.execute` accepts inventory identity and one prepared execution; the eAPI adapter owns connection resolution and a single command execution.
138
+
139
+ Each diagnostic kind supplies a `DiagnosticDefinition`: `prepare` validates the whole request before dispatch and returns the echoed request plus the executions every resolved host performs, `probeCount` prices one execution against the aggregate budget, and `interpret` turns observed output into a finding. The executor runs every prepared execution on every host and knows nothing kind-specific. Ping's definition, validation, compilation, and interpretation live in `src/diagnostics/ping.ts`; it prepares one execution per destination. Multiple destinations are therefore a property of ping's definition, not of the executor.
140
+
141
+ Admission queueing is separate from reservation state transitions; shutdown rejects queued work with `diagnostic_server_stopping`. The eAPI adapter separates connection validation from dispatch/error handling instead of maintaining a dispatch flag. `DiagnosticExecutionError` carries a typed `not_dispatched`, `completed`, or `unknown` classification; ordinary errors and malformed acknowledgements remain uncertain.
142
+
143
+ The request, observation, and finding types are discriminated by `kind`; only `ping` is implemented. Add other real operations at this seam without exposing arbitrary command strings. A destination-based operation such as traceroute can reuse multi-destination fan-out by preparing one execution per destination, although its much longer execution bound makes large requests hard to fit in the overall deadline. Packet capture needs a separate decision about transport, privilege, visibility, asynchronous lifetime, artifacts, and sensitive data; no capture-session abstractions are fabricated now.
144
+
145
+ ## Verification
146
+
147
+ Validated against the repository's cEOS 4.34.3M lab over HTTPS eAPI:
148
+
149
+ - IPv4/IPv6 loopback, explicit source and default VRF, and measured RTTs.
150
+ - Explicit one-second pacing, finite repeat count, and reply-timeout behavior.
151
+ - Group fan-out and 100% packet loss in the MGMT VRF.
152
+ - Multi-destination fan-out: six 10-packet pings from one host to its BGP peers completed in 18.6 s (about 56 s when issued as serial calls). A capacity sampler observed five simultaneous active reservations, and direct concurrent eAPI requests confirmed the device ran at most three commands at once.
153
+ - Invalid VRF and unavailable source fail without fallback.
154
+ - Overall HTTP cancellation can leave a short-lived device ping; observed processes exited after their finite probe sequence.
155
+ - MCP stdio execution loads configured defaults and returns real measurements.
156
+
157
+ Older EOS versions and physical platforms have not been qualified for this new tool; the existing read-path minimum does not establish ping compatibility.
158
+
159
+ ```bash
160
+ npm test
161
+ npm run typecheck
162
+ npm run lint
163
+ npm run build
164
+ INTEGRATION=1 npm test
165
+ ```
166
+
167
+ Integration tests use the isolated `clab/testlab.clab.yaml` lab (admin/admin and lab-only TLS override). The read-path suite now defaults to that topology's unprefixed node names; `EOS_MCP_TEST_PREFIX=clab-testlab-` supports older prefixed deployments. All 13 read-path checks have been run alongside the ping suites, including the EOS code-1003 unconverted-command JSON-to-text read fallback; active ping still never retries. Optional `EOS_PING_TEST_HOST`, `EOS_PING_TEST_HOST_2`, and `EOS_PING_TEST_SILENT_HOST` override its device/silent-destination addresses. Unit tests additionally cover partial loss, input injection, mapped-address restrictions, fail-closed policy, invalid settings, concurrent calls, multi-destination ordering, concurrent destination dispatch, per-destination failure isolation, whole-request destination validation, multi-destination cancellation, uncertain reservations and their timed release, early release on a late confirmed response, cancellation, and response-size enforcement. Additional protocol regressions cover missing/stale IDs, invalid versions, contradictory result/error envelopes, and no retry on an uncorrelated read error. Live MCP integration checks timeout-to-uncertain visibility and runs a second server against the same inventory while the first holds an uncertain slot.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Product boundary
4
4
 
5
- The server is a permanent read-only MCP service. Scope is limited to inventory inspection, device probing, validated EOS `show` commands, bounded logging, facts, and running-configuration retrieval.
5
+ The server is a permanent read-only MCP service. Scope includes inventory inspection, device probing, validated EOS `show` commands, bounded logging, facts, running-configuration retrieval, and explicitly authorized non-configuring diagnostics. Ping is implemented; traceroute and packet capture are future tools requiring separate capability/lifecycle work.
6
6
 
7
7
  ## Implemented work
8
8
 
@@ -11,7 +11,8 @@ The server is a permanent read-only MCP service. Scope is limited to inventory i
11
11
  - Strict inventory validation, inherited variables, EOS eligibility, and fail-closed read policy.
12
12
  - Inventory-authoritative host/group target resolution.
13
13
  - HTTPS eAPI client with TLS validation, custom CA support, credential validation, request cancellation, and response-size limits.
14
- - Seven MCP tools: server info, inventory, probe, show, logging, facts, and running configuration.
14
+ - Eight MCP tools: server info, inventory, probe, ping, show, logging, facts, and running configuration.
15
+ - Typed diagnostic execution with inherited opt-in, configurable budgets/timeouts, in-memory per-server admission, no retries, uncertain-execution reservations held for the command budget, and live sanitized capacity visibility. Machine-verified remote completion/deadline qualification remains follow-up work.
15
16
  - Local `validate-inventory`, `print-server-info`, and `probe` commands.
16
17
  - Bounded concurrency, target counts, command counts, logging messages, device responses, and aggregate tool responses.
17
18
  - Unit, MCP stdio smoke, package-policy, and optional cEOS integration coverage.
@@ -26,7 +27,7 @@ The server is a permanent read-only MCP service. Scope is limited to inventory i
26
27
 
27
28
  ## Completion criteria
28
29
 
29
- - Every registered MCP tool is read-only and inventory-scoped.
30
+ - Every registered MCP tool is non-configuring and inventory-scoped at the execution target; active diagnostics are explicitly authorized.
30
31
  - All device-facing paths use the shared target, connection, timeout, and result-envelope safeguards.
31
32
  - Documentation lists only supported tools and configuration fields.
32
33
  - `npm test`, `npm run typecheck`, and `npm run build` pass.
@@ -8,18 +8,16 @@ This guide gets you from the npm package to a working MCP server inside Claude C
8
8
  - Claude Code installed (`npm install -g @anthropic-ai/claude-code`)
9
9
  - One or more Arista EOS devices reachable via eAPI (HTTPS)
10
10
 
11
- ## 1. Install the package globally
11
+ ## 1. Check that the package runs
12
12
 
13
- ```bash
14
- npm install -g @aristanetworks/eos-mcp-server
15
- ```
16
-
17
- Verify it's on your PATH:
13
+ The server is published on npm as `@aristanetworks/eos-mcp-server`, and `npx` runs it without a separate install step:
18
14
 
19
15
  ```bash
20
- eos-mcp-server --help
16
+ npx -y @aristanetworks/eos-mcp-server --help
21
17
  ```
22
18
 
19
+ If you prefer a global install, run `npm install -g @aristanetworks/eos-mcp-server` and use `eos-mcp-server` in place of `npx -y @aristanetworks/eos-mcp-server` in the commands below.
20
+
23
21
  ## 2. Create an inventory file
24
22
 
25
23
  Create `inventory.yml` with your EOS devices. Replace the hostnames, IPs, and credentials to match your environment.
@@ -62,13 +60,13 @@ export EOS_MCP_PASSWORD='your-device-password'
62
60
  Validate the inventory:
63
61
 
64
62
  ```bash
65
- eos-mcp-server validate-inventory --inventory inventory.yml
63
+ npx -y @aristanetworks/eos-mcp-server validate-inventory --inventory inventory.yml
66
64
  ```
67
65
 
68
66
  Probe a device to confirm connectivity:
69
67
 
70
68
  ```bash
71
- eos-mcp-server probe --inventory inventory.yml --target leaf1
69
+ npx -y @aristanetworks/eos-mcp-server probe --inventory inventory.yml --target leaf1
72
70
  ```
73
71
 
74
72
  You should see:
@@ -82,7 +80,7 @@ Devices: 1/1 succeeded
82
80
  ## 5. Add the MCP server to Claude Code
83
81
 
84
82
  ```bash
85
- claude mcp add eos -- eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
83
+ claude mcp add eos -- npx -y @aristanetworks/eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
86
84
  ```
87
85
 
88
86
  Use absolute paths for the inventory file since Claude Code may launch the server from any working directory.
@@ -92,7 +90,7 @@ For additional options such as timeouts, request limits, or a default connection
92
90
  To make it available across all your projects, add `-s user`:
93
91
 
94
92
  ```bash
95
- claude mcp add -s user eos -- eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
93
+ claude mcp add -s user eos -- npx -y @aristanetworks/eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
96
94
  ```
97
95
 
98
96
  ### Passing the password to Claude Code
@@ -102,7 +100,7 @@ The simplest approach is to export `EOS_MCP_PASSWORD` in your shell profile (`~/
102
100
  Alternatively, add it inline when registering the server:
103
101
 
104
102
  ```bash
105
- claude mcp add -e EOS_MCP_PASSWORD=your-device-password eos -- eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
103
+ claude mcp add -e EOS_MCP_PASSWORD=your-device-password eos -- npx -y @aristanetworks/eos-mcp-server serve --inventory /absolute/path/to/inventory.yml
106
104
  ```
107
105
 
108
106
  ## 6. Verify in Claude Code
@@ -139,7 +137,9 @@ Once connected, Claude Code has access to these tools:
139
137
 
140
138
  ## Troubleshooting
141
139
 
142
- **`npm install -g` fails with permission errors** — Run with `sudo` or configure npm to use a user-writable prefix (`npm config set prefix ~/.npm-global`).
140
+ **`npm install -g` fails with permission errors** — Use `npx` as shown above, or configure npm to use a user-writable prefix (`npm config set prefix ~/.npm-global`).
141
+
142
+ **An old version runs after switching to npm** — Versions up to 0.6.1-beta were installed from a `.tgz` under the unscoped name `eos-mcp-server`. Remove that install with `npm uninstall -g eos-mcp-server`.
143
143
 
144
144
  **`Password env var ... is not set`** — Export `EOS_MCP_PASSWORD` before launching Claude Code, or pass it via `-e` when adding the server.
145
145
 
@@ -0,0 +1,5 @@
1
+ # Allow explicitly authorized, non-configuring diagnostics on EOS
2
+
3
+ Extend operational inspection with bounded diagnostics executed on inventory-selected EOS devices, starting with ping and leaving traceroute and packet capture for later design. Preserve the permanent prohibition on configuration changes, but do not equate non-configuring diagnostics with passive or harmless reads: probes generate traffic, while packet capture can expose sensitive data. Require explicit, inherited diagnostic authorization in inventory, denied by default and always vetoed by read denial; reject a whole group before execution if any member is unauthorized. Diagnostic destinations may be literal IP addresses outside inventory, because inventory identifies where operations execute rather than every address they may test.
4
+
5
+ Use eAPI for the initial ping implementation, without automatic SSH or shell fallback. Keep operation-specific inputs behind a shared typed diagnostic execution interface, not arbitrary command execution. Enforce both device-side bounds and server-wide execution limits; ending an HTTP request is not proof that a device command has stopped. This deliberately adds authorization and lifecycle controls rather than silently extending existing read permissions or relaxing the show-only tool. Future packet capture requires a separate privilege, transport, lifecycle, and data-handling decision.
@@ -0,0 +1,9 @@
1
+ # Retain uncertain diagnostic capacity instead of expiring it
2
+
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** Uncertain capacity now frees itself after the execution budget.
4
+
5
+ Persistence follow-up: [ADR 0003](0003-durable-diagnostic-ledger.md) extends this first slice with a durable ledger; the safety-over-availability decision below remains in force.
6
+
7
+ A finite probe count and reply timeout do not establish when an already-delivered eAPI request will start. Releasing a reservation after a guessed command/HTTP window can therefore admit replacement work while the original command can still execute. Prefer safety over availability: retain unknown-completion reservations without automatic expiry, releasing them only after confirmed completion or definitive rejection before execution. When all capacity is uncertain, reject new and queued diagnostics with `diagnostic_capacity_uncertain`; expose sanitized live reservation metadata through server info rather than suggesting automatic retries or a larger cooldown.
8
+
9
+ This first implementation is scoped to one running server instance. It adds no SSH/shell fallback, remote kill timer, operator reset endpoint, or persistent journal. Restarting loses reservations and is not safe reconciliation. Durable recovery and qualification of any native EOS execution lifecycle/deadline require separate work; this change closes the timer-based capacity-reuse gap, not those remaining guarantees.
@@ -0,0 +1,11 @@
1
+ # Preserve diagnostic reservations across restart in a private SQLite ledger
2
+
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** The ledger and its ownership lock were removed.
4
+
5
+ Operator recovery follow-up: [ADR 0004](0004-offline-operator-attested-release.md) adds offline attested release and schema-2 audit; the persistence guarantees below remain in force.
6
+
7
+ In-memory fail-closed reservations are insufficient when an MCP process restarts while EOS work may still run or start later. Production diagnostic execution therefore uses Node's built-in SQLite on private persistent local storage: commit a reservation before dispatch, commit confirmed removal before capacity reuse, and restore unfinished records as uncertain. A separate SQLite ownership database holds an exclusive OS-managed lock for the server lifetime, so process crashes release ownership without guessing from PID or lock age. Prefer one active server per ledger over independent processes silently allocating fresh capacity. Existing state is validated and bound to the canonical inventory identity; corruption, missing companion files, incompatible schemas, and write failures block work rather than recreate empty capacity.
8
+
9
+ The default path is inventory-keyed beneath the user's XDG state directory; `diagnostics.stateFile` permits an explicit stable location. State files must stay together on a filesystem honoring SQLite locking/sync. Changing inventory identity, relocating/deleting all state, ephemeral volumes, and old in-memory executions are not automatic migrations or recovery mechanisms. Credentials, destinations, and EOS output are not journaled. `inspect-diagnostics` provides offline read-only inspection without device calls or ownership takeover, but no reset/release command is introduced.
10
+
11
+ This extends the persistence limitation in ADR 0002, not its requirement for positive completion evidence. Installed EOS exposes an execution-stage requestTimeout, but no dispatch-relative/process-exit bound or durable status token was qualified; see [the investigation](../research/eos-diagnostic-lifecycle.md). Post-dispatch RPC interruption errors remain uncertain. Native timer enablement, operator attestation, and out-of-band fencing/reconciliation require explicit qualification rather than inferring completion from silence.
@@ -0,0 +1,7 @@
1
+ # Allow offline operator-attested release with atomic audit
2
+
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** `release-diagnostics` and `inspect-diagnostics` were removed.
4
+
5
+ EOS completion remains unprovable from silence or an interruption error, but permanently retaining uncertain capacity leaves no operational recovery path. The user explicitly authorized offline operator authority grounded in independently verified fencing of running, accepted queued and replayable work. Permit only exact reservation IDs, matching immutable execution identities, a private bounded attestation file and explicit confirmation, under exclusive ledger ownership. This is not automatic proof of EOS completion, a fencing action, a wildcard clear, or an MCP tool.
6
+
7
+ Commit original reservation snapshots, the attestation, content checksums, recording time and actual OS account in the same FULL-synchronous SQLite transaction as every selected deletion. Keep the claimed verifier distinct from OS identity; neither a field label nor an explicit flag proves human presence. The trusted filesystem owner/OS access policy is the authority, and hashes detect accidental inconsistency rather than malicious owner forgery. Exact committed retries return the prior event without further mutation; changed reuse is rejected. Upgrade schema 1 transactionally to schema 2 so older binaries fail closed rather than ignore operator audit. Read-only inspection does not migrate state. False human evidence can violate remote concurrency safety; the program cannot certify external fencing truth.
@@ -0,0 +1,9 @@
1
+ # Accept multiple destinations in one ping request
2
+
3
+ Testing several destinations from the same target, such as every BGP peer of a switch, required one `eos_ping` call per destination. MCP clients do not reliably run those calls concurrently: measured on 2026-10-28, Claude Code dispatched six pings issued in one turn strictly one at a time, and Codex runs MCP calls serially unless the server connection opts into parallel calls. Labelling ping read-only to change client scheduling would misstate an active diagnostic that generates traffic and writes durable reservations. Instead, `eos_ping` takes `destinations` (one or more unique IP literals) and runs one execution per resolved host and destination concurrently inside the server, so every client gets the same behavior from one call.
4
+
5
+ Make this a breaking change rather than adding a second tool or an optional alias: one interface serves a single ping (a one-element array) and a sweep, and responses always contain one result per host and destination, each naming its destination. The removed `destination` field is rejected with guidance instead of being silently accepted. `summary` counts host/destination executions; `resolved_devices` still lists hosts.
6
+
7
+ Keep the safety model of ADRs 0001 to 0004 unchanged ([ADR 0006](0006-in-memory-timed-diagnostic-capacity.md) later replaced the reservation model of 0002 to 0004). Every execution is admitted, journaled, released, or retained as uncertain exactly like a single ping, under the existing shared `deviceConcurrency`. The whole request is validated before any dispatch, the aggregate probe budget counts hosts × destinations × count, and `maxDestinationsPerRequest` bounds request size. The cost is that one cancelled or timed-out request can now leave up to `deviceConcurrency` uncertain reservations from a single host target, as group targets already could. Large requests must fit the overall deadline in successive waves of available capacity. No up-front feasibility rejection or per-request concurrency cap is added: a conservative bound would reject deliberately short deadlines that existing integration tests and operators use, and a per-request cap would diverge from existing group fan-out.
8
+
9
+ Fan-out across destinations belongs to ping's `DiagnosticDefinition`, which prepares one execution per destination. The executor only runs prepared executions on resolved hosts, so a future destination-based diagnostic can reuse the pattern, and diagnostics without destinations such as packet capture are not forced into it.
@@ -0,0 +1,21 @@
1
+ # Replace the durable diagnostic ledger with in-memory, time-bounded capacity
2
+
3
+ Supersedes [ADR 0002](0002-fail-closed-diagnostic-reservations.md), [ADR 0003](0003-durable-diagnostic-ledger.md) and [ADR 0004](0004-offline-operator-attested-release.md). [ADR 0001](0001-non-configuring-diagnostics.md) and [ADR 0005](0005-multi-destination-ping.md) stay in force.
4
+
5
+ ADRs 0002 to 0004 held an execution's slot forever when its completion was unknown, because a delivered eAPI request might in principle sit in an EOS queue and start later. Holding slots forever then required a durable SQLite ledger so restarts could not reset them, an exclusive ownership lock so a second process could not allocate fresh capacity, and an offline operator attestation and audit workflow to recover stuck slots. The exclusive lock was taken at server start whenever any host allowed diagnostics, so only one MCP server per inventory could run. A second chat session, or the desktop app alongside a Code session, failed to start at all, losing every read-only tool as well as `eos_ping`.
6
+
7
+ That machinery guarded a failure mode the project never observed: the lifecycle investigation reports that no delayed-queue fault was reproduced. Being wrong about it costs little. A ping execution is capped at `maxPingCount` 64-byte probes at one-second pacing, changes no configuration, and cEOS ran at most three eAPI commands concurrently per device regardless of server capacity. Against that, the design cost every user concurrent sessions, could permanently remove capacity after one timeout, and required a human attestation to recover.
8
+
9
+ Capacity is now per server process and in memory. A confirmed completion or a rejection established before dispatch frees its slot at once. An execution whose completion is unknown (transport loss, HTTP timeout, malformed or uncorrelated response, cancellation, or a post-dispatch RPC error) keeps its slot as **uncertain** for `commandTimeoutMs`, the configured worst-case execution budget, measured from the moment it became uncertain. The slot then frees itself; a late confirmed response frees it sooner. Uncertain slots count against `deviceConcurrency` like active ones, so new work queues behind them within the overall deadline instead of failing with a dedicated error.
10
+
11
+ Accepted consequences:
12
+
13
+ - `deviceConcurrency` limits one server process, not an inventory. N concurrent sessions can run up to N × `deviceConcurrency` pings. The per-request limits (`maxPingCount`, `maxProbesPerRequest`, `maxDestinationsPerRequest`) and the device's own eAPI concurrency still bound load.
14
+ - A restart forgets uncertain slots. Under normal EOS scheduling the original command has finished within `commandTimeoutMs` of dispatch anyway.
15
+ - If EOS did queue a request for longer than the hold, replacement work could overlap it. This is the risk ADR 0002 refused; it is now accepted as bounded and low-impact for ping.
16
+
17
+ Removed: the SQLite ledger and its ownership lock, `diagnostics.stateFile`, the `inspect-diagnostics` and `release-diagnostics` commands, fencing attestations and release audit, the `diagnostic_capacity_uncertain` and `diagnostic_state_*` error codes, and the `scope`, `storage_error_code` and `rpc_request_id` fields of the capacity snapshot (`reservation_id` is the wire RPC ID). Existing ledger files under `$XDG_STATE_HOME/eos-mcp-server/` are no longer read and can be deleted. JSON-RPC response correlation, write-ahead classification of dispatch state, and the no-automatic-retry rule are unchanged.
18
+
19
+ Ping requests also send EOS's native `runCmds.requestTimeout`, set to `commandTimeoutMs`, so the device interrupts a command that outlives the same budget the server holds an uncertain slot for. It is on by default and can be disabled with `diagnostics.nativeRequestTimeout: false` for an EOS version that rejects the field. It covers execution only, not time queued before execution, and its timeout error is still treated as unknown completion.
20
+
21
+ Revisit this if a diagnostic with real side effects or long remote lifetimes is added, such as packet capture. That operation should get its own lifecycle decision rather than inheriting ping's.
@@ -0,0 +1,86 @@
1
+ # EOS HTTPS eAPI diagnostic lifecycle
2
+
3
+ > Historical investigation. [ADR 0006](../adr/0006-in-memory-timed-diagnostic-capacity.md) later chose to hold uncertain capacity for the command budget instead of indefinitely, accepting the unreproduced delayed-queue risk described here.
4
+
5
+ ## Conclusion
6
+
7
+ **No dispatch-relative deadline, durable execution-token lifecycle, or definitive cancellation guarantee was qualified for ping.** However, EOS **does expose a native `runCmds.requestTimeout`**: saying eAPI has only a client-side timeout would be incorrect. Its installed implementation is an execution-stage interruption timer, not a verified admission-to-process-exit bound. Losing HTTP completion evidence must therefore remain an uncertain reservation, not become free capacity after a cooldown.
8
+
9
+ Scope: installed cEOS lab image `ceos-lab:4.34.3M`, inspected on `node1-1`; HTTPS checks against `172.20.20.11`, 2026-10-27 UTC. Repository context: `docs/DIAGNOSTICS.md` and ADRs `0001`/`0002`. Those documents describe requirements/current behavior, not independent proof of EOS capabilities.
10
+
11
+ ## Evidence and qualification
12
+
13
+ ### Measured in this investigation
14
+
15
+ - HTTPS `runCmds`, version 1, text format, commands `enable` and `show version`, with `requestTimeout: 5.0`, `ignoreDisconnect: false`, and `timestamps: true`, returned HTTP 200 and matching JSON-RPC ID in approximately **0.023 s**. EOS reported **4.34.3M-44102239.4343M (engineering build)**. This proves these parameters are accepted on this device, **not** that timeout expiry terminates ping.
16
+ - HTTPS `getCommandHelp` and enable-mode `getCommandCompletions` inspected ping syntax without executing ping. Completion after `ping 127.0.0.1 ` offered `repeat`, `timeout`, `interval`, size/source/interface/pattern/DF/record-route options; timeout accepted `<0-3600>` seconds. A `deadline` suffix produced no completions. This agrees with the installed grammar, but is not an exhaustive test of every possible CLI wrapper.
17
+ - **The initial source investigation emitted zero ping probes**; it performed no connection-loss experiment, forced timeout, process termination, configuration change, recovery/reset, SSH, capture, or load test. A later bounded expiry experiment is recorded separately below. HTTP calls had a 10-second client timeout and bounded response reads. Lab certificate verification was disabled only for these research calls; no credentials or configuration are included here.
18
+
19
+ ### Bounded native timeout-expiry follow-up
20
+
21
+ A later normal-load HTTPS experiment used text-mode `enable` plus `ping ip 127.0.0.1 repeat 4 timeout 1 interval 1 size 64`, with `requestTimeout: 0.25`, `ignoreDisconnect: false`, `timestamps: true`, version `latest`, and an eight-second client limit. The command could emit at most four loopback probes; no configuration change, SSH, capture, overload, recovery/reset, or external process kill was performed.
22
+
23
+ - The matching JSON-RPC response arrived after **273 ms**, HTTP 200, with error code **1001**, message ending in `unexpected error 408`, and an error entry `Command exceeded its allowed time limit`.
24
+ - No exact-name `ping` process was observed in samples at **274, 409, 940, and 4492 ms** after dispatch.
25
+ - This establishes native interruption behavior in one normal-load case on the installed engineering build. It **does not** establish a bound on admission delay, cleanup/reaping under faults, queued-request invalidation, disconnect propagation, other versions, or the public support contract.
26
+ - Sampled process absence and an interruption response are not durable fencing evidence. The product still does not enable this internal option or release uncertain reservations on native timeout errors. A regression fixture now covers the observed 1001/408 error shape.
27
+
28
+ Local raw observations and the bounded reproducer are in `.local-artifacts/diagnostics-native-timeout/observations.json` and `experiment.mjs`; credentials are not recorded in the evidence JSON. These are measured follow-up artifacts, not a substitute for a supported lifecycle guarantee.
29
+
30
+ ### Source-backed capabilities
31
+
32
+ Installed paths below are **inside `node1-1`**, under `/usr/lib/python3.9/site-packages/` unless otherwise stated. Findings paraphrase implementation rather than reproduce proprietary source.
33
+
34
+ | Evidence | What it establishes |
35
+ | --- | --- |
36
+ | `JsonCli.py:345-389`, especially `350`, `364-373`; forwarding at `137-162` | `runCmds` accepts `requestTimeout` in seconds, described as an internal CAPI/eAPI request-termination mechanism distinct from socket timeout. `ignoreDisconnect` defaults false; its description distinguishes operational commands from configuration-command continuation. These are real exposed parameters, not product inventions. |
37
+ | `CliApi.py:479-526` | The timer is created **after session update**, when the backend reaches `executeCommands`, before executing the command list. It is not an absolute timestamp carried from the client, nor a timer armed at client dispatch/HTTP admission. |
38
+ | `CliApi.py:105-143`, `411-477` | Expiry signals tracked child process groups with SIGINT where applicable and interrupts the command thread. The command loop handles interruption and terminates its session. This is a native attempt to interrupt the request, not merely abandonment of the HTTP response. |
39
+ | `CliServerLib.py:545-558`, `729-743`, `850-865`, `1023-1050` | EOS tracks child PIDs, attempts group signalling, and has disconnect/final-cleanup paths. SIGINT can skip marked children; signalling errors are caught. These paths do not supply an externally queryable process-reaped acknowledgement or a bounded signal-to-exit contract. |
40
+ | `CapiUwsgiServer.py:224-281`; `EapiClientLib.py:206-220`; `CliPatchSigint.py:186-208` | HTTPS command handling creates a stateless backend connection and closes it during cleanup; backend disconnect can cause interruption/child cleanup. This does **not** prove when an external HTTP loss is detected or propagated through the HTTP server and backend, or what the client can verify after losing the response. |
41
+ | `CliPlugin/NetworkToolsCli.py:208-257`, `287-373`, `388-411` | Enable-mode IPv4/IPv6 ping has finite-count/pacing/reply-wait options. EOS maps `repeat` to `-c`, `timeout` to **`-W`**, and `interval` to `-i`; the inspected handler supplies no `-w` whole-ping deadline. It waits using `communicate()` on the child. No ping deadline/job/status/cancel option occurs in this grammar. |
42
+ | `JsonCli.py:180-211`, `215-216`, `284-285`, `344-354`; `CliServerLib.py:593-620`; `JsonRpcBase.py:119-123`, `260-304`, `393-395` | The inspected backend exports completion/help/prompt, configuration operations, and `runCmds`; it exports no execution-token status/cancel method. JSON-RPC `id` is client-supplied, echoed, and cleared from request-local state. It is correlation, not demonstrated durable identity, deduplication, or a retrievable execution record. Configuration sessions are not diagnostic jobs. |
43
+ | `CliApi.py:495-497`, `596-626` | `timestamps` supplies command execution timing in returned results, not a lookup handle or deadline. |
44
+
45
+ The installed API explorer independently describes ordered command execution and an echoed request ID: `/usr/share/nginx/html/eapi/main.js:2`, protocol prose near character offsets 1,340,000–1,369,000. Its request builder on the same line, near offset 1,191,844, exposes ordinary execution options but omits `requestTimeout`/`ignoreDisconnect`. **Installed acceptance/internal documentation is established; a public support contract for these internal controls is not.**
46
+
47
+ Official public client sources corroborate the transport distinction:
48
+
49
+ - Arista [goeapi, pinned revision, request builder](https://github.com/aristanetworks/goeapi/blob/7090068b8735dc15c22444cbda080db4052ae8af/eapilib.go#L183-L190) sends `runCmds`; its [timeout implementation](https://github.com/aristanetworks/goeapi/blob/7090068b8735dc15c22444cbda080db4052ae8af/eapilib.go#L455-L461) sets an HTTP-client timeout, not a remote deadline.
50
+ - Arista EOS+ [pyeapi, pinned revision](https://github.com/arista-eosplus/pyeapi/blob/6816d9e498f06d773fd97121b805dec198c9f6e2/pyeapi/eapilib.py#L314-L364) builds the echoed client ID and filters allowed request parameters; this revision's allowlist does not forward `requestTimeout`/`ignoreDisconnect`. Its [HTTPS connection](https://github.com/arista-eosplus/pyeapi/blob/6816d9e498f06d773fd97121b805dec198c9f6e2/pyeapi/eapilib.py#L669-L686) passes timeout to the transport. Client-library omissions do not establish server incapability.
51
+
52
+ ## Answers
53
+
54
+ ### 1. Supported dispatch-relative or whole-process deadline without shell execution?
55
+
56
+ **Native execution-stage timer: yes. Qualified dispatch-relative/whole-process deadline: no.** `requestTimeout` can accompany the normal text-mode `enable` + ping request without a shell command. It applies to the backend command list, not to each ping reply. But backend/session/admission delay before timer creation is outside its measured interval; expiry uses asynchronous interruption, not a verified process-exit deadline. A stalled scheduler is also outside any demonstrated bound.
57
+
58
+ The supported ping grammar's `timeout` remains a reply wait. Finite repeat/interval/reply settings constrain normal work, not start time. Treat any count-based duration formula as an **assumption-dependent execution estimate**, not permission to release capacity at dispatch time plus that estimate. Hard-deadline behavior and public support for the internal request timer remain unqualified. The follow-up experiment above observes expiry/interruption under normal load, not an admission-to-process-exit guarantee.
59
+
60
+ ### 2. Lost-request lookup or cancellation using a durable token?
61
+
62
+ **Not established.** The inspected HTTPS dispatcher has no such exported method, and its JSON-RPC ID is not an execution token. `ignoreDisconnect: false` enables EOS interruption behavior; it is neither an explicit cancellation RPC nor an acknowledgement that pending work cannot later execute. Reusing an ID is not demonstrated idempotency and must not justify retrying a ping.
63
+
64
+ This is a scoped finding about inspected code/protocol, **not proof that every EOS release, extension, or separate management service lacks lifecycle facilities**.
65
+
66
+ ### 3. Positive evidence that can safely authorize capacity release?
67
+
68
+ For the exact reserved attempt, with trustworthy request/device correlation:
69
+
70
+ - **Normal final synchronous completion:** a complete EOS result after the ping handler's child wait returned. Reachability success is unnecessary; a normally completed zero-reply ping also qualifies.
71
+ - **Definitive rejection before diagnostic execution:** evidence from a known EOS processing path that the diagnostic was not dispatched and cannot remain queued. Generic HTTP/proxy errors, malformed payloads, or arbitrary JSON-RPC error labels are not enough.
72
+ - **Separately verified terminal/fencing evidence:** evidence that both executing work and any previously accepted/queued work for that attempt can no longer run. No native token-bound acknowledgement meeting this condition was qualified here.
73
+
74
+ Caution: `CliApi.py:387-390, 415-430, 459-477` initializes an interrupted command's response as request-timeout status and returns on `KeyboardInterrupt`. A timeout-shaped error is therefore **not by itself a process-reaped certificate**. Qualify the specific terminal/error path before interpreting it as completion.
75
+
76
+ Elapsed time, local abort, an empty process list, packet-counter silence, or availability of another HTTP request are not positive evidence against later dispatch. No delayed-queue fault was reproduced; its exclusion is simply not guaranteed by the evidence available.
77
+
78
+ ### 4. Purely observational automatic reconciliation?
79
+
80
+ **Not defensible from process snapshots/time alone.** Repeated absence of a running ping cannot rule out a request not yet started. A source-understood late final response could automatically reconcile its own reservation; that is positive completion evidence, not inference from silence.
81
+
82
+ Without such evidence or a qualified durable native lifecycle, recovery requires **operator attestation grounded in out-of-band evidence** covering the original device/execution epoch, running work, pending accepted requests, and prevention of later dispatch/replay. An operator merely waiting or observing no process is insufficient. A persistent local journal preserves uncertainty and correlation across restart; it cannot manufacture remote completion proof. New product reservations persist the exact `rpc_request_id` before dispatch and accept only a JSON-RPC 2.0 response matching that ID, with exactly one result/error member. Legacy journal entries without a wire ID remain unknown; no ID is invented for them. These are product correlation checks, not an EOS status-token facility. This research defines no recovery mechanism or new product tool.
83
+
84
+ ## Limits
85
+
86
+ The official [EOS Command API manual URL](https://www.arista.com/en/um-eos/eos-command-api) returned a “Client Challenge” page rather than manual content. That access failure is **not evidence of feature absence**. Public support guarantees, other releases/platforms, undisclosed/native lifecycle interfaces, expiry behavior under faults, and bounded HTTP-disconnect propagation remain unverified. The installed engineering-build findings should be confirmed with Arista before relying on them as a production lifecycle contract.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aristanetworks/eos-mcp-server",
3
- "version": "0.6.1-beta",
3
+ "version": "0.8.1-beta",
4
4
  "description": "MCP server for Arista EOS eAPI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,2 +0,0 @@
1
- import type { InventoryVars, NormalizedInventory, ValidationContext } from "./internalTypes.js";
2
- export declare function validateWritePolicyContradictions(hostName: string, globalVars: InventoryVars, lineageGroups: Set<string>, hostVars: InventoryVars, inventory: NormalizedInventory, groupDepths: Map<string, number>, context: ValidationContext): void;
@@ -1,36 +0,0 @@
1
- import { readBoolean } from "../utils/value.js";
2
- export function validateWritePolicyContradictions(hostName, globalVars, lineageGroups, hostVars, inventory, groupDepths, context) {
3
- const falseDepths = [];
4
- const globalWrite = readBoolean(globalVars.mcp_write_allowed);
5
- if (globalWrite === false) {
6
- falseDepths.push(0);
7
- }
8
- for (const groupName of lineageGroups) {
9
- const groupWrite = readBoolean(inventory.groups.get(groupName)?.vars.mcp_write_allowed);
10
- if (groupWrite === false) {
11
- falseDepths.push(groupDepths.get(groupName) ?? 1);
12
- }
13
- }
14
- if (falseDepths.length === 0) {
15
- return;
16
- }
17
- if (readBoolean(hostVars.mcp_write_allowed) === true) {
18
- context.errors.push({
19
- code: "inventory_write_policy_contradiction",
20
- message: `Host ${hostName} sets mcp_write_allowed=true beneath an inherited false`,
21
- path: hostName
22
- });
23
- }
24
- for (const groupName of lineageGroups) {
25
- const groupWrite = readBoolean(inventory.groups.get(groupName)?.vars.mcp_write_allowed);
26
- const depth = groupDepths.get(groupName) ?? 1;
27
- if (groupWrite === true && falseDepths.some((falseDepth) => falseDepth < depth)) {
28
- context.errors.push({
29
- code: "inventory_write_policy_contradiction",
30
- message: `Host ${hostName} inherits mcp_write_allowed=true in group ${groupName} beneath an inherited false`,
31
- path: hostName
32
- });
33
- }
34
- }
35
- }
36
- //# sourceMappingURL=writePolicy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"writePolicy.js","sourceRoot":"","sources":["../../src/inventory/writePolicy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,UAAU,iCAAiC,CAC/C,QAAgB,EAChB,UAAyB,EACzB,aAA0B,EAC1B,QAAuB,EACvB,SAA8B,EAC9B,WAAgC,EAChC,OAA0B;IAE1B,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC9D,IAAI,WAAW,KAAK,KAAK,EAAE,CAAC;QAC1B,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,aAAa,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACxF,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACzB,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,IAAI,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,KAAK,IAAI,EAAE,CAAC;QACrD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YAClB,IAAI,EAAE,sCAAsC;YAC5C,OAAO,EAAE,QAAQ,QAAQ,yDAAyD;YAClF,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,aAAa,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACxF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE9C,IAAI,UAAU,KAAK,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,KAAK,CAAC,EAAE,CAAC;YAChF,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,sCAAsC;gBAC5C,OAAO,EAAE,QAAQ,QAAQ,6CAA6C,SAAS,6BAA6B;gBAC5G,IAAI,EAAE,QAAQ;aACf,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC"}