@aristanetworks/eos-mcp-server 0.8.0-beta → 0.9.0-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 (88) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +19 -42
  3. package/dist/cli.d.ts +1 -4
  4. package/dist/cli.js +4 -63
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/probe.js +2 -1
  7. package/dist/commands/probe.js.map +1 -1
  8. package/dist/config/diagnostics.d.ts +15 -5
  9. package/dist/config/diagnostics.js +8 -3
  10. package/dist/config/diagnostics.js.map +1 -1
  11. package/dist/config/loadConfig.js +2 -5
  12. package/dist/config/loadConfig.js.map +1 -1
  13. package/dist/config/schema.d.ts +123 -15
  14. package/dist/config/schema.js +14 -1
  15. package/dist/config/schema.js.map +1 -1
  16. package/dist/connection/eosDeviceReader.d.ts +6 -1
  17. package/dist/connection/eosDeviceReader.js +14 -3
  18. package/dist/connection/eosDeviceReader.js.map +1 -1
  19. package/dist/diagnostics/eapiRunner.js +7 -1
  20. package/dist/diagnostics/eapiRunner.js.map +1 -1
  21. package/dist/diagnostics/executor.d.ts +7 -4
  22. package/dist/diagnostics/executor.js +16 -14
  23. package/dist/diagnostics/executor.js.map +1 -1
  24. package/dist/diagnostics/types.d.ts +22 -6
  25. package/dist/eapi/client.js +5 -4
  26. package/dist/eapi/client.js.map +1 -1
  27. package/dist/eapi/types.d.ts +3 -0
  28. package/dist/eapi/types.js.map +1 -1
  29. package/dist/index.js +0 -10
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/createServer.js +21 -21
  32. package/dist/mcp/createServer.js.map +1 -1
  33. package/dist/mcp/tools/ping.d.ts +2 -9
  34. package/dist/mcp/tools/ping.js +13 -26
  35. package/dist/mcp/tools/ping.js.map +1 -1
  36. package/dist/operations/eapiSlots.d.ts +68 -0
  37. package/dist/operations/eapiSlots.js +237 -0
  38. package/dist/operations/eapiSlots.js.map +1 -0
  39. package/dist/operations/readExecution.js +1 -1
  40. package/dist/operations/readExecution.js.map +1 -1
  41. package/dist/serverInfo/buildServerInfo.js +4 -1
  42. package/dist/serverInfo/buildServerInfo.js.map +1 -1
  43. package/dist/serverInfo/types.d.ts +3 -0
  44. package/docs/CHECKPOINT.md +3 -6
  45. package/docs/CODE_WALKTHROUGH.md +6 -11
  46. package/docs/DEMO_SKILLS_YOUTUBE.md +346 -0
  47. package/docs/DESIGN.md +4 -4
  48. package/docs/DIAGNOSTICS.md +26 -74
  49. package/docs/IMPLEMENTATION_PLAN.md +2 -2
  50. package/docs/adr/0002-fail-closed-diagnostic-reservations.md +2 -0
  51. package/docs/adr/0003-durable-diagnostic-ledger.md +2 -0
  52. package/docs/adr/0004-offline-operator-attested-release.md +2 -0
  53. package/docs/adr/0005-multi-destination-ping.md +5 -1
  54. package/docs/adr/0006-in-memory-timed-diagnostic-capacity.md +23 -0
  55. package/docs/adr/0007-per-device-diagnostic-concurrency.md +16 -0
  56. package/docs/adr/0008-read-only-tool-annotations.md +16 -0
  57. package/docs/adr/0009-shared-per-device-eapi-slots.md +40 -0
  58. package/docs/research/eos-diagnostic-lifecycle.md +2 -0
  59. package/docs/research/eos-linter-integration.md +70 -0
  60. package/package.json +1 -1
  61. package/dist/commands/inspectDiagnostics.d.ts +0 -3
  62. package/dist/commands/inspectDiagnostics.js +0 -35
  63. package/dist/commands/inspectDiagnostics.js.map +0 -1
  64. package/dist/commands/releaseDiagnostics.d.ts +0 -9
  65. package/dist/commands/releaseDiagnostics.js +0 -87
  66. package/dist/commands/releaseDiagnostics.js.map +0 -1
  67. package/dist/diagnostics/concurrency.d.ts +0 -33
  68. package/dist/diagnostics/concurrency.js +0 -157
  69. package/dist/diagnostics/concurrency.js.map +0 -1
  70. package/dist/diagnostics/journal.d.ts +0 -14
  71. package/dist/diagnostics/journal.js +0 -317
  72. package/dist/diagnostics/journal.js.map +0 -1
  73. package/dist/diagnostics/journalTypes.d.ts +0 -8
  74. package/dist/diagnostics/journalTypes.js +0 -2
  75. package/dist/diagnostics/journalTypes.js.map +0 -1
  76. package/dist/diagnostics/releaseEvidence.d.ts +0 -502
  77. package/dist/diagnostics/releaseEvidence.js +0 -115
  78. package/dist/diagnostics/releaseEvidence.js.map +0 -1
  79. package/dist/diagnostics/reservationSchema.d.ts +0 -49
  80. package/dist/diagnostics/reservationSchema.js +0 -18
  81. package/dist/diagnostics/reservationSchema.js.map +0 -1
  82. package/dist/diagnostics/statePath.d.ts +0 -4
  83. package/dist/diagnostics/statePath.js +0 -25
  84. package/dist/diagnostics/statePath.js.map +0 -1
  85. package/dist/utils/privateFile.d.ts +0 -3
  86. package/dist/utils/privateFile.js +0 -7
  87. package/dist/utils/privateFile.js.map +0 -1
  88. package/docs/examples/diagnostic-release-evidence.template.json +0 -26
