@tmhs/homelab-mcp 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +25 -1
  2. package/dist/index.js +11 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/tools/adguardFilters.d.ts +3 -0
  5. package/dist/tools/adguardFilters.d.ts.map +1 -0
  6. package/dist/tools/adguardFilters.js +37 -0
  7. package/dist/tools/adguardFilters.js.map +1 -0
  8. package/dist/tools/adguardQueryLog.d.ts +3 -0
  9. package/dist/tools/adguardQueryLog.d.ts.map +1 -0
  10. package/dist/tools/adguardQueryLog.js +56 -0
  11. package/dist/tools/adguardQueryLog.js.map +1 -0
  12. package/dist/tools/adguardStats.d.ts +3 -0
  13. package/dist/tools/adguardStats.d.ts.map +1 -0
  14. package/dist/tools/adguardStats.js +37 -0
  15. package/dist/tools/adguardStats.js.map +1 -0
  16. package/dist/tools/alertList.d.ts.map +1 -1
  17. package/dist/tools/alertList.js +15 -1
  18. package/dist/tools/alertList.js.map +1 -1
  19. package/dist/tools/grafanaSnapshot.d.ts.map +1 -1
  20. package/dist/tools/grafanaSnapshot.js +33 -1
  21. package/dist/tools/grafanaSnapshot.js.map +1 -1
  22. package/dist/tools/npmCerts.d.ts +3 -0
  23. package/dist/tools/npmCerts.d.ts.map +1 -0
  24. package/dist/tools/npmCerts.js +51 -0
  25. package/dist/tools/npmCerts.js.map +1 -0
  26. package/dist/tools/npmProxyHosts.d.ts +3 -0
  27. package/dist/tools/npmProxyHosts.d.ts.map +1 -0
  28. package/dist/tools/npmProxyHosts.js +51 -0
  29. package/dist/tools/npmProxyHosts.js.map +1 -0
  30. package/dist/tools/prometheusQuery.d.ts.map +1 -1
  31. package/dist/tools/prometheusQuery.js +15 -1
  32. package/dist/tools/prometheusQuery.js.map +1 -1
  33. package/dist/tools/speedtestResults.d.ts.map +1 -1
  34. package/dist/tools/speedtestResults.js +15 -1
  35. package/dist/tools/speedtestResults.js.map +1 -1
  36. package/dist/tools/uptimeKumaStatus.d.ts.map +1 -1
  37. package/dist/tools/uptimeKumaStatus.js +15 -1
  38. package/dist/tools/uptimeKumaStatus.js.map +1 -1
  39. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Home Lab MCP Server
2
2
 
3
- MCP (Model Context Protocol) server for home lab operations. Connects to a Raspberry Pi via SSH and provides 15 tools for system management, Docker Compose stacks, service monitoring, networking, and backups.
3
+ MCP (Model Context Protocol) server for home lab operations. Connects to a Raspberry Pi via SSH and provides 25 tools for system management, Docker Compose stacks, service monitoring, networking, and backups.
4
4
 
5
5
  ## Tools
6
6
 
@@ -20,6 +20,16 @@ MCP (Model Context Protocol) server for home lab operations. Connects to a Raspb
20
20
  | Network | `homelab_networkInfo` | IP addresses, DNS, Tailscale status |
21
21
  | Backup | `homelab_backupStatus` | Check latest restic snapshots |
22
22
  | Backup | `homelab_backupRun` | Trigger restic backup |
23
+ | Monitoring | `homelab_prometheusQuery` | Run PromQL queries against Prometheus |
24
+ | Monitoring | `homelab_grafanaSnapshot` | Export Grafana dashboard config by UID |
25
+ | Monitoring | `homelab_uptimeKumaStatus` | Get Uptime Kuma monitor statuses |
26
+ | Monitoring | `homelab_alertList` | List Alertmanager alerts by state |
27
+ | Monitoring | `homelab_speedtestResults` | Get recent Speedtest Tracker results |
28
+ | DNS/Proxy | `homelab_adguardStats` | AdGuard Home DNS statistics |
29
+ | DNS/Proxy | `homelab_adguardFilters` | List AdGuard filter lists and status |
30
+ | DNS/Proxy | `homelab_adguardQueryLog` | Search AdGuard DNS query log |
31
+ | DNS/Proxy | `homelab_npmProxyHosts` | List NPM proxy host configs |
32
+ | DNS/Proxy | `homelab_npmCerts` | List SSL certificates and expiry |
23
33
  | SSH | `homelab_sshTest` | Test SSH connectivity |
24
34
 
25
35
  ## Setup
@@ -41,6 +51,20 @@ Set environment variables:
41
51
  | `HOMELAB_PI_KEY_PATH` | (empty) | Path to SSH private key |
