hostinger-api-mcp 1.6.2 → 1.7.1
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 +27 -3
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/core/runtime.js +532 -1
- package/src/core/runtime.ts +540 -1
- package/src/core/tools/agency-hosting.js +68 -0
- package/src/core/tools/agency-hosting.ts +68 -0
- package/src/core/tools/all.js +92 -0
- package/src/core/tools/all.ts +92 -0
- package/src/core/tools/domains.js +24 -0
- package/src/core/tools/domains.ts +24 -0
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
- package/types.d.ts +58 -0
package/src/core/runtime.ts
CHANGED
|
@@ -52,7 +52,7 @@ const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* MCP Server for Hostinger API
|
|
55
|
-
* Generated from OpenAPI spec version 1.7.
|
|
55
|
+
* Generated from OpenAPI spec version 1.7.1
|
|
56
56
|
*/
|
|
57
57
|
class MCPServer {
|
|
58
58
|
private readonly name: string;
|
|
@@ -227,6 +227,10 @@ class MCPServer {
|
|
|
227
227
|
|
|
228
228
|
private async executeCustomTool(tool: OpenApiTool, params: Record<string, any>): Promise<any> {
|
|
229
229
|
switch (tool.name) {
|
|
230
|
+
case 'agencyHosting_deployNodeStaticWebsite':
|
|
231
|
+
return await this.handleNodeStaticDeploy(params);
|
|
232
|
+
case 'agencyHosting_deployPhpApplication':
|
|
233
|
+
return await this.handlePhpAppDeploy(params);
|
|
230
234
|
case 'hosting_importWordpressWebsite':
|
|
231
235
|
return await this.handleWordpressWebsiteImport(params);
|
|
232
236
|
case 'hosting_deployWordpressPlugin':
|
|
@@ -246,6 +250,541 @@ class MCPServer {
|
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
|
|
253
|
+
private agencyHosting_normalizePath(pathString: string): string {
|
|
254
|
+
return pathString.replace(/\\/g, '/');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private agencyHosting_generateUploadDir(): string {
|
|
258
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
259
|
+
let result = '';
|
|
260
|
+
for (let i = 0; i < 12; i++) {
|
|
261
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private agencyHosting_validateRequiredParams(params: Record<string, any>): void {
|
|
267
|
+
const { domain, archivePath, removeArchive } = params;
|
|
268
|
+
|
|
269
|
+
if (!domain || typeof domain !== 'string') {
|
|
270
|
+
throw new Error('domain is required and must be a string');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!archivePath || typeof archivePath !== 'string') {
|
|
274
|
+
throw new Error('archivePath is required and must be a string');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
|
|
278
|
+
throw new Error('removeArchive must be a boolean if provided');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private agencyHosting_validateArchiveFile(archivePath: string): void {
|
|
283
|
+
if (!fs.existsSync(archivePath)) {
|
|
284
|
+
throw new Error(`Archive file not found: ${archivePath}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const archiveStats = fs.statSync(archivePath);
|
|
288
|
+
if (!archiveStats.isFile()) {
|
|
289
|
+
throw new Error(`Archive path is not a file: ${archivePath}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private agencyHosting_removeArchive(archivePath: string, removeArchive: boolean): boolean {
|
|
294
|
+
if (!removeArchive) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
this.log('info', `Removing archive file: ${archivePath}`);
|
|
300
|
+
fs.unlinkSync(archivePath);
|
|
301
|
+
this.log('info', `Successfully removed archive file: ${archivePath}`);
|
|
302
|
+
return true;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
305
|
+
this.log('error', `Failed to remove archive file: ${errorMessage}`);
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private async agencyHosting_resolveWebsiteUid(domain: string): Promise<string> {
|
|
311
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
312
|
+
const perPage = 100;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const bearerToken = await this.getAuthToken();
|
|
316
|
+
let page = 1;
|
|
317
|
+
let total = 0;
|
|
318
|
+
|
|
319
|
+
do {
|
|
320
|
+
const url = new URL(
|
|
321
|
+
`api/agency-hosting/v1/domains?page=${page}&per_page=${perPage}`,
|
|
322
|
+
baseUrl
|
|
323
|
+
).toString();
|
|
324
|
+
|
|
325
|
+
const config: AxiosRequestConfig = {
|
|
326
|
+
method: 'get',
|
|
327
|
+
url,
|
|
328
|
+
headers: {
|
|
329
|
+
...this.headers,
|
|
330
|
+
'Authorization': `Bearer ${bearerToken}`
|
|
331
|
+
},
|
|
332
|
+
timeout: 60000, // 60s
|
|
333
|
+
validateStatus: function (status: number): boolean {
|
|
334
|
+
return status < 500;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const response = await axios(config);
|
|
339
|
+
|
|
340
|
+
if (response.status !== 200) {
|
|
341
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const domains = response.data?.data ?? [];
|
|
345
|
+
const match = domains.find((item: any) => item.fqdn === domain);
|
|
346
|
+
if (match?.website_uid) {
|
|
347
|
+
this.log('info', `Resolved website UID: ${match.website_uid} for domain: ${domain}`);
|
|
348
|
+
return match.website_uid;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
total = response.data?.meta?.total ?? domains.length;
|
|
352
|
+
page++;
|
|
353
|
+
} while ((page - 1) * perPage < total);
|
|
354
|
+
|
|
355
|
+
throw new Error(`No Agency Plan website found for domain: ${domain}`);
|
|
356
|
+
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
359
|
+
this.log('error', `Failed to resolve website UID for domain ${domain}: ${errorMessage}`);
|
|
360
|
+
|
|
361
|
+
if (axios.isAxiosError(error)) {
|
|
362
|
+
const responseData = error.response?.data;
|
|
363
|
+
const responseStatus = error.response?.status;
|
|
364
|
+
this.log('error', 'API Error Details:', {
|
|
365
|
+
status: responseStatus,
|
|
366
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private async agencyHosting_fetchUploadCredentials(
|
|
375
|
+
websiteUid: string
|
|
376
|
+
): Promise<{ url: string; auth_key: string; rest_auth_key: string }> {
|
|
377
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
378
|
+
const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/files/upload-urls`, baseUrl).toString();
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const bearerToken = await this.getAuthToken();
|
|
382
|
+
|
|
383
|
+
const config: AxiosRequestConfig = {
|
|
384
|
+
method: 'post',
|
|
385
|
+
url,
|
|
386
|
+
headers: {
|
|
387
|
+
...this.headers,
|
|
388
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
389
|
+
'Content-Type': 'application/json'
|
|
390
|
+
},
|
|
391
|
+
timeout: 60000, // 60s
|
|
392
|
+
validateStatus: function (status: number): boolean {
|
|
393
|
+
return status < 500;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const response = await axios(config);
|
|
398
|
+
|
|
399
|
+
if (response.status !== 200) {
|
|
400
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return response.data;
|
|
404
|
+
|
|
405
|
+
} catch (error) {
|
|
406
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
407
|
+
this.log('error', `Failed to fetch upload credentials: ${errorMessage}`);
|
|
408
|
+
|
|
409
|
+
if (axios.isAxiosError(error)) {
|
|
410
|
+
const responseData = error.response?.data;
|
|
411
|
+
const responseStatus = error.response?.status;
|
|
412
|
+
this.log('error', 'API Error Details:', {
|
|
413
|
+
status: responseStatus,
|
|
414
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private async agencyHosting_uploadFile(
|
|
423
|
+
filePath: string,
|
|
424
|
+
relativePath: string,
|
|
425
|
+
uploadUrl: string,
|
|
426
|
+
authRestToken: string,
|
|
427
|
+
authToken: string
|
|
428
|
+
): Promise<{ url: string; filename: string }> {
|
|
429
|
+
return new Promise(async (resolve, reject) => {
|
|
430
|
+
try {
|
|
431
|
+
const stats = fs.statSync(filePath);
|
|
432
|
+
const fileStream = fs.createReadStream(filePath);
|
|
433
|
+
|
|
434
|
+
const cleanUploadUrl = uploadUrl.replace(new RegExp('/$'), '');
|
|
435
|
+
const normalizedPath = this.agencyHosting_normalizePath(relativePath);
|
|
436
|
+
const uploadUrlWithFile = `${cleanUploadUrl}/${normalizedPath}?override=true`;
|
|
437
|
+
|
|
438
|
+
const requestHeaders: Record<string, string> = {
|
|
439
|
+
'X-Auth': authToken,
|
|
440
|
+
'X-Auth-Rest': authRestToken,
|
|
441
|
+
'upload-length': stats.size.toString(),
|
|
442
|
+
'upload-offset': '0'
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
this.log('debug', `Making pre-upload POST request to ${uploadUrlWithFile}`);
|
|
447
|
+
await axios.post(uploadUrlWithFile, '', {
|
|
448
|
+
headers: requestHeaders,
|
|
449
|
+
timeout: 60000, // 60s
|
|
450
|
+
validateStatus: function (status: number): boolean {
|
|
451
|
+
return status == 201;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
456
|
+
|
|
457
|
+
if (axios.isAxiosError(error)) {
|
|
458
|
+
const responseData = error.response?.data;
|
|
459
|
+
const responseStatus = error.response?.status;
|
|
460
|
+
const responseHeaders = error.response?.headers;
|
|
461
|
+
const responseText = typeof responseData === 'object' ? JSON.stringify(responseData) : responseData;
|
|
462
|
+
|
|
463
|
+
this.log('error', 'Pre-upload POST request failed - Full Response Details:', {
|
|
464
|
+
status: responseStatus,
|
|
465
|
+
headers: responseHeaders,
|
|
466
|
+
data: responseText,
|
|
467
|
+
message: errorMessage
|
|
468
|
+
});
|
|
469
|
+
reject(new Error(`Pre-upload request failed: ${errorMessage}`));
|
|
470
|
+
return;
|
|
471
|
+
} else {
|
|
472
|
+
this.log('error', `Pre-upload POST request failed: ${errorMessage}`);
|
|
473
|
+
reject(new Error(`Pre-upload request failed: ${errorMessage}`));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const upload = new tus.Upload(fileStream, {
|
|
479
|
+
uploadUrl: uploadUrlWithFile,
|
|
480
|
+
retryDelays: [1000, 2000, 4000, 8000, 16000, 20000],
|
|
481
|
+
uploadDataDuringCreation: false,
|
|
482
|
+
parallelUploads: 1,
|
|
483
|
+
chunkSize: 10485760,
|
|
484
|
+
headers: requestHeaders,
|
|
485
|
+
removeFingerprintOnSuccess: true,
|
|
486
|
+
uploadSize: stats.size,
|
|
487
|
+
metadata: {
|
|
488
|
+
filename: path.basename(relativePath)
|
|
489
|
+
},
|
|
490
|
+
onError: (error: Error) => {
|
|
491
|
+
this.log('error', `TUS upload error for ${relativePath}`, { error: error.message });
|
|
492
|
+
reject(new Error(`Upload failed: ${error.message}`));
|
|
493
|
+
},
|
|
494
|
+
onSuccess: () => {
|
|
495
|
+
this.log('info', `TUS upload completed for ${relativePath}`, { url: upload.url });
|
|
496
|
+
resolve({
|
|
497
|
+
url: upload.url || uploadUrlWithFile,
|
|
498
|
+
filename: relativePath
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
upload.start();
|
|
504
|
+
|
|
505
|
+
} catch (error) {
|
|
506
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
507
|
+
this.log('error', `Error preparing upload for ${filePath}`, { error: errorMessage });
|
|
508
|
+
reject(new Error(`Failed to prepare upload: ${errorMessage}`));
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private async agencyHosting_deployNodeStatic_triggerBuildAssets(websiteUid: string, archivePath: string): Promise<any> {
|
|
514
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
515
|
+
const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/build-assets`, baseUrl).toString();
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const bearerToken = await this.getAuthToken();
|
|
519
|
+
|
|
520
|
+
const config: AxiosRequestConfig = {
|
|
521
|
+
method: 'post',
|
|
522
|
+
url,
|
|
523
|
+
headers: {
|
|
524
|
+
...this.headers,
|
|
525
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
526
|
+
'Content-Type': 'application/json'
|
|
527
|
+
},
|
|
528
|
+
data: {
|
|
529
|
+
archive_path: archivePath
|
|
530
|
+
},
|
|
531
|
+
timeout: 60000,
|
|
532
|
+
validateStatus: function (status: number): boolean {
|
|
533
|
+
return status < 500;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const response = await axios(config);
|
|
538
|
+
|
|
539
|
+
if (response.status < 200 || response.status >= 300) {
|
|
540
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.log('info', `Successfully triggered build for website ${websiteUid}`);
|
|
544
|
+
return response.data;
|
|
545
|
+
|
|
546
|
+
} catch (error) {
|
|
547
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
548
|
+
this.log('error', `Failed to trigger build: ${errorMessage}`);
|
|
549
|
+
|
|
550
|
+
if (axios.isAxiosError(error)) {
|
|
551
|
+
const responseData = error.response?.data;
|
|
552
|
+
const responseStatus = error.response?.status;
|
|
553
|
+
this.log('error', 'API Error Details:', {
|
|
554
|
+
status: responseStatus,
|
|
555
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private async handleNodeStaticDeploy(params: Record<string, any>): Promise<any> {
|
|
564
|
+
const { domain, archivePath, removeArchive = true } = params;
|
|
565
|
+
|
|
566
|
+
this.agencyHosting_validateRequiredParams(params);
|
|
567
|
+
this.agencyHosting_validateArchiveFile(archivePath);
|
|
568
|
+
|
|
569
|
+
this.log('info', `Resolving website UID from domain: ${domain}`);
|
|
570
|
+
const websiteUid = await this.agencyHosting_resolveWebsiteUid(domain);
|
|
571
|
+
|
|
572
|
+
this.log('info', `Starting archive upload for ${domain}`);
|
|
573
|
+
|
|
574
|
+
let uploadCredentials: any;
|
|
575
|
+
try {
|
|
576
|
+
uploadCredentials = await this.agencyHosting_fetchUploadCredentials(websiteUid);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
579
|
+
throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
|
|
583
|
+
|
|
584
|
+
if (!uploadUrl || !authToken || !authRestToken) {
|
|
585
|
+
throw new Error('Invalid upload credentials received from API');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const archiveBasename = path.basename(archivePath);
|
|
589
|
+
const uploadDir = `.h5g/${this.agencyHosting_generateUploadDir()}`;
|
|
590
|
+
let uploadResult: any;
|
|
591
|
+
try {
|
|
592
|
+
uploadResult = await this.agencyHosting_uploadFile(
|
|
593
|
+
archivePath,
|
|
594
|
+
`${uploadDir}/${archiveBasename}`,
|
|
595
|
+
uploadUrl,
|
|
596
|
+
authRestToken,
|
|
597
|
+
authToken
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
603
|
+
throw new Error(`Failed to upload archive: ${errorMessage}`);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let deployResult: any;
|
|
607
|
+
try {
|
|
608
|
+
this.log('info', `Triggering build for ${domain}`);
|
|
609
|
+
deployResult = await this.agencyHosting_deployNodeStatic_triggerBuildAssets(websiteUid, uploadDir);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
612
|
+
this.log('error', `Failed to trigger build: ${errorMessage}`);
|
|
613
|
+
const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
upload: {
|
|
617
|
+
status: 'success',
|
|
618
|
+
data: {
|
|
619
|
+
filename: uploadResult.filename
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
deploy: {
|
|
623
|
+
status: 'error',
|
|
624
|
+
error: errorMessage
|
|
625
|
+
},
|
|
626
|
+
removeArchive: {
|
|
627
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
upload: {
|
|
636
|
+
status: 'success',
|
|
637
|
+
data: {
|
|
638
|
+
filename: uploadResult.filename
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
deploy: {
|
|
642
|
+
status: 'success',
|
|
643
|
+
data: deployResult
|
|
644
|
+
},
|
|
645
|
+
removeArchive: {
|
|
646
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private async agencyHosting_deployPhpApp_triggerImportArchive(websiteUid: string, archiveName: string): Promise<any> {
|
|
652
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
653
|
+
const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/files/import-archive`, baseUrl).toString();
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
const bearerToken = await this.getAuthToken();
|
|
657
|
+
|
|
658
|
+
const config: AxiosRequestConfig = {
|
|
659
|
+
method: 'post',
|
|
660
|
+
url,
|
|
661
|
+
headers: {
|
|
662
|
+
...this.headers,
|
|
663
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
664
|
+
'Content-Type': 'application/json'
|
|
665
|
+
},
|
|
666
|
+
data: {
|
|
667
|
+
archive_name: archiveName
|
|
668
|
+
},
|
|
669
|
+
timeout: 60000,
|
|
670
|
+
validateStatus: function (status: number): boolean {
|
|
671
|
+
return status < 500;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
const response = await axios(config);
|
|
676
|
+
|
|
677
|
+
if (response.status < 200 || response.status >= 300) {
|
|
678
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
this.log('info', `Successfully triggered import for website ${websiteUid}`);
|
|
682
|
+
return response.data;
|
|
683
|
+
|
|
684
|
+
} catch (error) {
|
|
685
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
686
|
+
this.log('error', `Failed to trigger import: ${errorMessage}`);
|
|
687
|
+
|
|
688
|
+
if (axios.isAxiosError(error)) {
|
|
689
|
+
const responseData = error.response?.data;
|
|
690
|
+
const responseStatus = error.response?.status;
|
|
691
|
+
this.log('error', 'API Error Details:', {
|
|
692
|
+
status: responseStatus,
|
|
693
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private async handlePhpAppDeploy(params: Record<string, any>): Promise<any> {
|
|
702
|
+
const { domain, archivePath, removeArchive = true } = params;
|
|
703
|
+
|
|
704
|
+
this.agencyHosting_validateRequiredParams(params);
|
|
705
|
+
this.agencyHosting_validateArchiveFile(archivePath);
|
|
706
|
+
|
|
707
|
+
this.log('info', `Resolving website UID from domain: ${domain}`);
|
|
708
|
+
const websiteUid = await this.agencyHosting_resolveWebsiteUid(domain);
|
|
709
|
+
|
|
710
|
+
this.log('info', `Starting archive upload for ${domain}`);
|
|
711
|
+
|
|
712
|
+
let uploadCredentials: any;
|
|
713
|
+
try {
|
|
714
|
+
uploadCredentials = await this.agencyHosting_fetchUploadCredentials(websiteUid);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
717
|
+
throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
|
|
721
|
+
|
|
722
|
+
if (!uploadUrl || !authToken || !authRestToken) {
|
|
723
|
+
throw new Error('Invalid upload credentials received from API');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const archiveBasename = path.basename(archivePath);
|
|
727
|
+
let uploadResult: any;
|
|
728
|
+
try {
|
|
729
|
+
uploadResult = await this.agencyHosting_uploadFile(
|
|
730
|
+
archivePath,
|
|
731
|
+
`.h5g/${archiveBasename}`,
|
|
732
|
+
uploadUrl,
|
|
733
|
+
authRestToken,
|
|
734
|
+
authToken
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
740
|
+
throw new Error(`Failed to upload archive: ${errorMessage}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
let deployResult: any;
|
|
744
|
+
try {
|
|
745
|
+
this.log('info', `Triggering import for ${domain}`);
|
|
746
|
+
deployResult = await this.agencyHosting_deployPhpApp_triggerImportArchive(websiteUid, archiveBasename);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
749
|
+
this.log('error', `Failed to trigger import: ${errorMessage}`);
|
|
750
|
+
const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
|
|
751
|
+
|
|
752
|
+
return {
|
|
753
|
+
upload: {
|
|
754
|
+
status: 'success',
|
|
755
|
+
data: {
|
|
756
|
+
filename: uploadResult.filename
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
deploy: {
|
|
760
|
+
status: 'error',
|
|
761
|
+
error: errorMessage
|
|
762
|
+
},
|
|
763
|
+
removeArchive: {
|
|
764
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
upload: {
|
|
773
|
+
status: 'success',
|
|
774
|
+
data: {
|
|
775
|
+
filename: uploadResult.filename
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
deploy: {
|
|
779
|
+
status: 'success',
|
|
780
|
+
data: deployResult
|
|
781
|
+
},
|
|
782
|
+
removeArchive: {
|
|
783
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
249
788
|
private normalizePath(pathString: string): string {
|
|
250
789
|
return pathString.replace(/\\/g, '/');
|
|
251
790
|
}
|
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
// Auto-generated tool list for group: agency-hosting
|
|
2
2
|
export default [
|
|
3
|
+
{
|
|
4
|
+
"name": "agencyHosting_deployNodeStaticWebsite",
|
|
5
|
+
"topic": "agency-hosting",
|
|
6
|
+
"description": "Deploy a node-static Agency Plan (h5g) website from an archive file. WARNING: this overwrites the website's existing contents and cannot be undone — always confirm with the user before proceeding. Use this for Agency Plan websites of type node-static (a Node.js-built static site that requires a build step or a plain simple static site). The tool resolves the website from its domain, uploads the archive to the website's file browser over TUS, and triggers the build-assets process which builds the site and deploys the result to public_html. This operation is synchronous: the build and deployment complete before the tool returns, so the website is live as soon as the tool finishes successfully — there is no separate asynchronous build to wait for or poll. For plain PHP applications that should be extracted as-is, use agencyHosting_deployPhpApplication instead. The website UID is automatically resolved from the domain.",
|
|
7
|
+
"method": "",
|
|
8
|
+
"path": "",
|
|
9
|
+
"inputSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"domain": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Domain name of the Agency Plan website (e.g., example.com)"
|
|
15
|
+
},
|
|
16
|
+
"archivePath": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Absolute or relative path to the website archive file. Supported formats: zip, tar, tar.gz, tgz. The archive must contain the application source files. If user provides a directory path, create an archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mywebsite_20250115_143022.zip)"
|
|
19
|
+
},
|
|
20
|
+
"removeArchive": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"description": "Whether to remove the local archive file after successful deployment (default: true)"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": [
|
|
26
|
+
"domain",
|
|
27
|
+
"archivePath"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"security": [],
|
|
31
|
+
"custom": true,
|
|
32
|
+
"templateFile": "deploy-node-static.template.js",
|
|
33
|
+
"templateFileTS": "deploy-node-static.template.ts",
|
|
34
|
+
"handlerMethod": "handleNodeStaticDeploy",
|
|
35
|
+
"group": "agency-hosting"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "agencyHosting_deployPhpApplication",
|
|
39
|
+
"topic": "agency-hosting",
|
|
40
|
+
"description": "Deploy a PHP (or other non-build) Agency Plan (h5g) website from an archive file. WARNING: this overwrites the website's existing contents and cannot be undone — always confirm with the user before proceeding. Use this for Agency Plan websites where the archive contents should be extracted and served as-is with no build step (e.g., PHP applications). The tool resolves the website from its domain, uploads the archive to the website's file browser over TUS, and triggers the import-archive process which overwrites the website contents with the archive contents. This operation is synchronous: the archive is extracted and deployed before the tool returns, so the website is live as soon as the tool finishes successfully — there is no separate asynchronous build to wait for or poll. For node-static websites that require a build step, use agencyHosting_deployNodeStaticWebsite instead. The website UID is automatically resolved from the domain.",
|
|
41
|
+
"method": "",
|
|
42
|
+
"path": "",
|
|
43
|
+
"inputSchema": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"domain": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Domain name of the Agency Plan website (e.g., example.com)"
|
|
49
|
+
},
|
|
50
|
+
"archivePath": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Absolute or relative path to the website archive file. Supported formats: zip, tar, tar.gz, tgz. If user provides a directory path, create an archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mywebsite_20250115_143022.zip)"
|
|
53
|
+
},
|
|
54
|
+
"removeArchive": {
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"description": "Whether to remove the local archive file after successful deployment (default: true)"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": [
|
|
60
|
+
"domain",
|
|
61
|
+
"archivePath"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"security": [],
|
|
65
|
+
"custom": true,
|
|
66
|
+
"templateFile": "deploy-php-app.template.js",
|
|
67
|
+
"templateFileTS": "deploy-php-app.template.ts",
|
|
68
|
+
"handlerMethod": "handlePhpAppDeploy",
|
|
69
|
+
"group": "agency-hosting"
|
|
70
|
+
},
|
|
3
71
|
{
|
|
4
72
|
"name": "agency-hosting_listAvailableDatacentersForAnAgencyPlanOrderV1",
|
|
5
73
|
"description": "Lists the datacenters available for provisioning a new website on the given Agency Plan\nhosting order.\n\nEach datacenter includes a `pinger_url` you can ping from the client to measure round-trip\nlatency; comparing the results across datacenters lets you pick the nearest one (lowest\nping) before choosing its `code` as the `datacenter_code` when creating a website setup.",
|