@@ -0,0 +1,346 @@
1
+ # Five-minute demo: EOS MCP Server + Skills
2
+
3
+ **Suggested YouTube title:** Arista EOS + AI Skills: Get Started with Claude Code and Codex
4
+
5
+ **Audience:** Network engineers who are new to AI skills. No MCP or skills experience assumed; basic familiarity with switches and BGP helps.
6
+
7
+ **Format:** Screen recording with voiceover. Show installation for both clients, then run BGP health in Claude Code and interface health in Codex. Aim for five minutes after trimming downloads and tool waits. The server connection is prepared before recording; the complete setup is included below for the video description or companion notes.
8
+
9
+ **Recording environment:** Use `example-codex/eos-mcp-server-config.yml` for both clients. It selects `clab/ansible-inventory.yml` for the containerlab topology in `clab/testlab.clab.yaml`: eight cEOS nodes, with two spines and six leaves. This demonstration focuses on BGP and virtual link state; CRC errors and physical optics diagnostics are not demonstration goals.
10
+
11
+ Only text marked **Say** is narration. **Screen** and **Presenter notes** are production directions. Results must come from the recording lab; the script does not assume a particular fault or invent output.
12
+
13
+ ## Recording script
14
+
15
+ ### 0:00–0:20 — Hook
16
+
17
+ **Screen:** Start with a readable BGP report from a rehearsed lab run, then show the two client terminals labeled “Claude Code” and “Codex.”
18
+
19
+ **Say:**
20
+
21
+ > What if you could ask your AI assistant to check BGP or verify your lab links, using a runbook you can actually read?
22
+ >
23
+ > In this video, we'll install skills for the EOS MCP server in both Claude Code and Codex, then try two everyday network checks.
24
+
25
+ ### 0:20–0:55 — What is a skill?
26
+
27
+ **Screen:** Open `skills/check-bgp-health/SKILL.md`. Highlight its name, description, commands, and report format. Use this simple overlay:
28
+
29
+ ```text
30
+ You ask a question
31
+ ↓
32
+ AI assistant follows a skill (the runbook)
33
+ ↓
34
+ EOS MCP server provides tools
35
+ ↓
36
+ Arista EOS devices return live data through eAPI
37
+ ```
38
+
39
+ **Say:**
40
+
41
+ > A skill is a reusable set of instructions for an AI assistant. Think of it as a runbook: what to check, which commands to use, what to investigate next, and how to report the findings.
42
+ >
43
+ > The MCP server provides access to device data. The skill provides the procedure for using that data.
44
+ >
45
+ > This BGP skill is a Markdown file. You can inspect its checks and reporting instructions before using it. These EOS workflows are read-only.
46
+
47
+ ### 0:55–1:15 — Introduce the lab
48
+
49
+ **Screen:** Show `example-codex/eos-mcp-server-config.yml`, then the `arista_ceos` group in `clab/ansible-inventory.yml` and the topology in `clab/testlab.clab.yaml`. Briefly show `eos` connected in each client's `/mcp` view. Prepare those views in advance.
50
+
51
+ **Say:**
52
+
53
+ > Both clients use the example-codex server config, pointing to our containerlab inventory. We have eight cEOS switches: two spines and six leaves, in the “arista_ceos” group.
54
+ >
55
+ > These are containers, so we'll check BGP and virtual links. The inventory defines our targets.
56
+
57
+ ### 1:15–2:05 — Install skills in both clients
58
+
59
+ **Screen:** Show the clone step as a command card for viewers, then run the remaining commands from the existing recording checkout at `/home/fred/aristanetworks/eos-mcp-server`. The local containerlab is already prepared there.
60
+
61
+ ```bash
62
+ git clone https://github.com/aristanetworks/eos-mcp-server.git
63
+ cd eos-mcp-server
64
+
65
+ mkdir -p .claude/skills .agents/skills
66
+ ```
67
+
68
+ ```bash
69
+ # Claude Code
70
+ ln -s "$PWD/skills/check-bgp-health" .claude/skills/
71
+ ln -s "$PWD/skills/check-interface-health" .claude/skills/
72
+ ln -s "$PWD/skills/check-route-health" .claude/skills/
73
+
74
+ # Codex
75
+ ln -s "$PWD/skills/check-bgp-health" .agents/skills/
76
+ ln -s "$PWD/skills/check-interface-health" .agents/skills/
77
+ ln -s "$PWD/skills/check-route-health" .agents/skills/
78
+ ```
79
+
80
+ **Say:**
81
+
82
+ > The skills live in the GitHub repository and are separate from the server's npm package. First, clone the repository.
83
+ >
84
+ > Claude Code looks in dot-claude slash skills. Codex looks in dot-agents slash skills. We create both folders.
85
+ >
86
+ > We're installing three workflows: BGP health, interface health, and route health. Each command links one skill folder into one client. Three for Claude Code, then the same three for Codex.
87
+ >
88
+ > Both clients now point at the same skill folders in this project.
89
+ >
90
+ > Keep the clone in place because the links point to it. Now open a fresh session in each client. In Claude Code, type slash to find a skill. In Codex, use slash-skills or type a dollar sign.
91
+
92
+ **Presenter notes:** Show each selector briefly. These are project-level installations; launch both clients in the project where you installed the links. The automated recording uses an isolated project under `.local-artifacts/eos-skills-demo/workspace`, with links back to this checkout's `skills/` folder. The clone command is shown as a viewer setup step because the recording checkout already exists. For installation across all projects, use the user-level destinations documented in `skills/README.md`. Each `ln -s` links into the client's skills folder, keeping the skill's folder name, and fails rather than overwriting if that name already exists; if a destination already exists, inspect it during rehearsal and reuse it if appropriate. Link individual skill folders, each containing `SKILL.md`.
93
+
94
+ Discovery locations and invocation syntax are documented in [Claude Code Skills](https://code.claude.com/docs/en/skills) and [OpenAI's Codex skills documentation](https://learn.chatgpt.com/docs/build-skills).
95
+
96
+ ### 2:05–3:10 — Scenario 1: BGP health in Claude Code
97
+
98
+ **Screen:** In Claude Code, select `/check-bgp-health` and submit:
99
+
100
+ ```text
101
+ /check-bgp-health Check the arista_ceos inventory group. Summarize neighbor states,
102
+ prefix counts, and uptime. Separate observed problems from possible causes,
103
+ and report any devices or checks you could not assess.
104
+ ```
105
+
106
+ **Say while the request runs:**
107
+
108
+ > First: a fabric-wide BGP check. I'm choosing the skill explicitly and targeting the arista_ceos group, covering all eight switches.
109
+ >
110
+ > The skill asks for IPv4, IPv6, and EVPN summaries. The server queries the devices, and the assistant turns the results into a report.
111
+
112
+ **Screen:** Expand one `eos_run_show` call. Highlight the group target and command list, then return to the report.
113
+
114
+ **Say:**
115
+
116
+ > Notice the group target and the commands behind the answer. We can inspect the evidence.
117
+ >
118
+ > Look for Established sessions, prefix counts, and recent session starts. A short uptime means a session started recently; we need more evidence to call it repeated flapping.
119
+ >
120
+ > If a session is down, the skill can check neighbor details. A possible cause still needs supporting evidence.
121
+
122
+ **Presenter notes:** Point to actual values. If all sessions are Established, say “The queried sessions are Established in this snapshot.” If there is a failure, identify the observed state and any retrieved reset reason. Never narrate a specific cause unless the result supports it. Distinguish an explicitly unconfigured address family from an unsupported command or failed query.
123
+
124
+ ### 3:10–4:15 — Scenario 2: Interface health in Codex
125
+
126
+ **Screen:** Switch to Codex. Select `$check-interface-health` and submit:
127
+
128
+ ```text
129
+ $check-interface-health Check node2-1. Its expected uplinks are Ethernet1
130
+ toward node1-1 and Ethernet2 toward node1-2. Report observed link states
131
+ and descriptions, and flag any expected uplink that is down. This is a
132
+ cEOS containerlab: skip optics and physical hardware diagnostics. Treat
133
+ unused ports separately and report any checks you could not complete.
134
+ ```
135
+
136
+ **Say:**
137
+
138
+ > Now let's use Codex to verify the links on node2-1. Codex uses a dollar sign to select the skill; Claude Code uses a slash. Both clients can use these same workflow files.
139
+ >
140
+ > This leaf has two expected uplinks: Ethernet1 toward node1-1, and Ethernet2 toward node1-2.
141
+
142
+ **Screen:** Show the initial `show interfaces status` and `show interfaces counters errors` tool inputs, followed by the report. Highlight `Ethernet1` and `Ethernet2` and compare their observed state and descriptions with the topology.
143
+
144
+ **Say:**
145
+
146
+ > Check whether both expected uplinks are connected. An unused, disconnected port elsewhere may be expected.
147
+ >
148
+ > The skill also reads error counters, but this container demo isn't a physical cable test. We're demonstrating link state, with no CRC errors expected.
149
+ >
150
+ > If both uplinks are up, that's a useful result: we've checked the expected connections and can inspect the evidence.
151
+
152
+ **Presenter notes:** A healthy report is the planned outcome; do not introduce a fault or wait for CRC errors. The topology defines the intended links, while the live tool output establishes their current state. Skip transceiver DOM, physical cable fault diagnosis, and ASIC resource checks in this cEOS demo.
153
+
154
+ ### 4:15–4:40 — More usage scenarios
155
+
156
+ **Screen:** Show a prompt card rather than waiting for a third live workflow:
157
+
158
+ ```text
159
+ Use check-route-health on node2-1. Summarize VRFs and route counts.
160
+ This is cEOS: skip hardware capacity checks. Flag unexpected empty
161
+ tables and state what you could not verify.
162
+ ```
163
+
164
+ **Say:**
165
+
166
+ > We also installed route health, which checks VRFs and routing tables.
167
+ >
168
+ > You can use these skills for morning checks, after maintenance, or to collect evidence for an incident. You can ask in ordinary language too; the assistant may select a matching skill. Naming it explicitly makes the workflow easier to follow in a demo.
169
+
170
+ ### 4:40–5:00 — Close
171
+
172
+ **Screen:** Return to the skill file beside one completed report. End card: repository link and “Read the runbook • Choose a target • Inspect the results.”
173
+
174
+ **Say:**
175
+
176
+ > To get started, connect the EOS MCP server, install the skill folders, and choose a target from your inventory.
177
+ >
178
+ > The server provides the device tools. Skills turn those tools into reusable workflows you can inspect and share.
179
+ >
180
+ > The repository, setup instructions, and example prompts are linked below. Try one check in your lab and inspect the results.
181
+
182
+ ## Presenter preparation — outside the five-minute cut
183
+
184
+ ### Prerequisites and lab
185
+
186
+ - Bash on Linux, macOS, or WSL; Git; Node.js **24+**, as required by the current `package.json`.
187
+ - Claude Code and Codex CLI installed, signed in, and ready. Use their official setup guides for client installation.
188
+ - The existing `clab/testlab.clab.yaml` containerlab, with HTTPS eAPI reachable on all eight cEOS nodes and credentials authorized to run the checks.
189
+ - The lab's routing configuration already deployed. The topology alone defines nodes and links; it does not establish BGP configuration or live neighbor health. Rehearse against the deployed lab; IPv6 BGP may be unconfigured.
190
+ - A sanitized inventory and terminal layout suitable for publication. Enter credentials off camera and keep saved credential configuration out of the recording.
191
+
192
+ Use a disposable demo workspace or a dedicated recording account so unrelated MCP servers and duplicate skill names do not clutter the selectors. Rehearse both clients against the same lab. No fault is required for a useful demo.
193
+
194
+ ### Use the existing config and containerlab inventory
195
+
196
+ Use `example-codex/eos-mcp-server-config.yml` unchanged for this recording. It contains:
197
+
198
+ ```yaml
199
+ inventory: /home/fred/aristanetworks/eos-mcp-server/clab/ansible-inventory.yml
200
+ ```
201
+
202
+ The inventory defines group **`arista_ceos`** with `node1-1`, `node1-2`, and `node2-1` through `node2-6`. It supplies the `admin` username, `EOS_MCP_PASSWORD` password reference, and disabled certificate validation for the lab. Use the existing password for these devices.
203
+
204
+ The topology specifies `ceos-lab:4.34.3M` and connects each leaf to both spines. The selected leaf's intended links are:
205
+
206
+ | Leaf interface | Spine interface |
207
+ | --- | --- |
208
+ | `node2-1:Ethernet1` | `node1-1:Ethernet1` |
209
+ | `node2-1:Ethernet2` | `node1-2:Ethernet1` |
210
+
211
+ `clab/` is ignored by Git in this checkout. A fresh public clone supplies the skills but does not supply this local lab. The recording uses the existing checkout; viewers using another lab must provide their own inventory and adjust the absolute inventory path in their server config.
212
+
213
+ In Bash, enter the password without placing its literal value in shell history:
214
+
215
+ ```bash
216
+ read -rsp 'EOS lab password: ' EOS_MCP_PASSWORD
217
+ printf '\n'
218
+ export EOS_MCP_PASSWORD
219
+
220
+ # Recording checkout; viewers substitute their own checkout/config path.
221
+ EOS_DEMO_REPO="/home/fred/aristanetworks/eos-mcp-server"
222
+ EOS_DEMO_CONFIG="$EOS_DEMO_REPO/example-codex/eos-mcp-server-config.yml"
223
+
224
+ npx -y @aristanetworks/eos-mcp-server validate-inventory \
225
+ --config "$EOS_DEMO_CONFIG"
226
+
227
+ npx -y @aristanetworks/eos-mcp-server probe \
228
+ --config "$EOS_DEMO_CONFIG" --target arista_ceos
229
+ ```
230
+
231
+ `validate-inventory` checks inventory and connection settings, including password resolution; it does not contact the devices. `probe` makes live device requests. Proceed to recording once all eight devices probe successfully.
232
+
233
+ ### Register the server in both clients
234
+
235
+ Run these commands in the same shell as the exported password and absolute config path. Both clients use the config in `example-codex`. Register the server as **`eos`** because the skill files refer to that server's tools.
236
+
237
+ **Claude Code:**
238
+
239
+ ```bash
240
+ claude mcp add --scope user --transport stdio eos \
241
+ --env EOS_MCP_PASSWORD="$EOS_MCP_PASSWORD" \
242
+ -- npx -y @aristanetworks/eos-mcp-server serve \
243
+ --config "$EOS_DEMO_CONFIG"
244
+ ```
245
+
246
+ **Codex:**
247
+
248
+ ```bash
249
+ codex mcp add eos \
250
+ --env EOS_MCP_PASSWORD="$EOS_MCP_PASSWORD" \
251
+ -- npx -y @aristanetworks/eos-mcp-server serve \
252
+ --config "$EOS_DEMO_CONFIG"
253
+ ```
254
+
255
+ These CLI examples store the expanded password in each client's saved configuration. Keep those files private and off camera. For Codex, you can instead configure dynamic environment forwarding in `~/.codex/config.toml`:
256
+
257
+ ```toml
258
+ [mcp_servers.eos]
259
+ command = "npx"
260
+ args = ["-y", "@aristanetworks/eos-mcp-server", "serve", "--config", "/home/fred/aristanetworks/eos-mcp-server/example-codex/eos-mcp-server-config.yml"]
261
+ env_vars = ["EOS_MCP_PASSWORD"]
262
+ ```
263
+
264
+ Use this TOML table as an alternative to the CLI-created entry, replacing its stored `env` password entry; do not append a duplicate table. Launch Codex from a shell where the password is exported. See the [Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp?surface=cli) and [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) for client configuration details.
265
+
266
+ The MCP client launches the server; you do not need a separate foreground `serve` process. Download the npm package during rehearsal. For repeatable recordings, choose and pin a released package version in both client commands and record the Git commit used for the skills.
267
+
268
+ ### Verify installation before recording
269
+
270
+ 1. Run the skill-link commands from the recording script. If a skill already exists, inspect its destination rather than overwriting it.
271
+ 2. Launch `claude` and `codex` in separate terminals. In each client, check `/mcp` for the `eos` server and its tools.
272
+ 3. Confirm the three skill names appear: type `/check-` in Claude Code; use `/skills` or `$check-` in Codex. Restart a client if newly added skills are missing.
273
+ 4. Ask each client: “Use eos_list_inventory to list the available EOS hosts and groups.” Confirm all eight lab nodes and the `arista_ceos` group appear.
274
+ 5. Rehearse both prompts. Confirm the assistant loads the named skill and calls EOS MCP tools rather than answering from general knowledge.
275
+
276
+ ## Prompt reference and optional follow-ups
277
+
278
+ | Scenario | Claude Code | Codex |
279
+ | --- | --- | --- |
280
+ | Fabric BGP health | `/check-bgp-health Check arista_ceos.` | `$check-bgp-health Check arista_ceos.` |
281
+ | Virtual uplink state | `/check-interface-health Check node2-1 uplinks Ethernet1 and Ethernet2; skip optics.` | `$check-interface-health Check node2-1 uplinks Ethernet1 and Ethernet2; skip optics.` |
282
+ | Routing and VRFs | `/check-route-health Check VRFs and route counts on node2-1; skip hardware checks.` | `$check-route-health Check VRFs and route counts on node2-1; skip hardware checks.` |
283
+
284
+ Useful follow-ups in either client:
285
+
286
+ ```text
287
+ Which findings are directly supported by device output, and which are hypotheses?
288
+ ```
289
+
290
+ ```text
291
+ Recheck Ethernet1 and Ethernet2 on node2-1. Compare their current link states
292
+ with the previous reading and summarize any changes. Keep this read-only.
293
+ ```
294
+
295
+ ```text
296
+ Summarize the results in five lines for an incident handoff, including targets,
297
+ observations, checks that failed, and the next read-only investigation.
298
+ ```
299
+
300
+ The repository also has EVPN, Port-Channel, VLAN, multicast, PTP, and Segment Routing workflows. Introduce these as follow-up topics; keep the first video focused on the two live checks.
301
+
302
+ ## Recording contingencies
303
+
304
+ | What happens | Presenter response |
305
+ | --- | --- |
306
+ | Everything looks healthy | Explain the observed states and counts. A healthy lab is a valid demonstration. |
307
+ | Interface counters are all zero | Continue with the expected-uplink state check. CRC faults and optics alarms are not expected demonstrations in this container lab. |
308
+ | Skill missing from a selector | Check the per-skill link and `SKILL.md`, then restart the client. Fix this during rehearsal. |
309
+ | Server connected, devices unreachable | Check inventory, eAPI reachability, credentials, and TLS. A connected MCP server does not establish device connectivity. |
310
+ | An EOS command is unsupported | Report that check as unavailable. Do not convert a command failure into a healthy result or a missing-feature claim. |
311
+ | A skill overstates a cause | Point back to the evidence and ask it to separate observations from hypotheses. |
312
+ | A tool call takes too long for the edit | Trim the wait while retaining the submitted prompt, an actual tool call, and the returned report. |
313
+
314
+ Keep screenshots from a successful rehearsal as a fallback, clearly labeled “recorded lab result” if inserted. Do not substitute fabricated device output. Show one short tool trace per client and spend the remaining time explaining the reports.
315
+
316
+ ## Suggested video description
317
+
318
+ ```text
319
+ Learn what AI skills are and use them with the Arista EOS MCP server in
320
+ Claude Code and Codex. We install shared workflow files, then check BGP
321
+ neighbors and virtual uplink state against an eight-node cEOS containerlab.
322
+ Both clients use example-codex/eos-mcp-server-config.yml and the local
323
+ clab inventory. This demo does not depend on CRC errors or physical optics.
324
+
325
+ 00:00 What we'll build
326
+ 00:20 Skills and MCP explained
327
+ 00:55 The lab
328
+ 01:15 Install skills for Claude Code and Codex
329
+ 02:05 BGP health in Claude Code
330
+ 03:10 Interface health in Codex
331
+ 04:15 More workflows
332
+ 04:40 Getting started
333
+
334
+ Repository: https://github.com/aristanetworks/eos-mcp-server
335
+ Skills and installation: https://github.com/aristanetworks/eos-mcp-server/tree/main/skills
336
+ Server setup: https://github.com/aristanetworks/eos-mcp-server#connect-to-an-mcp-client
337
+
338
+ Requires reachable Arista EOS devices with eAPI, Node.js 24+, and your
339
+ chosen AI client. These workflows use read-only device tools.
340
+ ```
341
+
342
+ Adjust chapter timestamps to match the final edit.
343
+
344
+ ## Verification notes
345
+
346
+ Prepared against this checkout's `skills/README.md`, the BGP/interface/route skill files, MCP tool schemas, CLI implementation, and `package.json`. Lab targets and intended links were checked against `example-codex/eos-mcp-server-config.yml`, `clab/ansible-inventory.yml`, and `clab/testlab.clab.yaml`. Client registration syntax was checked with installed Codex CLI 0.163.0 and Claude Code 2.1.306 help, and skill discovery/invocation was checked against the linked official documentation. Live lab outputs and recording timings must be rehearsed in the presenter's environment.
package/docs/DESIGN.md CHANGED
@@ -48,19 +48,19 @@ Canonical Ansible-style YAML and simplified YAML are supported. Structural keys
48
48
 
49
49
  ## Runtime controls
50
50
 
51
- Read requests have configured per-device timeout, optional overall timeout, device concurrency, target-count, command-count, logging-message, and response-size limits. The overall timeout aborts in-flight requests and halts further scheduling. Device HTTP responses and final serialized tool results are size-limited.
51
+ Read requests have configured per-device timeout, optional overall timeout, per-call fan-out (`readFanOut`), target-count, command-count, logging-message, and response-size limits. The overall timeout aborts in-flight requests and halts further scheduling. Every eAPI request, read or diagnostic, holds one of its device's `deviceConcurrency` slots, shared across all calls in the server process ([ADR 0009](adr/0009-shared-per-device-eapi-slots.md)); `readTimeoutMs` starts once the slot is granted. Device HTTP responses and final serialized tool results are size-limited.
52
52
 
53
53
  ### Active diagnostic controls
54
54
 
55
- `eos_ping` uses independent `diagnostics` settings with finite defaults: count 3/max 10, aggregate probes 100 (hosts × destinations × count), 16 destinations per call, server-wide device concurrency 5, reply timeout 1000 ms, command budget 30000 ms, HTTP timeout 35000 ms, and overall timeout 60000 ms. All are configurable; target and response-size limits are shared with reads. Config validation requires consistent counts, execution budgets, and HTTP timeouts.
55
+ `eos_ping` uses independent `diagnostics` settings with finite defaults: count 3/max 10, aggregate probes 100 (hosts × destinations × count), 16 destinations per call, per-device concurrency 2 (within the shared per-device limit of 3) under a per-server ceiling of 32, reply timeout 1000 ms, command budget 30000 ms, HTTP timeout 35000 ms, and overall timeout 60000 ms. All are configurable; target and response-size limits are shared with reads. Config validation requires consistent counts, execution budgets, and HTTP timeouts.
56
56
 
57
- A ping request names one or more destinations and sends exactly one text-mode eAPI request per host and destination, without automatic retries. Those executions run concurrently under the shared admission controller, each with its own reservation, and return one result per host and destination. Fixed count, one-second interval, fixed size 64, and reply timeout bound device work; the command budget is not a native remote kill timer. HTTP abort is not proof of remote completion. Unknown completion retains its global admission slot indefinitely; elapsed time is never completion evidence. When all capacity is uncertain, new and queued work fail closed with `diagnostic_capacity_uncertain`. Server info exposes current sanitized capacity and reservation state. Production commits reservations before dispatch to a private SQLite ledger and commits confirmed removal before reusing capacity. Unfinished entries restore as uncertain across restarts; only one server may own a ledger. Invalid, mismatched, or missing companion state fails closed. `inspect-diagnostics` provides read-only offline inspection without ownership takeover or device access. Ledger location is configurable as `diagnostics.stateFile`, otherwise derived from the canonical inventory path under the user's state directory. Offline operator-attested release requires independent fencing evidence covering running, accepted queued and replayable work, exact IDs, explicit confirmation and exclusive ownership. Schema-2 audit and deletions commit atomically; inspection is read-only, and neither a reset nor a release is exposed through MCP. Authority is the OS-authorized operator, not automated validation of fencing truth. A qualified native remote deadline remains unresolved; timeout-shaped RPC errors are not assumed to confirm process exit. Cancellation stops scheduling and aborts HTTP requests; overall timeout remains a request-level failure without partial results. See [DIAGNOSTICS.md](DIAGNOSTICS.md) for the full lifecycle assumptions and limits.
57
+ A ping request names one or more destinations and sends exactly one text-mode eAPI request per host and destination, without automatic retries. Those executions run concurrently under the shared admission controller, which limits each device separately under a server-wide ceiling ([ADR 0007](adr/0007-per-device-diagnostic-concurrency.md)), each with its own reservation, and return one result per host and destination. Fixed count, one-second interval, fixed size 64, and reply timeout bound device work; by default each request also carries EOS `requestTimeout` set to the command budget (`diagnostics.nativeRequestTimeout`), which interrupts execution on the device but does not cover time queued before execution. HTTP abort is not proof of remote completion. Unknown completion holds its admission slot for the command budget, then frees it; a late confirmed response frees it sooner. Capacity is held in memory per server process, so several servers can use one inventory at once ([ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md)). Server info exposes current sanitized capacity and reservation state. A qualified native remote deadline remains unresolved; timeout-shaped RPC errors are not assumed to confirm process exit. Cancellation stops scheduling and aborts HTTP requests; overall timeout remains a request-level failure without partial results. Every tool, ping included, advertises MCP `readOnlyHint: true` so clients may run independent calls concurrently; these server-side limits, not client scheduling, bound the load ([ADR 0008](adr/0008-read-only-tool-annotations.md)). See [DIAGNOSTICS.md](DIAGNOSTICS.md) for the full lifecycle assumptions and limits.
58
58
 
59
59
  Only unicast literal destinations are supported; DNS, scoped/link-local IPv6, multicast, unspecified, and limited broadcast addresses are rejected. Optional VRF/source selection must not fall back. Directed-broadcast detection is not guaranteed from an address alone.
60
60
 
61
61
  ## Result and error model
62
62
 
63
- Every eAPI response must identify JSON-RPC 2.0, match its request ID exactly, and contain exactly one result/error member before interpretation. Missing or mismatched identity is `eapi_response_uncorrelated`, not completion evidence. Diagnostic wire IDs are journaled before dispatch and survive uncertain restarts; legacy records without wire IDs are not retroactively correlated. Correlation IDs do not provide an EOS job lookup, cancellation, or idempotency facility.
63
+ Every eAPI response must identify JSON-RPC 2.0, match its request ID exactly, and contain exactly one result/error member before interpretation. Missing or mismatched identity is `eapi_response_uncorrelated`, not completion evidence. Each diagnostic execution's reservation ID is its wire ID. Correlation IDs do not provide an EOS job lookup, cancellation, or idempotency facility.
64
64
 
65
65
  Device-facing tools return target metadata, resolved devices, summary counts, and per-device results. Request-level failures and known device failures use `AppError` machine-readable codes. Supported read tools expose raw eAPI payloads only through an explicit `include_raw` option. Ping separates successful execution from a reachability finding and returns normalized loss/RTT measurements; original EOS text is opt-in. Unrecognized output is an explicit device failure, never an invented finding.
66
66
 
@@ -58,11 +58,13 @@ diagnostics:
58
58
  maxPingCount: 10
59
59
  maxProbesPerRequest: 100
60
60
  maxDestinationsPerRequest: 16
61
- deviceConcurrency: 5
61
+ deviceConcurrency: 2
62
+ maxConcurrentDiagnostics: 32
62
63
  pingReplyTimeoutMs: 1000
63
64
  commandTimeoutMs: 30000
64
65
  httpTimeoutMs: 35000
65
66
  overallOperationTimeoutMs: 60000
67
+ nativeRequestTimeout: true
66
68
  ```
67
69
 
68
70
  | Setting | Meaning |
@@ -71,13 +73,15 @@ diagnostics:
71
73
  | `maxPingCount` | Maximum probes per device and destination |
72
74
  | `maxProbesPerRequest` | Maximum resolved host count × destination count × requested probes |
73
75
  | `maxDestinationsPerRequest` | Maximum destinations in one `eos_ping` call |
74
- | `deviceConcurrency` | Ledger-scoped diagnostic capacity, shared by active and uncertain executions across calls and preserved across server restarts |
76
+ | `deviceConcurrency` | Diagnostic slots one inventory host may hold across all calls, counting active and uncertain executions; must not exceed `maxConcurrentDiagnostics` or the top-level `deviceConcurrency`, the host's eAPI slots shared with reads |
77
+ | `maxConcurrentDiagnostics` | Diagnostic slots of one server process across all devices and calls |
75
78
  | `pingReplyTimeoutMs` | EOS per-reply timeout; a positive multiple of 1000 ms |
76
79
  | `commandTimeoutMs` | Conservative device execution budget; see lifecycle below |
77
80
  | `httpTimeoutMs` | Per-device diagnostic eAPI HTTP timeout, separate from `readTimeoutMs` |
78
- | `overallOperationTimeoutMs` | Whole diagnostic call deadline, including waiting for global capacity |
81
+ | `overallOperationTimeoutMs` | Whole diagnostic call deadline, including waiting for per-device and server-wide capacity |
82
+ | `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 |
79
83
 
80
- Optional `diagnostics.stateFile` selects the durable SQLite ledger; relative paths resolve beside the settings file. Otherwise, the server uses `$XDG_STATE_HOME/eos-mcp-server/<inventory-identity>/diagnostics.sqlite`, falling back to `~/.local/state` when `XDG_STATE_HOME` is absent or not absolute. The identity hashes the inventory's canonical filesystem path, so symlink aliases share the default ledger. Keep the inventory identity and ledger location stable across restarts. Changing/copying inventory paths or selecting another state file does not migrate prior reservations and must not be used to bypass them.
84
+ 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` and `maxConcurrentDiagnostics`. 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).
81
85
 
82
86
  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`.
83
87
 
@@ -91,84 +95,34 @@ The HTTP timeout must exceed the command budget by at least 1000 ms. Individual
91
95
 
92
96
  ## Execution and lifecycle
93
97
 
94
- Each host/destination execution is one text-mode eAPI request containing `enable` followed by the generated ping command. The exact wire request ID is committed as `rpc_request_id` before dispatch. 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.
98
+ 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.
95
99
 
96
- 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.
100
+ 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).
97
101
 
