@aristanetworks/eos-mcp-server 0.6.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 (170) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +203 -0
  3. package/README.md +652 -0
  4. package/dist/cli.d.ts +16 -0
  5. package/dist/cli.js +188 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/printServerInfo.d.ts +2 -0
  8. package/dist/commands/printServerInfo.js +14 -0
  9. package/dist/commands/printServerInfo.js.map +1 -0
  10. package/dist/commands/probe.d.ts +2 -0
  11. package/dist/commands/probe.js +26 -0
  12. package/dist/commands/probe.js.map +1 -0
  13. package/dist/commands/validateInventory.d.ts +9 -0
  14. package/dist/commands/validateInventory.js +59 -0
  15. package/dist/commands/validateInventory.js.map +1 -0
  16. package/dist/config/loadConfig.d.ts +3 -0
  17. package/dist/config/loadConfig.js +84 -0
  18. package/dist/config/loadConfig.js.map +1 -0
  19. package/dist/config/schema.d.ts +151 -0
  20. package/dist/config/schema.js +54 -0
  21. package/dist/config/schema.js.map +1 -0
  22. package/dist/configuration/getRunningConfig.d.ts +24 -0
  23. package/dist/configuration/getRunningConfig.js +38 -0
  24. package/dist/configuration/getRunningConfig.js.map +1 -0
  25. package/dist/connection/eosDeviceReader.d.ts +18 -0
  26. package/dist/connection/eosDeviceReader.js +13 -0
  27. package/dist/connection/eosDeviceReader.js.map +1 -0
  28. package/dist/connection/resolveConnection.d.ts +4 -0
  29. package/dist/connection/resolveConnection.js +58 -0
  30. package/dist/connection/resolveConnection.js.map +1 -0
  31. package/dist/connection/validateStartupConnections.d.ts +4 -0
  32. package/dist/connection/validateStartupConnections.js +28 -0
  33. package/dist/connection/validateStartupConnections.js.map +1 -0
  34. package/dist/core/errors.d.ts +8 -0
  35. package/dist/core/errors.js +22 -0
  36. package/dist/core/errors.js.map +1 -0
  37. package/dist/core/version.d.ts +2 -0
  38. package/dist/core/version.js +7 -0
  39. package/dist/core/version.js.map +1 -0
  40. package/dist/eapi/client.d.ts +8 -0
  41. package/dist/eapi/client.js +159 -0
  42. package/dist/eapi/client.js.map +1 -0
  43. package/dist/eapi/commands.d.ts +2 -0
  44. package/dist/eapi/commands.js +25 -0
  45. package/dist/eapi/commands.js.map +1 -0
  46. package/dist/eapi/transport.d.ts +21 -0
  47. package/dist/eapi/transport.js +143 -0
  48. package/dist/eapi/transport.js.map +1 -0
  49. package/dist/eapi/types.d.ts +49 -0
  50. package/dist/eapi/types.js +15 -0
  51. package/dist/eapi/types.js.map +1 -0
  52. package/dist/facts/getFacts.d.ts +33 -0
  53. package/dist/facts/getFacts.js +50 -0
  54. package/dist/facts/getFacts.js.map +1 -0
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +66 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/inventory/buildModel.d.ts +3 -0
  59. package/dist/inventory/buildModel.js +79 -0
  60. package/dist/inventory/buildModel.js.map +1 -0
  61. package/dist/inventory/effectiveVars.d.ts +2 -0
  62. package/dist/inventory/effectiveVars.js +52 -0
  63. package/dist/inventory/effectiveVars.js.map +1 -0
  64. package/dist/inventory/graph.d.ts +6 -0
  65. package/dist/inventory/graph.js +81 -0
  66. package/dist/inventory/graph.js.map +1 -0
  67. package/dist/inventory/internalTypes.d.ts +17 -0
  68. package/dist/inventory/internalTypes.js +2 -0
  69. package/dist/inventory/internalTypes.js.map +1 -0
  70. package/dist/inventory/internalUtils.d.ts +10 -0
  71. package/dist/inventory/internalUtils.js +68 -0
  72. package/dist/inventory/internalUtils.js.map +1 -0
  73. package/dist/inventory/listInventoryView.d.ts +23 -0
  74. package/dist/inventory/listInventoryView.js +22 -0
  75. package/dist/inventory/listInventoryView.js.map +1 -0
  76. package/dist/inventory/loadInventory.d.ts +4 -0
  77. package/dist/inventory/loadInventory.js +87 -0
  78. package/dist/inventory/loadInventory.js.map +1 -0
  79. package/dist/inventory/normalize.d.ts +4 -0
  80. package/dist/inventory/normalize.js +251 -0
  81. package/dist/inventory/normalize.js.map +1 -0
  82. package/dist/inventory/parse.d.ts +6 -0
  83. package/dist/inventory/parse.js +31 -0
  84. package/dist/inventory/parse.js.map +1 -0
  85. package/dist/inventory/policy.d.ts +11 -0
  86. package/dist/inventory/policy.js +27 -0
  87. package/dist/inventory/policy.js.map +1 -0
  88. package/dist/inventory/resolveTarget.d.ts +14 -0
  89. package/dist/inventory/resolveTarget.js +55 -0
  90. package/dist/inventory/resolveTarget.js.map +1 -0
  91. package/dist/inventory/types.d.ts +50 -0
  92. package/dist/inventory/types.js +2 -0
  93. package/dist/inventory/types.js.map +1 -0
  94. package/dist/inventory/validateNormalized.d.ts +2 -0
  95. package/dist/inventory/validateNormalized.js +64 -0
  96. package/dist/inventory/validateNormalized.js.map +1 -0
  97. package/dist/inventory/varValidation.d.ts +2 -0
  98. package/dist/inventory/varValidation.js +60 -0
  99. package/dist/inventory/varValidation.js.map +1 -0
  100. package/dist/inventory/writePolicy.d.ts +2 -0
  101. package/dist/inventory/writePolicy.js +36 -0
  102. package/dist/inventory/writePolicy.js.map +1 -0
  103. package/dist/logging/loggingQuery.d.ts +15 -0
  104. package/dist/logging/loggingQuery.js +38 -0
  105. package/dist/logging/loggingQuery.js.map +1 -0
  106. package/dist/logging/showLogging.d.ts +28 -0
  107. package/dist/logging/showLogging.js +37 -0
  108. package/dist/logging/showLogging.js.map +1 -0
  109. package/dist/mcp/createServer.d.ts +3 -0
  110. package/dist/mcp/createServer.js +82 -0
  111. package/dist/mcp/createServer.js.map +1 -0
  112. package/dist/mcp/toolResult.d.ts +7 -0
  113. package/dist/mcp/toolResult.js +13 -0
  114. package/dist/mcp/toolResult.js.map +1 -0
  115. package/dist/mcp/tools/getFacts.d.ts +21 -0
  116. package/dist/mcp/tools/getFacts.js +17 -0
  117. package/dist/mcp/tools/getFacts.js.map +1 -0
  118. package/dist/mcp/tools/getRunningConfig.d.ts +21 -0
  119. package/dist/mcp/tools/getRunningConfig.js +17 -0
  120. package/dist/mcp/tools/getRunningConfig.js.map +1 -0
  121. package/dist/mcp/tools/getServerInfo.d.ts +10 -0
  122. package/dist/mcp/tools/getServerInfo.js +9 -0
  123. package/dist/mcp/tools/getServerInfo.js.map +1 -0
  124. package/dist/mcp/tools/listInventory.d.ts +16 -0
  125. package/dist/mcp/tools/listInventory.js +15 -0
  126. package/dist/mcp/tools/listInventory.js.map +1 -0
  127. package/dist/mcp/tools/probeDevices.d.ts +21 -0
  128. package/dist/mcp/tools/probeDevices.js +14 -0
  129. package/dist/mcp/tools/probeDevices.js.map +1 -0
  130. package/dist/mcp/tools/runShow.d.ts +30 -0
  131. package/dist/mcp/tools/runShow.js +34 -0
  132. package/dist/mcp/tools/runShow.js.map +1 -0
  133. package/dist/mcp/tools/showLogging.d.ts +24 -0
  134. package/dist/mcp/tools/showLogging.js +20 -0
  135. package/dist/mcp/tools/showLogging.js.map +1 -0
  136. package/dist/operations/readExecution.d.ts +57 -0
  137. package/dist/operations/readExecution.js +132 -0
  138. package/dist/operations/readExecution.js.map +1 -0
  139. package/dist/probe/probeDevices.d.ts +24 -0
  140. package/dist/probe/probeDevices.js +24 -0
  141. package/dist/probe/probeDevices.js.map +1 -0
  142. package/dist/serverInfo/buildServerInfo.d.ts +2 -0
  143. package/dist/serverInfo/buildServerInfo.js +49 -0
  144. package/dist/serverInfo/buildServerInfo.js.map +1 -0
  145. package/dist/serverInfo/types.d.ts +46 -0
  146. package/dist/serverInfo/types.js +2 -0
  147. package/dist/serverInfo/types.js.map +1 -0
  148. package/dist/show/runShow.d.ts +29 -0
  149. package/dist/show/runShow.js +40 -0
  150. package/dist/show/runShow.js.map +1 -0
  151. package/dist/utils/json.d.ts +1 -0
  152. package/dist/utils/json.js +4 -0
  153. package/dist/utils/json.js.map +1 -0
  154. package/dist/utils/path.d.ts +2 -0
  155. package/dist/utils/path.js +14 -0
  156. package/dist/utils/path.js.map +1 -0
  157. package/dist/utils/value.d.ts +6 -0
  158. package/dist/utils/value.js +28 -0
  159. package/dist/utils/value.js.map +1 -0
  160. package/docs/CHECKPOINT.md +46 -0
  161. package/docs/CODE_WALKTHROUGH.md +112 -0
  162. package/docs/DESIGN.md +56 -0
  163. package/docs/DEVELOPMENT.md +72 -0
  164. package/docs/EXAMPLES.md +178 -0
  165. package/docs/IMPLEMENTATION_PLAN.md +32 -0
  166. package/docs/QUICKSTART.md +152 -0
  167. package/example-inventories/canonical-fabric.yaml +45 -0
  168. package/example-inventories/multi-password.yaml +35 -0
  169. package/example-inventories/simplified-lab.yaml +33 -0
  170. package/package.json +57 -0
