@serenichron/mcp-cloudron 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/dist/cloudron-client.d.ts +158 -1
- package/dist/cloudron-client.d.ts.map +1 -1
- package/dist/cloudron-client.js +553 -0
- package/dist/cloudron-client.js.map +1 -1
- package/dist/server.js +691 -0
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +182 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
package/dist/server.js
CHANGED
|
@@ -42,6 +42,268 @@ const TOOLS = [
|
|
|
42
42
|
required: [],
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
name: 'cloudron_task_status',
|
|
47
|
+
description: 'Get the status of an async operation (backup, install, restore, etc.) by task ID. Returns state (pending/running/success/error/cancelled), progress (0-100%), and message.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
taskId: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'The unique identifier of the task to check',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['taskId'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'cloudron_cancel_task',
|
|
61
|
+
description: 'Cancel a running async operation (kill switch). Returns updated task status with state "cancelled". Already completed tasks cannot be cancelled. Cancelled tasks cleanup resources (e.g., partial backups deleted).',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
taskId: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'The unique identifier of the task to cancel',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['taskId'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'cloudron_check_storage',
|
|
75
|
+
description: 'Check available disk space before operations that create data (backup, install). Returns available/total/used disk space in MB, plus warning and critical threshold alerts.',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
requiredMB: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Optional: Required disk space in MB. If provided, checks if available >= requiredMB',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: [],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'cloudron_validate_operation',
|
|
89
|
+
description: 'Pre-flight validation for destructive operations (uninstall app, delete user, restore backup). Returns validation result with blocking errors, warnings, and recommendations.',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
operation: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: ['uninstall_app', 'delete_user', 'restore_backup'],
|
|
96
|
+
description: 'Type of destructive operation to validate',
|
|
97
|
+
},
|
|
98
|
+
resourceId: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'ID of the resource being operated on (appId, userId, or backupId)',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ['operation', 'resourceId'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'cloudron_control_app',
|
|
108
|
+
description: 'Control app lifecycle (start, stop, restart). Returns 202 Accepted with task ID for async operation tracking via cloudron_task_status.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
appId: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'The unique identifier of the application to control',
|
|
115
|
+
},
|
|
116
|
+
action: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
enum: ['start', 'stop', 'restart'],
|
|
119
|
+
description: 'Action to perform on the app',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
required: ['appId', 'action'],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'cloudron_configure_app',
|
|
127
|
+
description: 'Update application configuration including environment variables, memory limits, and access control settings. Returns 200 OK with updated app config and restart requirement flag.',
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
appId: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: 'The unique identifier of the application to configure',
|
|
134
|
+
},
|
|
135
|
+
config: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
description: 'Configuration object with env vars, memoryLimit, and/or accessRestriction',
|
|
138
|
+
properties: {
|
|
139
|
+
env: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
description: 'Environment variables as key-value pairs (optional)',
|
|
142
|
+
additionalProperties: { type: 'string' },
|
|
143
|
+
},
|
|
144
|
+
memoryLimit: {
|
|
145
|
+
type: 'number',
|
|
146
|
+
description: 'Memory limit in MB (optional)',
|
|
147
|
+
},
|
|
148
|
+
accessRestriction: {
|
|
149
|
+
type: ['string', 'null'],
|
|
150
|
+
description: 'Access control settings (optional)',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
required: ['appId', 'config'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'cloudron_list_backups',
|
|
160
|
+
description: 'List all backups available on the Cloudron instance. Returns backup details including ID, timestamp, size, app count, and status. Backups are sorted by timestamp (newest first).',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {},
|
|
164
|
+
required: [],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'cloudron_create_backup',
|
|
169
|
+
description: 'Create a new backup of the Cloudron instance. Performs F36 pre-flight storage check (requires 5GB minimum). Returns task ID for tracking backup progress via cloudron_task_status (F34).',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {},
|
|
173
|
+
required: [],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'cloudron_list_users',
|
|
178
|
+
description: 'List all users on the Cloudron instance. Returns user details including ID, email, username, role, and creation date. Users are sorted by role (admin, user, guest) then email.',
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
properties: {},
|
|
182
|
+
required: [],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'cloudron_search_apps',
|
|
187
|
+
description: 'Search the Cloudron App Store for available applications. Returns app details including name, description, version, icon URL, and install count. Results are sorted by relevance score. Empty query returns all available apps.',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
query: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
description: 'Search query to filter apps (optional - empty returns all apps)',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: [],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'cloudron_validate_manifest',
|
|
201
|
+
description: 'Validate app manifest before installation (pre-flight safety check). Checks storage sufficiency via F36, dependency availability, and manifest schema validity. Returns validation report with errors and warnings.',
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
appId: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'The App Store ID to validate',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ['appId'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'cloudron_create_user',
|
|
215
|
+
description: 'Create a new user on the Cloudron instance with role assignment (atomic operation). Password must be at least 8 characters long and contain at least 1 uppercase letter and 1 number. Returns 201 Created with user object.',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
email: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'User email address (must be valid format)',
|
|
222
|
+
},
|
|
223
|
+
password: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
description: 'User password (8+ characters, 1 uppercase, 1 number)',
|
|
226
|
+
},
|
|
227
|
+
role: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
enum: ['admin', 'user', 'guest'],
|
|
230
|
+
description: 'User role: admin (full access), user (standard access), or guest (limited access)',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
required: ['email', 'password', 'role'],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'cloudron_get_logs',
|
|
238
|
+
description: 'Get logs for an app or service. Logs are formatted with timestamps and severity levels for readability. Type parameter determines endpoint: "app" calls GET /api/v1/apps/:id/logs, "service" calls GET /api/v1/services/:id/logs.',
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
resourceId: {
|
|
243
|
+
type: 'string',
|
|
244
|
+
description: 'App ID or service ID to retrieve logs for',
|
|
245
|
+
},
|
|
246
|
+
type: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
enum: ['app', 'service'],
|
|
249
|
+
description: 'Type of resource: "app" for application logs, "service" for system service logs',
|
|
250
|
+
},
|
|
251
|
+
lines: {
|
|
252
|
+
type: 'number',
|
|
253
|
+
description: 'Optional: Number of log lines to retrieve (default 100, max 1000)',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
required: ['resourceId', 'type'],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'cloudron_uninstall_app',
|
|
261
|
+
description: 'Uninstall an application with pre-flight safety validation. DESTRUCTIVE OPERATION. First validates via cloudron_validate_operation (checks app exists, no dependencies, backup recommended), then calls DELETE /api/v1/apps/:id. Returns 202 Accepted with task ID for async operation tracking.',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
appId: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
description: 'The unique identifier of the application to uninstall',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
required: ['appId'],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'cloudron_install_app',
|
|
275
|
+
description: 'Install application from Cloudron App Store with pre-flight validation. Calls F23a (cloudron_validate_manifest) to verify app exists and F36 (cloudron_check_storage) to ensure sufficient disk space. Returns task ID for async operation tracking via cloudron_task_status.',
|
|
276
|
+
inputSchema: {
|
|
277
|
+
type: 'object',
|
|
278
|
+
properties: {
|
|
279
|
+
manifestId: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: 'App manifest ID from App Store',
|
|
282
|
+
},
|
|
283
|
+
location: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Subdomain for app installation',
|
|
286
|
+
},
|
|
287
|
+
domain: {
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: 'Domain where app will be installed (REQUIRED)',
|
|
290
|
+
},
|
|
291
|
+
portBindings: {
|
|
292
|
+
type: 'object',
|
|
293
|
+
description: 'Optional port bindings',
|
|
294
|
+
},
|
|
295
|
+
accessRestriction: {
|
|
296
|
+
type: ['string', 'null'],
|
|
297
|
+
description: 'Access control setting (can be null for no restriction)',
|
|
298
|
+
},
|
|
299
|
+
env: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
description: 'Environment variables',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
required: ['manifestId', 'location', 'domain', 'accessRestriction'],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
45
307
|
];
|
|
46
308
|
// Create server instance
|
|
47
309
|
const server = new Server({
|
|
@@ -119,6 +381,435 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
119
381
|
],
|
|
120
382
|
};
|
|
121
383
|
}
|
|
384
|
+
case 'cloudron_task_status': {
|
|
385
|
+
const taskId = args.taskId;
|
|
386
|
+
const taskStatus = await cloudron.getTaskStatus(taskId);
|
|
387
|
+
let statusText = `Task Status:
|
|
388
|
+
ID: ${taskStatus.id}
|
|
389
|
+
State: ${taskStatus.state}
|
|
390
|
+
Progress: ${taskStatus.progress}%
|
|
391
|
+
Message: ${taskStatus.message}`;
|
|
392
|
+
if (taskStatus.state === 'success' && taskStatus.result) {
|
|
393
|
+
statusText += `\n Result: ${JSON.stringify(taskStatus.result, null, 2)}`;
|
|
394
|
+
}
|
|
395
|
+
if (taskStatus.state === 'error' && taskStatus.error) {
|
|
396
|
+
statusText += `\n Error: ${taskStatus.error.message}`;
|
|
397
|
+
if (taskStatus.error.code) {
|
|
398
|
+
statusText += `\n Error Code: ${taskStatus.error.code}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (taskStatus.state === 'cancelled') {
|
|
402
|
+
statusText += '\n ℹ️ Task was cancelled by user request';
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
content: [
|
|
406
|
+
{
|
|
407
|
+
type: 'text',
|
|
408
|
+
text: statusText,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
case 'cloudron_cancel_task': {
|
|
414
|
+
const taskId = args.taskId;
|
|
415
|
+
const taskStatus = await cloudron.cancelTask(taskId);
|
|
416
|
+
let statusText = `Task Cancellation:
|
|
417
|
+
Task ID: ${taskStatus.id}
|
|
418
|
+
New State: ${taskStatus.state}
|
|
419
|
+
Message: ${taskStatus.message}`;
|
|
420
|
+
if (taskStatus.state === 'cancelled') {
|
|
421
|
+
statusText += '\n\n✅ Task successfully cancelled. Resources have been cleaned up.';
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
statusText += `\n\n⚠️ Task is in state '${taskStatus.state}' (expected 'cancelled'). Cancellation may not have completed.`;
|
|
425
|
+
}
|
|
426
|
+
statusText += `\n\nUse cloudron_task_status with taskId '${taskId}' to verify final state.`;
|
|
427
|
+
return {
|
|
428
|
+
content: [
|
|
429
|
+
{
|
|
430
|
+
type: 'text',
|
|
431
|
+
text: statusText,
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
case 'cloudron_check_storage': {
|
|
437
|
+
const requiredMB = args.requiredMB;
|
|
438
|
+
const storageInfo = await cloudron.checkStorage(requiredMB);
|
|
439
|
+
let statusText = `Storage Status:
|
|
440
|
+
Available: ${storageInfo.available_mb} MB
|
|
441
|
+
Total: ${storageInfo.total_mb} MB
|
|
442
|
+
Used: ${storageInfo.used_mb} MB`;
|
|
443
|
+
if (requiredMB !== undefined) {
|
|
444
|
+
statusText += `\n Required: ${requiredMB} MB`;
|
|
445
|
+
statusText += `\n Sufficient: ${storageInfo.sufficient ? 'Yes' : 'No'}`;
|
|
446
|
+
}
|
|
447
|
+
if (storageInfo.critical) {
|
|
448
|
+
statusText += '\n ⚠️ CRITICAL: Less than 5% disk space remaining!';
|
|
449
|
+
}
|
|
450
|
+
else if (storageInfo.warning) {
|
|
451
|
+
statusText += '\n ⚠️ WARNING: Less than 10% disk space remaining';
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: 'text',
|
|
457
|
+
text: statusText,
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
case 'cloudron_validate_operation': {
|
|
463
|
+
const { operation, resourceId } = args;
|
|
464
|
+
const validationResult = await cloudron.validateOperation(operation, resourceId);
|
|
465
|
+
let statusText = `Validation Result for ${operation} on resource '${resourceId}':
|
|
466
|
+
Valid: ${validationResult.valid ? 'Yes' : 'No'}`;
|
|
467
|
+
if (validationResult.errors.length > 0) {
|
|
468
|
+
statusText += '\n\nBlocking Errors:';
|
|
469
|
+
validationResult.errors.forEach((error, i) => {
|
|
470
|
+
statusText += `\n ${i + 1}. ${error}`;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (validationResult.warnings.length > 0) {
|
|
474
|
+
statusText += '\n\nWarnings:';
|
|
475
|
+
validationResult.warnings.forEach((warning, i) => {
|
|
476
|
+
statusText += `\n ${i + 1}. ${warning}`;
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (validationResult.recommendations.length > 0) {
|
|
480
|
+
statusText += '\n\nRecommendations:';
|
|
481
|
+
validationResult.recommendations.forEach((rec, i) => {
|
|
482
|
+
statusText += `\n ${i + 1}. ${rec}`;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (validationResult.valid) {
|
|
486
|
+
statusText += '\n\n✅ Operation can proceed (warnings should be reviewed)';
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
statusText += '\n\n❌ Operation blocked due to errors listed above';
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
content: [
|
|
493
|
+
{
|
|
494
|
+
type: 'text',
|
|
495
|
+
text: statusText,
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
case 'cloudron_control_app': {
|
|
501
|
+
const { appId, action } = args;
|
|
502
|
+
// Validate action enum
|
|
503
|
+
if (!['start', 'stop', 'restart'].includes(action)) {
|
|
504
|
+
return {
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: 'text',
|
|
508
|
+
text: `Invalid action: ${action}. Valid options: start, stop, restart`,
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
isError: true,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// Execute action
|
|
515
|
+
let result;
|
|
516
|
+
switch (action) {
|
|
517
|
+
case 'start':
|
|
518
|
+
result = await cloudron.startApp(appId);
|
|
519
|
+
break;
|
|
520
|
+
case 'stop':
|
|
521
|
+
result = await cloudron.stopApp(appId);
|
|
522
|
+
break;
|
|
523
|
+
case 'restart':
|
|
524
|
+
result = await cloudron.restartApp(appId);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
content: [
|
|
529
|
+
{
|
|
530
|
+
type: 'text',
|
|
531
|
+
text: `App ${action} initiated successfully.
|
|
532
|
+
App ID: ${appId}
|
|
533
|
+
Task ID: ${result.taskId}
|
|
534
|
+
|
|
535
|
+
Use cloudron_task_status with taskId '${result.taskId}' to track completion.`,
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
case 'cloudron_configure_app': {
|
|
541
|
+
const { appId, config } = args;
|
|
542
|
+
// Validate config object is provided and not empty
|
|
543
|
+
if (!config || Object.keys(config).length === 0) {
|
|
544
|
+
return {
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
type: 'text',
|
|
548
|
+
text: 'Config object is required and cannot be empty. Provide at least one of: env, memoryLimit, accessRestriction',
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
isError: true,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const result = await cloudron.configureApp(appId, config);
|
|
555
|
+
// Format config changes summary
|
|
556
|
+
const configChanges = Object.keys(config).map(key => {
|
|
557
|
+
if (key === 'env') {
|
|
558
|
+
const envCount = Object.keys(config.env).length;
|
|
559
|
+
return ` - Environment variables: ${envCount} variable(s) updated`;
|
|
560
|
+
}
|
|
561
|
+
else if (key === 'memoryLimit') {
|
|
562
|
+
return ` - Memory limit: ${config.memoryLimit} MB`;
|
|
563
|
+
}
|
|
564
|
+
else if (key === 'accessRestriction') {
|
|
565
|
+
return ` - Access restriction: ${config.accessRestriction ?? 'none'}`;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
return ` - ${key}: updated`;
|
|
569
|
+
}
|
|
570
|
+
}).join('\n');
|
|
571
|
+
const restartNote = result.restartRequired
|
|
572
|
+
? '\n⚠️ App restart required for configuration changes to take effect. Use cloudron_control_app with action "restart".'
|
|
573
|
+
: '\n✓ Configuration applied. No restart required.';
|
|
574
|
+
return {
|
|
575
|
+
content: [
|
|
576
|
+
{
|
|
577
|
+
type: 'text',
|
|
578
|
+
text: `App configuration updated successfully.
|
|
579
|
+
App ID: ${appId}
|
|
580
|
+
|
|
581
|
+
Configuration changes:
|
|
582
|
+
${configChanges}
|
|
583
|
+
${restartNote}`,
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
case 'cloudron_list_backups': {
|
|
589
|
+
const backups = await cloudron.listBackups();
|
|
590
|
+
if (backups.length === 0) {
|
|
591
|
+
return {
|
|
592
|
+
content: [
|
|
593
|
+
{
|
|
594
|
+
type: 'text',
|
|
595
|
+
text: 'No backups found.',
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const formatted = backups.map((backup, i) => {
|
|
601
|
+
const timestamp = new Date(backup.creationTime).toLocaleString();
|
|
602
|
+
const size = backup.size ? `${Math.round(backup.size / 1024 / 1024)} MB` : 'N/A';
|
|
603
|
+
const appCount = backup.appCount !== undefined ? backup.appCount : 'N/A';
|
|
604
|
+
return `${i + 1}. Backup ${backup.id}
|
|
605
|
+
Timestamp: ${timestamp}
|
|
606
|
+
Version: ${backup.version}
|
|
607
|
+
Type: ${backup.type}
|
|
608
|
+
State: ${backup.state}
|
|
609
|
+
Size: ${size}
|
|
610
|
+
App Count: ${appCount}${backup.errorMessage ? `\n Error: ${backup.errorMessage}` : ''}`;
|
|
611
|
+
}).join('\n\n');
|
|
612
|
+
return {
|
|
613
|
+
content: [
|
|
614
|
+
{
|
|
615
|
+
type: 'text',
|
|
616
|
+
text: `Found ${backups.length} backup(s):\n\n${formatted}`,
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
case 'cloudron_create_backup': {
|
|
622
|
+
// F36 pre-flight storage check performed in createBackup()
|
|
623
|
+
const taskId = await cloudron.createBackup();
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: 'text',
|
|
628
|
+
text: `Backup creation started successfully.
|
|
629
|
+
|
|
630
|
+
Task ID: ${taskId}
|
|
631
|
+
|
|
632
|
+
Use cloudron_task_status with taskId="${taskId}" to track backup progress.
|
|
633
|
+
|
|
634
|
+
Note: Pre-flight storage check passed (5GB minimum required).`,
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
case 'cloudron_list_users': {
|
|
640
|
+
const users = await cloudron.listUsers();
|
|
641
|
+
if (users.length === 0) {
|
|
642
|
+
return {
|
|
643
|
+
content: [
|
|
644
|
+
{
|
|
645
|
+
type: 'text',
|
|
646
|
+
text: 'No users found.',
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const formatted = users.map((user, i) => {
|
|
652
|
+
const createdAt = new Date(user.createdAt).toLocaleString();
|
|
653
|
+
return `${i + 1}. ${user.username} (${user.email})
|
|
654
|
+
ID: ${user.id}
|
|
655
|
+
Role: ${user.role}
|
|
656
|
+
Created: ${createdAt}`;
|
|
657
|
+
}).join('\n\n');
|
|
658
|
+
return {
|
|
659
|
+
content: [
|
|
660
|
+
{
|
|
661
|
+
type: 'text',
|
|
662
|
+
text: `Found ${users.length} user(s):\n\n${formatted}`,
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
case 'cloudron_search_apps': {
|
|
668
|
+
const { query } = args;
|
|
669
|
+
const apps = await cloudron.searchApps(query);
|
|
670
|
+
if (apps.length === 0) {
|
|
671
|
+
return {
|
|
672
|
+
content: [
|
|
673
|
+
{
|
|
674
|
+
type: 'text',
|
|
675
|
+
text: query
|
|
676
|
+
? `No apps found matching query: "${query}"`
|
|
677
|
+
: 'No apps available in the App Store.',
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const formatted = apps.map((app, i) => {
|
|
683
|
+
const installCount = app.installCount !== undefined ? app.installCount : 'N/A';
|
|
684
|
+
const iconUrl = app.iconUrl || 'N/A';
|
|
685
|
+
const score = app.relevanceScore !== undefined ? app.relevanceScore.toFixed(2) : 'N/A';
|
|
686
|
+
return `${i + 1}. ${app.name} (${app.id})
|
|
687
|
+
Version: ${app.version}
|
|
688
|
+
Description: ${app.description}
|
|
689
|
+
Install Count: ${installCount}
|
|
690
|
+
Icon URL: ${iconUrl}
|
|
691
|
+
Relevance Score: ${score}`;
|
|
692
|
+
}).join('\n\n');
|
|
693
|
+
const searchInfo = query ? `Search results for "${query}"` : 'All available apps';
|
|
694
|
+
return {
|
|
695
|
+
content: [
|
|
696
|
+
{
|
|
697
|
+
type: 'text',
|
|
698
|
+
text: `${searchInfo}:\n\nFound ${apps.length} app(s):\n\n${formatted}`,
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
case 'cloudron_validate_manifest': {
|
|
704
|
+
const { appId } = args;
|
|
705
|
+
const result = await cloudron.validateManifest(appId);
|
|
706
|
+
if (result.valid) {
|
|
707
|
+
const warningText = result.warnings.length > 0
|
|
708
|
+
? `\n\nWarnings:\n${result.warnings.map(w => ` - ${w}`).join('\n')}`
|
|
709
|
+
: '';
|
|
710
|
+
return {
|
|
711
|
+
content: [
|
|
712
|
+
{
|
|
713
|
+
type: 'text',
|
|
714
|
+
text: `Manifest validation passed for app: ${appId}
|
|
715
|
+
|
|
716
|
+
App is ready for installation.${warningText}`,
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
const errorsText = result.errors.map(e => ` - ${e}`).join('\n');
|
|
723
|
+
const warningsText = result.warnings.length > 0
|
|
724
|
+
? `\n\nWarnings:\n${result.warnings.map(w => ` - ${w}`).join('\n')}`
|
|
725
|
+
: '';
|
|
726
|
+
return {
|
|
727
|
+
content: [
|
|
728
|
+
{
|
|
729
|
+
type: 'text',
|
|
730
|
+
text: `Manifest validation failed for app: ${appId}
|
|
731
|
+
|
|
732
|
+
Errors (must be resolved):
|
|
733
|
+
${errorsText}${warningsText}`,
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
case 'cloudron_create_user': {
|
|
740
|
+
const { email, password, role } = args;
|
|
741
|
+
const user = await cloudron.createUser(email, password, role);
|
|
742
|
+
return {
|
|
743
|
+
content: [
|
|
744
|
+
{
|
|
745
|
+
type: 'text',
|
|
746
|
+
text: `User created successfully:
|
|
747
|
+
ID: ${user.id}
|
|
748
|
+
Email: ${user.email}
|
|
749
|
+
Username: ${user.username}
|
|
750
|
+
Role: ${user.role}
|
|
751
|
+
Created: ${new Date(user.createdAt).toLocaleString()}`,
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
case 'cloudron_get_logs': {
|
|
757
|
+
const { resourceId, type, lines } = args;
|
|
758
|
+
const logEntries = await cloudron.getLogs(resourceId, type, lines);
|
|
759
|
+
// Format logs for display
|
|
760
|
+
const formattedLogs = logEntries.map(entry => `[${entry.timestamp}] [${entry.severity}] ${entry.message}`).join('\n');
|
|
761
|
+
const logType = type === 'app' ? 'Application' : 'Service';
|
|
762
|
+
return {
|
|
763
|
+
content: [
|
|
764
|
+
{
|
|
765
|
+
type: 'text',
|
|
766
|
+
text: `${logType} logs for ${resourceId} (${logEntries.length} entries):\n\n${formattedLogs}`,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
case 'cloudron_uninstall_app': {
|
|
772
|
+
const { appId } = args;
|
|
773
|
+
const result = await cloudron.uninstallApp(appId);
|
|
774
|
+
return {
|
|
775
|
+
content: [
|
|
776
|
+
{
|
|
777
|
+
type: 'text',
|
|
778
|
+
text: `Uninstall operation initiated for app: ${appId}
|
|
779
|
+
Task ID: ${result.taskId}
|
|
780
|
+
Status: Pending (202 Accepted)
|
|
781
|
+
|
|
782
|
+
Use cloudron_task_status with taskId '${result.taskId}' to track uninstall progress.
|
|
783
|
+
|
|
784
|
+
Note: This is a DESTRUCTIVE operation. The app and its data will be removed once the task completes.`,
|
|
785
|
+
},
|
|
786
|
+
],
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
case 'cloudron_install_app': {
|
|
790
|
+
const { manifestId, location, domain, portBindings, accessRestriction, env } = args;
|
|
791
|
+
const params = { manifestId, location, domain, accessRestriction };
|
|
792
|
+
if (portBindings !== undefined)
|
|
793
|
+
params.portBindings = portBindings;
|
|
794
|
+
if (env !== undefined)
|
|
795
|
+
params.env = env;
|
|
796
|
+
const taskId = await cloudron.installApp(params);
|
|
797
|
+
return {
|
|
798
|
+
content: [
|
|
799
|
+
{
|
|
800
|
+
type: 'text',
|
|
801
|
+
text: `Installation initiated for app: ${manifestId}
|
|
802
|
+
Location: ${location}
|
|
803
|
+
Task ID: ${taskId}
|
|
804
|
+
Status: Pending (202 Accepted)
|
|
805
|
+
|
|
806
|
+
Use cloudron_task_status with taskId '${taskId}' to track installation progress.
|
|
807
|
+
|
|
808
|
+
Note: Pre-flight validation (F23a + F36) passed. Installation is in progress.`,
|
|
809
|
+
},
|
|
810
|
+
],
|
|
811
|
+
};
|
|
812
|
+
}
|
|
122
813
|
default:
|
|
123
814
|
return {
|
|
124
815
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|