42
52
  | `HOMELAB_COMPOSE_DIR` | `/opt/homelab/docker` | Compose project directory on Pi |
43
53
  | `HOMELAB_BACKUP_REPO` | `/mnt/backup/restic` | Restic backup repo path on Pi |
54
+ | `HOMELAB_GRAFANA_TOKEN` | (empty) | Grafana API token (preferred auth method) |
55
+ | `HOMELAB_GRAFANA_USER` | `admin` | Grafana basic auth username |
56
+ | `HOMELAB_GRAFANA_PASSWORD` | (empty) | Grafana basic auth password (falls back to admin/admin) |
57
+ | `HOMELAB_PROMETHEUS_PORT` | `9090` | Prometheus port override |
58
+ | `HOMELAB_GRAFANA_PORT` | `3000` | Grafana port override |
59
+ | `HOMELAB_ALERTMANAGER_PORT` | `9093` | Alertmanager port override |
60
+ | `HOMELAB_UPTIME_KUMA_PORT` | `3001` | Uptime Kuma port override |
61
+ | `HOMELAB_SPEEDTEST_PORT` | `8765` | Speedtest Tracker port override |
62
+ | `HOMELAB_ADGUARD_USER` | `admin` | AdGuard Home username |
63
+ | `HOMELAB_ADGUARD_PASSWORD` | `admin` | AdGuard Home password |
64
+ | `HOMELAB_ADGUARD_PORT` | `3000` | AdGuard Home port override |
65
+ | `HOMELAB_NPM_EMAIL` | `admin@example.com` | NPM admin email |
66
+ | `HOMELAB_NPM_PASSWORD` | `changeme` | NPM admin password |
67
+ | `HOMELAB_NPM_PORT` | `81` | NPM admin API port override |
44
68
 
45
69
  ## Usage with Cursor
46
70
 
package/dist/index.js CHANGED
@@ -21,9 +21,14 @@ import { register as registerGrafanaSnapshot } from "./tools/grafanaSnapshot.js"
21
21
  import { register as registerUptimeKumaStatus } from "./tools/uptimeKumaStatus.js";
22
22
  import { register as registerAlertList } from "./tools/alertList.js";
23
23
  import { register as registerSpeedtestResults } from "./tools/speedtestResults.js";
24
+ import { register as registerAdguardStats } from "./tools/adguardStats.js";
25
+ import { register as registerAdguardFilters } from "./tools/adguardFilters.js";
26
+ import { register as registerAdguardQueryLog } from "./tools/adguardQueryLog.js";
27
+ import { register as registerNpmProxyHosts } from "./tools/npmProxyHosts.js";
28
+ import { register as registerNpmCerts } from "./tools/npmCerts.js";
24
29
  const server = new McpServer({
25
30
  name: "homelab-mcp",
26
- version: "0.2.0",
31
+ version: "0.3.0",
27
32
  });
28
33
  registerPiStatus(server);
29
34
  registerPiReboot(server);
@@ -45,6 +50,11 @@ registerGrafanaSnapshot(server);
45
50
  registerUptimeKumaStatus(server);
46
51
  registerAlertList(server);
47
52
  registerSpeedtestResults(server);