@@ -0,0 +1,29 @@
1
+ import type { ResolvedServerConfig } from "../config/schema.js";
2
+ import type { EosCommandResult } from "../eapi/types.js";
3
+ import type { EosDeviceReader } from "../connection/eosDeviceReader.js";
4
+ import type { InventoryModel } from "../inventory/types.js";
5
+ import { type DeviceResultSummary } from "../operations/readExecution.js";
6
+ export interface RunShowOptions {
7
+ target: string;
8
+ commands: string[];
9
+ outputFormat: "auto" | "json" | "text";
10
+ includeRaw?: boolean;
11
+ }
12
+ export type NormalizedCommandResult = EosCommandResult;
13
+ export interface RunShowResult {
14
+ target: string;
15
+ target_type: "host" | "group";
16
+ requested_output_format: "auto" | "json" | "text";
17
+ resolved_devices: string[];
18
+ summary: DeviceResultSummary;
19
+ results: Array<{
20
+ inventory_hostname: string;
21
+ resolved_endpoint: string;
22
+ status: "success" | "failed";
23
+ actual_output_format?: "json" | "text";
24
+ command_results?: NormalizedCommandResult[];
25
+ error_code?: string;
26
+ message?: string;
27
+ }>;
28
+ }
29
+ export declare function runShow(model: InventoryModel, config: ResolvedServerConfig, options: RunShowOptions, reader: EosDeviceReader): Promise<RunShowResult>;
@@ -0,0 +1,40 @@
1
+ import { AppError } from "../core/errors.js";
2
+ import { normalizeShowCommand } from "../eapi/commands.js";
3
+ import { buildReadDeviceFailure, buildReadDeviceSuccess, buildReadOperationResultEnvelope, enforceShowCommandLimit, executeReadOperation } from "../operations/readExecution.js";
4
+ export async function runShow(model, config, options, reader) {
5
+ const commands = normalizeShowCommands(options.commands);
6
+ enforceShowCommandLimit(config, commands.length);
7
+ const operation = await executeReadOperation(model, config, {
8
+ target: options.target,
9
+ operationName: "eos_run_show",
10
+ run: async (host, signal) => {
11
+ const commandResults = await reader.runShowCommands(host, commands, options.outputFormat, {
12
+ signal,
13
+ includeRawEntries: options.includeRaw === true
14
+ });
15
+ const firstResult = commandResults[0];
16
+ return buildReadDeviceSuccess(host, {
17
+ actual_output_format: firstResult?.output_format ?? "json",
18
+ command_results: commandResults
19
+ });
20
+ },
21
+ onError: (host, error) => buildReadDeviceFailure(host, "show_command_failed", error)
22
+ });
23
+ return buildReadOperationResultEnvelope(options.target, operation, {
24
+ extra: {
25
+ requested_output_format: options.outputFormat
26
+ },
27
+ responseSizeLimit: {
28
+ config,
29
+ operationName: "eos_run_show",
30
+ narrowingGuidance: "Reduce the number of target devices, use fewer commands per request, or request text format for more concise output."
31
+ }
32
+ });
33
+ }
34
+ function normalizeShowCommands(commands) {
35
+ if (commands.length === 0) {
36
+ throw new AppError("show_commands_missing", "runShow requires at least one command");
37
+ }
38
+ return commands.map((command) => normalizeShowCommand(command));
39
+ }
40
+ //# sourceMappingURL=runShow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runShow.js","sourceRoot":"","sources":["../../src/show/runShow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAI3D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,gCAAgC,EAChC,uBAAuB,EACvB,oBAAoB,EAErB,MAAM,gCAAgC,CAAC;AA4BxC,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAqB,EACrB,MAA4B,EAC5B,OAAuB,EACvB,MAAuB;IAEvB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEjD,MAAM,SAAS,GAAG,MAAM,oBAAoB,CAAmC,KAAK,EAAE,MAAM,EAAE;QAC5F,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,aAAa,EAAE,cAAc;QAC7B,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YAC1B,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,YAAY,EAAE;gBACxF,MAAM;gBACN,iBAAiB,EAAE,OAAO,CAAC,UAAU,KAAK,IAAI;aAC/C,CAAC,CAAC;YACH,MAAM,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;YAEtC,OAAO,sBAAsB,CAAC,IAAI,EAAE;gBAClC,oBAAoB,EAAE,WAAW,EAAE,aAAa,IAAI,MAAM;gBAC1D,eAAe,EAAE,cAAc;aAChC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,EAAE,qBAAqB,EAAE,KAAK,CAAC;KACrF,CAAC,CAAC;IAEH,OAAO,gCAAgC,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE;QACjE,KAAK,EAAE;YACL,uBAAuB,EAAE,OAAO,CAAC,YAAY;SAC9C;QACD,iBAAiB,EAAE;YACjB,MAAM;YACN,aAAa,EAAE,cAAc;YAC7B,iBAAiB,EAAE,sHAAsH;SAC1I;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAkB;IAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAAC,uBAAuB,EAAE,uCAAuC,CAAC,CAAC;IACvF,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function prettyJson(value: unknown): string;
@@ -0,0 +1,4 @@
1
+ export function prettyJson(value) {
2
+ return JSON.stringify(value, null, 2);
3
+ }
4
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/utils/json.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function resolvePathFromConfig(configPath: string, value: string): string;
2
+ export declare function resolvePathFromCwd(value: string): string;
@@ -0,0 +1,14 @@
1
+ import path from "node:path";
2
+ export function resolvePathFromConfig(configPath, value) {
3
+ if (path.isAbsolute(value)) {
4
+ return value;
5
+ }
6
+ return path.resolve(path.dirname(configPath), value);
7
+ }
8
+ export function resolvePathFromCwd(value) {
9
+ if (path.isAbsolute(value)) {
10
+ return value;
11
+ }
12
+ return path.resolve(process.cwd(), value);
13
+ }
14
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,qBAAqB,CAAC,UAAkB,EAAE,KAAa;IACrE,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function readString(value: unknown): string | undefined;
2
+ export declare function readNonEmptyString(value: unknown): string | undefined;
3
+ export declare function readBoolean(value: unknown): boolean | undefined;
4
+ export declare function readNumber(value: unknown): number | undefined;
5
+ export declare function readPositiveInteger(value: unknown): number | undefined;
6
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,28 @@
1
+ export function readString(value) {
2
+ return typeof value === "string" ? value : undefined;
3
+ }
4
+ export function readNonEmptyString(value) {
5
+ return typeof value === "string" && value.length > 0 ? value : undefined;
6
+ }
7
+ export function readBoolean(value) {
8
+ return typeof value === "boolean" ? value : undefined;
9
+ }
10
+ export function readNumber(value) {
11
+ return typeof value === "number" ? value : undefined;
12
+ }
13
+ export function readPositiveInteger(value) {
14
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
15
+ return value;
16
+ }
17
+ if (typeof value === "string" && value.length > 0) {
18
+ const parsed = Number(value);
19
+ if (Number.isInteger(parsed) && parsed > 0) {
20
+ return parsed;
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ export function isObject(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+ //# sourceMappingURL=value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value.js","sourceRoot":"","sources":["../../src/utils/value.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,46 @@
1
+ # EOS MCP Server Checkpoint
2
+
3
+ ## Current state
4
+
5
+ The server is a test-backed, read-only MCP service for Arista EOS eAPI. The product boundary is permanent: configuration-changing operations are outside the implementation and specification.
6
+
7
+ ## Implemented surface
8
+
9
+ ### MCP tools
10
+
11
+ - `eos_get_server_info`
12
+ - `eos_list_inventory`
13
+ - `eos_probe_devices`
14
+ - `eos_run_show`
15
+ - `eos_show_logging`
16
+ - `eos_get_facts`
17
+ - `eos_get_running_config`
18
+
19
+ ### Local commands
20
+
21
+ - `serve`
22
+ - `validate-inventory`
23
+ - `print-server-info`
24
+ - `probe`
25
+
26
+ ## Safety and operational controls
27
+
28
+ - Inventory-only host and group targeting.
29
+ - Explicit EOS eligibility and inherited read-policy enforcement.
30
+ - Fail-closed groups containing ineligible or read-denied hosts.
31
+ - Strict password-source and environment-prefix checks.
32
+ - HTTPS with certificate validation by default and optional custom CA support.
33
+ - Validated show commands and bounded logging/running-configuration queries.
34
+ - Read-operation concurrency, target, response-size, and timeout controls.
35
+ - Sanitized result envelopes with stable machine-readable errors.
36
+
37
+ ## Re-entry reading
38
+
39
+ 1. [`README.md`](../README.md) for operator usage.
40
+ 2. [`docs/DESIGN.md`](DESIGN.md) for the authoritative specification.
41
+ 3. [`docs/CODE_WALKTHROUGH.md`](CODE_WALKTHROUGH.md) for source navigation.
42
+ 4. [`src/operations/readExecution.ts`](../src/operations/readExecution.ts) for shared device operation controls.
43
+
44
+ ## Validation
45
+
46
+ Run `npm test`, `npm run typecheck`, and `npm run build` after changes. Optional integration tests require the configured cEOS environment.
@@ -0,0 +1,112 @@
1
+ # Code Walkthrough
2
+
3
+ ## Top Level
4
+
5
+ This repository is a single TypeScript/Node package.
6
+
7
+ - `src/` contains all source code.
8
+ - `test/` contains Vitest tests.
9
+ - `dist/` is generated build output from `tsc`.
10
+ - `docs/` contains project documentation (design, checkpoint, implementation plan, etc.).
11
+ - `Makefile` wraps build, lint/static policy checks, test, typecheck, clean, and packaging.
12
+ - `package.json` defines scripts: `npm test`, `npm run typecheck`, `npm run lint`, and `npm run build`. The package `prepack` hook runs `npm run build`.
13
+ - `scripts/static-policy.mjs` performs lightweight source policy checks for banned patterns such as plain `throw new Error`, `@ts-ignore`, `eslint-disable`, and `as any`.
14
+
15
+ ## Runtime Entry
16
+
17
+ Execution starts at `src/index.ts`. It parses CLI args, loads config, then dispatches one of four commands:
18
+
19
+ - `serve`: load inventory, validate startup credentials, start MCP stdio server.
20
+ - `validate-inventory`: validate inventory and print the result.
21
+ - `print-server-info`: print sanitized server metadata.
22
+ - `probe`: run the same probe logic as the MCP probe tool.
23
+
24
+ CLI parsing lives in `src/cli.ts`. The app name and version are read from `package.json` at runtime via `src/core/version.ts`. Shared error handling lives in `src/core/errors.ts`, which defines `AppError` and the stable machine-readable error codes used across the CLI and MCP layers.
25
+
26
+ ## Config And Inventory
27
+
28
+ Config loading is in `src/config/loadConfig.ts`, with Zod schemas in `src/config/schema.ts`. It merges optional config-file values with CLI overrides and resolves paths.
29
+
30
+ `src/inventory/loadInventory.ts` orchestrates inventory loading. It supports simplified YAML and canonical Ansible-style YAML by delegating to a pipeline: parsing (`src/inventory/parse.ts`), schema detection and normalization (`src/inventory/normalize.ts`), structural and variable-type validation (`src/inventory/validateNormalized.ts`, `src/inventory/varValidation.ts`), and model construction (`src/inventory/buildModel.ts`). Together these validate structure, build effective host vars, resolve groups, and produce an `InventoryModel`.
31
+
32
+ Supporting inventory modules:
33
+
34
+ - `src/inventory/types.ts`: public inventory model types.
35
+ - `src/inventory/internalTypes.ts` / `src/inventory/internalUtils.ts`: shared types and helpers used across the parsing/normalization/validation pipeline.
36
+ - `src/inventory/effectiveVars.ts`: global/group/host variable inheritance and same-depth conflict handling.
37
+ - `src/inventory/graph.ts`: group parent maps, group depths, host lineage, direct memberships, and resolved group hosts.
38
+ - `src/inventory/policy.ts`: EOS eligibility and read access evaluation, including the `mcp_read_allowed` fail-closed check.
39
+ - `src/inventory/resolveTarget.ts`: host/group target resolution and policy fail-closed checks.
40
+ - `src/inventory/listInventoryView.ts`: sanitized inventory view for MCP output.
41
+
42
+ ## Connection And eAPI
43
+
44
+ Connection resolution is in `src/connection/resolveConnection.ts`. It turns effective host vars plus server config into an eAPI connection: endpoint, username, password, TLS flags, CA file, timeout, eAPI output version, and response-size limit.
45
+
46
+ `src/connection/eosDeviceReader.ts` defines the host-oriented `EosDeviceReader` interface. Its `EapiDeviceReader` adapter owns per-host connection resolution and delegates command execution to the low-level eAPI runner.
47
+
48
+ Startup credential validation is in `src/connection/validateStartupConnections.ts`.
49
+
50
+ eAPI protocol and transport are split:
51
+
52
+ - `src/eapi/client.ts`: builds JSON-RPC `runCmds` requests using the connection's `eapiVersion` (`"latest"` by default, `1` when pinned in config), handles HTTP errors, invalid JSON, JSON-RPC errors, and unexpected result counts. Supports opt-in enable mode (`{ enable: true }`) for privileged commands by prepending `enable` to the wire commands and stripping the extra result entry transparently. Per-request timeout and caller cancellation are combined with `AbortSignal`.
53
+ - `src/eapi/transport.ts`: production HTTPS transport and test-friendly fetch transport. The production transport enforces `maxResponseSizeBytes` while chunks are received, before buffering an oversized device response.
54
+ - `src/eapi/types.ts`: shared eAPI types, the low-level `EosCommandRunner` interface used by `EapiDeviceReader`, `EapiCommandOptions`, and helpers for validating/drilling into eAPI JSON-RPC response payloads.
55
+
56
+ ## Read Tool Services
57
+
58
+ The core read behavior is implemented as service modules:
59
+
60
+ - `src/probe/probeDevices.ts`: runs `show version` to verify readiness.
61
+ - `src/eapi/commands.ts`: normalizes single-line EOS command inputs, rejects risky CLI modifiers/metacharacters, and validates `show` command boundaries.
62
+ - `src/show/runShow.ts`: validates strict `show` commands and runs them with `auto`, `json`, or `text` behavior.
63
+ - `src/logging/loggingQuery.ts`: validates Log Severity and message-count inputs, applies logging defaults/limits, and builds the EOS logging command.
64
+ - `src/logging/showLogging.ts`: executes the bounded Logging Query and returns unparsed log text.
65
+ - `src/facts/getFacts.ts`: collects fixed facts from `show version`.
66
+ - `src/configuration/getRunningConfig.ts`: returns running config text, requiring `section` for group targets. Uses enable mode since `show running-config` requires privileged access.
67
+
68
+ All device-facing read services accept an `EosDeviceReader` (defined in `src/connection/eosDeviceReader.ts`). They provide inventory hosts and command intent; the eAPI adapter resolves each host's connection before executing commands.
69
+
70
+ Shared read orchestration lives in `src/operations/readExecution.ts`. It centralizes target resolution, target limits, concurrency, overall timeout cancellation, common per-device result fields, result summaries (including the shared `DeviceResultSummary` type used by all result interfaces), final result-envelope response-size enforcement, and per-device `AppError.code` preservation.
71
+
72
+ Shared type-narrowing utilities (`isObject`, `readString`, `readBoolean`, `readNumber`, etc.) live in `src/utils/value.ts`.
73
+
74
+ ## MCP Layer
75
+
76
+ The MCP server is assembled in `src/mcp/createServer.ts`. It creates the official SDK `McpServer`, registers seven read-only tools, and connects over stdio.
77
+
78
+ Tool adapters live under `src/mcp/tools/`:
79
+
80
+ - `getServerInfo.ts`
81
+ - `listInventory.ts`
82
+ - `probeDevices.ts`
83
+ - `runShow.ts`
84
+ - `showLogging.ts`
85
+ - `getFacts.ts`
86
+ - `getRunningConfig.ts`
87
+
88
+ These adapters define Zod input schemas, call the service layer, and return both text JSON and structured MCP content.
89
+
90
+ ## Server Info And Commands
91
+
92
+ Server metadata is built by `src/serverInfo/buildServerInfo.ts`, with types in `src/serverInfo/types.ts`.
93
+
94
+ Local CLI commands live in `src/commands/`:
95
+
96
+ - `validateInventory.ts`
97
+ - `printServerInfo.ts`
98
+ - `probe.ts`
99
+
100
+ ## Tests
101
+
102
+ Tests mirror the source layout:
103
+
104
+ - CLI and config: `cli.test.ts`, `loadConfig.test.ts`, `validateInventoryCommand.test.ts`
105
+ - Inventory and target behavior: `inventory.test.ts`, `targetResolution.test.ts`
106
+ - eAPI and connection behavior: `eapiClient.test.ts`, `eosDeviceReader.test.ts`, `startupValidation.test.ts`, `responseSize.test.ts`
107
+ - Read services and orchestration: `probeDevices.test.ts`, `runShow.test.ts`, `loggingQuery.test.ts`, `showLogging.test.ts`, `getFacts.test.ts`, `getRunningConfig.test.ts`, `readExecution.test.ts`
108
+ - Server info and errors: `buildServerInfo.test.ts`, `errors.test.ts`, `errorTaxonomy.test.ts`
109
+ - MCP adapters and stdio: `readToolsMcp.test.ts`, `probeDevicesTool.test.ts`, `listInventoryTool.test.ts`, `mcpStdioSmoke.test.ts`
110
+ - Package/static policy: `packagePolicy.test.ts`, `packageSmoke.test.ts`
111
+ - Optional cEOS integration (requires a configured lab): `integration.test.ts`
112
+ - Shared test helpers: `test/helpers.ts`
package/docs/DESIGN.md ADDED
@@ -0,0 +1,56 @@
1
+ # EOS MCP Server Design Specification
2
+
3
+ ## Scope
4
+
5
+ EOS MCP Server is an ESM TypeScript MCP server that provides inventory-scoped, read-only access to Arista EOS devices over eAPI (HTTPS). It runs over stdio MCP transport and is permanently read-only: the implementation exposes operational inspection and diagnostics only, and never exposes configuration-changing operations.
6
+
7
+ ## Tool surface
8
+
9
+ | Tool | Purpose |
10
+ | --- | --- |
11
+ | `eos_get_server_info` | Sanitized runtime, limits, and inventory summary |
12
+ | `eos_list_inventory` | Eligible hosts and groups |
13
+ | `eos_probe_devices` | Connectivity, authentication, and harmless command check |
14
+ | `eos_run_show` | Validated single-line `show` commands |
15
+ | `eos_show_logging` | Bounded logging retrieval |
16
+ | `eos_get_facts` | Fixed device facts |
17
+ | `eos_get_running_config` | Running configuration retrieval, with a required section selector for groups |
18
+
19
+ Local CLI commands provide inventory validation, server-info output, and probing.
20
+
21
+ ## Target boundary
22
+
23
+ - A target is exactly one inventory host or group name.
24
+ - Address, hostname, pattern, and ad-hoc targets are rejected.
25
+ - Host and group names use `^[A-Za-z0-9_.-]+$` and cannot collide.
26
+ - Group hosts are deduplicated and ordered alphabetically.
27
+ - Canonical inventories merge repeated group declarations with host/child union and later-wins variables.
28
+
29
+ ## Inventory and policy
30
+
31
+ Canonical Ansible-style YAML and simplified YAML are supported. Structural keys are strict; arbitrary variables remain permitted. EOS eligibility requires `ansible_network_os: eos` or `mcp_platform: arista_eos`; conflicting declarations fail validation.
32
+
33
+ `mcp_read_allowed` defaults to `true` for eligible EOS hosts and follows inherited inventory variable precedence. Operational requests fail closed when any resolved host is ineligible or read-denied — a mixed or ineligible target fails as a whole, and devices are never silently skipped. The inventory is the only policy source.
34
+
35
+ ## Connection security
36
+
37
+ - HTTPS is required.
38
+ - Certificate validation defaults to enabled; inventory may set an explicit lab override and server config may provide a CA bundle.
39
+ - A host resolves exactly one password source: `mcp_password_env` or `ansible_password`. The environment reference is preferred and must match a configured prefix (`EOS_MCP_` by default).
40
+ - Startup validation verifies connection data for all eligible hosts.
41
+
42
+ ## Command safety
43
+
44
+ `eos_run_show` accepts only trimmed, single-line commands beginning with `show`. It rejects control characters, shell metacharacters, and CLI output modifiers. `eos_get_running_config.section` uses the same single-line restrictions. Enable mode is used only as needed to retrieve running configuration.
45
+
46
+ ## Runtime controls
47
+
48
+ 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.
49
+
50
+ ## Result and error model
51
+
52
+ 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 diagnostic tools expose raw eAPI payloads only through an explicit `include_raw` option.
53
+
54
+ ## Compatibility and validation
55
+
56
+ The read surface is validated against cEOS 4.34.3M. EOS 4.20 is the documented minimum for supported read operations. Changes must preserve strict TypeScript checks and pass the unit, stdio smoke, and package-policy test suites.
@@ -0,0 +1,72 @@
1
+ # Developer workflow
2
+
3
+ This guide is for contributors and for users who need to build `eos-mcp-server` from a source checkout. For a normal installation, download a package from the [latest GitHub release](https://github.com/aristanetworks/eos-mcp-server/releases/latest) and follow the [README](../README.md).
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 24+
8
+ - npm
9
+
10
+ ## Build from source
11
+
12
+ From the repository root:
13
+
14
+ ```bash
15
+ npm install
16
+ make build
17
+ ```
18
+
19
+ Run the built CLI directly:
20
+
21
+ ```bash
22
+ node dist/index.js serve --inventory path/to/inventory.yml
23
+ ```
24
+
25
+ For development without compiling first:
26
+
27
+ ```bash
28
+ npm run dev -- serve --inventory path/to/inventory.yml
29
+ ```
30
+
31
+ ## Make a local installation
32
+
33
+ To expose the command from a checkout:
34
+
35
+ ```bash
36
+ npm install
37
+ make build
38
+ npm link
39
+ ```
40
+
41
+ Or install the checkout directly:
42
+
43
+ ```bash
44
+ npm install -g /absolute/path/to/eos-mcp-server
45
+ ```
46
+
47
+ Remove a linked installation later with:
48
+
49
+ ```bash
50
+ npm unlink -g @aristanetworks/eos-mcp-server
51
+ ```
52
+
53
+ ## Package a release artifact
54
+
55
+ ```bash
56
+ make pack
57
+ ```
58
+
59
+ This performs a clean build and produces `aristanetworks-eos-mcp-server-<version>.tgz`, which can be installed with `npm install -g ./aristanetworks-eos-mcp-server-<version>.tgz`. `npm pack` also rebuilds through the `prepack` hook.
60
+
61
+ ## Publish to npm
62
+
63
+ The package is published as `@aristanetworks/eos-mcp-server`. Pushing a `v*` tag runs `.github/workflows/release.yml`, which tests, packs, publishes to npm through [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC, no token secret), and creates the GitHub release. Tags containing `-alpha`, `-beta`, or `-rc` publish under the `next` dist-tag; other tags publish as `latest`. Published versions carry npm provenance attestations.
64
+
65
+ ## Verify changes
66
+
67
+ ```bash
68
+ make test
69
+ make typecheck
70
+ ```
71
+
72
+ The repository's [AGENTS.md](../AGENTS.md) lists all common commands and architectural guidance.
@@ -0,0 +1,178 @@
1
+ # EOS MCP Server — Example Prompts
2
+
3
+ These examples show how to interact with the EOS MCP server through Claude. All examples are drawn from a real cEOSLab testlab session.
4
+
5
+ This assumes `eos-mcp-server` is already installed and you have an inventory file — see [Install and get started](../README.md#install-and-get-started) or the [Quickstart](QUICKSTART.md) if not.
6
+
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ### Add the MCP server from `.mcp.json`
12
+
13
+ As an alternative to `claude mcp add` (see the README's [Connect to an MCP client](../README.md#connect-to-an-mcp-client)), place a `.mcp.json` file in your project directory — Claude Code auto-discovers it without any import command:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "eos": {
19
+ "command": "eos-mcp-server",
20
+ "args": [
21
+ "serve",
22
+ "--inventory",
23
+ "./ansible-inventory.yml"
24
+ ],
25
+ "env": {
26
+ "EOS_MCP_PASSWORD": "replace-with-device-password"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Inventory
36
+
37
+ ### List all devices and groups
38
+
39
+ > What devices are in the inventory?
40
+
41
+ > List the inventory
42
+
43
+ ### Check connectivity to all devices
44
+
45
+ > Probe all the EOS devices in inventory
46
+
47
+ Example output: 8/8 devices reachable, showing hostname, endpoint IP, and EOS version.
48
+
49
+ ---
50
+
51
+ ## Device Facts
52
+
53
+ ### Collect facts across the entire fabric
54
+
55
+ > Get facts for all the EOS devices
56
+
57
+ Returns model, EOS version, serial number, uptime, and system MAC for every host.
58
+
59
+ ---
60
+
61
+ ## Topology
62
+
63
+ ### Generate a topology diagram from LLDP
64
+
65
+ > Create a topology diagram for all the devices using LLDP neighbors
66
+
67
+ Claude queries `show lldp neighbors` across all devices, infers the topology, and renders a Mermaid diagram. Example output for a 2-tier spine-leaf lab:
68
+
69
+ ```mermaid
70
+ graph TB
71
+ subgraph tier1["Tier 1 — Spine"]
72
+ node1-1["node1-1\n172.20.20.2"]
73
+ node1-2["node1-2\n172.20.20.6"]
74
+ end
75
+
76
+ subgraph tier2["Tier 2 — Leaf"]
77
+ node2-1["node2-1\n172.20.20.4"]
78
+ node2-2["node2-2\n172.20.20.5"]
79
+ node2-3["node2-3\n172.20.20.9"]
80
+ node2-4["node2-4\n172.20.20.7"]
81
+ node2-5["node2-5\n172.20.20.3"]
82
+ node2-6["node2-6\n172.20.20.8"]
83
+ end
84
+
85
+ node1-1 -- "Et1 ↔ Et1" --- node2-1
86
+ node1-1 -- "Et2 ↔ Et1" --- node2-2
87
+ node1-1 -- "Et3 ↔ Et1" --- node2-3
88
+ node1-1 -- "Et4 ↔ Et1" --- node2-4
89
+ node1-1 -- "Et5 ↔ Et1" --- node2-5
90
+ node1-1 -- "Et6 ↔ Et1" --- node2-6
91
+
92
+ node1-2 -- "Et1 ↔ Et2" --- node2-1
93
+ node1-2 -- "Et2 ↔ Et2" --- node2-2
94
+ node1-2 -- "Et3 ↔ Et2" --- node2-3
95
+ node1-2 -- "Et4 ↔ Et2" --- node2-4
96
+ node1-2 -- "Et5 ↔ Et2" --- node2-5
97
+ node1-2 -- "Et6 ↔ Et2" --- node2-6
98
+ ```
99
+
100
+ ---
101
+
102
+ ## VXLAN
103
+
104
+ ### Check VXLAN status across all leaf switches
105
+
106
+ > Check on the VXLAN status for all the leaf switches
107
+
108
+ Claude runs `show vxlan vtep`, `show vxlan vni`, and `show interfaces Vxlan1` and summarizes:
109
+
110
+ - VTEP source IP and interface per leaf
111
+ - VNI-to-VLAN mappings
112
+ - Replication mode (headend VCS vs multicast)
113
+ - Flood list membership
114
+ - Interface line protocol status
115
+
116
+ ### Example findings
117
+
118
+ ```
119
+ node2-1 VTEP 192.168.2.3 Loopback1 VNI 1100 → VLAN 100 up
120
+ node2-2 VTEP 192.168.2.4 Loopback1 VNI 1100 → VLAN 100 up
121
+ node2-3 VTEP 192.168.2.5 Loopback1 VNI 1100 → VLAN 100 up
122
+ node2-4 VTEP 192.168.2.6 Loopback1 VNI 1100 → VLAN 100 up
123
+ node2-5 VTEP 192.168.2.7 Loopback1 VNI 1100 → VLAN 100 up
124
+ node2-6 VTEP 192.168.2.8 Loopback1 VNI 1100 → VLAN 100 up
125
+ ```
126
+
127
+ ---
128
+
129
+ ## BGP EVPN
130
+
131
+ ### Check EVPN session and route state
132
+
133
+ > Check on EVPN
134
+
135
+ Claude runs `show bgp evpn summary` and `show bgp evpn` across all leaves and summarizes:
136
+
137
+ - Session state per peer (Established / Idle / etc.)
138
+ - Prefixes received and advertised per peer
139
+ - EVPN route types present (Type-2 MAC/IP, Type-3 IMET, etc.)
140
+ - ECMP path count per remote VTEP
141
+ - AS path to identify route reflectors and leaf ASNs
142
+
143
+ ### Example findings
144
+
145
+ All 6 leaves peered to both spines (AS 65000), each leaf with a unique ASN (65001–65006). Only Type-3 IMET routes present — flood list built from EVPN, dual-path ECMP via both spines confirmed.
146
+
147
+ ---
148
+
149
+ ## VLANs
150
+
151
+ ### Inspect a specific VLAN across all leaves
152
+
153
+ > What can you tell me about VLAN 100?
154
+
155
+ Claude runs `show vlan 100`, `show mac address-table vlan 100`, and `show interfaces Vlan100` across all leaves and reports:
156
+
157
+ - VLAN name and status on each leaf
158
+ - Which ports are members (access ports, trunk ports, VXLAN tunnel)
159
+ - MAC address table entries (learned hosts)
160
+ - Whether an SVI exists and its IP/state
161
+
162
+ ### Example findings
163
+
164
+ VLAN 100 (`VLAN100`) active on all leaves, member of `Vx1` only, empty MAC table, no SVI — fully provisioned L2 overlay segment with no hosts connected yet.
165
+
166
+ ---
167
+
168
+ ## Available Tools
169
+
170
+ | Tool | Description |
171
+ |---|---|
172
+ | `eos_probe_devices` | Validate connectivity, auth, and eAPI reachability |
173
+ | `eos_list_inventory` | Show eligible hosts and groups from the loaded inventory |
174
+ | `eos_get_facts` | Collect model, version, serial, uptime, system MAC |
175
+ | `eos_get_running_config` | Retrieve running config (full for hosts, section for groups) |
176
+ | `eos_run_show` | Run arbitrary `show ...` commands; returns JSON or text |
177
+ | `eos_show_logging` | Retrieve bounded logging output with minimum severity threshold |
178
+ | `eos_get_server_info` | Show server capabilities, runtime mode, inventory summary |
@@ -0,0 +1,32 @@
1
+ # EOS MCP Server Implementation Plan
2
+
3
+ ## Product boundary
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.
6
+
7
+ ## Implemented work
8
+
9
+ - TypeScript ESM CLI and stdio MCP server.
10
+ - Canonical Ansible-style and simplified YAML inventories.
11
+ - Strict inventory validation, inherited variables, EOS eligibility, and fail-closed read policy.
12
+ - Inventory-authoritative host/group target resolution.
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.
15
+ - Local `validate-inventory`, `print-server-info`, and `probe` commands.
16
+ - Bounded concurrency, target counts, command counts, logging messages, device responses, and aggregate tool responses.
17
+ - Unit, MCP stdio smoke, package-policy, and optional cEOS integration coverage.
18
+
19
+ ## Ongoing priorities
20
+
21
+ 1. Preserve the read-only tool boundary in schemas, CLI, inventory model, and MCP registration.
22
+ 2. Maintain strict input validation and stable `AppError` codes.
23
+ 3. Expand read-side test coverage when adding supported EOS query behavior.
24
+ 4. Keep operator documentation synchronized with the tool and configuration surface.
25
+ 5. Validate supported read behavior against maintained EOS/cEOS versions.
26
+
27
+ ## Completion criteria
28
+
29
+ - Every registered MCP tool is read-only and inventory-scoped.
30
+ - All device-facing paths use the shared target, connection, timeout, and result-envelope safeguards.
31
+ - Documentation lists only supported tools and configuration fields.
32
+ - `npm test`, `npm run typecheck`, and `npm run build` pass.