98
- One admission controller is shared by all diagnostic calls using the server's ledger. The reservation is durably committed **before** the device runner can dispatch. Confirmed synchronous completion or a locally established rejection before dispatch releases capacity only after the removal commits. 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 and never expires automatically**. RPC interruption errors are not process-reaped certificates; even failed commands may conservatively retain capacity. Neither elapsed time nor HTTP abort proves that earlier work stopped or cannot start later. A late confirmed response can release its reservation only while the original owner still has the ledger open; there is no reset exposed through MCP.
102
+ One admission controller is shared by every eAPI request in a server process, reads and diagnostics alike ([ADR 0009](adr/0009-shared-per-device-eapi-slots.md)). It grants a diagnostic slot only while the device holds fewer than the top-level `deviceConcurrency` slots in total, fewer than `diagnostics.deviceConcurrency` diagnostic slots, and the process holds fewer than `maxConcurrentDiagnostics`. Because the diagnostic share is below the device total, pings can never fill a device and hold up its reads. Waiters are served oldest first, skipping any that cannot run yet, so a ping waiting on a busy device never holds up a later one to a free device ([ADR 0007](adr/0007-per-device-diagnostic-concurrency.md)). 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.
99
103
 
100
- The ledger uses Node's built-in SQLite (Node 24+, potentially marked experimental by the runtime), FULL synchronous commits, and a separate SQLite ownership database. Only one server may own a ledger; another owner fails with `diagnostic_state_in_use`. OS locking releases ownership when a process crashes, without PID-age guessing or deleting a stale lock. All unfinished records, including records persisted as active before a crash, reopen as uncertain. Corrupt, incompatible, inventory-mismatched, or half-missing state is rejected rather than initialized as fresh capacity. A full SQLite integrity check runs before restored capacity is accepted and during offline inspection; malformed indexes must not hide occupied reservations. State write failure blocks further diagnostics with `diagnostic_state_write_failed`; an abort listener does not throw if writing uncertainty fails, because the earlier committed record still preserves the reservation.
104
+ A request fans out into one execution per resolved host and destination. All of them queue with the same admission controller as every other call at once; each takes and releases its own reservation exactly as a single ping does. Different devices run in parallel, each up to `deviceConcurrency`, within `maxConcurrentDiagnostics` for the whole process. Queued executions wait for capacity inside the same overall deadline, so size requests so that roughly `ceil(destinations / deviceConcurrency)` successive waves of pings per host fit within `overallOperationTimeoutMs`. That estimate assumes the server ceiling has room: once hosts × destinations, plus the executions of other concurrent calls, exceed `maxConcurrentDiagnostics`, waves also wait on the ceiling. Uncertain reservations reduce both device and server capacity. On cEOS 4.34.3M eAPI ran at most three requests at once per device, pings and reads combined; further delivered requests, including show commands, waited on the device. The top-level `deviceConcurrency` default of 3 matches that, and the diagnostic default of 2 keeps one slot for reads. Set the top-level value to the device's own eAPI concurrency for other platforms.
101
105
 
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; queued executions 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.
106
+ 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 on each targeted device 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
107
 
