@tmhs/homelab-mcp 0.1.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/LICENSE +31 -0
- package/README.md +71 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/aptUpdate.d.ts +3 -0
- package/dist/tools/aptUpdate.d.ts.map +1 -0
- package/dist/tools/aptUpdate.js +25 -0
- package/dist/tools/aptUpdate.js.map +1 -0
- package/dist/tools/backupRun.d.ts +3 -0
- package/dist/tools/backupRun.d.ts.map +1 -0
- package/dist/tools/backupRun.js +27 -0
- package/dist/tools/backupRun.js.map +1 -0
- package/dist/tools/backupStatus.d.ts +3 -0
- package/dist/tools/backupStatus.d.ts.map +1 -0
- package/dist/tools/backupStatus.js +24 -0
- package/dist/tools/backupStatus.js.map +1 -0
- package/dist/tools/composeDown.d.ts +3 -0
- package/dist/tools/composeDown.d.ts.map +1 -0
- package/dist/tools/composeDown.js +34 -0
- package/dist/tools/composeDown.js.map +1 -0
- package/dist/tools/composePs.d.ts +3 -0
- package/dist/tools/composePs.d.ts.map +1 -0
- package/dist/tools/composePs.js +19 -0
- package/dist/tools/composePs.js.map +1 -0
- package/dist/tools/composePull.d.ts +3 -0
- package/dist/tools/composePull.d.ts.map +1 -0
- package/dist/tools/composePull.js +28 -0
- package/dist/tools/composePull.js.map +1 -0
- package/dist/tools/composeUp.d.ts +3 -0
- package/dist/tools/composeUp.d.ts.map +1 -0
- package/dist/tools/composeUp.js +28 -0
- package/dist/tools/composeUp.js.map +1 -0
- package/dist/tools/diskUsage.d.ts +3 -0
- package/dist/tools/diskUsage.d.ts.map +1 -0
- package/dist/tools/diskUsage.js +37 -0
- package/dist/tools/diskUsage.js.map +1 -0
- package/dist/tools/networkInfo.d.ts +3 -0
- package/dist/tools/networkInfo.d.ts.map +1 -0
- package/dist/tools/networkInfo.js +25 -0
- package/dist/tools/networkInfo.js.map +1 -0
- package/dist/tools/piReboot.d.ts +3 -0
- package/dist/tools/piReboot.d.ts.map +1 -0
- package/dist/tools/piReboot.js +38 -0
- package/dist/tools/piReboot.js.map +1 -0
- package/dist/tools/piStatus.d.ts +3 -0
- package/dist/tools/piStatus.d.ts.map +1 -0
- package/dist/tools/piStatus.js +28 -0
- package/dist/tools/piStatus.js.map +1 -0
- package/dist/tools/serviceHealth.d.ts +3 -0
- package/dist/tools/serviceHealth.d.ts.map +1 -0
- package/dist/tools/serviceHealth.js +29 -0
- package/dist/tools/serviceHealth.js.map +1 -0
- package/dist/tools/serviceLogs.d.ts +3 -0
- package/dist/tools/serviceLogs.d.ts.map +1 -0
- package/dist/tools/serviceLogs.js +29 -0
- package/dist/tools/serviceLogs.js.map +1 -0
- package/dist/tools/serviceRestart.d.ts +3 -0
- package/dist/tools/serviceRestart.d.ts.map +1 -0
- package/dist/tools/serviceRestart.js +23 -0
- package/dist/tools/serviceRestart.js.map +1 -0
- package/dist/tools/sshTest.d.ts +3 -0
- package/dist/tools/sshTest.d.ts.map +1 -0
- package/dist/tools/sshTest.js +25 -0
- package/dist/tools/sshTest.js.map +1 -0
- package/dist/utils/errors.d.ts +18 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/ssh-api.d.ts +10 -0
- package/dist/utils/ssh-api.d.ts.map +1 -0
- package/dist/utils/ssh-api.js +130 -0
- package/dist/utils/ssh-api.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TM Hospitality Strategies
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
|
6
|
+
|
|
7
|
+
You are free to:
|
|
8
|
+
|
|
9
|
+
Share - copy and redistribute the material in any medium or format.
|
|
10
|
+
|
|
11
|
+
The licensor cannot revoke these freedoms as long as you follow the
|
|
12
|
+
license terms.
|
|
13
|
+
|
|
14
|
+
Under the following terms:
|
|
15
|
+
|
|
16
|
+
Attribution - You must give appropriate credit, provide a link to
|
|
17
|
+
the license, and indicate if changes were made. You may do so in
|
|
18
|
+
any reasonable manner, but not in any way that suggests the licensor
|
|
19
|
+
endorses you or your use.
|
|
20
|
+
|
|
21
|
+
NonCommercial - You may not use the material for commercial purposes.
|
|
22
|
+
|
|
23
|
+
NoDerivatives - If you remix, transform, or build upon the material,
|
|
24
|
+
you may not distribute the modified material.
|
|
25
|
+
|
|
26
|
+
No additional restrictions - You may not apply legal terms or
|
|
27
|
+
technological measures that legally restrict others from doing
|
|
28
|
+
anything the license permits.
|
|
29
|
+
|
|
30
|
+
Full license text:
|
|
31
|
+
https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Home Lab MCP Server
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Category | Tool | Description |
|
|
8
|
+
|----------|------|-------------|
|
|
9
|
+
| System | `homelab_piStatus` | CPU temp, memory, disk, uptime, throttle state |
|
|
10
|
+
| System | `homelab_piReboot` | Safe reboot with pre-checks |
|
|
11
|
+
| System | `homelab_diskUsage` | Disk usage breakdown by directory |
|
|
12
|
+
| System | `homelab_aptUpdate` | Run apt update, list upgradable packages |
|
|
13
|
+
| Containers | `homelab_serviceHealth` | Docker container health status |
|
|
14
|
+
| Containers | `homelab_serviceLogs` | Tail container logs |
|
|
15
|
+
| Containers | `homelab_serviceRestart` | Restart a container |
|
|
16
|
+
| Compose | `homelab_composeUp` | Start compose stacks |
|
|
17
|
+
| Compose | `homelab_composeDown` | Stop compose stacks |
|
|
18
|
+
| Compose | `homelab_composePull` | Pull latest images |
|
|
19
|
+
| Compose | `homelab_composePs` | List running containers |
|
|
20
|
+
| Network | `homelab_networkInfo` | IP addresses, DNS, Tailscale status |
|
|
21
|
+
| Backup | `homelab_backupStatus` | Check latest restic snapshots |
|
|
22
|
+
| Backup | `homelab_backupRun` | Trigger restic backup |
|
|
23
|
+
| SSH | `homelab_sshTest` | Test SSH connectivity |
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd mcp-server
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
Set environment variables:
|
|
36
|
+
|
|
37
|
+
| Variable | Default | Description |
|
|
38
|
+
|----------|---------|-------------|
|
|
39
|
+
| `HOMELAB_PI_HOST` | `raspi5.local` | Pi hostname or IP |
|
|
40
|
+
| `HOMELAB_PI_USER` | `tmhs` | SSH username |
|
|
41
|
+
| `HOMELAB_PI_KEY_PATH` | (empty) | Path to SSH private key |
|
|
42
|
+
| `HOMELAB_COMPOSE_DIR` | `/opt/homelab/docker` | Compose project directory on Pi |
|
|
43
|
+
| `HOMELAB_BACKUP_REPO` | `/mnt/backup/restic` | Restic backup repo path on Pi |
|
|
44
|
+
|
|
45
|
+
## Usage with Cursor
|
|
46
|
+
|
|
47
|
+
Add to `.cursor/mcp.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"homelab": {
|
|
53
|
+
"command": "node",
|
|
54
|
+
"args": ["path/to/Home-Lab-Developer-Tools/mcp-server/dist/index.js"],
|
|
55
|
+
"env": {
|
|
56
|
+
"HOMELAB_PI_HOST": "raspi5.local",
|
|
57
|
+
"HOMELAB_PI_USER": "tmhs",
|
|
58
|
+
"HOMELAB_PI_KEY_PATH": "~/.ssh/id_ed25519_pi"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run dev # Watch mode with tsx
|
|
69
|
+
npm test # Run Vitest tests
|
|
70
|
+
npm run build # Compile TypeScript
|
|
71
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { register as registerPiStatus } from "./tools/piStatus.js";
|
|
5
|
+
import { register as registerPiReboot } from "./tools/piReboot.js";
|
|
6
|
+
import { register as registerServiceHealth } from "./tools/serviceHealth.js";
|
|
7
|
+
import { register as registerServiceLogs } from "./tools/serviceLogs.js";
|
|
8
|
+
import { register as registerServiceRestart } from "./tools/serviceRestart.js";
|
|
9
|
+
import { register as registerComposeUp } from "./tools/composeUp.js";
|
|
10
|
+
import { register as registerComposeDown } from "./tools/composeDown.js";
|
|
11
|
+
import { register as registerComposePull } from "./tools/composePull.js";
|
|
12
|
+
import { register as registerComposePs } from "./tools/composePs.js";
|
|
13
|
+
import { register as registerNetworkInfo } from "./tools/networkInfo.js";
|
|
14
|
+
import { register as registerDiskUsage } from "./tools/diskUsage.js";
|
|
15
|
+
import { register as registerBackupStatus } from "./tools/backupStatus.js";
|
|
16
|
+
import { register as registerBackupRun } from "./tools/backupRun.js";
|
|
17
|
+
import { register as registerAptUpdate } from "./tools/aptUpdate.js";
|
|
18
|
+
import { register as registerSshTest } from "./tools/sshTest.js";
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: "homelab-mcp",
|
|
21
|
+
version: "0.1.0",
|
|
22
|
+
});
|
|
23
|
+
registerPiStatus(server);
|
|
24
|
+
registerPiReboot(server);
|
|
25
|
+
registerServiceHealth(server);
|
|
26
|
+
registerServiceLogs(server);
|
|
27
|
+
registerServiceRestart(server);
|
|
28
|
+
registerComposeUp(server);
|
|
29
|
+
registerComposeDown(server);
|
|
30
|
+
registerComposePull(server);
|
|
31
|
+
registerComposePs(server);
|
|
32
|
+
registerNetworkInfo(server);
|
|
33
|
+
registerDiskUsage(server);
|
|
34
|
+
registerBackupStatus(server);
|
|
35
|
+
registerBackupRun(server);
|
|
36
|
+
registerAptUpdate(server);
|
|
37
|
+
registerSshTest(server);
|
|
38
|
+
async function main() {
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
await server.connect(transport);
|
|
41
|
+
}
|
|
42
|
+
main().catch((error) => {
|
|
43
|
+
console.error("Fatal error:", error);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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;AAEjE,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;AAExB,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 @@
|
|
|
1
|
+
{"version":3,"file":"aptUpdate.d.ts","sourceRoot":"","sources":["../../src/tools/aptUpdate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoBhD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
upgrade: z
|
|
5
|
+
.boolean()
|
|
6
|
+
.optional()
|
|
7
|
+
.default(false)
|
|
8
|
+
.describe("Also run apt upgrade after update (default: false, just lists upgradable)"),
|
|
9
|
+
};
|
|
10
|
+
export function register(server) {
|
|
11
|
+
server.tool("homelab_aptUpdate", "Run apt update on the Pi and list upgradable packages", inputSchema, async (args) => {
|
|
12
|
+
try {
|
|
13
|
+
let cmd = "sudo apt-get update -qq 2>&1 && apt list --upgradable 2>/dev/null";
|
|
14
|
+
if (args.upgrade) {
|
|
15
|
+
cmd += " && sudo apt-get upgrade -y 2>&1";
|
|
16
|
+
}
|
|
17
|
+
const output = await execSSH(cmd);
|
|
18
|
+
return { content: [{ type: "text", text: output }] };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return errorResponse(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=aptUpdate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aptUpdate.js","sourceRoot":"","sources":["../../src/tools/aptUpdate.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,OAAO,EAAE,CAAC;SACP,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,2EAA2E,CAAC;CACzF,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,uDAAuD,EACvD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,mEAAmE,CAAC;YAC9E,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,GAAG,IAAI,kCAAkC,CAAC;YAC5C,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;YAElC,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backupRun.d.ts","sourceRoot":"","sources":["../../src/tools/backupRun.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0BhD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
confirm: z
|
|
5
|
+
.boolean()
|
|
6
|
+
.describe("Must be true to trigger a backup. Safety check."),
|
|
7
|
+
};
|
|
8
|
+
export function register(server) {
|
|
9
|
+
server.tool("homelab_backupRun", "Trigger a restic backup on the Pi", inputSchema, async (args) => {
|
|
10
|
+
try {
|
|
11
|
+
if (!args.confirm) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: "Backup cancelled. Set confirm=true to proceed.",
|
|
16
|
+
}],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const output = await execSSH("sudo /opt/backup/backup.sh 2>&1 || echo 'Backup script not found at /opt/backup/backup.sh'");
|
|
20
|
+
return { content: [{ type: "text", text: output }] };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return errorResponse(error);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=backupRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backupRun.js","sourceRoot":"","sources":["../../src/tools/backupRun.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,OAAO,EAAE,CAAC;SACP,OAAO,EAAE;SACT,QAAQ,CAAC,iDAAiD,CAAC;CAC/D,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,mCAAmC,EACnC,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,gDAAgD;yBACvD,CAAC;iBACH,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,4FAA4F,CAC7F,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backupStatus.d.ts","sourceRoot":"","sources":["../../src/tools/backupStatus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAazE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkBhD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
count: z
|
|
5
|
+
.number()
|
|
6
|
+
.int()
|
|
7
|
+
.positive()
|
|
8
|
+
.optional()
|
|
9
|
+
.default(5)
|
|
10
|
+
.describe("Number of recent snapshots to show"),
|
|
11
|
+
};
|
|
12
|
+
export function register(server) {
|
|
13
|
+
server.tool("homelab_backupStatus", "Check the latest restic backup snapshots on the Pi", inputSchema, async (args) => {
|
|
14
|
+
try {
|
|
15
|
+
const repo = process.env.HOMELAB_BACKUP_REPO || "/mnt/backup/restic";
|
|
16
|
+
const output = await execSSH(`sudo restic -r ${repo} snapshots --latest ${args.count} 2>/dev/null || echo "No backup repo found at ${repo} or restic not configured"`);
|
|
17
|
+
return { content: [{ type: "text", text: output }] };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return errorResponse(error);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=backupStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backupStatus.js","sourceRoot":"","sources":["../../src/tools/backupStatus.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,oCAAoC,CAAC;CAClD,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,oDAAoD,EACpD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,oBAAoB,CAAC;YACrE,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,kBAAkB,IAAI,uBAAuB,IAAI,CAAC,KAAK,iDAAiD,IAAI,4BAA4B,CACzI,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeDown.d.ts","sourceRoot":"","sources":["../../src/tools/composeDown.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyBhD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const COMPOSE_DIR = process.env.HOMELAB_COMPOSE_DIR || "/opt/homelab/docker";
|
|
4
|
+
const inputSchema = {
|
|
5
|
+
stacks: z
|
|
6
|
+
.array(z.string())
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Specific compose files to stop. Omit for all."),
|
|
9
|
+
removeVolumes: z
|
|
10
|
+
.boolean()
|
|
11
|
+
.optional()
|
|
12
|
+
.default(false)
|
|
13
|
+
.describe("Also remove named volumes declared in compose file"),
|
|
14
|
+
};
|
|
15
|
+
export function register(server) {
|
|
16
|
+
server.tool("homelab_composeDown", "Stop Docker Compose stacks on the Pi", inputSchema, async (args) => {
|
|
17
|
+
try {
|
|
18
|
+
let files = "-f compose.base.yml";
|
|
19
|
+
if (args.stacks && args.stacks.length > 0) {
|
|
20
|
+
files += " " + args.stacks.map((s) => `-f ${s}`).join(" ");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
files += " -f compose.monitoring.yml -f compose.network.yml -f compose.apps.yml -f compose.security.yml -f compose.storage.yml -f compose.tools.yml";
|
|
24
|
+
}
|
|
25
|
+
const volFlag = args.removeVolumes ? " -v" : "";
|
|
26
|
+
const output = await execSSH(`cd ${COMPOSE_DIR} && docker compose ${files} down${volFlag} 2>&1`);
|
|
27
|
+
return { content: [{ type: "text", text: output }] };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return errorResponse(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=composeDown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeDown.js","sourceRoot":"","sources":["../../src/tools/composeDown.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,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,qBAAqB,CAAC;AAE7E,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,CAAC;SACN,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,EAAE;SACV,QAAQ,CAAC,+CAA+C,CAAC;IAC5D,aAAa,EAAE,CAAC;SACb,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,oDAAoD,CAAC;CAClE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,sCAAsC,EACtC,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,qBAAqB,CAAC;YAClC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,KAAK,IAAI,2IAA2I,CAAC;YACvJ,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,MAAM,WAAW,sBAAsB,KAAK,QAAQ,OAAO,OAAO,CACnE,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composePs.d.ts","sourceRoot":"","sources":["../../src/tools/composePs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsBhD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
2
|
+
const COMPOSE_DIR = process.env.HOMELAB_COMPOSE_DIR || "/opt/homelab/docker";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("homelab_composePs", "List running Docker Compose containers on the Pi", {}, async () => {
|
|
5
|
+
try {
|
|
6
|
+
const output = await execSSH(`cd ${COMPOSE_DIR} && docker compose -f compose.base.yml -f compose.monitoring.yml -f compose.network.yml -f compose.apps.yml -f compose.security.yml -f compose.storage.yml -f compose.tools.yml ps --format "table {{.Name}}\\t{{.Status}}\\t{{.Ports}}" 2>&1`);
|
|
7
|
+
return {
|
|
8
|
+
content: [{
|
|
9
|
+
type: "text",
|
|
10
|
+
text: output || "No compose containers running.",
|
|
11
|
+
}],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return errorResponse(error);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=composePs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composePs.js","sourceRoot":"","sources":["../../src/tools/composePs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,qBAAqB,CAAC;AAE7E,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,kDAAkD,EAClD,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,MAAM,WAAW,+OAA+O,CACjQ,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,IAAI,gCAAgC;qBACjD,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composePull.d.ts","sourceRoot":"","sources":["../../src/tools/composePull.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwBhD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const COMPOSE_DIR = process.env.HOMELAB_COMPOSE_DIR || "/opt/homelab/docker";
|
|
4
|
+
const inputSchema = {
|
|
5
|
+
stacks: z
|
|
6
|
+
.array(z.string())
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Specific compose files to pull images for. Omit for all."),
|
|
9
|
+
};
|
|
10
|
+
export function register(server) {
|
|
11
|
+
server.tool("homelab_composePull", "Pull latest Docker images for compose stacks on the Pi", inputSchema, async (args) => {
|
|
12
|
+
try {
|
|
13
|
+
let files = "-f compose.base.yml";
|
|
14
|
+
if (args.stacks && args.stacks.length > 0) {
|
|
15
|
+
files += " " + args.stacks.map((s) => `-f ${s}`).join(" ");
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
files += " -f compose.monitoring.yml -f compose.network.yml -f compose.apps.yml -f compose.security.yml -f compose.storage.yml -f compose.tools.yml";
|
|
19
|
+
}
|
|
20
|
+
const output = await execSSH(`cd ${COMPOSE_DIR} && docker compose ${files} pull 2>&1`);
|
|
21
|
+
return { content: [{ type: "text", text: output }] };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return errorResponse(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=composePull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composePull.js","sourceRoot":"","sources":["../../src/tools/composePull.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,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,qBAAqB,CAAC;AAE7E,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,CAAC;SACN,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,EAAE;SACV,QAAQ,CAAC,0DAA0D,CAAC;CACxE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,wDAAwD,EACxD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,qBAAqB,CAAC;YAClC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,KAAK,IAAI,2IAA2I,CAAC;YACvJ,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,MAAM,WAAW,sBAAsB,KAAK,YAAY,CACzD,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeUp.d.ts","sourceRoot":"","sources":["../../src/tools/composeUp.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwBhD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const COMPOSE_DIR = process.env.HOMELAB_COMPOSE_DIR || "/opt/homelab/docker";
|
|
4
|
+
const inputSchema = {
|
|
5
|
+
stacks: z
|
|
6
|
+
.array(z.string())
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Specific compose files to start (e.g. ['compose.monitoring.yml']). Omit for all."),
|
|
9
|
+
};
|
|
10
|
+
export function register(server) {
|
|
11
|
+
server.tool("homelab_composeUp", "Start Docker Compose stacks on the Pi", inputSchema, async (args) => {
|
|
12
|
+
try {
|
|
13
|
+
let files = "-f compose.base.yml";
|
|
14
|
+
if (args.stacks && args.stacks.length > 0) {
|
|
15
|
+
files += " " + args.stacks.map((s) => `-f ${s}`).join(" ");
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
files += " -f compose.monitoring.yml -f compose.network.yml -f compose.apps.yml -f compose.security.yml -f compose.storage.yml -f compose.tools.yml";
|
|
19
|
+
}
|
|
20
|
+
const output = await execSSH(`cd ${COMPOSE_DIR} && docker compose ${files} up -d --remove-orphans 2>&1`);
|
|
21
|
+
return { content: [{ type: "text", text: output }] };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return errorResponse(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=composeUp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeUp.js","sourceRoot":"","sources":["../../src/tools/composeUp.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,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,qBAAqB,CAAC;AAE7E,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,CAAC;SACN,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,EAAE;SACV,QAAQ,CAAC,kFAAkF,CAAC;CAChG,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,uCAAuC,EACvC,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,qBAAqB,CAAC;YAClC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,KAAK,IAAI,2IAA2I,CAAC;YACvJ,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,MAAM,WAAW,sBAAsB,KAAK,8BAA8B,CAC3E,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diskUsage.d.ts","sourceRoot":"","sources":["../../src/tools/diskUsage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwBhD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
path: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.default("/")
|
|
8
|
+
.describe("Path to check disk usage for (default: /)"),
|
|
9
|
+
depth: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.positive()
|
|
13
|
+
.optional()
|
|
14
|
+
.default(1)
|
|
15
|
+
.describe("Directory depth for breakdown (default: 1)"),
|
|
16
|
+
};
|
|
17
|
+
export function register(server) {
|
|
18
|
+
server.tool("homelab_diskUsage", "Get detailed disk usage breakdown on the Pi", inputSchema, async (args) => {
|
|
19
|
+
try {
|
|
20
|
+
const output = await execSSH([
|
|
21
|
+
'echo "=== Filesystem ==="',
|
|
22
|
+
"df -h",
|
|
23
|
+
'echo ""',
|
|
24
|
+
`echo "=== Directory Usage (${args.path}, depth ${args.depth}) ==="`,
|
|
25
|
+
`sudo du -h --max-depth=${args.depth} ${args.path} 2>/dev/null | sort -rh | head -20`,
|
|
26
|
+
'echo ""',
|
|
27
|
+
'echo "=== Docker Disk Usage ==="',
|
|
28
|
+
"docker system df 2>/dev/null || echo 'Docker not available'",
|
|
29
|
+
].join(" && "));
|
|
30
|
+
return { content: [{ type: "text", text: output }] };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return errorResponse(error);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=diskUsage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diskUsage.js","sourceRoot":"","sources":["../../src/tools/diskUsage.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,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,2CAA2C,CAAC;IACxD,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,4CAA4C,CAAC;CAC1D,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,6CAA6C,EAC7C,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,2BAA2B;gBAC3B,OAAO;gBACP,SAAS;gBACT,8BAA8B,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,KAAK,QAAQ;gBACpE,0BAA0B,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,oCAAoC;gBACrF,SAAS;gBACT,kCAAkC;gBAClC,6DAA6D;aAC9D,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAEhB,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkInfo.d.ts","sourceRoot":"","sources":["../../src/tools/networkInfo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2BhD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
2
|
+
export function register(server) {
|
|
3
|
+
server.tool("homelab_networkInfo", "Get network info from the Pi: IP addresses, interfaces, DNS, and Tailscale status", {}, async () => {
|
|
4
|
+
try {
|
|
5
|
+
const output = await execSSH([
|
|
6
|
+
'echo "=== IP Addresses ==="',
|
|
7
|
+
"ip -4 addr show | grep inet | awk '{print $NF, $2}'",
|
|
8
|
+
'echo ""',
|
|
9
|
+
'echo "=== Default Gateway ==="',
|
|
10
|
+
"ip route | grep default",
|
|
11
|
+
'echo ""',
|
|
12
|
+
'echo "=== DNS ==="',
|
|
13
|
+
"cat /etc/resolv.conf | grep nameserver",
|
|
14
|
+
'echo ""',
|
|
15
|
+
'echo "=== Tailscale ==="',
|
|
16
|
+
"tailscale status 2>/dev/null || echo 'Tailscale not installed or not running'",
|
|
17
|
+
].join(" && "));
|
|
18
|
+
return { content: [{ type: "text", text: output }] };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return errorResponse(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=networkInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkInfo.js","sourceRoot":"","sources":["../../src/tools/networkInfo.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,mFAAmF,EACnF,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,6BAA6B;gBAC7B,qDAAqD;gBACrD,SAAS;gBACT,gCAAgC;gBAChC,yBAAyB;gBACzB,SAAS;gBACT,oBAAoB;gBACpB,wCAAwC;gBACxC,SAAS;gBACT,0BAA0B;gBAC1B,+EAA+E;aAChF,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAEhB,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piReboot.d.ts","sourceRoot":"","sources":["../../src/tools/piReboot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsChD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
confirm: z
|
|
5
|
+
.boolean()
|
|
6
|
+
.describe("Must be true to proceed with reboot. Safety check to prevent accidental reboots."),
|
|
7
|
+
};
|
|
8
|
+
export function register(server) {
|
|
9
|
+
server.tool("homelab_piReboot", "Safely reboot the Raspberry Pi. Checks for running containers before rebooting.", inputSchema, async (args) => {
|
|
10
|
+
try {
|
|
11
|
+
if (!args.confirm) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: "Reboot cancelled. Set confirm=true to proceed.",
|
|
16
|
+
}],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const containers = await execSSH("docker ps --format '{{.Names}}' 2>/dev/null | wc -l");
|
|
20
|
+
const count = parseInt(containers, 10) || 0;
|
|
21
|
+
let preCheck = "";
|
|
22
|
+
if (count > 0) {
|
|
23
|
+
preCheck = `Warning: ${count} running container(s) will be stopped.\n`;
|
|
24
|
+
}
|
|
25
|
+
const uptime = await execSSH("uptime -p");
|
|
26
|
+
preCheck += `Current uptime: ${uptime}\n`;
|
|
27
|
+
preCheck += "Initiating reboot...\n";
|
|
28
|
+
await execSSH("sudo shutdown -r +0");
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: preCheck + "Reboot command sent." }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return errorResponse(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=piReboot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piReboot.js","sourceRoot":"","sources":["../../src/tools/piReboot.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,OAAO,EAAE,CAAC;SACP,OAAO,EAAE;SACT,QAAQ,CAAC,kFAAkF,CAAC;CAChG,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,iFAAiF,EACjF,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,gDAAgD;yBACvD,CAAC;iBACH,CAAC;YACJ,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,qDAAqD,CAAC,CAAC;YACxF,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,QAAQ,GAAG,EAAE,CAAC;YAClB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,QAAQ,GAAG,YAAY,KAAK,0CAA0C,CAAC;YACzE,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;YAC1C,QAAQ,IAAI,mBAAmB,MAAM,IAAI,CAAC;YAC1C,QAAQ,IAAI,wBAAwB,CAAC;YAErC,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;YAErC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAAC;aAC9E,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piStatus.d.ts","sourceRoot":"","sources":["../../src/tools/piStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8BhD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
2
|
+
export function register(server) {
|
|
3
|
+
server.tool("homelab_piStatus", "Get Raspberry Pi system status: CPU temp, memory, disk, uptime, and throttle state", {}, async () => {
|
|
4
|
+
try {
|
|
5
|
+
const output = await execSSH([
|
|
6
|
+
'echo "=== Uptime ==="',
|
|
7
|
+
"uptime",
|
|
8
|
+
'echo ""',
|
|
9
|
+
'echo "=== CPU Temperature ==="',
|
|
10
|
+
"cat /sys/class/thermal/thermal_zone0/temp | awk '{printf \"%.1f C\\n\", $1/1000}'",
|
|
11
|
+
'echo ""',
|
|
12
|
+
'echo "=== Memory ==="',
|
|
13
|
+
"free -h | head -2",
|
|
14
|
+
'echo ""',
|
|
15
|
+
'echo "=== Disk ==="',
|
|
16
|
+
"df -h / | tail -1",
|
|
17
|
+
'echo ""',
|
|
18
|
+
'echo "=== Throttle Status ==="',
|
|
19
|
+
"vcgencmd get_throttled 2>/dev/null || echo 'N/A'",
|
|
20
|
+
].join(" && "));
|
|
21
|
+
return { content: [{ type: "text", text: output }] };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return errorResponse(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=piStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piStatus.js","sourceRoot":"","sources":["../../src/tools/piStatus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,oFAAoF,EACpF,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,uBAAuB;gBACvB,QAAQ;gBACR,SAAS;gBACT,gCAAgC;gBAChC,mFAAmF;gBACnF,SAAS;gBACT,uBAAuB;gBACvB,mBAAmB;gBACnB,SAAS;gBACT,qBAAqB;gBACrB,mBAAmB;gBACnB,SAAS;gBACT,gCAAgC;gBAChC,kDAAkD;aACnD,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAEhB,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceHealth.d.ts","sourceRoot":"","sources":["../../src/tools/serviceHealth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6BhD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
service: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Specific container name to check, or omit for all"),
|
|
8
|
+
};
|
|
9
|
+
export function register(server) {
|
|
10
|
+
server.tool("homelab_serviceHealth", "Check Docker container health status on the Pi", inputSchema, async (args) => {
|
|
11
|
+
try {
|
|
12
|
+
const filter = args.service ? `--filter "name=${args.service}"` : "";
|
|
13
|
+
const ps = await execSSH(`docker ps ${filter} --format "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}"`);
|
|
14
|
+
const unhealthy = await execSSH(`docker ps ${filter} --filter "health=unhealthy" --format "{{.Names}}"`);
|
|
15
|
+
let text = ps || "No containers found.";
|
|
16
|
+
if (unhealthy) {
|
|
17
|
+
text += `\n\nUnhealthy containers: ${unhealthy}`;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
text += "\n\nAll containers healthy.";
|
|
21
|
+
}
|
|
22
|
+
return { content: [{ type: "text", text }] };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return errorResponse(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=serviceHealth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceHealth.js","sourceRoot":"","sources":["../../src/tools/serviceHealth.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,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,mDAAmD,CAAC;CACjE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,gDAAgD,EAChD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,EAAE,GAAG,MAAM,OAAO,CACtB,aAAa,MAAM,yDAAyD,CAC7E,CAAC;YAEF,MAAM,SAAS,GAAG,MAAM,OAAO,CAC7B,aAAa,MAAM,oDAAoD,CACxE,CAAC;YAEF,IAAI,IAAI,GAAG,EAAE,IAAI,sBAAsB,CAAC;YACxC,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,IAAI,6BAA6B,SAAS,EAAE,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,6BAA6B,CAAC;YACxC,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceLogs.d.ts","sourceRoot":"","sources":["../../src/tools/serviceLogs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAczE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsBhD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
service: z.string().min(1).describe("Container name to get logs from"),
|
|
5
|
+
lines: z
|
|
6
|
+
.number()
|
|
7
|
+
.int()
|
|
8
|
+
.positive()
|
|
9
|
+
.optional()
|
|
10
|
+
.default(50)
|
|
11
|
+
.describe("Number of recent log lines to return"),
|
|
12
|
+
};
|
|
13
|
+
export function register(server) {
|
|
14
|
+
server.tool("homelab_serviceLogs", "Tail recent logs from a Docker container on the Pi", inputSchema, async (args) => {
|
|
15
|
+
try {
|
|
16
|
+
const output = await execSSH(`docker logs --tail ${args.lines} ${args.service} 2>&1`);
|
|
17
|
+
return {
|
|
18
|
+
content: [{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: output || `No logs found for ${args.service}.`,
|
|
21
|
+
}],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return errorResponse(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=serviceLogs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceLogs.js","sourceRoot":"","sources":["../../src/tools/serviceLogs.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,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IACtE,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,sCAAsC,CAAC;CACpD,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,oDAAoD,EACpD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,sBAAsB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,OAAO,CACxD,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,IAAI,qBAAqB,IAAI,CAAC,OAAO,GAAG;qBACrD,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceRestart.d.ts","sourceRoot":"","sources":["../../src/tools/serviceRestart.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuBhD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
3
|
+
const inputSchema = {
|
|
4
|
+
service: z.string().min(1).describe("Container name to restart"),
|
|
5
|
+
};
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.tool("homelab_serviceRestart", "Restart a Docker container on the Pi", inputSchema, async (args) => {
|
|
8
|
+
try {
|
|
9
|
+
await execSSH(`docker restart ${args.service}`);
|
|
10
|
+
const status = await execSSH(`docker ps --filter "name=${args.service}" --format "{{.Names}}\\t{{.Status}}"`);
|
|
11
|
+
return {
|
|
12
|
+
content: [{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `Restarted ${args.service}.\n${status}`,
|
|
15
|
+
}],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return errorResponse(error);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=serviceRestart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceRestart.js","sourceRoot":"","sources":["../../src/tools/serviceRestart.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,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,2BAA2B,CAAC;CACjE,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,sCAAsC,EACtC,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,kBAAkB,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,4BAA4B,IAAI,CAAC,OAAO,uCAAuC,CAChF,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,aAAa,IAAI,CAAC,OAAO,MAAM,MAAM,EAAE;qBAC9C,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshTest.d.ts","sourceRoot":"","sources":["../../src/tools/sshTest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4BhD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSSH, errorResponse } from "../utils/ssh-api.js";
|
|
2
|
+
export function register(server) {
|
|
3
|
+
server.tool("homelab_sshTest", "Test SSH connectivity to the Raspberry Pi", {}, async () => {
|
|
4
|
+
try {
|
|
5
|
+
const host = process.env.HOMELAB_PI_HOST || "raspi5.local";
|
|
6
|
+
const user = process.env.HOMELAB_PI_USER || "tmhs";
|
|
7
|
+
const output = await execSSH("echo ok && hostname && uptime -p");
|
|
8
|
+
const lines = output.split("\n");
|
|
9
|
+
return {
|
|
10
|
+
content: [{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: [
|
|
13
|
+
`SSH connection to ${user}@${host}: OK`,
|
|
14
|
+
`Hostname: ${lines[1] || "unknown"}`,
|
|
15
|
+
`Uptime: ${lines[2] || "unknown"}`,
|
|
16
|
+
].join("\n"),
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return errorResponse(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sshTest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshTest.js","sourceRoot":"","sources":["../../src/tools/sshTest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,2CAA2C,EAC3C,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,cAAc,CAAC;YAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,MAAM,CAAC;YAEnD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,kCAAkC,CAAC,CAAC;YACjE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEjC,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;4BACJ,qBAAqB,IAAI,IAAI,IAAI,MAAM;4BACvC,aAAa,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE;4BACpC,WAAW,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE;yBACnC,CAAC,IAAI,CAAC,IAAI,CAAC;qBACb,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class SSHError extends Error {
|
|
2
|
+
readonly command?: string;
|
|
3
|
+
constructor(message: string, command?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class SSHConnectionError extends SSHError {
|
|
6
|
+
constructor(host: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class SSHAuthError extends SSHError {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
export declare class SSHTimeoutError extends SSHError {
|
|
12
|
+
constructor(timeoutMs: number);
|
|
13
|
+
}
|
|
14
|
+
export declare class CommandFailedError extends SSHError {
|
|
15
|
+
readonly exitCode: number;
|
|
16
|
+
constructor(command: string, exitCode: number, stderr: string);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAK9C;AAED,qBAAa,kBAAmB,SAAQ,QAAQ;gBAClC,IAAI,EAAE,MAAM;CAIzB;AAED,qBAAa,YAAa,SAAQ,QAAQ;;CAOzC;AAED,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,SAAS,EAAE,MAAM;CAI9B;AAED,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK9D"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class SSHError extends Error {
|
|
2
|
+
command;
|
|
3
|
+
constructor(message, command) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "SSHError";
|
|
6
|
+
this.command = command;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class SSHConnectionError extends SSHError {
|
|
10
|
+
constructor(host) {
|
|
11
|
+
super(`Cannot connect to ${host}. Verify the host is reachable and SSH is running.`);
|
|
12
|
+
this.name = "SSHConnectionError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class SSHAuthError extends SSHError {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("SSH authentication failed. Check your key path (HOMELAB_PI_KEY_PATH) and username (HOMELAB_PI_USER).");
|
|
18
|
+
this.name = "SSHAuthError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class SSHTimeoutError extends SSHError {
|
|
22
|
+
constructor(timeoutMs) {
|
|
23
|
+
super(`SSH connection timed out after ${timeoutMs}ms.`);
|
|
24
|
+
this.name = "SSHTimeoutError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class CommandFailedError extends SSHError {
|
|
28
|
+
exitCode;
|
|
29
|
+
constructor(command, exitCode, stderr) {
|
|
30
|
+
super(stderr.trim() || `Command exited with code ${exitCode}`, command);
|
|
31
|
+
this.name = "CommandFailedError";
|
|
32
|
+
this.exitCode = exitCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjB,OAAO,CAAU;IAEjC,YAAY,OAAe,EAAE,OAAgB;QAC3C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;IAC9C,YAAY,IAAY;QACtB,KAAK,CAAC,qBAAqB,IAAI,oDAAoD,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACxC;QACE,KAAK,CACH,sGAAsG,CACvG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAC3C,YAAY,SAAiB;QAC3B,KAAK,CAAC,kCAAkC,SAAS,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;IAC9B,QAAQ,CAAS;IAEjC,YAAY,OAAe,EAAE,QAAgB,EAAE,MAAc;QAC3D,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,4BAA4B,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC;QACxE,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function execSSH(command: string): Promise<string>;
|
|
2
|
+
export declare function checkSSHAvailable(): Promise<void>;
|
|
3
|
+
export declare function errorResponse(error: unknown): {
|
|
4
|
+
content: {
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}[];
|
|
8
|
+
isError: true;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=ssh-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh-api.d.ts","sourceRoot":"","sources":["../../src/utils/ssh-api.ts"],"names":[],"mappings":"AAqBA,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyE9D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMvD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG;IAC7C,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,OAAO,EAAE,IAAI,CAAC;CACf,CA4BA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { Client } from "ssh2";
|
|
3
|
+
import { SSHError, SSHConnectionError, SSHAuthError, SSHTimeoutError, CommandFailedError, } from "./errors.js";
|
|
4
|
+
const CONNECT_TIMEOUT_MS = 10_000;
|
|
5
|
+
const EXEC_TIMEOUT_MS = 30_000;
|
|
6
|
+
function getConfig() {
|
|
7
|
+
const host = process.env.HOMELAB_PI_HOST || "raspi5.local";
|
|
8
|
+
const username = process.env.HOMELAB_PI_USER || "tmhs";
|
|
9
|
+
const keyPath = process.env.HOMELAB_PI_KEY_PATH || "";
|
|
10
|
+
return { host, username, keyPath };
|
|
11
|
+
}
|
|
12
|
+
export async function execSSH(command) {
|
|
13
|
+
const { host, username, keyPath } = getConfig();
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const conn = new Client();
|
|
16
|
+
let stdout = "";
|
|
17
|
+
let stderr = "";
|
|
18
|
+
let timedOut = false;
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
timedOut = true;
|
|
21
|
+
conn.end();
|
|
22
|
+
reject(new SSHTimeoutError(EXEC_TIMEOUT_MS));
|
|
23
|
+
}, EXEC_TIMEOUT_MS);
|
|
24
|
+
conn
|
|
25
|
+
.on("ready", () => {
|
|
26
|
+
conn.exec(command, (err, stream) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
conn.end();
|
|
30
|
+
return reject(new SSHError(err.message, command));
|
|
31
|
+
}
|
|
32
|
+
stream
|
|
33
|
+
.on("close", (code) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
conn.end();
|
|
36
|
+
if (timedOut)
|
|
37
|
+
return;
|
|
38
|
+
if (code !== 0 && code !== null) {
|
|
39
|
+
reject(new CommandFailedError(command, code, stderr));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
resolve(stdout.trim());
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.on("data", (data) => {
|
|
46
|
+
stdout += data.toString();
|
|
47
|
+
})
|
|
48
|
+
.stderr.on("data", (data) => {
|
|
49
|
+
stderr += data.toString();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
.on("error", (err) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
if (timedOut)
|
|
56
|
+
return;
|
|
57
|
+
if (err.message.includes("authentication") ||
|
|
58
|
+
err.message.includes("All configured authentication methods failed")) {
|
|
59
|
+
reject(new SSHAuthError());
|
|
60
|
+
}
|
|
61
|
+
else if (err.message.includes("ECONNREFUSED") ||
|
|
62
|
+
err.message.includes("ENOTFOUND") ||
|
|
63
|
+
err.message.includes("ETIMEDOUT") ||
|
|
64
|
+
err.level === "client-timeout") {
|
|
65
|
+
reject(new SSHConnectionError(host));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
reject(new SSHError(err.message));
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.connect({
|
|
72
|
+
host,
|
|
73
|
+
port: 22,
|
|
74
|
+
username,
|
|
75
|
+
privateKey: keyPath ? readFileSync(keyPath) : undefined,
|
|
76
|
+
agent: process.env.SSH_AUTH_SOCK,
|
|
77
|
+
readyTimeout: CONNECT_TIMEOUT_MS,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function checkSSHAvailable() {
|
|
82
|
+
try {
|
|
83
|
+
await execSSH("echo ok");
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function errorResponse(error) {
|
|
90
|
+
if (error instanceof SSHError) {
|
|
91
|
+
const parts = [`[${error.name}] ${error.message}`];
|
|
92
|
+
if (error.command) {
|
|
93
|
+
parts.push(`Command: ${error.command}`);
|
|
94
|
+
}
|
|
95
|
+
const suggestion = getErrorSuggestion(error);
|
|
96
|
+
if (suggestion) {
|
|
97
|
+
parts.push(`Suggestion: ${suggestion}`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: error.message }],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text", text: "An unknown error occurred." }],
|
|
112
|
+
isError: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getErrorSuggestion(error) {
|
|
116
|
+
if (error instanceof SSHConnectionError) {
|
|
117
|
+
return "Check that the Pi is powered on and reachable. Verify HOMELAB_PI_HOST is correct.";
|
|
118
|
+
}
|
|
119
|
+
if (error instanceof SSHAuthError) {
|
|
120
|
+
return "Verify HOMELAB_PI_KEY_PATH points to a valid private key and HOMELAB_PI_USER is correct.";
|
|
121
|
+
}
|
|
122
|
+
if (error instanceof SSHTimeoutError) {
|
|
123
|
+
return "The Pi may be under heavy load or unreachable. Try again or check network connectivity.";
|
|
124
|
+
}
|
|
125
|
+
if (error instanceof CommandFailedError) {
|
|
126
|
+
return "The command failed on the Pi. Check the error output above for details.";
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=ssh-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh-api.js","sourceRoot":"","sources":["../../src/utils/ssh-api.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tmhs/homelab-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for home lab operations via SSH - 15 tools for system status, Docker Compose management, service health, networking, backups, and administration on a Raspberry Pi.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"homelab-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">= 20.0.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsx watch src/index.ts",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
|
+
"ssh2": "^1.16.0",
|
|
29
|
+
"zod": "^3.23.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.5.2",
|
|
33
|
+
"@types/ssh2": "^1.15.0",
|
|
34
|
+
"tsx": "^4.19.0",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^4.1.2"
|
|
37
|
+
},
|
|
38
|
+
"license": "CC-BY-NC-ND-4.0",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/TMHSDigital/Home-Lab-Developer-Tools.git",
|
|
42
|
+
"directory": "mcp-server"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/TMHSDigital/Home-Lab-Developer-Tools",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/TMHSDigital/Home-Lab-Developer-Tools/issues"
|
|
47
|
+
},
|
|
48
|
+
"author": "TMHSDigital"
|
|
49
|
+
}
|