53
+ registerAdguardStats(server);
54
+ registerAdguardFilters(server);
55
+ registerAdguardQueryLog(server);
56
+ registerNpmProxyHosts(server);
57
+ registerNpmCerts(server);
48
58
  async function main() {
49
59
  const transport = new StdioServerTransport();
50
60
  await server.connect(transport);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,QAAQ,IAAI,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,QAAQ,IAAI,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,QAAQ,IAAI,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,QAAQ,IAAI,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,QAAQ,IAAI,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,QAAQ,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,QAAQ,IAAI,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,QAAQ,IAAI,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEnE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACzB,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACzB,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,eAAe,CAAC,MAAM,CAAC,CAAC;AACxB,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzB,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=adguardFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardFilters.d.ts","sourceRoot":"","sources":["../../src/tools/adguardFilters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqChD"}
@@ -0,0 +1,37 @@
1
+ import { execSSH, errorResponse } from "../utils/ssh-api.js";
2
+ import { CommandFailedError } from "../utils/errors.js";
3
+ const DEFAULT_PORT = 3000;
4
+ const SERVICE_NAME = "AdGuard Home";
5
+ function getPort() {
6
+ const override = process.env.HOMELAB_ADGUARD_PORT;
7
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
8
+ }
9
+ function buildAuth() {
10
+ const user = process.env.HOMELAB_ADGUARD_USER || "admin";
11
+ const password = process.env.HOMELAB_ADGUARD_PASSWORD || "admin";
12
+ return `-u '${user}:${password}'`;
13
+ }
14
+ export function register(server) {
15
+ server.tool("homelab_adguardFilters", "List AdGuard Home filter lists with name, URL, enabled status, and rule count", {}, async () => {
16
+ const port = getPort();
17
+ try {
18
+ const auth = buildAuth();
19
+ const output = await execSSH(`curl -sf ${auth} 'http://localhost:${port}/control/filtering/status'`);
20
+ return { content: [{ type: "text", text: output }] };
21
+ }
22
+ catch (error) {
23
+ if (error instanceof CommandFailedError) {
24
+ if (error.exitCode === 7) {
25
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
26
+ `Set HOMELAB_ADGUARD_PORT if using a non-default port.`));
27
+ }
28
+ if (error.exitCode === 22) {
29
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
30
+ `set HOMELAB_ADGUARD_USER and HOMELAB_ADGUARD_PASSWORD.`));
31
+ }
32
+ }
33
+ return errorResponse(error);
34
+ }
35
+ });
36
+ }
37
+ //# sourceMappingURL=adguardFilters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardFilters.js","sourceRoot":"","sources":["../../src/tools/adguardFilters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,OAAO,CAAC;IACjE,OAAO,OAAO,IAAI,IAAI,QAAQ,GAAG,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,+EAA+E,EAC/E,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,YAAY,IAAI,sBAAsB,IAAI,4BAA4B,CACvE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,uDAAuD,CAC1D,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,wDAAwD,CAC3D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=adguardQueryLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardQueryLog.d.ts","sourceRoot":"","sources":["../../src/tools/adguardQueryLog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgCzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0ChD"}
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { execSSH, errorResponse } from "../utils/ssh-api.js";
3
+ import { CommandFailedError } from "../utils/errors.js";
4
+ const DEFAULT_PORT = 3000;
5
+ const SERVICE_NAME = "AdGuard Home";
6
+ const inputSchema = {
7
+ search: z
8
+ .string()
9
+ .optional()
10
+ .describe("Filter queries by domain or client IP"),
11
+ count: z
12
+ .number()
13
+ .int()
14
+ .positive()
15
+ .optional()
16
+ .default(25)
17
+ .describe("Number of recent queries to return"),
18
+ };
19
+ function getPort() {
20
+ const override = process.env.HOMELAB_ADGUARD_PORT;
21
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
22
+ }
23
+ function buildAuth() {
24
+ const user = process.env.HOMELAB_ADGUARD_USER || "admin";
25
+ const password = process.env.HOMELAB_ADGUARD_PASSWORD || "admin";
26
+ return `-u '${user}:${password}'`;
27
+ }
28
+ export function register(server) {
29
+ server.tool("homelab_adguardQueryLog", "Search the AdGuard Home DNS query log", inputSchema, async (args) => {
30
+ const port = getPort();
31
+ try {
32
+ const auth = buildAuth();
33
+ const params = [`limit=${args.count}`];
34
+ if (args.search) {
35
+ params.push(`search=${encodeURIComponent(args.search)}`);
36
+ }
37
+ const query = params.join("&");
38
+ const output = await execSSH(`curl -sf ${auth} 'http://localhost:${port}/control/querylog?${query}'`);
39
+ return { content: [{ type: "text", text: output }] };
40
+ }
41
+ catch (error) {
42
+ if (error instanceof CommandFailedError) {
43
+ if (error.exitCode === 7) {
44
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
45
+ `Set HOMELAB_ADGUARD_PORT if using a non-default port.`));
46
+ }
47
+ if (error.exitCode === 22) {
48
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
49
+ `set HOMELAB_ADGUARD_USER and HOMELAB_ADGUARD_PASSWORD.`));
50
+ }
51
+ }
52
+ return errorResponse(error);
53
+ }
54
+ });
55
+ }
56
+ //# sourceMappingURL=adguardQueryLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardQueryLog.js","sourceRoot":"","sources":["../../src/tools/adguardQueryLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,uCAAuC,CAAC;IACpD,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,oCAAoC,CAAC;CAClD,CAAC;AAEF,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,OAAO,CAAC;IACjE,OAAO,OAAO,IAAI,IAAI,QAAQ,GAAG,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,uCAAuC,EACvC,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAa,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACjD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC,UAAU,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,YAAY,IAAI,sBAAsB,IAAI,qBAAqB,KAAK,GAAG,CACxE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,uDAAuD,CAC1D,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,wDAAwD,CAC3D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=adguardStats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardStats.d.ts","sourceRoot":"","sources":["../../src/tools/adguardStats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqChD"}
@@ -0,0 +1,37 @@
1
+ import { execSSH, errorResponse } from "../utils/ssh-api.js";
2
+ import { CommandFailedError } from "../utils/errors.js";
3
+ const DEFAULT_PORT = 3000;
4
+ const SERVICE_NAME = "AdGuard Home";
5
+ function getPort() {
6
+ const override = process.env.HOMELAB_ADGUARD_PORT;
7
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
8
+ }
9
+ function buildAuth() {
10
+ const user = process.env.HOMELAB_ADGUARD_USER || "admin";
11
+ const password = process.env.HOMELAB_ADGUARD_PASSWORD || "admin";
12
+ return `-u '${user}:${password}'`;
13
+ }
14
+ export function register(server) {
15
+ server.tool("homelab_adguardStats", "Get AdGuard Home DNS statistics including query counts, blocked domains, and top clients", {}, async () => {
16
+ const port = getPort();
17
+ try {
18
+ const auth = buildAuth();
19
+ const output = await execSSH(`curl -sf ${auth} 'http://localhost:${port}/control/stats'`);
20
+ return { content: [{ type: "text", text: output }] };
21
+ }
22
+ catch (error) {
23
+ if (error instanceof CommandFailedError) {
24
+ if (error.exitCode === 7) {
25
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
26
+ `Set HOMELAB_ADGUARD_PORT if using a non-default port.`));
27
+ }
28
+ if (error.exitCode === 22) {
29
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
30
+ `set HOMELAB_ADGUARD_USER and HOMELAB_ADGUARD_PASSWORD.`));
31
+ }
32
+ }
33
+ return errorResponse(error);
34
+ }
35
+ });
36
+ }
37
+ //# sourceMappingURL=adguardStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adguardStats.js","sourceRoot":"","sources":["../../src/tools/adguardStats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,OAAO,CAAC;IACjE,OAAO,OAAO,IAAI,IAAI,QAAQ,GAAG,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,0FAA0F,EAC1F,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,YAAY,IAAI,sBAAsB,IAAI,iBAAiB,CAC5D,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,uDAAuD,CAC1D,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,wDAAwD,CAC3D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"alertList.d.ts","sourceRoot":"","sources":["../../src/tools/alertList.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkBhD"}
1
+ {"version":3,"file":"alertList.d.ts","sourceRoot":"","sources":["../../src/tools/alertList.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6BhD"}
@@ -1,19 +1,33 @@
1
1
  import { z } from "zod";
2
2
  import { execSSH, errorResponse } from "../utils/ssh-api.js";
3
+ import { CommandFailedError } from "../utils/errors.js";
4
+ const DEFAULT_PORT = 9093;
5
+ const SERVICE_NAME = "Alertmanager";
3
6
  const inputSchema = {
4
7
  state: z
5
8
  .enum(["active", "suppressed", "unprocessed"])
6
9
  .optional()
7
10
  .describe("Filter alerts by state. Returns all states if omitted"),
8
11
  };
12
+ function getPort() {
13
+ const override = process.env.HOMELAB_ALERTMANAGER_PORT;
14
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
15
+ }
9
16
  export function register(server) {
10
17
  server.tool("homelab_alertList", "List alerts from Alertmanager, optionally filtered by state", inputSchema, async (args) => {
18
+ const port = getPort();
11
19
  try {
12
20
  const stateParam = args.state ? `?state=${args.state}` : "";
13
- const output = await execSSH(`curl -sf 'http://localhost:9093/api/v2/alerts${stateParam}'`);
21
+ const output = await execSSH(`curl -sf 'http://localhost:${port}/api/v2/alerts${stateParam}'`);
14
22
  return { content: [{ type: "text", text: output }] };
15
23
  }
16
24
  catch (error) {
25
+ if (error instanceof CommandFailedError) {
26
+ if (error.exitCode === 7) {
27
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
28
+ `Set HOMELAB_ALERTMANAGER_PORT if using a non-default port.`));
29
+ }
30
+ }
17
31
  return errorResponse(error);
18
32
  }
19
33
  });