106
108
  ## Uncertain execution and capacity visibility
107
109
 
108
- `eos_get_server_info` returns a fresh `diagnostics.capacity` snapshot with:
110
+ `eos_get_server_info` returns a fresh `diagnostics.capacity` snapshot for that server process with:
109
111
 
110
- - `scope: "state-file"` for the production durable ledger, `limit`, and `available_count`. A deliberately in-memory executor reports `server-instance`; production can use that mode only when no inventory host has diagnostic permission.
111
- - `active_count`, `uncertain_count`, and `queued_count`.
112
- - `reservations`: ID, inventory hostname, diagnostic kind, state, reservation timestamp, and (when uncertain) uncertainty timestamp and machine-readable reason code. New reservations also carry `rpc_request_id`, matching the journaled reservation ID and the actual wire ID. Legacy records without this field remain uncorrelated; no wire identity is invented for them. Destinations, endpoints, credentials, and original EOS output are not included.
113
- - `storage_error_code` when journal I/O has failed; new work is blocked even if the numeric limit would otherwise leave space.
112
+ - `limit` (`maxConcurrentDiagnostics`), `device_limit` (`diagnostics.deviceConcurrency`), `eapi_device_limit` (top-level `deviceConcurrency`), and server-wide `available_count`.
113
+ - Server-wide `active_count`, `uncertain_count`, and `queued_count`.
114
+ - `devices`: for each inventory host holding or awaiting a slot, its diagnostic `available_count` (its diagnostic headroom, capped by its shared eAPI headroom and server-wide availability), `active_count`, `uncertain_count`, and `queued_count`, plus the read slots it holds and awaits (`read_active_count`, `read_queued_count`).
115
+ - `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.
114
116
 
115
- When every slot is uncertain, new requests fail immediately with `diagnostic_capacity_uncertain`. The MCP result has `isError: true` and structured `error_code`/`message`. Work already waiting for capacity is also rejected instead of waiting indefinitely; if uncertainty arises during group execution, those failures use the existing per-device envelope. If some healthy capacity remains, it can still execute diagnostics without freeing uncertain slots.
117
+ Uncertain slots count against their device's `deviceConcurrency` and against `maxConcurrentDiagnostics` like active ones. New work for that device 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`. Other devices keep running without waiting for them.
116
118
 
