@samik081/mcp-pve 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 +21 -0
- package/README.md +368 -0
- package/dist/core/client.d.ts +43 -0
- package/dist/core/client.js +143 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +68 -0
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/errors.js +43 -0
- package/dist/core/logger.d.ts +15 -0
- package/dist/core/logger.js +26 -0
- package/dist/core/server.d.ts +15 -0
- package/dist/core/server.js +30 -0
- package/dist/core/tools.d.ts +29 -0
- package/dist/core/tools.js +64 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +51 -0
- package/dist/tools/access.d.ts +7 -0
- package/dist/tools/access.js +234 -0
- package/dist/tools/backup.d.ts +7 -0
- package/dist/tools/backup.js +176 -0
- package/dist/tools/cluster.d.ts +7 -0
- package/dist/tools/cluster.js +150 -0
- package/dist/tools/firewall.d.ts +7 -0
- package/dist/tools/firewall.js +215 -0
- package/dist/tools/ha.d.ts +9 -0
- package/dist/tools/ha.js +138 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/lxc.d.ts +7 -0
- package/dist/tools/lxc.js +371 -0
- package/dist/tools/network.d.ts +7 -0
- package/dist/tools/network.js +157 -0
- package/dist/tools/nodes.d.ts +7 -0
- package/dist/tools/nodes.js +131 -0
- package/dist/tools/pools.d.ts +7 -0
- package/dist/tools/pools.js +98 -0
- package/dist/tools/qemu.d.ts +7 -0
- package/dist/tools/qemu.js +394 -0
- package/dist/tools/storage.d.ts +7 -0
- package/dist/tools/storage.js +216 -0
- package/dist/tools/tasks.d.ts +7 -0
- package/dist/tools/tasks.js +106 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.js +17 -0
- package/package.json +55 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QEMU VM tools: list, status, config, snapshots, power actions, and lifecycle.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { registerTool } from "../core/tools.js";
|
|
6
|
+
export function registerQemuTools(server, client, config) {
|
|
7
|
+
// --- Read-only tools ---
|
|
8
|
+
registerTool(server, config, {
|
|
9
|
+
name: "pve_list_qemu_vms",
|
|
10
|
+
description: "List all QEMU virtual machines on a specific node",
|
|
11
|
+
category: "qemu",
|
|
12
|
+
accessTier: "read-only",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
node: z.string().describe("The node name"),
|
|
15
|
+
},
|
|
16
|
+
handler: async (args) => {
|
|
17
|
+
const data = await client.get(`/nodes/${args.node}/qemu`);
|
|
18
|
+
return JSON.stringify(data, null, 2);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
registerTool(server, config, {
|
|
22
|
+
name: "pve_get_qemu_status",
|
|
23
|
+
description: "Get the current status of a QEMU VM including CPU, memory, disk, and network usage",
|
|
24
|
+
category: "qemu",
|
|
25
|
+
accessTier: "read-only",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
node: z.string().describe("The node name"),
|
|
28
|
+
vmid: z.number().describe("The VM ID"),
|
|
29
|
+
},
|
|
30
|
+
handler: async (args) => {
|
|
31
|
+
const data = await client.get(`/nodes/${args.node}/qemu/${args.vmid}/status/current`);
|
|
32
|
+
return JSON.stringify(data, null, 2);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
registerTool(server, config, {
|
|
36
|
+
name: "pve_get_qemu_config",
|
|
37
|
+
description: "Get the configuration of a QEMU VM",
|
|
38
|
+
category: "qemu",
|
|
39
|
+
accessTier: "read-only",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
node: z.string().describe("The node name"),
|
|
42
|
+
vmid: z.number().describe("The VM ID"),
|
|
43
|
+
},
|
|
44
|
+
handler: async (args) => {
|
|
45
|
+
const data = await client.get(`/nodes/${args.node}/qemu/${args.vmid}/config`);
|
|
46
|
+
return JSON.stringify(data, null, 2);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
registerTool(server, config, {
|
|
50
|
+
name: "pve_get_qemu_rrddata",
|
|
51
|
+
description: "Get RRD statistics (CPU, memory, disk, network) for a QEMU VM over a time period",
|
|
52
|
+
category: "qemu",
|
|
53
|
+
accessTier: "read-only",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
node: z.string().describe("The node name"),
|
|
56
|
+
vmid: z.number().describe("The VM ID"),
|
|
57
|
+
timeframe: z
|
|
58
|
+
.enum(["hour", "day", "week", "month", "year"])
|
|
59
|
+
.describe("Time frame for the RRD data"),
|
|
60
|
+
},
|
|
61
|
+
handler: async (args) => {
|
|
62
|
+
const data = await client.get(`/nodes/${args.node}/qemu/${args.vmid}/rrddata?timeframe=${args.timeframe}`);
|
|
63
|
+
return JSON.stringify(data, null, 2);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
registerTool(server, config, {
|
|
67
|
+
name: "pve_list_qemu_snapshots",
|
|
68
|
+
description: "List all snapshots of a QEMU VM",
|
|
69
|
+
category: "qemu",
|
|
70
|
+
accessTier: "read-only",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
node: z.string().describe("The node name"),
|
|
73
|
+
vmid: z.number().describe("The VM ID"),
|
|
74
|
+
},
|
|
75
|
+
handler: async (args) => {
|
|
76
|
+
const data = await client.get(`/nodes/${args.node}/qemu/${args.vmid}/snapshot`);
|
|
77
|
+
return JSON.stringify(data, null, 2);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
// --- Read-execute tools (power actions) ---
|
|
81
|
+
const powerActions = [
|
|
82
|
+
{ action: "start", method: "start", desc: "Start" },
|
|
83
|
+
{ action: "stop", method: "stop", desc: "Stop (immediate)" },
|
|
84
|
+
{ action: "shutdown", method: "shutdown", desc: "Gracefully shut down" },
|
|
85
|
+
{ action: "reboot", method: "reboot", desc: "Reboot" },
|
|
86
|
+
{ action: "suspend", method: "suspend", desc: "Suspend" },
|
|
87
|
+
{ action: "resume", method: "resume", desc: "Resume" },
|
|
88
|
+
{ action: "reset", method: "reset", desc: "Reset (hard)" },
|
|
89
|
+
];
|
|
90
|
+
for (const { action, method, desc } of powerActions) {
|
|
91
|
+
registerTool(server, config, {
|
|
92
|
+
name: `pve_${action}_qemu_vm`,
|
|
93
|
+
description: `${desc} a QEMU virtual machine`,
|
|
94
|
+
category: "qemu",
|
|
95
|
+
accessTier: "read-execute",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
node: z.string().describe("The node name"),
|
|
98
|
+
vmid: z.number().describe("The VM ID"),
|
|
99
|
+
},
|
|
100
|
+
handler: async (args) => {
|
|
101
|
+
const data = await client.post(`/nodes/${args.node}/qemu/${args.vmid}/status/${method}`);
|
|
102
|
+
return `VM ${args.vmid} ${action} initiated on node ${args.node}. Task: ${data}`;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
registerTool(server, config, {
|
|
107
|
+
name: "pve_migrate_qemu_vm",
|
|
108
|
+
description: "Migrate a QEMU VM to another node in the cluster",
|
|
109
|
+
category: "qemu",
|
|
110
|
+
accessTier: "read-execute",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
node: z.string().describe("The source node name"),
|
|
113
|
+
vmid: z.number().describe("The VM ID"),
|
|
114
|
+
target: z.string().describe("The target node name"),
|
|
115
|
+
online: z
|
|
116
|
+
.boolean()
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Perform an online (live) migration (default: false)"),
|
|
119
|
+
},
|
|
120
|
+
handler: async (args) => {
|
|
121
|
+
const body = { target: args.target };
|
|
122
|
+
if (args.online !== undefined)
|
|
123
|
+
body.online = args.online ? 1 : 0;
|
|
124
|
+
const data = await client.post(`/nodes/${args.node}/qemu/${args.vmid}/migrate`, body);
|
|
125
|
+
return `VM ${args.vmid} migration to ${args.target} initiated. Task: ${data}`;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
// --- Full access tools ---
|
|
129
|
+
registerTool(server, config, {
|
|
130
|
+
name: "pve_create_qemu_vm",
|
|
131
|
+
description: "Create a new QEMU virtual machine",
|
|
132
|
+
category: "qemu",
|
|
133
|
+
accessTier: "full",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
node: z.string().describe("The node name"),
|
|
136
|
+
vmid: z.number().describe("The VM ID"),
|
|
137
|
+
name: z.string().optional().describe("VM name"),
|
|
138
|
+
memory: z
|
|
139
|
+
.number()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Memory in MB (default: 512)"),
|
|
142
|
+
cores: z
|
|
143
|
+
.number()
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Number of CPU cores (default: 1)"),
|
|
146
|
+
sockets: z
|
|
147
|
+
.number()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Number of CPU sockets (default: 1)"),
|
|
150
|
+
ostype: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("OS type (e.g. l26, win10, other)"),
|
|
154
|
+
ide2: z
|
|
155
|
+
.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("IDE device config (e.g. local:iso/image.iso,media=cdrom)"),
|
|
158
|
+
scsi0: z
|
|
159
|
+
.string()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe("SCSI disk config (e.g. local-lvm:32)"),
|
|
162
|
+
net0: z
|
|
163
|
+
.string()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("Network config (e.g. virtio,bridge=vmbr0)"),
|
|
166
|
+
scsihw: z
|
|
167
|
+
.string()
|
|
168
|
+
.optional()
|
|
169
|
+
.describe("SCSI controller type (e.g. virtio-scsi-pci)"),
|
|
170
|
+
boot: z
|
|
171
|
+
.string()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Boot order (e.g. order=scsi0;ide2;net0)"),
|
|
174
|
+
start: z
|
|
175
|
+
.boolean()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Start VM after creation"),
|
|
178
|
+
},
|
|
179
|
+
handler: async (args) => {
|
|
180
|
+
const body = { vmid: args.vmid };
|
|
181
|
+
if (args.name !== undefined)
|
|
182
|
+
body.name = args.name;
|
|
183
|
+
if (args.memory !== undefined)
|
|
184
|
+
body.memory = args.memory;
|
|
185
|
+
if (args.cores !== undefined)
|
|
186
|
+
body.cores = args.cores;
|
|
187
|
+
if (args.sockets !== undefined)
|
|
188
|
+
body.sockets = args.sockets;
|
|
189
|
+
if (args.ostype !== undefined)
|
|
190
|
+
body.ostype = args.ostype;
|
|
191
|
+
if (args.ide2 !== undefined)
|
|
192
|
+
body.ide2 = args.ide2;
|
|
193
|
+
if (args.scsi0 !== undefined)
|
|
194
|
+
body.scsi0 = args.scsi0;
|
|
195
|
+
if (args.net0 !== undefined)
|
|
196
|
+
body.net0 = args.net0;
|
|
197
|
+
if (args.scsihw !== undefined)
|
|
198
|
+
body.scsihw = args.scsihw;
|
|
199
|
+
if (args.boot !== undefined)
|
|
200
|
+
body.boot = args.boot;
|
|
201
|
+
if (args.start !== undefined)
|
|
202
|
+
body.start = args.start ? 1 : 0;
|
|
203
|
+
const data = await client.post(`/nodes/${args.node}/qemu`, body);
|
|
204
|
+
return `VM ${args.vmid} creation initiated on node ${args.node}. Task: ${data}`;
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
registerTool(server, config, {
|
|
208
|
+
name: "pve_delete_qemu_vm",
|
|
209
|
+
description: "Delete a QEMU virtual machine and all its data. The VM must be stopped first.",
|
|
210
|
+
category: "qemu",
|
|
211
|
+
accessTier: "full",
|
|
212
|
+
annotations: { destructiveHint: true },
|
|
213
|
+
inputSchema: {
|
|
214
|
+
node: z.string().describe("The node name"),
|
|
215
|
+
vmid: z.number().describe("The VM ID"),
|
|
216
|
+
purge: z
|
|
217
|
+
.boolean()
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Remove from all related configurations (e.g. backup jobs, HA)"),
|
|
220
|
+
"destroy-unreferenced-disks": z
|
|
221
|
+
.boolean()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe("Delete unreferenced disks owned by the VM"),
|
|
224
|
+
},
|
|
225
|
+
handler: async (args) => {
|
|
226
|
+
const params = new URLSearchParams();
|
|
227
|
+
if (args.purge)
|
|
228
|
+
params.set("purge", "1");
|
|
229
|
+
if (args["destroy-unreferenced-disks"])
|
|
230
|
+
params.set("destroy-unreferenced-disks", "1");
|
|
231
|
+
const qs = params.toString();
|
|
232
|
+
const path = `/nodes/${args.node}/qemu/${args.vmid}${qs ? `?${qs}` : ""}`;
|
|
233
|
+
const data = await client.delete(path);
|
|
234
|
+
return `VM ${args.vmid} deletion initiated on node ${args.node}. Task: ${data}`;
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
registerTool(server, config, {
|
|
238
|
+
name: "pve_update_qemu_config",
|
|
239
|
+
description: "Update the configuration of a QEMU VM",
|
|
240
|
+
category: "qemu",
|
|
241
|
+
accessTier: "full",
|
|
242
|
+
inputSchema: {
|
|
243
|
+
node: z.string().describe("The node name"),
|
|
244
|
+
vmid: z.number().describe("The VM ID"),
|
|
245
|
+
name: z.string().optional().describe("VM name"),
|
|
246
|
+
memory: z.number().optional().describe("Memory in MB"),
|
|
247
|
+
cores: z.number().optional().describe("Number of CPU cores"),
|
|
248
|
+
sockets: z.number().optional().describe("Number of CPU sockets"),
|
|
249
|
+
description: z.string().optional().describe("VM description"),
|
|
250
|
+
onboot: z
|
|
251
|
+
.boolean()
|
|
252
|
+
.optional()
|
|
253
|
+
.describe("Start on boot"),
|
|
254
|
+
net0: z
|
|
255
|
+
.string()
|
|
256
|
+
.optional()
|
|
257
|
+
.describe("Network device config"),
|
|
258
|
+
scsi0: z
|
|
259
|
+
.string()
|
|
260
|
+
.optional()
|
|
261
|
+
.describe("SCSI disk config"),
|
|
262
|
+
},
|
|
263
|
+
handler: async (args) => {
|
|
264
|
+
const body = {};
|
|
265
|
+
if (args.name !== undefined)
|
|
266
|
+
body.name = args.name;
|
|
267
|
+
if (args.memory !== undefined)
|
|
268
|
+
body.memory = args.memory;
|
|
269
|
+
if (args.cores !== undefined)
|
|
270
|
+
body.cores = args.cores;
|
|
271
|
+
if (args.sockets !== undefined)
|
|
272
|
+
body.sockets = args.sockets;
|
|
273
|
+
if (args.description !== undefined)
|
|
274
|
+
body.description = args.description;
|
|
275
|
+
if (args.onboot !== undefined)
|
|
276
|
+
body.onboot = args.onboot ? 1 : 0;
|
|
277
|
+
if (args.net0 !== undefined)
|
|
278
|
+
body.net0 = args.net0;
|
|
279
|
+
if (args.scsi0 !== undefined)
|
|
280
|
+
body.scsi0 = args.scsi0;
|
|
281
|
+
await client.put(`/nodes/${args.node}/qemu/${args.vmid}/config`, body);
|
|
282
|
+
return `VM ${args.vmid} configuration updated on node ${args.node}.`;
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
registerTool(server, config, {
|
|
286
|
+
name: "pve_clone_qemu_vm",
|
|
287
|
+
description: "Clone a QEMU VM to create a new VM from it",
|
|
288
|
+
category: "qemu",
|
|
289
|
+
accessTier: "full",
|
|
290
|
+
inputSchema: {
|
|
291
|
+
node: z.string().describe("The source node name"),
|
|
292
|
+
vmid: z.number().describe("The source VM ID"),
|
|
293
|
+
newid: z.number().describe("The new VM ID for the clone"),
|
|
294
|
+
name: z.string().optional().describe("Name for the cloned VM"),
|
|
295
|
+
target: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("Target node for the clone (default: same node)"),
|
|
299
|
+
full: z
|
|
300
|
+
.boolean()
|
|
301
|
+
.optional()
|
|
302
|
+
.describe("Full clone (true) or linked clone (false)"),
|
|
303
|
+
description: z
|
|
304
|
+
.string()
|
|
305
|
+
.optional()
|
|
306
|
+
.describe("Description for the cloned VM"),
|
|
307
|
+
snapname: z
|
|
308
|
+
.string()
|
|
309
|
+
.optional()
|
|
310
|
+
.describe("Snapshot name to clone from"),
|
|
311
|
+
storage: z
|
|
312
|
+
.string()
|
|
313
|
+
.optional()
|
|
314
|
+
.describe("Target storage for full clone"),
|
|
315
|
+
},
|
|
316
|
+
handler: async (args) => {
|
|
317
|
+
const body = { newid: args.newid };
|
|
318
|
+
if (args.name !== undefined)
|
|
319
|
+
body.name = args.name;
|
|
320
|
+
if (args.target !== undefined)
|
|
321
|
+
body.target = args.target;
|
|
322
|
+
if (args.full !== undefined)
|
|
323
|
+
body.full = args.full ? 1 : 0;
|
|
324
|
+
if (args.description !== undefined)
|
|
325
|
+
body.description = args.description;
|
|
326
|
+
if (args.snapname !== undefined)
|
|
327
|
+
body.snapname = args.snapname;
|
|
328
|
+
if (args.storage !== undefined)
|
|
329
|
+
body.storage = args.storage;
|
|
330
|
+
const data = await client.post(`/nodes/${args.node}/qemu/${args.vmid}/clone`, body);
|
|
331
|
+
return `VM ${args.vmid} clone to VMID ${args.newid} initiated. Task: ${data}`;
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
registerTool(server, config, {
|
|
335
|
+
name: "pve_create_qemu_snapshot",
|
|
336
|
+
description: "Create a snapshot of a QEMU VM",
|
|
337
|
+
category: "qemu",
|
|
338
|
+
accessTier: "full",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
node: z.string().describe("The node name"),
|
|
341
|
+
vmid: z.number().describe("The VM ID"),
|
|
342
|
+
snapname: z.string().describe("Name for the snapshot"),
|
|
343
|
+
description: z
|
|
344
|
+
.string()
|
|
345
|
+
.optional()
|
|
346
|
+
.describe("Description for the snapshot"),
|
|
347
|
+
vmstate: z
|
|
348
|
+
.boolean()
|
|
349
|
+
.optional()
|
|
350
|
+
.describe("Include VM RAM state in snapshot"),
|
|
351
|
+
},
|
|
352
|
+
handler: async (args) => {
|
|
353
|
+
const body = { snapname: args.snapname };
|
|
354
|
+
if (args.description !== undefined)
|
|
355
|
+
body.description = args.description;
|
|
356
|
+
if (args.vmstate !== undefined)
|
|
357
|
+
body.vmstate = args.vmstate ? 1 : 0;
|
|
358
|
+
const data = await client.post(`/nodes/${args.node}/qemu/${args.vmid}/snapshot`, body);
|
|
359
|
+
return `Snapshot '${args.snapname}' creation initiated for VM ${args.vmid}. Task: ${data}`;
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
registerTool(server, config, {
|
|
363
|
+
name: "pve_delete_qemu_snapshot",
|
|
364
|
+
description: "Delete a snapshot of a QEMU VM",
|
|
365
|
+
category: "qemu",
|
|
366
|
+
accessTier: "full",
|
|
367
|
+
annotations: { destructiveHint: true },
|
|
368
|
+
inputSchema: {
|
|
369
|
+
node: z.string().describe("The node name"),
|
|
370
|
+
vmid: z.number().describe("The VM ID"),
|
|
371
|
+
snapname: z.string().describe("Name of the snapshot to delete"),
|
|
372
|
+
},
|
|
373
|
+
handler: async (args) => {
|
|
374
|
+
const data = await client.delete(`/nodes/${args.node}/qemu/${args.vmid}/snapshot/${args.snapname}`);
|
|
375
|
+
return `Snapshot '${args.snapname}' deletion initiated for VM ${args.vmid}. Task: ${data}`;
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
registerTool(server, config, {
|
|
379
|
+
name: "pve_rollback_qemu_snapshot",
|
|
380
|
+
description: "Rollback a QEMU VM to a previous snapshot state",
|
|
381
|
+
category: "qemu",
|
|
382
|
+
accessTier: "full",
|
|
383
|
+
annotations: { destructiveHint: true },
|
|
384
|
+
inputSchema: {
|
|
385
|
+
node: z.string().describe("The node name"),
|
|
386
|
+
vmid: z.number().describe("The VM ID"),
|
|
387
|
+
snapname: z.string().describe("Name of the snapshot to rollback to"),
|
|
388
|
+
},
|
|
389
|
+
handler: async (args) => {
|
|
390
|
+
const data = await client.post(`/nodes/${args.node}/qemu/${args.vmid}/snapshot/${args.snapname}/rollback`);
|
|
391
|
+
return `Rollback to snapshot '${args.snapname}' initiated for VM ${args.vmid}. Task: ${data}`;
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage tools: list, status, content, and CRUD for PVE storage backends.
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { PveClient } from "../core/client.js";
|
|
6
|
+
import type { AppConfig } from "../types/index.js";
|
|
7
|
+
export declare function registerStorageTools(server: McpServer, client: PveClient, config: AppConfig): void;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage tools: list, status, content, and CRUD for PVE storage backends.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { registerTool } from "../core/tools.js";
|
|
6
|
+
export function registerStorageTools(server, client, config) {
|
|
7
|
+
// --- Read-only tools ---
|
|
8
|
+
registerTool(server, config, {
|
|
9
|
+
name: "pve_list_storage",
|
|
10
|
+
description: "List all configured storage backends in the cluster",
|
|
11
|
+
category: "storage",
|
|
12
|
+
accessTier: "read-only",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter by storage type (e.g. dir, lvm, nfs, zfspool, cephfs)"),
|
|
18
|
+
},
|
|
19
|
+
handler: async (args) => {
|
|
20
|
+
let path = "/storage";
|
|
21
|
+
if (args.type)
|
|
22
|
+
path += `?type=${args.type}`;
|
|
23
|
+
const data = await client.get(path);
|
|
24
|
+
return JSON.stringify(data, null, 2);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
registerTool(server, config, {
|
|
28
|
+
name: "pve_get_storage_config",
|
|
29
|
+
description: "Get the configuration of a specific storage backend",
|
|
30
|
+
category: "storage",
|
|
31
|
+
accessTier: "read-only",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
storage: z.string().describe("The storage ID"),
|
|
34
|
+
},
|
|
35
|
+
handler: async (args) => {
|
|
36
|
+
const data = await client.get(`/storage/${args.storage}`);
|
|
37
|
+
return JSON.stringify(data, null, 2);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
registerTool(server, config, {
|
|
41
|
+
name: "pve_list_node_storage",
|
|
42
|
+
description: "List available storage on a specific node with usage information",
|
|
43
|
+
category: "storage",
|
|
44
|
+
accessTier: "read-only",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
node: z.string().describe("The node name"),
|
|
47
|
+
content: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Filter by content type (e.g. images, rootdir, iso, vztmpl, backup)"),
|
|
51
|
+
},
|
|
52
|
+
handler: async (args) => {
|
|
53
|
+
let path = `/nodes/${args.node}/storage`;
|
|
54
|
+
if (args.content)
|
|
55
|
+
path += `?content=${args.content}`;
|
|
56
|
+
const data = await client.get(path);
|
|
57
|
+
return JSON.stringify(data, null, 2);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
registerTool(server, config, {
|
|
61
|
+
name: "pve_get_storage_status",
|
|
62
|
+
description: "Get the status and usage of a specific storage on a node",
|
|
63
|
+
category: "storage",
|
|
64
|
+
accessTier: "read-only",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
node: z.string().describe("The node name"),
|
|
67
|
+
storage: z.string().describe("The storage ID"),
|
|
68
|
+
},
|
|
69
|
+
handler: async (args) => {
|
|
70
|
+
const data = await client.get(`/nodes/${args.node}/storage/${args.storage}/status`);
|
|
71
|
+
return JSON.stringify(data, null, 2);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
registerTool(server, config, {
|
|
75
|
+
name: "pve_list_storage_content",
|
|
76
|
+
description: "List the content (disk images, ISOs, templates, backups) of a specific storage on a node",
|
|
77
|
+
category: "storage",
|
|
78
|
+
accessTier: "read-only",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
node: z.string().describe("The node name"),
|
|
81
|
+
storage: z.string().describe("The storage ID"),
|
|
82
|
+
content: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Filter by content type (e.g. images, iso, vztmpl, backup, rootdir)"),
|
|
86
|
+
},
|
|
87
|
+
handler: async (args) => {
|
|
88
|
+
let path = `/nodes/${args.node}/storage/${args.storage}/content`;
|
|
89
|
+
if (args.content)
|
|
90
|
+
path += `?content=${args.content}`;
|
|
91
|
+
const data = await client.get(path);
|
|
92
|
+
return JSON.stringify(data, null, 2);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
// --- Full access tools ---
|
|
96
|
+
registerTool(server, config, {
|
|
97
|
+
name: "pve_create_storage",
|
|
98
|
+
description: "Create a new storage backend in the cluster",
|
|
99
|
+
category: "storage",
|
|
100
|
+
accessTier: "full",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
storage: z.string().describe("The storage ID"),
|
|
103
|
+
type: z
|
|
104
|
+
.string()
|
|
105
|
+
.describe("Storage type (e.g. dir, lvm, nfs, zfspool, cifs, cephfs, rbd)"),
|
|
106
|
+
content: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Allowed content types, comma-separated (e.g. images,rootdir,iso)"),
|
|
110
|
+
path: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Filesystem path (for dir, nfs types)"),
|
|
114
|
+
server: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Server address (for nfs, cifs, cephfs types)"),
|
|
118
|
+
export: z
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("NFS export path"),
|
|
122
|
+
vgname: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("LVM volume group name"),
|
|
126
|
+
pool: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("ZFS/Ceph pool name"),
|
|
130
|
+
nodes: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Comma-separated list of nodes where storage is available"),
|
|
134
|
+
shared: z
|
|
135
|
+
.boolean()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Whether the storage is shared across nodes"),
|
|
138
|
+
},
|
|
139
|
+
handler: async (args) => {
|
|
140
|
+
const body = {
|
|
141
|
+
storage: args.storage,
|
|
142
|
+
type: args.type,
|
|
143
|
+
};
|
|
144
|
+
if (args.content !== undefined)
|
|
145
|
+
body.content = args.content;
|
|
146
|
+
if (args.path !== undefined)
|
|
147
|
+
body.path = args.path;
|
|
148
|
+
if (args.server !== undefined)
|
|
149
|
+
body.server = args.server;
|
|
150
|
+
if (args.export !== undefined)
|
|
151
|
+
body.export = args.export;
|
|
152
|
+
if (args.vgname !== undefined)
|
|
153
|
+
body.vgname = args.vgname;
|
|
154
|
+
if (args.pool !== undefined)
|
|
155
|
+
body.pool = args.pool;
|
|
156
|
+
if (args.nodes !== undefined)
|
|
157
|
+
body.nodes = args.nodes;
|
|
158
|
+
if (args.shared !== undefined)
|
|
159
|
+
body.shared = args.shared ? 1 : 0;
|
|
160
|
+
await client.post("/storage", body);
|
|
161
|
+
return `Storage '${args.storage}' (type: ${args.type}) created successfully.`;
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
registerTool(server, config, {
|
|
165
|
+
name: "pve_update_storage",
|
|
166
|
+
description: "Update the configuration of an existing storage backend",
|
|
167
|
+
category: "storage",
|
|
168
|
+
accessTier: "full",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
storage: z.string().describe("The storage ID"),
|
|
171
|
+
content: z
|
|
172
|
+
.string()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe("Allowed content types, comma-separated"),
|
|
175
|
+
nodes: z
|
|
176
|
+
.string()
|
|
177
|
+
.optional()
|
|
178
|
+
.describe("Comma-separated list of nodes where storage is available"),
|
|
179
|
+
shared: z
|
|
180
|
+
.boolean()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("Whether the storage is shared across nodes"),
|
|
183
|
+
disable: z
|
|
184
|
+
.boolean()
|
|
185
|
+
.optional()
|
|
186
|
+
.describe("Disable the storage"),
|
|
187
|
+
},
|
|
188
|
+
handler: async (args) => {
|
|
189
|
+
const body = {};
|
|
190
|
+
if (args.content !== undefined)
|
|
191
|
+
body.content = args.content;
|
|
192
|
+
if (args.nodes !== undefined)
|
|
193
|
+
body.nodes = args.nodes;
|
|
194
|
+
if (args.shared !== undefined)
|
|
195
|
+
body.shared = args.shared ? 1 : 0;
|
|
196
|
+
if (args.disable !== undefined)
|
|
197
|
+
body.disable = args.disable ? 1 : 0;
|
|
198
|
+
await client.put(`/storage/${args.storage}`, body);
|
|
199
|
+
return `Storage '${args.storage}' updated successfully.`;
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
registerTool(server, config, {
|
|
203
|
+
name: "pve_delete_storage",
|
|
204
|
+
description: "Delete a storage backend configuration from the cluster",
|
|
205
|
+
category: "storage",
|
|
206
|
+
accessTier: "full",
|
|
207
|
+
annotations: { destructiveHint: true },
|
|
208
|
+
inputSchema: {
|
|
209
|
+
storage: z.string().describe("The storage ID to delete"),
|
|
210
|
+
},
|
|
211
|
+
handler: async (args) => {
|
|
212
|
+
await client.delete(`/storage/${args.storage}`);
|
|
213
|
+
return `Storage '${args.storage}' deleted successfully.`;
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task tools: list, status, log, and management of PVE background tasks.
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { PveClient } from "../core/client.js";
|
|
6
|
+
import type { AppConfig } from "../types/index.js";
|
|
7
|
+
export declare function registerTaskTools(server: McpServer, client: PveClient, config: AppConfig): void;
|