@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 +4 -4
- package/dist/index.js +106 -0
- package/dist/mcp-server.js +108 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
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 (
|
|
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 (
|
|
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
|
|
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 {
|
package/dist/mcp-server.js
CHANGED
|
@@ -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.
|
|
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
|
}));
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"id": "librenms",
|
|
4
4
|
"name": "LibreNMS",
|
|
5
|
-
"version": "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 },
|