117
119
  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.
118
120
 
119
- ### Offline inspection and operator recovery
120
-
121
- ```bash
122
- eos-mcp-server inspect-diagnostics --config eos-mcp-server.yml --json
123
- # Or derive the default ledger from the same inventory identity:
124
- eos-mcp-server inspect-diagnostics --inventory inventory.yml --json
125
- ```
126
-
127
- Inspection opens existing state read-only, including while another server owns it; it does not resolve credentials, contact devices, take over ownership, create a missing ledger, or clear records. Integrity, schema, reservations, and audit are read in one transaction, so a concurrent commit cannot mix before/after release states. A competing writer may fail closed while this short read snapshot is held; inspect current state before retrying an administrative release. Inspection reports recorded state, not proof that remote work completed.
128
-
129
- **Operational constraints:** use a persistent local filesystem that honors SQLite locking and sync, private owner-controlled state files (POSIX mode 0600), and an owner-controlled directory not writable by group/others. Network filesystems and ephemeral/container storage are not qualified durability mechanisms. Preserve the ledger, its `.lock.sqlite` ownership file, and any SQLite recovery files together. Do not delete, replace, copy, rotate, roll back, or change the ledger path to clear reservations. Loss of both ledger files cannot be distinguished locally from first initialization. Before upgrading from the earlier in-memory implementation, unresolved old executions require independent reconciliation; they cannot be recovered from a journal that did not yet exist.
130
-
131
- Restarting with the same ledger retains uncertainty. Recovery is now available only as an **offline, audited Operator Release** backed by a **Fencing Attestation**. There is no MCP reset, release-all operation, native completion-token lookup, or automatic timeout-based release. Stopping the MCP process establishes local ownership transfer, not remote termination.
132
-
133
- #### Operator release workflow
134
-
135
- 1. Stop the server owning this ledger and prevent diagnostic resubmission. The release command refuses a live ledger owner; it does not stop one.
136
- 2. Run `inspect-diagnostics --json` and retain the `inventory_identity`, exact selected reservation IDs, immutable host/admission timestamps, and recorded RPC IDs. Establish the original physical device and execution epoch from independent evidence/history, not just today's inventory mapping.
137
- 3. Independently verify that the fence terminates running work, invalidates **accepted queued** work, and prevents later dispatch/replay. A timeout, empty process list, quiet counters, or repeated observations are insufficient.
138
- 4. Prepare a private JSON attestation from [the deliberately invalid template](examples/diagnostic-release-evidence.template.json). Replace every placeholder with actual evidence. Change each assertion to true only after verification. Do not put credentials, raw packet contents, or EOS output in the file; use evidence references instead.
139
- 5. Release only the exact covered IDs, then inspect the audit:
140
-
141
- ```bash
142
- # Prepare with private permissions; the template cannot authorize a release as-is.
143
- umask 077
144
- cp docs/examples/diagnostic-release-evidence.template.json ./evidence.json
145
- # Human: complete the attestation from independently verified fencing evidence.
146
- chmod 600 ./evidence.json
147
-
148
- eos-mcp-server release-diagnostics --config eos-mcp-server.yml \
149
- --reservation-id '<exact-id>' --evidence-file ./evidence.json \
150
- --confirm-fenced --json
151
- # Repeat --reservation-id only for other IDs covered by the same attestation.
152
- eos-mcp-server inspect-diagnostics --config eos-mcp-server.yml --json
153
- ```
154
-
155
- `--confirm-fenced`, an evidence file, and explicit unique IDs are mandatory. Evidence is private, owner-controlled UTF-8 JSON with unique keys, at most 65536 bytes and 100 explicitly covered reservations. There is no wildcard selection. The command performs no credential resolution, device call, URI fetch, fencing action, or network retry. A state-only config with an explicit `diagnostics.stateFile` can be used if the original inventory is unavailable; the attestation still must match the ledger's stored inventory identity. If config declares an inventory path, its canonical identity must also match.
156
-
157
- The attestation requires `evidence_version: 1`, a new UUID `attestation_id`, the exact `inventory_identity`, an accountable `verified_by`, a past UTC `verified_at`, a reason, and four literal-true assertions: `independent_evidence_verified`, `running_work_terminated`, `accepted_queued_work_cannot_execute`, and `replay_prevented`. Each covered reservation must match its exact ID, host and `reserved_at`, and its exact `rpc_request_id` (explicit JSON null for legacy records without one). It must also identify the physical device, fenced execution epoch, fencing time, and independent evidence reference. Fencing must follow reservation admission and precede verification. The selected CLI IDs and evidence scope must match exactly.
158
-
159
- #### Audit and authority
160
-
161
- The release audit and every selected deletion commit in **one FULL-synchronous SQLite transaction** under exclusive ledger ownership. A failed batch rolls back earlier deletions and its audit; unselected reservations remain held. The append-only-through-the-application audit records original reservation snapshots, the complete attestation, its canonical SHA-256 digest, an audit-content checksum, server recording time, and actual local OS username/UID. `verified_by` is a claimed accountable person, not authenticated identity; the OS account is collected independently. Local operators must be trusted and protected by OS access controls. This is not a cryptographic human-presence check, and an untrusted agent with the ledger owner's filesystem privileges is outside that protection.
162
-
163
- `inspect-diagnostics --json` exposes `release_audit` to local operators; MCP server info does not expose the evidence or audit. Checksums detect accidental inconsistency, not deliberate tampering by the trusted filesystem owner. The program validates consistency and explicit authority; it **cannot establish the truth of external fencing evidence**. Releasing on false attestation can violate remote concurrency safety.
164
-
165
- If CLI completion is lost, inspect the audit first. Repeating the exact committed attestation returns its original release ID with `already_committed: true` and makes no additional changes. Reusing an attestation UUID for changed evidence is rejected. This local administrative idempotency does not make EOS ping requests idempotent; never retry a ping just because its RPC ID is known.
166
-
167
- Ledger schema 1 upgrades atomically to schema 2, preserving unresolved reservations and adding release history. Read-only inspection of schema 1 does not migrate it. A rejected release also rolls back any migration. Schema-2 ledgers with missing or invalid audit history fail closed, and older schema-1-only binaries must not be used to bypass the audit.
168
-
169
121
  ### Native EOS timeout qualification