@@ -1 +1 @@
1
- {"version":3,"file":"alertList.js","sourceRoot":"","sources":["../../src/tools/alertList.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC;SACL,IAAI,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;SAC7C,QAAQ,EAAE;SACV,QAAQ,CAAC,uDAAuD,CAAC;CACrE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,6DAA6D,EAC7D,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,gDAAgD,UAAU,GAAG,CAC9D,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"alertList.js","sourceRoot":"","sources":["../../src/tools/alertList.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC;SACL,IAAI,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;SAC7C,QAAQ,EAAE;SACV,QAAQ,CAAC,uDAAuD,CAAC;CACrE,CAAC;AAEF,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACvD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,6DAA6D,EAC7D,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,8BAA8B,IAAI,iBAAiB,UAAU,GAAG,CACjE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,4DAA4D,CAC/D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"grafanaSnapshot.d.ts","sourceRoot":"","sources":["../../src/tools/grafanaSnapshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiBhD"}
1
+ {"version":3,"file":"grafanaSnapshot.d.ts","sourceRoot":"","sources":["../../src/tools/grafanaSnapshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6BzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsChD"}
@@ -1,15 +1,47 @@
1
1
  import { z } from "zod";
2
2
  import { execSSH, errorResponse } from "../utils/ssh-api.js";
3
+ import { CommandFailedError } from "../utils/errors.js";
4
+ const DEFAULT_PORT = 3000;
5
+ const SERVICE_NAME = "Grafana";
3
6
  const inputSchema = {
4
7
  dashboard: z.string().min(1).describe("Dashboard UID to export"),
5
8
  };
9
+ function getPort() {
10
+ const override = process.env.HOMELAB_GRAFANA_PORT;
11
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
12
+ }
13
+ function buildAuthHeader() {
14
+ const token = process.env.HOMELAB_GRAFANA_TOKEN;
15
+ if (token) {
16
+ return `-H 'Authorization: Bearer ${token}'`;
17
+ }
18
+ const user = process.env.HOMELAB_GRAFANA_USER || "admin";
19
+ const password = process.env.HOMELAB_GRAFANA_PASSWORD;
20
+ if (password) {
21
+ return `-u '${user}:${password}'`;
22
+ }
23
+ return `-u 'admin:admin'`;
24
+ }
6
25
  export function register(server) {
7
26
  server.tool("homelab_grafanaSnapshot", "Export a Grafana dashboard configuration by UID", inputSchema, async (args) => {
27
+ const port = getPort();
8
28
  try {
9
- const output = await execSSH(`curl -sf 'http://localhost:3000/api/dashboards/uid/${args.dashboard}'`);
29
+ const auth = buildAuthHeader();
30
+ const output = await execSSH(`curl -sf ${auth} 'http://localhost:${port}/api/dashboards/uid/${args.dashboard}'`);
10
31
  return { content: [{ type: "text", text: output }] };
11
32
  }
12
33
  catch (error) {
34
+ if (error instanceof CommandFailedError) {
35
+ if (error.exitCode === 7) {
36
+ return errorResponse(new Error(`Could not reach ${SERVICE_NAME} API on port ${port}. Is it running? ` +
37
+ `Set HOMELAB_GRAFANA_PORT if using a non-default port.`));
38
+ }
39
+ if (error.exitCode === 22) {
40
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
41
+ `set HOMELAB_GRAFANA_TOKEN (API key) or HOMELAB_GRAFANA_USER/HOMELAB_GRAFANA_PASSWORD. ` +
42
+ `Also verify the dashboard UID "${args.dashboard}" exists.`));
43
+ }
44
+ }
13
45
  return errorResponse(error);
14
46
  }
15
47
  });
@@ -1 +1 @@
1
- {"version":3,"file":"grafanaSnapshot.js","sourceRoot":"","sources":["../../src/tools/grafanaSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,GAAG;IAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;CACjE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,iDAAiD,EACjD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,sDAAsD,IAAI,CAAC,SAAS,GAAG,CACxE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"grafanaSnapshot.js","sourceRoot":"","sources":["../../src/tools/grafanaSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,SAAS,CAAC;AAE/B,MAAM,WAAW,GAAG;IAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;CACjE,CAAC;AAEF,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,eAAe;IACtB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAChD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,6BAA6B,KAAK,GAAG,CAAC;IAC/C,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACtD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,OAAO,IAAI,IAAI,QAAQ,GAAG,CAAC;IACpC,CAAC;IACD,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,iDAAiD,EACjD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,YAAY,IAAI,sBAAsB,IAAI,uBAAuB,IAAI,CAAC,SAAS,GAAG,CACnF,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,mBAAmB,YAAY,gBAAgB,IAAI,mBAAmB;wBACpE,uDAAuD,CAC1D,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,wFAAwF;wBACxF,kCAAkC,IAAI,CAAC,SAAS,WAAW,CAC9D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=npmCerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npmCerts.d.ts","sourceRoot":"","sources":["../../src/tools/npmCerts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8BzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8ChD"}
@@ -0,0 +1,51 @@
1
+ import { execSSH, errorResponse } from "../utils/ssh-api.js";
2
+ import { CommandFailedError } from "../utils/errors.js";
3
+ const DEFAULT_PORT = 81;
4
+ const SERVICE_NAME = "Nginx Proxy Manager";
5
+ function getPort() {
6
+ const override = process.env.HOMELAB_NPM_PORT;
7
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
8
+ }
9
+ function getCredentials() {
10
+ return {
11
+ email: process.env.HOMELAB_NPM_EMAIL || "admin@example.com",
12
+ password: process.env.HOMELAB_NPM_PASSWORD || "changeme",
13
+ };
14
+ }
15
+ async function getNpmToken(port) {
16
+ const { email, password } = getCredentials();
17
+ const payload = JSON.stringify({ identity: email, secret: password });
18
+ const output = await execSSH(`curl -sf -X POST -H 'Content-Type: application/json' ` +
19
+ `-d '${payload}' 'http://localhost:${port}/api/tokens'`);
20
+ const parsed = JSON.parse(output);
21
+ return parsed.token;
22
+ }
23
+ export function register(server) {
24
+ server.tool("homelab_npmCerts", "List SSL certificates managed by Nginx Proxy Manager with expiry dates", {}, async () => {
25
+ const port = getPort();
26
+ try {
27
+ const token = await getNpmToken(port);
28
+ const output = await execSSH(`curl -sf -H 'Authorization: Bearer ${token}' ` +
29
+ `'http://localhost:${port}/api/nginx/certificates'`);
30
+ return { content: [{ type: "text", text: output }] };
31
+ }
32
+ catch (error) {
33
+ if (error instanceof CommandFailedError) {
34
+ if (error.exitCode === 7) {
35
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
36
+ `Set HOMELAB_NPM_PORT if using a non-default port.`));
37
+ }
38
+ if (error.exitCode === 22) {
39
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
40
+ `set HOMELAB_NPM_EMAIL and HOMELAB_NPM_PASSWORD.`));
41
+ }
42
+ }
43
+ if (error instanceof SyntaxError) {
44
+ return errorResponse(new Error(`Failed to parse ${SERVICE_NAME} authentication response. ` +
45
+ `Verify HOMELAB_NPM_EMAIL and HOMELAB_NPM_PASSWORD are correct.`));
46
+ }
47
+ return errorResponse(error);
48
+ }
49
+ });
50
+ }
51
+ //# sourceMappingURL=npmCerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npmCerts.js","sourceRoot":"","sources":["../../src/tools/npmCerts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAE3C,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC9C,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,cAAc;IACrB,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,mBAAmB;QAC3D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU;KACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,cAAc,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,uDAAuD;QACrD,OAAO,OAAO,uBAAuB,IAAI,cAAc,CAC1D,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,wEAAwE,EACxE,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,sCAAsC,KAAK,IAAI;gBAC7C,qBAAqB,IAAI,0BAA0B,CACtD,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,mDAAmD,CACtD,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,iDAAiD,CACpD,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;gBACjC,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,mBAAmB,YAAY,4BAA4B;oBACzD,gEAAgE,CACnE,CACF,CAAC;YACJ,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=npmProxyHosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npmProxyHosts.d.ts","sourceRoot":"","sources":["../../src/tools/npmProxyHosts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8BzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8ChD"}
@@ -0,0 +1,51 @@
1
+ import { execSSH, errorResponse } from "../utils/ssh-api.js";
2
+ import { CommandFailedError } from "../utils/errors.js";
3
+ const DEFAULT_PORT = 81;
4
+ const SERVICE_NAME = "Nginx Proxy Manager";
5
+ function getPort() {
6
+ const override = process.env.HOMELAB_NPM_PORT;
7
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
8
+ }
9
+ function getCredentials() {
10
+ return {
11
+ email: process.env.HOMELAB_NPM_EMAIL || "admin@example.com",
12
+ password: process.env.HOMELAB_NPM_PASSWORD || "changeme",
13
+ };
14
+ }
15
+ async function getNpmToken(port) {
16
+ const { email, password } = getCredentials();
17
+ const payload = JSON.stringify({ identity: email, secret: password });
18
+ const output = await execSSH(`curl -sf -X POST -H 'Content-Type: application/json' ` +
19
+ `-d '${payload}' 'http://localhost:${port}/api/tokens'`);
20
+ const parsed = JSON.parse(output);
21
+ return parsed.token;
22
+ }
23
+ export function register(server) {
24
+ server.tool("homelab_npmProxyHosts", "List all Nginx Proxy Manager proxy host configurations", {}, async () => {
25
+ const port = getPort();
26
+ try {
27
+ const token = await getNpmToken(port);
28
+ const output = await execSSH(`curl -sf -H 'Authorization: Bearer ${token}' ` +
29
+ `'http://localhost:${port}/api/nginx/proxy-hosts'`);
30
+ return { content: [{ type: "text", text: output }] };
31
+ }
32
+ catch (error) {
33
+ if (error instanceof CommandFailedError) {
34
+ if (error.exitCode === 7) {
35
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
36
+ `Set HOMELAB_NPM_PORT if using a non-default port.`));
37
+ }
38
+ if (error.exitCode === 22) {
39
+ return errorResponse(new Error(`${SERVICE_NAME} returned an HTTP error. Check authentication -- ` +
40
+ `set HOMELAB_NPM_EMAIL and HOMELAB_NPM_PASSWORD.`));
41
+ }
42
+ }
43
+ if (error instanceof SyntaxError) {
44
+ return errorResponse(new Error(`Failed to parse ${SERVICE_NAME} authentication response. ` +
45
+ `Verify HOMELAB_NPM_EMAIL and HOMELAB_NPM_PASSWORD are correct.`));
46
+ }
47
+ return errorResponse(error);
48
+ }
49
+ });
50
+ }
51
+ //# sourceMappingURL=npmProxyHosts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npmProxyHosts.js","sourceRoot":"","sources":["../../src/tools/npmProxyHosts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAE3C,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC9C,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,SAAS,cAAc;IACrB,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,mBAAmB;QAC3D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU;KACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,cAAc,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,uDAAuD;QACrD,OAAO,OAAO,uBAAuB,IAAI,cAAc,CAC1D,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,wDAAwD,EACxD,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,sCAAsC,KAAK,IAAI;gBAC7C,qBAAqB,IAAI,yBAAyB,CACrD,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,mDAAmD,CACtD,CACF,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;oBAC1B,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,GAAG,YAAY,mDAAmD;wBAChE,iDAAiD,CACpD,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;gBACjC,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,mBAAmB,YAAY,4BAA4B;oBACzD,gEAAgE,CACnE,CACF,CAAC;YACJ,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"prometheusQuery.d.ts","sourceRoot":"","sources":["../../src/tools/prometheusQuery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmBhD"}
1
+ {"version":3,"file":"prometheusQuery.d.ts","sourceRoot":"","sources":["../../src/tools/prometheusQuery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8BhD"}
@@ -1,5 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { execSSH, errorResponse } from "../utils/ssh-api.js";
3
+ import { CommandFailedError } from "../utils/errors.js";
4
+ const DEFAULT_PORT = 9090;
5
+ const SERVICE_NAME = "Prometheus";
3
6
  const inputSchema = {
4
7
  query: z.string().min(1).describe("PromQL expression to evaluate"),
5
8
  time: z
@@ -7,15 +10,26 @@ const inputSchema = {
7
10
  .optional()
8
11
  .describe("Evaluation timestamp (RFC3339 or Unix). Defaults to current time"),
9
12
  };
13
+ function getPort() {
14
+ const override = process.env.HOMELAB_PROMETHEUS_PORT;
15
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
16
+ }
10
17
  export function register(server) {
11
18
  server.tool("homelab_prometheusQuery", "Run a PromQL query against Prometheus and return the result", inputSchema, async (args) => {
19
+ const port = getPort();
12
20
  try {
13
21
  const encoded = encodeURIComponent(args.query);
14
22
  const timeParam = args.time ? `&time=${encodeURIComponent(args.time)}` : "";
15
- const output = await execSSH(`curl -sf 'http://localhost:9090/api/v1/query?query=${encoded}${timeParam}'`);
23
+ const output = await execSSH(`curl -sf 'http://localhost:${port}/api/v1/query?query=${encoded}${timeParam}'`);
16
24
  return { content: [{ type: "text", text: output }] };
17
25
  }
18
26
  catch (error) {
27
+ if (error instanceof CommandFailedError) {
28
+ if (error.exitCode === 7) {
29
+ return errorResponse(new Error(`Could not reach ${SERVICE_NAME} on port ${port}. Is it running? ` +
30
+ `Set HOMELAB_PROMETHEUS_PORT if using a non-default port.`));
31
+ }
32
+ }
19
33
  return errorResponse(error);
20
34
  }
21
35
  });
@@ -1 +1 @@
1
- {"version":3,"file":"prometheusQuery.js","sourceRoot":"","sources":["../../src/tools/prometheusQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IAClE,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,kEAAkE,CAAC;CAChF,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,6DAA6D,EAC7D,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,sDAAsD,OAAO,GAAG,SAAS,GAAG,CAC7E,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"prometheusQuery.js","sourceRoot":"","sources":["../../src/tools/prometheusQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IAClE,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,kEAAkE,CAAC;CAChF,CAAC;AAEF,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;IACrD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,6DAA6D,EAC7D,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,8BAA8B,IAAI,uBAAuB,OAAO,GAAG,SAAS,GAAG,CAChF,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,mBAAmB,YAAY,YAAY,IAAI,mBAAmB;wBAChE,0DAA0D,CAC7D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"speedtestResults.d.ts","sourceRoot":"","sources":["../../src/tools/speedtestResults.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAazE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiBhD"}
1
+ {"version":3,"file":"speedtestResults.d.ts","sourceRoot":"","sources":["../../src/tools/speedtestResults.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4BhD"}
@@ -1,5 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { execSSH, errorResponse } from "../utils/ssh-api.js";
3
+ import { CommandFailedError } from "../utils/errors.js";
4
+ const DEFAULT_PORT = 8765;
5
+ const SERVICE_NAME = "Speedtest Tracker";
3
6
  const inputSchema = {
4
7
  count: z
5
8
  .number()
@@ -9,13 +12,24 @@ const inputSchema = {
9
12
  .default(5)
10
13
  .describe("Number of recent speedtest results to return"),
11
14
  };
15
+ function getPort() {
16
+ const override = process.env.HOMELAB_SPEEDTEST_PORT;
17
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
18
+ }
12
19
  export function register(server) {
13
20
  server.tool("homelab_speedtestResults", "Get recent speedtest results from Speedtest Tracker", inputSchema, async (args) => {
21
+ const port = getPort();
14
22
  try {
15
- const output = await execSSH(`curl -sf 'http://localhost:8765/api/speedtest/latest?limit=${args.count}'`);
23
+ const output = await execSSH(`curl -sf 'http://localhost:${port}/api/speedtest/latest?limit=${args.count}'`);
16
24
  return { content: [{ type: "text", text: output }] };
17
25
  }
18
26
  catch (error) {
27
+ if (error instanceof CommandFailedError) {
28
+ if (error.exitCode === 7) {
29
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
30
+ `Set HOMELAB_SPEEDTEST_PORT if using a non-default port.`));
31
+ }
32
+ }
19
33
  return errorResponse(error);
20
34
  }
21
35
  });
@@ -1 +1 @@
1
- {"version":3,"file":"speedtestResults.js","sourceRoot":"","sources":["../../src/tools/speedtestResults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,8CAA8C,CAAC;CAC5D,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,qDAAqD,EACrD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,8DAA8D,IAAI,CAAC,KAAK,GAAG,CAC5E,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"speedtestResults.js","sourceRoot":"","sources":["../../src/tools/speedtestResults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAEzC,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,8CAA8C,CAAC;CAC5D,CAAC;AAEF,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IACpD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,qDAAqD,EACrD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,8BAA8B,IAAI,+BAA+B,IAAI,CAAC,KAAK,GAAG,CAC/E,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,yDAAyD,CAC5D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"uptimeKumaStatus.d.ts","sourceRoot":"","sources":["../../src/tools/uptimeKumaStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiBhD"}
1
+ {"version":3,"file":"uptimeKumaStatus.d.ts","sourceRoot":"","sources":["../../src/tools/uptimeKumaStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4BhD"}
@@ -1,11 +1,25 @@
1
1
  import { execSSH, errorResponse } from "../utils/ssh-api.js";
2
+ import { CommandFailedError } from "../utils/errors.js";
3
+ const DEFAULT_PORT = 3001;
4
+ const SERVICE_NAME = "Uptime Kuma";
5
+ function getPort() {
6
+ const override = process.env.HOMELAB_UPTIME_KUMA_PORT;
7
+ return override ? parseInt(override, 10) : DEFAULT_PORT;
8
+ }
2
9
  export function register(server) {
3
10
  server.tool("homelab_uptimeKumaStatus", "Get the status of all Uptime Kuma monitors", {}, async () => {
11
+ const port = getPort();
4
12
  try {
5
- const output = await execSSH(`curl -sf 'http://localhost:3001/api/status-page/heartbeat/default'`);
13
+ const output = await execSSH(`curl -sf 'http://localhost:${port}/api/status-page/heartbeat/default'`);
6
14
  return { content: [{ type: "text", text: output }] };
7
15
  }
8
16
  catch (error) {
17
+ if (error instanceof CommandFailedError) {
18
+ if (error.exitCode === 7) {
19
+ return errorResponse(new Error(`Could not connect to ${SERVICE_NAME} on port ${port}. Is it running? ` +
20
+ `Set HOMELAB_UPTIME_KUMA_PORT if using a non-default port.`));
21
+ }
22
+ }
9
23
  return errorResponse(error);
10
24
  }
11
25
  });
@@ -1 +1 @@
1
- {"version":3,"file":"uptimeKumaStatus.js","sourceRoot":"","sources":["../../src/tools/uptimeKumaStatus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,4CAA4C,EAC5C,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,oEAAoE,CACrE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"uptimeKumaStatus.js","sourceRoot":"","sources":["../../src/tools/uptimeKumaStatus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,SAAS,OAAO;IACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACtD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,4CAA4C,EAC5C,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,8BAA8B,IAAI,qCAAqC,CACxE,CAAC;YAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAClB,IAAI,KAAK,CACP,wBAAwB,YAAY,YAAY,IAAI,mBAAmB;wBACrE,2DAA2D,CAC9D,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tmhs/homelab-mcp",
3
- "version": "0.2.0",
4
- "description": "MCP server for home lab operations via SSH - 20 tools for system status, Docker Compose management, service health, monitoring, networking, backups, and administration on a Raspberry Pi.",
3
+ "version": "0.3.0",
4
+ "description": "MCP server for home lab operations via SSH - 25 tools for system status, Docker Compose management, service health, monitoring, DNS, reverse proxy, networking, backups, and administration on a Raspberry Pi.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {