@solomonneas/librenms-mcp 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -4,9 +4,9 @@ MCP server exposing LibreNMS read + safe-write tools via API token auth. Three-t
4
4
 
5
5
  ## Tools
6
6
 
7
- **Reads (8):** `librenms_status`, `librenms_list_devices`, `librenms_get_device`, `librenms_list_ports`, `librenms_port_health`, `librenms_list_alerts`, `librenms_get_alert`, `librenms_alert_history`.
7
+ **Reads (10):** `librenms_status`, `librenms_list_devices`, `librenms_get_device`, `librenms_list_ports`, `librenms_get_port`, `librenms_port_health`, `librenms_list_alerts`, `librenms_get_alert`, `librenms_alert_history`, `librenms_event_log`.
8
8
 
9
- **Safe writes (2, require `confirm: true`):** `librenms_ack_alert`, `librenms_set_maintenance`.
9
+ **Safe writes (3, require `confirm: true`):** `librenms_ack_alert`, `librenms_unmute_alert`, `librenms_set_maintenance`.
10
10
 
11
11
  **Destructive (tier 3):** not in v1. Operations like device deletion, alert rule removal, and bulk port resets are intentionally absent until the gate pattern has more field time.
12
12
 
@@ -120,8 +120,8 @@ LIBRENMS_TLS_INSECURE = "false"
120
120
 
121
121
  This MCP uses the same three-tier write-gating pattern as the rest of the `solomonneas/*-mcp` family:
122
122
 
123
- - **Tier 1 (reads):** open. No confirm flag needed. Status, device + port listings, port health, alert listings, alert history.
124
- - **Tier 2 (safe writes):** require an explicit `confirm: true` arg. The JSON schema documents this on every write tool. Alert acknowledge and device maintenance toggling live here. A hallucinated tool call without the confirm flag throws `WriteGateError` before any HTTP traffic.
123
+ - **Tier 1 (reads):** open. No confirm flag needed. Status, device + port listings, single-port detail, port health, alert listings, alert history, event log.
124
+ - **Tier 2 (safe writes):** require an explicit `confirm: true` arg. The JSON schema documents this on every write tool. Alert acknowledge/unmute and entering device maintenance live here. A hallucinated tool call without the confirm flag throws `WriteGateError` before any HTTP traffic.
125
125
  - **Tier 3 (destructive):** not implemented in v1. When added, ops like device deletion, alert rule removal, and bulk port resets will additionally require `destructive: true`. The model cannot bypass either gate from a hallucinated call.
126
126
 
127
127
  **API token scope recommendation:** start with a "Read Only" token role in LibreNMS (Settings > API > New API Token > Read Only) and verify the read tools work end-to-end. Grade up to "Normal User" or "Global Read/Write" only after you've confirmed the redactor is masking your token in your transcripts and that the model is honoring the confirm gate. Tokens can be revoked instantly from the same Settings > API screen.
package/dist/index.js CHANGED
@@ -65,6 +65,9 @@ var LibreNmsClient = class {
65
65
  async put(path, body) {
66
66
  return this.request("PUT", path, body);
67
67
  }
68
+ async delete(path) {
69
+ return this.request("DELETE", path);
70
+ }
68
71
  async request(method, path, body) {
69
72
  const url = this.cfg.url + "/api/v0" + path;
70
73
  const headers = { "x-auth-token": this.cfg.token };
@@ -514,6 +517,106 @@ function createLibrenmsSetMaintenanceTool(getClient) {
514
517
  };
515
518
  }
516
519
 
520
+ // src/tools/librenms_get_port.ts
521
+ import { Type as Type11 } from "@sinclair/typebox";
522
+ var Schema11 = Type11.Object(
523
+ {
524
+ port_id: Type11.Integer({
525
+ minimum: 1,
526
+ description: "LibreNMS internal port id (from librenms_list_ports response rows)."
527
+ })
528
+ },
529
+ { additionalProperties: false }
530
+ );
531
+ function createLibrenmsGetPortTool(getClient) {
532
+ return {
533
+ name: "librenms_get_port",
534
+ label: "librenms: get port",
535
+ description: "Single-port detail via GET /api/v0/ports/{port_id}. Returns full column set (admin/oper state, traffic + error counters, ifAlias, etc.).",
536
+ parameters: Schema11,
537
+ execute: async (_id, raw) => {
538
+ const args = raw;
539
+ const client = getClient();
540
+ const r = await client.get(`/ports/${args.port_id}`);
541
+ return jsonToolResult({ port: r.port?.[0] ?? null });
542
+ }
543
+ };
544
+ }
545
+
546
+ // src/tools/librenms_event_log.ts
547
+ import { Type as Type12 } from "@sinclair/typebox";
548
+ var Schema12 = Type12.Object(
549
+ {
550
+ device_id: Type12.Optional(
551
+ Type12.Integer({
552
+ minimum: 1,
553
+ description: "Optional device id to scope the event log."
554
+ })
555
+ ),
556
+ limit: Type12.Optional(
557
+ Type12.Integer({
558
+ minimum: 1,
559
+ description: "Max number of log entries. Default 25."
560
+ })
561
+ )
562
+ },
563
+ { additionalProperties: false }
564
+ );
565
+ function createLibrenmsEventLogTool(getClient) {
566
+ return {
567
+ name: "librenms_event_log",
568
+ label: "librenms: event log",
569
+ description: "Recent device events via GET /api/v0/logs/eventlog (optionally scoped to a device_id). Covers device up/down, syslog ingestion, etc. Distinct from alertlog.",
570
+ parameters: Schema12,
571
+ execute: async (_id, raw) => {
572
+ const args = raw ?? {};
573
+ const limit = args.limit ?? 25;
574
+ const path = args.device_id ? `/logs/eventlog/${args.device_id}?limit=${limit}` : `/logs/eventlog?limit=${limit}`;
575
+ const client = getClient();
576
+ const r = await client.get(path);
577
+ return jsonToolResult({
578
+ count: r.logs?.length ?? 0,
579
+ logs: r.logs ?? []
580
+ });
581
+ }
582
+ };
583
+ }
584
+
585
+ // src/tools/librenms_unmute_alert.ts
586
+ import { Type as Type13 } from "@sinclair/typebox";
587
+ var Schema13 = Type13.Object(
588
+ {
589
+ id: Type13.Integer({ minimum: 1, description: "Alert id to unmute." }),
590
+ note: Type13.Optional(
591
+ Type13.String({
592
+ description: "Optional note appended to the alert audit trail (LibreNMS prefixes it with the API user + timestamp)."
593
+ })
594
+ ),
595
+ confirm: Type13.Boolean({
596
+ description: "Must be true to write. Tier-2 safe-write gate."
597
+ })
598
+ },
599
+ { additionalProperties: false }
600
+ );
601
+ var NAME3 = "librenms_unmute_alert";
602
+ function createLibrenmsUnmuteAlertTool(getClient) {
603
+ return {
604
+ name: NAME3,
605
+ label: "librenms: unmute alert",
606
+ description: "Unmute an alert by id via PUT /api/v0/alerts/unmute/{id} (companion to librenms_ack_alert). Tier-2 write; requires confirm:true.",
607
+ parameters: Schema13,
608
+ execute: async (_id, raw) => {
609
+ assertConfirmedWrite(raw, NAME3);
610
+ const args = raw;
611
+ const client = getClient();
612
+ const body = {};
613
+ if (args.note !== void 0) body.note = args.note;
614
+ const r = await client.put(`/alerts/unmute/${args.id}`, body);
615
+ return jsonToolResult({ alert_id: args.id, unmuted: true, response: r });
616
+ }
617
+ };
618
+ }
619
+
517
620
  // index.ts
518
621
  function withRedactedErrors(tool) {
519
622
  const orig = tool.execute.bind(tool);
@@ -552,6 +655,9 @@ var index_default = definePluginEntry({
552
655
  register(createLibrenmsAlertHistoryTool(getClient));
553
656
  register(createLibrenmsAckAlertTool(getClient));
554
657
  register(createLibrenmsSetMaintenanceTool(getClient));
658
+ register(createLibrenmsGetPortTool(getClient));
659
+ register(createLibrenmsEventLogTool(getClient));
660
+ register(createLibrenmsUnmuteAlertTool(getClient));
555
661
  }
556
662
  });
557
663
  export {
@@ -67,6 +67,9 @@ var LibreNmsClient = class {
67
67
  async put(path, body) {
68
68
  return this.request("PUT", path, body);
69
69
  }
70
+ async delete(path) {
71
+ return this.request("DELETE", path);
72
+ }
70
73
  async request(method, path, body) {
71
74
  const url = this.cfg.url + "/api/v0" + path;
72
75
  const headers = { "x-auth-token": this.cfg.token };
@@ -516,6 +519,106 @@ function createLibrenmsSetMaintenanceTool(getClient2) {
516
519
  };
517
520
  }
518
521
 
522
+ // src/tools/librenms_get_port.ts
523
+ import { Type as Type11 } from "@sinclair/typebox";
524
+ var Schema11 = Type11.Object(
525
+ {
526
+ port_id: Type11.Integer({
527
+ minimum: 1,
528
+ description: "LibreNMS internal port id (from librenms_list_ports response rows)."
529
+ })
530
+ },
531
+ { additionalProperties: false }
532
+ );
533
+ function createLibrenmsGetPortTool(getClient2) {
534
+ return {
535
+ name: "librenms_get_port",
536
+ label: "librenms: get port",
537
+ description: "Single-port detail via GET /api/v0/ports/{port_id}. Returns full column set (admin/oper state, traffic + error counters, ifAlias, etc.).",
538
+ parameters: Schema11,
539
+ execute: async (_id, raw) => {
540
+ const args = raw;
541
+ const client = getClient2();
542
+ const r = await client.get(`/ports/${args.port_id}`);
543
+ return jsonToolResult({ port: r.port?.[0] ?? null });
544
+ }
545
+ };
546
+ }
547
+
548
+ // src/tools/librenms_event_log.ts
549
+ import { Type as Type12 } from "@sinclair/typebox";
550
+ var Schema12 = Type12.Object(
551
+ {
552
+ device_id: Type12.Optional(
553
+ Type12.Integer({
554
+ minimum: 1,
555
+ description: "Optional device id to scope the event log."
556
+ })
557
+ ),
558
+ limit: Type12.Optional(
559
+ Type12.Integer({
560
+ minimum: 1,
561
+ description: "Max number of log entries. Default 25."
562
+ })
563
+ )
564
+ },
565
+ { additionalProperties: false }
566
+ );
567
+ function createLibrenmsEventLogTool(getClient2) {
568
+ return {
569
+ name: "librenms_event_log",
570
+ label: "librenms: event log",
571
+ description: "Recent device events via GET /api/v0/logs/eventlog (optionally scoped to a device_id). Covers device up/down, syslog ingestion, etc. Distinct from alertlog.",
572
+ parameters: Schema12,
573
+ execute: async (_id, raw) => {
574
+ const args = raw ?? {};
575
+ const limit = args.limit ?? 25;
576
+ const path = args.device_id ? `/logs/eventlog/${args.device_id}?limit=${limit}` : `/logs/eventlog?limit=${limit}`;
577
+ const client = getClient2();
578
+ const r = await client.get(path);
579
+ return jsonToolResult({
580
+ count: r.logs?.length ?? 0,
581
+ logs: r.logs ?? []
582
+ });
583
+ }
584
+ };
585
+ }
586
+
587
+ // src/tools/librenms_unmute_alert.ts
588
+ import { Type as Type13 } from "@sinclair/typebox";
589
+ var Schema13 = Type13.Object(
590
+ {
591
+ id: Type13.Integer({ minimum: 1, description: "Alert id to unmute." }),
592
+ note: Type13.Optional(
593
+ Type13.String({
594
+ description: "Optional note appended to the alert audit trail (LibreNMS prefixes it with the API user + timestamp)."
595
+ })
596
+ ),
597
+ confirm: Type13.Boolean({
598
+ description: "Must be true to write. Tier-2 safe-write gate."
599
+ })
600
+ },
601
+ { additionalProperties: false }
602
+ );
603
+ var NAME3 = "librenms_unmute_alert";
604
+ function createLibrenmsUnmuteAlertTool(getClient2) {
605
+ return {
606
+ name: NAME3,
607
+ label: "librenms: unmute alert",
608
+ description: "Unmute an alert by id via PUT /api/v0/alerts/unmute/{id} (companion to librenms_ack_alert). Tier-2 write; requires confirm:true.",
609
+ parameters: Schema13,
610
+ execute: async (_id, raw) => {
611
+ assertConfirmedWrite(raw, NAME3);
612
+ const args = raw;
613
+ const client = getClient2();
614
+ const body = {};
615
+ if (args.note !== void 0) body.note = args.note;
616
+ const r = await client.put(`/alerts/unmute/${args.id}`, body);
617
+ return jsonToolResult({ alert_id: args.id, unmuted: true, response: r });
618
+ }
619
+ };
620
+ }
621
+
519
622
  // mcp-server.ts
520
623
  var cfg = resolveConfig(process.env);
521
624
  registerSecret(cfg.token);
@@ -530,10 +633,13 @@ var tools = [
530
633
  createLibrenmsGetAlertTool(getClient),
531
634
  createLibrenmsAlertHistoryTool(getClient),
532
635
  createLibrenmsAckAlertTool(getClient),
533
- createLibrenmsSetMaintenanceTool(getClient)
636
+ createLibrenmsSetMaintenanceTool(getClient),
637
+ createLibrenmsGetPortTool(getClient),
638
+ createLibrenmsEventLogTool(getClient),
639
+ createLibrenmsUnmuteAlertTool(getClient)
534
640
  ];
535
641
  var toolMap = new Map(tools.map((t) => [t.name, t]));
536
- var server = new Server({ name: "librenms-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
642
+ var server = new Server({ name: "librenms-mcp", version: "0.2.0" }, { capabilities: { tools: {} } });
537
643
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
538
644
  tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters }))
539
645
  }));
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "id": "librenms",
4
4
  "name": "LibreNMS",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "description": "LibreNMS read + safe-write tools: devices, ports, alerts, ack, maintenance.",
7
7
  "entry": "./dist/index.js",
8
8
  "activation": { "onStartup": true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solomonneas/librenms-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server exposing LibreNMS read + safe-write tools",
5
5
  "type": "module",
6
6
  "openclaw": {