170
122
 
171
- 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. The product does not enable it or treat timeout-shaped RPC errors as completion. 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
+ 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.
124
+
125
+ 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.
172
126
 
173
127
  ## Results
174
128
 
@@ -183,11 +137,11 @@ Execution `status: "success"` means ping executed and its output was understood.
183
137
 
184
138
  ## Extension seam
185
139
 
186
- `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.
140
+ `DiagnosticExecutor.execute` accepts typed diagnostic intent and owns authorization, aggregate budgets, fan-out, fail-closed per-device and server-wide 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.
187
141
 
188
142
  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.
189
143
 
190
- Admission queueing is separate from reservation creation and state transitions; shutdown, storage failure, and uncertain-capacity exhaustion share queue rejection. A queued admission failure does not invalidate an earlier command whose completion was already committed. 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. Capacity is reusable only after the corresponding durable removal succeeds.
144
+ 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.
191
145
 
192
146
  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.
193
147
 
@@ -198,15 +152,13 @@ Validated against the repository's cEOS 4.34.3M lab over HTTPS eAPI:
198
152
  - IPv4/IPv6 loopback, explicit source and default VRF, and measured RTTs.
199
153
  - Explicit one-second pacing, finite repeat count, and reply-timeout behavior.
200
154
  - Group fan-out and 100% packet loss in the MGMT VRF.
201
- - 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 ledger sampler observed five simultaneous active reservations, and direct concurrent eAPI requests confirmed the device ran at most three commands at once.
155
+ - 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.
202
156
  - Invalid VRF and unavailable source fail without fallback.
203
157
  - Overall HTTP cancellation can leave a short-lived device ping; observed processes exited after their finite probe sequence.
204
158
  - MCP stdio execution loads configured defaults and returns real measurements.
205
159
 
206
160
  Older EOS versions and physical platforms have not been qualified for this new tool; the existing read-path minimum does not establish ping compatibility.
207
161
 
208
- The refactor was also checked against synthetic ledgers and release audits written by the pre-refactor code (`0e208ec`): inspection, checksum validation, exact committed retries, legacy absent RPC IDs, and remaining reservations stayed compatible. Audit snapshots use the shared reservation codec, and evidence/audit digests share the same JSON SHA-256 implementation; the serialized formats and schema versions are unchanged.
209
-
210
162
  ```bash
211
163
  npm test
212
164
  npm run typecheck
@@ -215,4 +167,4 @@ npm run build
215
167
  INTEGRATION=1 npm test
216
168
  ```
217
169
 
218
- 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, cancellation, response-size enforcement, write-ahead ordering, blocked dispatch on storage failure, failed completion commits, private permissions, ownership exclusion, malformed/missing ledgers, actual child-process crash recovery, and offline non-mutating inspection. Operator-release tests use synthetic ledger records and synthetic attestations only: they cover exact scope, legacy null IDs, rejected/malformed evidence, private bounded file input, competing ownership, identity/timestamp mismatches, schema migration/rollback, partial deletion rollback, audit integrity and idempotent retry. They do not claim any real EOS execution was fenced or release live test reservations. Additional protocol regressions cover missing/stale IDs, invalid versions, contradictory result/error envelopes, no retry on an uncorrelated read error, persisted wire-ID ordering, and reservation survival across restart after an unrelated acknowledgement. The delayed-backend regression models a request timing out at 35 seconds and starting remotely after the former 65-second expiry: replacement work is now rejected, so modeled active execution stays within the configured ceiling. This is a deterministic fault model, not a claim to have reproduced live EOS queue overload. Live MCP integration also checks timeout-to-uncertain visibility and the structured capacity error before and after restarting with the same ledger.
170
+ 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.