hostinger-api-mcp 0.1.37 → 0.1.40

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.
@@ -1,26 +1,15 @@
1
- #!/usr/bin/env node
2
-
3
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
- import minimist from 'minimist';
7
- import cors from "cors";
8
- import express from "express";
9
- import axios from "axios";
10
- import { config as dotenvConfig } from "dotenv";
11
- import {
12
- ListToolsRequestSchema,
13
- CallToolRequestSchema,
14
- } from "@modelcontextprotocol/sdk/types.js";
15
- import * as tus from "tus-js-client";
16
- import fs from "fs";
17
- import path from "path";
18
-
19
- // Load environment variables
20
- dotenvConfig();
1
+ // Auto-generated tool list for group: all
2
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
3
+
4
+ export interface OpenApiTool extends Tool {
5
+ method: string;
6
+ path: string;
7
+ security: unknown[];
8
+ custom?: boolean;
9
+ group?: string;
10
+ }
21
11
 
22
- // Define tool schemas
23
- const TOOLS = [
12
+ const tools: OpenApiTool[] = [
24
13
  {
25
14
  "name": "hosting_importWordpressWebsite",
26
15
  "topic": "hosting",
@@ -53,7 +42,8 @@ const TOOLS = [
53
42
  "custom": true,
54
43
  "templateFile": "import-wordpress.template.js",
55
44
  "templateFileTS": "import-wordpress.template.ts",
56
- "handlerMethod": "handleWordpressWebsiteImport"
45
+ "handlerMethod": "handleWordpressWebsiteImport",
46
+ "group": "hosting"
57
47
  },
58
48
  {
59
49
  "name": "hosting_deployWordpressPlugin",
@@ -87,7 +77,8 @@ const TOOLS = [
87
77
  "custom": true,
88
78
  "templateFile": "deploy-wordpress-plugin.template.js",
89
79
  "templateFileTS": "deploy-wordpress-plugin.template.ts",
90
- "handlerMethod": "handleWordpressPluginDeploy"
80
+ "handlerMethod": "handleWordpressPluginDeploy",
81
+ "group": "hosting"
91
82
  },
92
83
  {
93
84
  "name": "hosting_deployWordpressTheme",
@@ -125,7 +116,8 @@ const TOOLS = [
125
116
  "custom": true,
126
117
  "templateFile": "deploy-wordpress-theme.template.js",
127
118
  "templateFileTS": "deploy-wordpress-theme.template.ts",
128
- "handlerMethod": "handleWordpressThemeDeploy"
119
+ "handlerMethod": "handleWordpressThemeDeploy",
120
+ "group": "hosting"
129
121
  },
130
122
  {
131
123
  "name": "hosting_deployJsApplication",
@@ -158,7 +150,8 @@ const TOOLS = [
158
150
  "custom": true,
159
151
  "templateFile": "deploy-javascript-app.template.js",
160
152
  "templateFileTS": "deploy-javascript-app.template.ts",
161
- "handlerMethod": "handleJavascriptApplicationDeploy"
153
+ "handlerMethod": "handleJavascriptApplicationDeploy",
154
+ "group": "hosting"
162
155
  },
163
156
  {
164
157
  "name": "hosting_deployStaticWebsite",
@@ -191,7 +184,8 @@ const TOOLS = [
191
184
  "custom": true,
192
185
  "templateFile": "deploy-static-website.template.js",
193
186
  "templateFileTS": "deploy-static-website.template.ts",
194
- "handlerMethod": "handleStaticWebsiteDeploy"
187
+ "handlerMethod": "handleStaticWebsiteDeploy",
188
+ "group": "hosting"
195
189
  },
196
190
  {
197
191
  "name": "hosting_listJsDeployments",
@@ -236,7 +230,8 @@ const TOOLS = [
236
230
  "custom": true,
237
231
  "templateFile": "list-javascript-deployments.template.js",
238
232
  "templateFileTS": "list-javascript-deployments.template.ts",
239
- "handlerMethod": "handleListJavascriptDeployments"
233
+ "handlerMethod": "handleListJavascriptDeployments",
234
+ "group": "hosting"
240
235
  },
241
236
  {
242
237
  "name": "hosting_showJsDeploymentLogs",
@@ -269,7 +264,8 @@ const TOOLS = [
269
264
  "custom": true,
270
265
  "templateFile": "show-javascript-deployment-logs.template.js",
271
266
  "templateFileTS": "show-javascript-deployment-logs.template.ts",
272
- "handlerMethod": "handleShowJsDeploymentLogs"
267
+ "handlerMethod": "handleShowJsDeploymentLogs",
268
+ "group": "hosting"
273
269
  },
274
270
  {
275
271
  "name": "billing_getCatalogItemListV1",
@@ -298,7 +294,8 @@ const TOOLS = [
298
294
  {
299
295
  "apiToken": []
300
296
  }
301
- ]
297
+ ],
298
+ "group": "billing"
302
299
  },
303
300
  {
304
301
  "name": "billing_setDefaultPaymentMethodV1",
@@ -321,7 +318,8 @@ const TOOLS = [
321
318
  {
322
319
  "apiToken": []
323
320
  }
324
- ]
321
+ ],
322
+ "group": "billing"
325
323
  },
326
324
  {
327
325
  "name": "billing_deletePaymentMethodV1",
@@ -344,7 +342,8 @@ const TOOLS = [
344
342
  {
345
343
  "apiToken": []
346
344
  }
347
- ]
345
+ ],
346
+ "group": "billing"
348
347
  },
349
348
  {
350
349
  "name": "billing_getPaymentMethodListV1",
@@ -360,7 +359,8 @@ const TOOLS = [
360
359
  {
361
360
  "apiToken": []
362
361
  }
363
- ]
362
+ ],
363
+ "group": "billing"
364
364
  },
365
365
  {
366
366
  "name": "billing_getSubscriptionListV1",
@@ -376,7 +376,8 @@ const TOOLS = [
376
376
  {
377
377
  "apiToken": []
378
378
  }
379
- ]
379
+ ],
380
+ "group": "billing"
380
381
  },
381
382
  {
382
383
  "name": "billing_disableAutoRenewalV1",
@@ -399,7 +400,8 @@ const TOOLS = [
399
400
  {
400
401
  "apiToken": []
401
402
  }
402
- ]
403
+ ],
404
+ "group": "billing"
403
405
  },
404
406
  {
405
407
  "name": "billing_enableAutoRenewalV1",
@@ -422,7 +424,8 @@ const TOOLS = [
422
424
  {
423
425
  "apiToken": []
424
426
  }
425
- ]
427
+ ],
428
+ "group": "billing"
426
429
  },
427
430
  {
428
431
  "name": "DNS_getDNSSnapshotV1",
@@ -450,7 +453,8 @@ const TOOLS = [
450
453
  {
451
454
  "apiToken": []
452
455
  }
453
- ]
456
+ ],
457
+ "group": "dns"
454
458
  },
455
459
  {
456
460
  "name": "DNS_getDNSSnapshotListV1",
@@ -473,7 +477,8 @@ const TOOLS = [
473
477
  {
474
478
  "apiToken": []
475
479
  }
476
- ]
480
+ ],
481
+ "group": "dns"
477
482
  },
478
483
  {
479
484
  "name": "DNS_restoreDNSSnapshotV1",
@@ -501,7 +506,8 @@ const TOOLS = [
501
506
  {
502
507
  "apiToken": []
503
508
  }
504
- ]
509
+ ],
510
+ "group": "dns"
505
511
  },
506
512
  {
507
513
  "name": "DNS_getDNSRecordsV1",
@@ -524,7 +530,8 @@ const TOOLS = [
524
530
  {
525
531
  "apiToken": []
526
532
  }
527
- ]
533
+ ],
534
+ "group": "dns"
528
535
  },
529
536
  {
530
537
  "name": "DNS_updateDNSRecordsV1",
@@ -608,7 +615,8 @@ const TOOLS = [
608
615
  {
609
616
  "apiToken": []
610
617
  }
611
- ]
618
+ ],
619
+ "group": "dns"
612
620
  },
613
621
  {
614
622
  "name": "DNS_deleteDNSRecordsV1",
@@ -631,7 +639,8 @@ const TOOLS = [
631
639
  {
632
640
  "apiToken": []
633
641
  }
634
- ]
642
+ ],
643
+ "group": "dns"
635
644
  },
636
645
  {
637
646
  "name": "DNS_resetDNSRecordsV1",
@@ -670,7 +679,8 @@ const TOOLS = [
670
679
  {
671
680
  "apiToken": []
672
681
  }
673
- ]
682
+ ],
683
+ "group": "dns"
674
684
  },
675
685
  {
676
686
  "name": "DNS_validateDNSRecordsV1",
@@ -754,7 +764,8 @@ const TOOLS = [
754
764
  {
755
765
  "apiToken": []
756
766
  }
757
- ]
767
+ ],
768
+ "group": "dns"
758
769
  },
759
770
  {
760
771
  "name": "v2_getDomainVerificationsDIRECT",
@@ -770,7 +781,8 @@ const TOOLS = [
770
781
  {
771
782
  "apiToken": []
772
783
  }
773
- ]
784
+ ],
785
+ "group": "domains"
774
786
  },
775
787
  {
776
788
  "name": "domains_checkDomainAvailabilityV1",
@@ -806,7 +818,8 @@ const TOOLS = [
806
818
  {
807
819
  "apiToken": []
808
820
  }
809
- ]
821
+ ],
822
+ "group": "domains"
810
823
  },
811
824
  {
812
825
  "name": "domains_getDomainForwardingV1",
@@ -829,7 +842,8 @@ const TOOLS = [
829
842
  {
830
843
  "apiToken": []
831
844
  }
832
- ]
845
+ ],
846
+ "group": "domains"
833
847
  },
834
848
  {
835
849
  "name": "domains_deleteDomainForwardingV1",
@@ -852,7 +866,8 @@ const TOOLS = [
852
866
  {
853
867
  "apiToken": []
854
868
  }
855
- ]
869
+ ],
870
+ "group": "domains"
856
871
  },
857
872
  {
858
873
  "name": "domains_createDomainForwardingV1",
@@ -889,7 +904,8 @@ const TOOLS = [
889
904
  {
890
905
  "apiToken": []
891
906
  }
892
- ]
907
+ ],
908
+ "group": "domains"
893
909
  },
894
910
  {
895
911
  "name": "domains_enableDomainLockV1",
@@ -912,7 +928,8 @@ const TOOLS = [
912
928
  {
913
929
  "apiToken": []
914
930
  }
915
- ]
931
+ ],
932
+ "group": "domains"
916
933
  },
917
934
  {
918
935
  "name": "domains_disableDomainLockV1",
@@ -935,7 +952,8 @@ const TOOLS = [
935
952
  {
936
953
  "apiToken": []
937
954
  }
938
- ]
955
+ ],
956
+ "group": "domains"
939
957
  },
940
958
  {
941
959
  "name": "domains_getDomainDetailsV1",
@@ -958,7 +976,8 @@ const TOOLS = [
958
976
  {
959
977
  "apiToken": []
960
978
  }
961
- ]
979
+ ],
980
+ "group": "domains"
962
981
  },
963
982
  {
964
983
  "name": "domains_getDomainListV1",
@@ -974,7 +993,8 @@ const TOOLS = [
974
993
  {
975
994
  "apiToken": []
976
995
  }
977
- ]
996
+ ],
997
+ "group": "domains"
978
998
  },
979
999
  {
980
1000
  "name": "domains_purchaseNewDomainV1",
@@ -1041,7 +1061,8 @@ const TOOLS = [
1041
1061
  {
1042
1062
  "apiToken": []
1043
1063
  }
1044
- ]
1064
+ ],
1065
+ "group": "domains"
1045
1066
  },
1046
1067
  {
1047
1068
  "name": "domains_enablePrivacyProtectionV1",
@@ -1064,7 +1085,8 @@ const TOOLS = [
1064
1085
  {
1065
1086
  "apiToken": []
1066
1087
  }
1067
- ]
1088
+ ],
1089
+ "group": "domains"
1068
1090
  },
1069
1091
  {
1070
1092
  "name": "domains_disablePrivacyProtectionV1",
@@ -1087,7 +1109,8 @@ const TOOLS = [
1087
1109
  {
1088
1110
  "apiToken": []
1089
1111
  }
1090
- ]
1112
+ ],
1113
+ "group": "domains"
1091
1114
  },
1092
1115
  {
1093
1116
  "name": "domains_updateDomainNameserversV1",
@@ -1128,7 +1151,8 @@ const TOOLS = [
1128
1151
  {
1129
1152
  "apiToken": []
1130
1153
  }
1131
- ]
1154
+ ],
1155
+ "group": "domains"
1132
1156
  },
1133
1157
  {
1134
1158
  "name": "domains_getWHOISProfileV1",
@@ -1151,7 +1175,8 @@ const TOOLS = [
1151
1175
  {
1152
1176
  "apiToken": []
1153
1177
  }
1154
- ]
1178
+ ],
1179
+ "group": "domains"
1155
1180
  },
1156
1181
  {
1157
1182
  "name": "domains_deleteWHOISProfileV1",
@@ -1174,7 +1199,8 @@ const TOOLS = [
1174
1199
  {
1175
1200
  "apiToken": []
1176
1201
  }
1177
- ]
1202
+ ],
1203
+ "group": "domains"
1178
1204
  },
1179
1205
  {
1180
1206
  "name": "domains_getWHOISProfileListV1",
@@ -1195,7 +1221,8 @@ const TOOLS = [
1195
1221
  {
1196
1222
  "apiToken": []
1197
1223
  }
1198
- ]
1224
+ ],
1225
+ "group": "domains"
1199
1226
  },
1200
1227
  {
1201
1228
  "name": "domains_createWHOISProfileV1",
@@ -1243,7 +1270,8 @@ const TOOLS = [
1243
1270
  {
1244
1271
  "apiToken": []
1245
1272
  }
1246
- ]
1273
+ ],
1274
+ "group": "domains"
1247
1275
  },
1248
1276
  {
1249
1277
  "name": "domains_getWHOISProfileUsageV1",
@@ -1266,7 +1294,8 @@ const TOOLS = [
1266
1294
  {
1267
1295
  "apiToken": []
1268
1296
  }
1269
- ]
1297
+ ],
1298
+ "group": "domains"
1270
1299
  },
1271
1300
  {
1272
1301
  "name": "hosting_listAvailableDatacentersV1",
@@ -1289,7 +1318,8 @@ const TOOLS = [
1289
1318
  {
1290
1319
  "apiToken": []
1291
1320
  }
1292
- ]
1321
+ ],
1322
+ "group": "hosting"
1293
1323
  },
1294
1324
  {
1295
1325
  "name": "hosting_generateAFreeSubdomainV1",
@@ -1305,7 +1335,8 @@ const TOOLS = [
1305
1335
  {
1306
1336
  "apiToken": []
1307
1337
  }
1308
- ]
1338
+ ],
1339
+ "group": "hosting"
1309
1340
  },
1310
1341
  {
1311
1342
  "name": "hosting_verifyDomainOwnershipV1",
@@ -1328,7 +1359,8 @@ const TOOLS = [
1328
1359
  {
1329
1360
  "apiToken": []
1330
1361
  }
1331
- ]
1362
+ ],
1363
+ "group": "hosting"
1332
1364
  },
1333
1365
  {
1334
1366
  "name": "hosting_listOrdersV1",
@@ -1375,7 +1407,8 @@ const TOOLS = [
1375
1407
  {
1376
1408
  "apiToken": []
1377
1409
  }
1378
- ]
1410
+ ],
1411
+ "group": "hosting"
1379
1412
  },
1380
1413
  {
1381
1414
  "name": "hosting_listWebsitesV1",
@@ -1416,7 +1449,8 @@ const TOOLS = [
1416
1449
  {
1417
1450
  "apiToken": []
1418
1451
  }
1419
- ]
1452
+ ],
1453
+ "group": "hosting"
1420
1454
  },
1421
1455
  {
1422
1456
  "name": "hosting_createWebsiteV1",
@@ -1448,7 +1482,8 @@ const TOOLS = [
1448
1482
  {
1449
1483
  "apiToken": []
1450
1484
  }
1451
- ]
1485
+ ],
1486
+ "group": "hosting"
1452
1487
  },
1453
1488
  {
1454
1489
  "name": "reach_deleteAContactV1",
@@ -1471,7 +1506,8 @@ const TOOLS = [
1471
1506
  {
1472
1507
  "apiToken": []
1473
1508
  }
1474
- ]
1509
+ ],
1510
+ "group": "reach"
1475
1511
  },
1476
1512
  {
1477
1513
  "name": "reach_listContactGroupsV1",
@@ -1487,7 +1523,8 @@ const TOOLS = [
1487
1523
  {
1488
1524
  "apiToken": []
1489
1525
  }
1490
- ]
1526
+ ],
1527
+ "group": "reach"
1491
1528
  },
1492
1529
  {
1493
1530
  "name": "reach_listContactsV1",
@@ -1520,7 +1557,8 @@ const TOOLS = [
1520
1557
  {
1521
1558
  "apiToken": []
1522
1559
  }
1523
- ]
1560
+ ],
1561
+ "group": "reach"
1524
1562
  },
1525
1563
  {
1526
1564
  "name": "reach_createANewContactV1",
@@ -1555,7 +1593,8 @@ const TOOLS = [
1555
1593
  {
1556
1594
  "apiToken": []
1557
1595
  }
1558
- ]
1596
+ ],
1597
+ "group": "reach"
1559
1598
  },
1560
1599
  {
1561
1600
  "name": "reach_listSegmentsV1",
@@ -1571,7 +1610,8 @@ const TOOLS = [
1571
1610
  {
1572
1611
  "apiToken": []
1573
1612
  }
1574
- ]
1613
+ ],
1614
+ "group": "reach"
1575
1615
  },
1576
1616
  {
1577
1617
  "name": "reach_createANewContactSegmentV1",
@@ -1674,7 +1714,8 @@ const TOOLS = [
1674
1714
  {
1675
1715
  "apiToken": []
1676
1716
  }
1677
- ]
1717
+ ],
1718
+ "group": "reach"
1678
1719
  },
1679
1720
  {
1680
1721
  "name": "reach_listSegmentContactsV1",
@@ -1705,7 +1746,8 @@ const TOOLS = [
1705
1746
  {
1706
1747
  "apiToken": []
1707
1748
  }
1708
- ]
1749
+ ],
1750
+ "group": "reach"
1709
1751
  },
1710
1752
  {
1711
1753
  "name": "reach_getSegmentDetailsV1",
@@ -1728,7 +1770,8 @@ const TOOLS = [
1728
1770
  {
1729
1771
  "apiToken": []
1730
1772
  }
1731
- ]
1773
+ ],
1774
+ "group": "reach"
1732
1775
  },
1733
1776
  {
1734
1777
  "name": "reach_createNewContactsV1",
@@ -1768,7 +1811,8 @@ const TOOLS = [
1768
1811
  {
1769
1812
  "apiToken": []
1770
1813
  }
1771
- ]
1814
+ ],
1815
+ "group": "reach"
1772
1816
  },
1773
1817
  {
1774
1818
  "name": "reach_listProfilesV1",
@@ -1784,7 +1828,8 @@ const TOOLS = [
1784
1828
  {
1785
1829
  "apiToken": []
1786
1830
  }
1787
- ]
1831
+ ],
1832
+ "group": "reach"
1788
1833
  },
1789
1834
  {
1790
1835
  "name": "VPS_getDataCenterListV1",
@@ -1800,7 +1845,8 @@ const TOOLS = [
1800
1845
  {
1801
1846
  "apiToken": []
1802
1847
  }
1803
- ]
1848
+ ],
1849
+ "group": "vps"
1804
1850
  },
1805
1851
  {
1806
1852
  "name": "VPS_getProjectContainersV1",
@@ -1828,7 +1874,8 @@ const TOOLS = [
1828
1874
  {
1829
1875
  "apiToken": []
1830
1876
  }
1831
- ]
1877
+ ],
1878
+ "group": "vps"
1832
1879
  },
1833
1880
  {
1834
1881
  "name": "VPS_getProjectContentsV1",
@@ -1856,7 +1903,8 @@ const TOOLS = [
1856
1903
  {
1857
1904
  "apiToken": []
1858
1905
  }
1859
- ]
1906
+ ],
1907
+ "group": "vps"
1860
1908
  },
1861
1909
  {
1862
1910
  "name": "VPS_deleteProjectV1",
@@ -1884,7 +1932,8 @@ const TOOLS = [
1884
1932
  {
1885
1933
  "apiToken": []
1886
1934
  }
1887
- ]
1935
+ ],
1936
+ "group": "vps"
1888
1937
  },
1889
1938
  {
1890
1939
  "name": "VPS_getProjectListV1",
@@ -1907,7 +1956,8 @@ const TOOLS = [
1907
1956
  {
1908
1957
  "apiToken": []
1909
1958
  }
1910
- ]
1959
+ ],
1960
+ "group": "vps"
1911
1961
  },
1912
1962
  {
1913
1963
  "name": "VPS_createNewProjectV1",
@@ -1944,7 +1994,8 @@ const TOOLS = [
1944
1994
  {
1945
1995
  "apiToken": []
1946
1996
  }
1947
- ]
1997
+ ],
1998
+ "group": "vps"
1948
1999
  },
1949
2000
  {
1950
2001
  "name": "VPS_getProjectLogsV1",
@@ -1972,7 +2023,8 @@ const TOOLS = [
1972
2023
  {
1973
2024
  "apiToken": []
1974
2025
  }
1975
- ]
2026
+ ],
2027
+ "group": "vps"
1976
2028
  },
1977
2029
  {
1978
2030
  "name": "VPS_restartProjectV1",
@@ -2000,7 +2052,8 @@ const TOOLS = [
2000
2052
  {
2001
2053
  "apiToken": []
2002
2054
  }
2003
- ]
2055
+ ],
2056
+ "group": "vps"
2004
2057
  },
2005
2058
  {
2006
2059
  "name": "VPS_startProjectV1",
@@ -2028,7 +2081,8 @@ const TOOLS = [
2028
2081
  {
2029
2082
  "apiToken": []
2030
2083
  }
2031
- ]
2084
+ ],
2085
+ "group": "vps"
2032
2086
  },
2033
2087
  {
2034
2088
  "name": "VPS_stopProjectV1",
@@ -2056,7 +2110,8 @@ const TOOLS = [
2056
2110
  {
2057
2111
  "apiToken": []
2058
2112
  }
2059
- ]
2113
+ ],
2114
+ "group": "vps"
2060
2115
  },
2061
2116
  {
2062
2117
  "name": "VPS_updateProjectV1",
@@ -2084,7 +2139,8 @@ const TOOLS = [
2084
2139
  {
2085
2140
  "apiToken": []
2086
2141
  }
2087
- ]
2142
+ ],
2143
+ "group": "vps"
2088
2144
  },
2089
2145
  {
2090
2146
  "name": "VPS_activateFirewallV1",
@@ -2112,7 +2168,8 @@ const TOOLS = [
2112
2168
  {
2113
2169
  "apiToken": []
2114
2170
  }
2115
- ]
2171
+ ],
2172
+ "group": "vps"
2116
2173
  },
2117
2174
  {
2118
2175
  "name": "VPS_deactivateFirewallV1",
@@ -2140,7 +2197,8 @@ const TOOLS = [
2140
2197
  {
2141
2198
  "apiToken": []
2142
2199
  }
2143
- ]
2200
+ ],
2201
+ "group": "vps"
2144
2202
  },
2145
2203
  {
2146
2204
  "name": "VPS_getFirewallDetailsV1",
@@ -2163,7 +2221,8 @@ const TOOLS = [
2163
2221
  {
2164
2222
  "apiToken": []
2165
2223
  }
2166
- ]
2224
+ ],
2225
+ "group": "vps"
2167
2226
  },
2168
2227
  {
2169
2228
  "name": "VPS_deleteFirewallV1",
@@ -2186,7 +2245,8 @@ const TOOLS = [
2186
2245
  {
2187
2246
  "apiToken": []
2188
2247
  }
2189
- ]
2248
+ ],
2249
+ "group": "vps"
2190
2250
  },
2191
2251
  {
2192
2252
  "name": "VPS_getFirewallListV1",
@@ -2207,7 +2267,8 @@ const TOOLS = [
2207
2267
  {
2208
2268
  "apiToken": []
2209
2269
  }
2210
- ]
2270
+ ],
2271
+ "group": "vps"
2211
2272
  },
2212
2273
  {
2213
2274
  "name": "VPS_createNewFirewallV1",
@@ -2230,7 +2291,8 @@ const TOOLS = [
2230
2291
  {
2231
2292
  "apiToken": []
2232
2293
  }
2233
- ]
2294
+ ],
2295
+ "group": "vps"
2234
2296
  },
2235
2297
  {
2236
2298
  "name": "VPS_updateFirewallRuleV1",
@@ -2297,7 +2359,8 @@ const TOOLS = [
2297
2359
  {
2298
2360
  "apiToken": []
2299
2361
  }
2300
- ]
2362
+ ],
2363
+ "group": "vps"
2301
2364
  },
2302
2365
  {
2303
2366
  "name": "VPS_deleteFirewallRuleV1",
@@ -2325,7 +2388,8 @@ const TOOLS = [
2325
2388
  {
2326
2389
  "apiToken": []
2327
2390
  }
2328
- ]
2391
+ ],
2392
+ "group": "vps"
2329
2393
  },
2330
2394
  {
2331
2395
  "name": "VPS_createFirewallRuleV1",
@@ -2387,7 +2451,8 @@ const TOOLS = [
2387
2451
  {
2388
2452
  "apiToken": []
2389
2453
  }
2390
- ]
2454
+ ],
2455
+ "group": "vps"
2391
2456
  },
2392
2457
  {
2393
2458
  "name": "VPS_syncFirewallV1",
@@ -2415,7 +2480,8 @@ const TOOLS = [
2415
2480
  {
2416
2481
  "apiToken": []
2417
2482
  }
2418
- ]
2483
+ ],
2484
+ "group": "vps"
2419
2485
  },
2420
2486
  {
2421
2487
  "name": "VPS_getPostInstallScriptV1",
@@ -2438,7 +2504,8 @@ const TOOLS = [
2438
2504
  {
2439
2505
  "apiToken": []
2440
2506
  }
2441
- ]
2507
+ ],
2508
+ "group": "vps"
2442
2509
  },
2443
2510
  {
2444
2511
  "name": "VPS_updatePostInstallScriptV1",
@@ -2471,7 +2538,8 @@ const TOOLS = [
2471
2538
  {
2472
2539
  "apiToken": []
2473
2540
  }
2474
- ]
2541
+ ],
2542
+ "group": "vps"
2475
2543
  },
2476
2544
  {
2477
2545
  "name": "VPS_deletePostInstallScriptV1",
@@ -2494,7 +2562,8 @@ const TOOLS = [
2494
2562
  {
2495
2563
  "apiToken": []
2496
2564
  }
2497
- ]
2565
+ ],
2566
+ "group": "vps"
2498
2567
  },
2499
2568
  {
2500
2569
  "name": "VPS_getPostInstallScriptsV1",
@@ -2515,7 +2584,8 @@ const TOOLS = [
2515
2584
  {
2516
2585
  "apiToken": []
2517
2586
  }
2518
- ]
2587
+ ],
2588
+ "group": "vps"
2519
2589
  },
2520
2590
  {
2521
2591
  "name": "VPS_createPostInstallScriptV1",
@@ -2543,7 +2613,8 @@ const TOOLS = [
2543
2613
  {
2544
2614
  "apiToken": []
2545
2615
  }
2546
- ]
2616
+ ],
2617
+ "group": "vps"
2547
2618
  },
2548
2619
  {
2549
2620
  "name": "VPS_attachPublicKeyV1",
@@ -2575,7 +2646,8 @@ const TOOLS = [
2575
2646
  {
2576
2647
  "apiToken": []
2577
2648
  }
2578
- ]
2649
+ ],
2650
+ "group": "vps"
2579
2651
  },
2580
2652
  {
2581
2653
  "name": "VPS_deletePublicKeyV1",
@@ -2598,7 +2670,8 @@ const TOOLS = [
2598
2670
  {
2599
2671
  "apiToken": []
2600
2672
  }
2601
- ]
2673
+ ],
2674
+ "group": "vps"
2602
2675
  },
2603
2676
  {
2604
2677
  "name": "VPS_getPublicKeysV1",
@@ -2619,7 +2692,8 @@ const TOOLS = [
2619
2692
  {
2620
2693
  "apiToken": []
2621
2694
  }
2622
- ]
2695
+ ],
2696
+ "group": "vps"
2623
2697
  },
2624
2698
  {
2625
2699
  "name": "VPS_createPublicKeyV1",
@@ -2647,7 +2721,8 @@ const TOOLS = [
2647
2721
  {
2648
2722
  "apiToken": []
2649
2723
  }
2650
- ]
2724
+ ],
2725
+ "group": "vps"
2651
2726
  },
2652
2727
  {
2653
2728
  "name": "VPS_getTemplateDetailsV1",
@@ -2670,7 +2745,8 @@ const TOOLS = [
2670
2745
  {
2671
2746
  "apiToken": []
2672
2747
  }
2673
- ]
2748
+ ],
2749
+ "group": "vps"
2674
2750
  },
2675
2751
  {
2676
2752
  "name": "VPS_getTemplatesV1",
@@ -2686,7 +2762,8 @@ const TOOLS = [
2686
2762
  {
2687
2763
  "apiToken": []
2688
2764
  }
2689
- ]
2765
+ ],
2766
+ "group": "vps"
2690
2767
  },
2691
2768
  {
2692
2769
  "name": "VPS_getActionDetailsV1",
@@ -2714,7 +2791,8 @@ const TOOLS = [
2714
2791
  {
2715
2792
  "apiToken": []
2716
2793
  }
2717
- ]
2794
+ ],
2795
+ "group": "vps"
2718
2796
  },
2719
2797
  {
2720
2798
  "name": "VPS_getActionsV1",
@@ -2741,7 +2819,8 @@ const TOOLS = [
2741
2819
  {
2742
2820
  "apiToken": []
2743
2821
  }
2744
- ]
2822
+ ],
2823
+ "group": "vps"
2745
2824
  },
2746
2825
  {
2747
2826
  "name": "VPS_getAttachedPublicKeysV1",
@@ -2768,7 +2847,8 @@ const TOOLS = [
2768
2847
  {
2769
2848
  "apiToken": []
2770
2849
  }
2771
- ]
2850
+ ],
2851
+ "group": "vps"
2772
2852
  },
2773
2853
  {
2774
2854
  "name": "VPS_getBackupsV1",
@@ -2795,7 +2875,8 @@ const TOOLS = [
2795
2875
  {
2796
2876
  "apiToken": []
2797
2877
  }
2798
- ]
2878
+ ],
2879
+ "group": "vps"
2799
2880
  },
2800
2881
  {
2801
2882
  "name": "VPS_restoreBackupV1",
@@ -2823,7 +2904,8 @@ const TOOLS = [
2823
2904
  {
2824
2905
  "apiToken": []
2825
2906
  }
2826
- ]
2907
+ ],
2908
+ "group": "vps"
2827
2909
  },
2828
2910
  {
2829
2911
  "name": "VPS_setHostnameV1",
@@ -2851,7 +2933,8 @@ const TOOLS = [
2851
2933
  {
2852
2934
  "apiToken": []
2853
2935
  }
2854
- ]
2936
+ ],
2937
+ "group": "vps"
2855
2938
  },
2856
2939
  {
2857
2940
  "name": "VPS_resetHostnameV1",
@@ -2874,7 +2957,8 @@ const TOOLS = [
2874
2957
  {
2875
2958
  "apiToken": []
2876
2959
  }
2877
- ]
2960
+ ],
2961
+ "group": "vps"
2878
2962
  },
2879
2963
  {
2880
2964
  "name": "VPS_getVirtualMachineDetailsV1",
@@ -2897,7 +2981,8 @@ const TOOLS = [
2897
2981
  {
2898
2982
  "apiToken": []
2899
2983
  }
2900
- ]
2984
+ ],
2985
+ "group": "vps"
2901
2986
  },
2902
2987
  {
2903
2988
  "name": "VPS_getVirtualMachinesV1",
@@ -2913,7 +2998,8 @@ const TOOLS = [
2913
2998
  {
2914
2999
  "apiToken": []
2915
3000
  }
2916
- ]
3001
+ ],
3002
+ "group": "vps"
2917
3003
  },
2918
3004
  {
2919
3005
  "name": "VPS_purchaseNewVirtualMachineV1",
@@ -2953,7 +3039,8 @@ const TOOLS = [
2953
3039
  {
2954
3040
  "apiToken": []
2955
3041
  }
2956
- ]
3042
+ ],
3043
+ "group": "vps"
2957
3044
  },
2958
3045
  {
2959
3046
  "name": "VPS_getScanMetricsV1",
@@ -2976,7 +3063,8 @@ const TOOLS = [
2976
3063
  {
2977
3064
  "apiToken": []
2978
3065
  }
2979
- ]
3066
+ ],
3067
+ "group": "vps"
2980
3068
  },
2981
3069
  {
2982
3070
  "name": "VPS_installMonarxV1",
@@ -2999,7 +3087,8 @@ const TOOLS = [
2999
3087
  {
3000
3088
  "apiToken": []
3001
3089
  }
3002
- ]
3090
+ ],
3091
+ "group": "vps"
3003
3092
  },
3004
3093
  {
3005
3094
  "name": "VPS_uninstallMonarxV1",
@@ -3022,7 +3111,8 @@ const TOOLS = [
3022
3111
  {
3023
3112
  "apiToken": []
3024
3113
  }
3025
- ]
3114
+ ],
3115
+ "group": "vps"
3026
3116
  },
3027
3117
  {
3028
3118
  "name": "VPS_getMetricsV1",
@@ -3055,7 +3145,8 @@ const TOOLS = [
3055
3145
  {
3056
3146
  "apiToken": []
3057
3147
  }
3058
- ]
3148
+ ],
3149
+ "group": "vps"
3059
3150
  },
3060
3151
  {
3061
3152
  "name": "VPS_setNameserversV1",
@@ -3091,7 +3182,8 @@ const TOOLS = [
3091
3182
  {
3092
3183
  "apiToken": []
3093
3184
  }
3094
- ]
3185
+ ],
3186
+ "group": "vps"
3095
3187
  },
3096
3188
  {
3097
3189
  "name": "VPS_createPTRRecordV1",
@@ -3124,7 +3216,8 @@ const TOOLS = [
3124
3216
  {
3125
3217
  "apiToken": []
3126
3218
  }
3127
- ]
3219
+ ],
3220
+ "group": "vps"
3128
3221
  },
3129
3222
  {
3130
3223
  "name": "VPS_deletePTRRecordV1",
@@ -3152,7 +3245,8 @@ const TOOLS = [
3152
3245
  {
3153
3246
  "apiToken": []
3154
3247
  }
3155
- ]
3248
+ ],
3249
+ "group": "vps"
3156
3250
  },
3157
3251
  {
3158
3252
  "name": "VPS_setPanelPasswordV1",
@@ -3180,7 +3274,8 @@ const TOOLS = [
3180
3274
  {
3181
3275
  "apiToken": []
3182
3276
  }
3183
- ]
3277
+ ],
3278
+ "group": "vps"
3184
3279
  },
3185
3280
  {
3186
3281
  "name": "VPS_startRecoveryModeV1",
@@ -3208,7 +3303,8 @@ const TOOLS = [
3208
3303
  {
3209
3304
  "apiToken": []
3210
3305
  }
3211
- ]
3306
+ ],
3307
+ "group": "vps"
3212
3308
  },
3213
3309
  {
3214
3310
  "name": "VPS_stopRecoveryModeV1",
@@ -3231,7 +3327,8 @@ const TOOLS = [
3231
3327
  {
3232
3328
  "apiToken": []
3233
3329
  }
3234
- ]
3330
+ ],
3331
+ "group": "vps"
3235
3332
  },
3236
3333
  {
3237
3334
  "name": "VPS_recreateVirtualMachineV1",
@@ -3271,7 +3368,8 @@ const TOOLS = [
3271
3368
  {
3272
3369
  "apiToken": []
3273
3370
  }
3274
- ]
3371
+ ],
3372
+ "group": "vps"
3275
3373
  },
3276
3374
  {
3277
3375
  "name": "VPS_restartVirtualMachineV1",
@@ -3294,7 +3392,8 @@ const TOOLS = [
3294
3392
  {
3295
3393
  "apiToken": []
3296
3394
  }
3297
- ]
3395
+ ],
3396
+ "group": "vps"
3298
3397
  },
3299
3398
  {
3300
3399
  "name": "VPS_setRootPasswordV1",
@@ -3322,7 +3421,8 @@ const TOOLS = [
3322
3421
  {
3323
3422
  "apiToken": []
3324
3423
  }
3325
- ]
3424
+ ],
3425
+ "group": "vps"
3326
3426
  },
3327
3427
  {
3328
3428
  "name": "VPS_setupPurchasedVirtualMachineV1",
@@ -3397,7 +3497,8 @@ const TOOLS = [
3397
3497
  {
3398
3498
  "apiToken": []
3399
3499
  }
3400
- ]
3500
+ ],
3501
+ "group": "vps"
3401
3502
  },
3402
3503
  {
3403
3504
  "name": "VPS_getSnapshotV1",
@@ -3420,7 +3521,8 @@ const TOOLS = [
3420
3521
  {
3421
3522
  "apiToken": []
3422
3523
  }
3423
- ]
3524
+ ],
3525
+ "group": "vps"
3424
3526
  },
3425
3527
  {
3426
3528
  "name": "VPS_createSnapshotV1",
@@ -3443,7 +3545,8 @@ const TOOLS = [
3443
3545
  {
3444
3546
  "apiToken": []
3445
3547
  }
3446
- ]
3548
+ ],
3549
+ "group": "vps"
3447
3550
  },
3448
3551
  {
3449
3552
  "name": "VPS_deleteSnapshotV1",
@@ -3466,7 +3569,8 @@ const TOOLS = [
3466
3569
  {
3467
3570
  "apiToken": []
3468
3571
  }
3469
- ]
3572
+ ],
3573
+ "group": "vps"
3470
3574
  },
3471
3575
  {
3472
3576
  "name": "VPS_restoreSnapshotV1",
@@ -3489,7 +3593,8 @@ const TOOLS = [
3489
3593
  {
3490
3594
  "apiToken": []
3491
3595
  }
3492
- ]
3596
+ ],
3597
+ "group": "vps"
3493
3598
  },
3494
3599
  {
3495
3600
  "name": "VPS_startVirtualMachineV1",
@@ -3512,7 +3617,8 @@ const TOOLS = [
3512
3617
  {
3513
3618
  "apiToken": []
3514
3619
  }
3515
- ]
3620
+ ],
3621
+ "group": "vps"
3516
3622
  },
3517
3623
  {
3518
3624
  "name": "VPS_stopVirtualMachineV1",
@@ -3535,2102 +3641,8 @@ const TOOLS = [
3535
3641
  {
3536
3642
  "apiToken": []
3537
3643
  }
3538
- ]
3644
+ ],
3645
+ "group": "vps"
3539
3646
  }
3540
3647
  ];
3541
- const SECURITY_SCHEMES = {
3542
- "apiToken": {
3543
- "type": "http",
3544
- "description": "API Token authentication",
3545
- "scheme": "bearer"
3546
- }
3547
- };
3548
-
3549
- /**
3550
- * MCP Server for Hostinger API
3551
- * Generated from OpenAPI spec version 0.11.7
3552
- */
3553
- class MCPServer {
3554
- constructor() {
3555
- // Initialize class properties
3556
- this.server = null;
3557
- this.tools = new Map();
3558
- this.debug = process.env.DEBUG === "true";
3559
- this.baseUrl = process.env.API_BASE_URL || "https://developers.hostinger.com";
3560
- this.headers = this.parseHeaders(process.env.API_HEADERS || "");
3561
-
3562
- // Initialize tools map - do this before creating server
3563
- this.initializeTools();
3564
-
3565
- // Create MCP server with correct capabilities
3566
- this.server = new Server(
3567
- {
3568
- name: "hostinger-api-mcp",
3569
- version: "0.1.37",
3570
- },
3571
- {
3572
- capabilities: {
3573
- tools: {}, // Enable tools capability
3574
- },
3575
- }
3576
- );
3577
-
3578
- // Set up request handlers - don't log here
3579
- this.setupHandlers();
3580
- }
3581
-
3582
- /**
3583
- * Parse headers from string
3584
- */
3585
- parseHeaders(headerStr) {
3586
- const headers = {};
3587
- if (headerStr) {
3588
- headerStr.split(",").forEach((header) => {
3589
- const [key, value] = header.split(":");
3590
- if (key && value) headers[key.trim()] = value.trim();
3591
- });
3592
- }
3593
-
3594
- headers['User-Agent'] = 'hostinger-mcp-server/0.1.37';
3595
-
3596
- return headers;
3597
- }
3598
-
3599
- /**
3600
- * Initialize tools map from OpenAPI spec
3601
- * This runs before the server is connected, so don't log here
3602
- */
3603
- initializeTools() {
3604
- // Initialize each tool in the tools map
3605
- for (const tool of TOOLS) {
3606
- this.tools.set(tool.name, {
3607
- name: tool.name,
3608
- description: tool.description,
3609
- inputSchema: tool.inputSchema,
3610
- // Don't include security at the tool level
3611
- });
3612
- }
3613
-
3614
- // Don't log here, we're not connected yet
3615
- console.error(`Initialized ${this.tools.size} tools`);
3616
- }
3617
-
3618
- /**
3619
- * Set up request handlers
3620
- */
3621
- setupHandlers() {
3622
- // Handle tool listing requests
3623
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
3624
- this.log('debug', "Handling ListTools request");
3625
- // Return tools in the format expected by MCP SDK
3626
- return {
3627
- tools: Array.from(this.tools.entries()).map(([id, tool]) => ({
3628
- id,
3629
- ...tool,
3630
- })),
3631
- };
3632
- });
3633
-
3634
- // Handle tool execution requests
3635
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
3636
- const { id, name, arguments: params } = request.params;
3637
- this.log('debug', "Handling CallTool request", { id, name, params });
3638
-
3639
- let toolName;
3640
- let toolDetails;
3641
-
3642
- // Find the requested tool
3643
- for (const [tid, tool] of this.tools.entries()) {
3644
- if (tool.name === name) {
3645
- toolName = name;
3646
- break;
3647
- }
3648
- }
3649
-
3650
- if (!toolName) {
3651
- throw new Error(`Tool not found: ${name}`);
3652
- }
3653
-
3654
- toolDetails = TOOLS.find(t => t.name === toolName);
3655
- if (!toolDetails) {
3656
- throw new Error(`Tool details not found for ID: ${toolName}`);
3657
- }
3658
-
3659
- try {
3660
- this.log('info', `Executing tool: ${toolName}`);
3661
-
3662
- let result;
3663
-
3664
- if (toolDetails.custom) {
3665
- result = await this.executeCustomTool(toolDetails, params || {});
3666
- } else {
3667
- result = await this.executeApiCall(toolDetails, params || {});
3668
- }
3669
-
3670
- // Return the result in the correct MCP format
3671
- return {
3672
- content: [
3673
- {
3674
- type: "text",
3675
- text: JSON.stringify(result)
3676
- }
3677
- ]
3678
- };
3679
-
3680
- } catch (error) {
3681
- const errorMessage = error instanceof Error ? error.message : String(error);
3682
- const response = error.response;
3683
- this.log('error', `Error executing tool ${name}: ${errorMessage}`);
3684
-
3685
- throw error;
3686
- }
3687
- });
3688
- }
3689
-
3690
- async executeCustomTool(tool, params) {
3691
- switch (tool.name) {
3692
- case 'hosting_importWordpressWebsite':
3693
- return await this.handleWordpressWebsiteImport(params);
3694
- case 'hosting_deployWordpressPlugin':
3695
- return await this.handleWordpressPluginDeploy(params);
3696
- case 'hosting_deployWordpressTheme':
3697
- return await this.handleWordpressThemeDeploy(params);
3698
- case 'hosting_deployJsApplication':
3699
- return await this.handleJavascriptApplicationDeploy(params);
3700
- case 'hosting_deployStaticWebsite':
3701
- return await this.handleStaticWebsiteDeploy(params);
3702
- case 'hosting_listJsDeployments':
3703
- return await this.handleListJavascriptDeployments(params);
3704
- case 'hosting_showJsDeploymentLogs':
3705
- return await this.handleShowJsDeploymentLogs(params);
3706
- default:
3707
- throw new Error(`Unknown custom tool: ${tool.name}`);
3708
- }
3709
- }
3710
-
3711
- normalizePath(pathString) {
3712
- return pathString.replace(/\\/g, '/');
3713
- }
3714
-
3715
- async resolveUsername(domain) {
3716
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
3717
- const url = new URL(`api/hosting/v1/websites?domain=${encodeURIComponent(domain)}`, baseUrl).toString();
3718
-
3719
- try {
3720
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
3721
- if (!bearerToken) {
3722
- throw new Error('API_TOKEN environment variable not found');
3723
- }
3724
-
3725
- const config = {
3726
- method: 'get',
3727
- url,
3728
- headers: {
3729
- ...this.headers,
3730
- 'Authorization': `Bearer ${bearerToken}`
3731
- },
3732
- timeout: 60000, // 60s
3733
- validateStatus: function (status) {
3734
- return status < 500;
3735
- }
3736
- };
3737
-
3738
- const response = await axios(config);
3739
-
3740
- if (response.status !== 200) {
3741
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
3742
- }
3743
-
3744
- const websites = response.data?.data;
3745
- if (!websites || websites.length === 0) {
3746
- throw new Error(`No website found for domain: ${domain}`);
3747
- }
3748
-
3749
- const username = websites[0].username;
3750
- if (!username) {
3751
- throw new Error(`Username not found in website data for domain: ${domain}`);
3752
- }
3753
-
3754
- this.log('info', `Resolved username: ${username} for domain: ${domain}`);
3755
- return username;
3756
-
3757
- } catch (error) {
3758
- const errorMessage = error instanceof Error ? error.message : String(error);
3759
- this.log('error', `Failed to resolve username for domain ${domain}: ${errorMessage}`);
3760
-
3761
- if (axios.isAxiosError(error)) {
3762
- const responseData = error.response?.data;
3763
- const responseStatus = error.response?.status;
3764
- this.log('error', 'API Error Details:', {
3765
- status: responseStatus,
3766
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
3767
- });
3768
- }
3769
-
3770
- throw error;
3771
- }
3772
- }
3773
-
3774
- async fetchUploadCredentials(username, domain) {
3775
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
3776
- const url = new URL('api/hosting/v1/files/upload-urls', baseUrl).toString();
3777
-
3778
- try {
3779
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
3780
- if (!bearerToken) {
3781
- throw new Error('API_TOKEN environment variable not found');
3782
- }
3783
-
3784
- const config = {
3785
- method: 'post',
3786
- url,
3787
- headers: {
3788
- ...this.headers,
3789
- 'Authorization': `Bearer ${bearerToken}`,
3790
- 'Content-Type': 'application/json'
3791
- },
3792
- data: {
3793
- username,
3794
- domain
3795
- },
3796
- timeout: 60000, // 60s
3797
- validateStatus: function (status) {
3798
- return status < 500;
3799
- }
3800
- };
3801
-
3802
- const response = await axios(config);
3803
-
3804
- if (response.status !== 200) {
3805
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
3806
- }
3807
-
3808
- return response.data;
3809
-
3810
- } catch (error) {
3811
- const errorMessage = error instanceof Error ? error.message : String(error);
3812
- this.log('error', `Failed to fetch upload credentials: ${errorMessage}`);
3813
-
3814
- if (axios.isAxiosError(error)) {
3815
- const responseData = error.response?.data;
3816
- const responseStatus = error.response?.status;
3817
- this.log('error', 'API Error Details:', {
3818
- status: responseStatus,
3819
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
3820
- });
3821
- }
3822
-
3823
- throw error;
3824
- }
3825
- }
3826
-
3827
- async uploadFile(filePath, relativePath, uploadUrl, authRestToken, authToken) {
3828
- return new Promise(async (resolve, reject) => {
3829
- try {
3830
- const stats = fs.statSync(filePath);
3831
- const fileStream = fs.createReadStream(filePath);
3832
-
3833
- const cleanUploadUrl = uploadUrl.replace(new RegExp('/$'), '');
3834
- const normalizedPath = this.normalizePath(relativePath);
3835
- const uploadUrlWithFile = `${cleanUploadUrl}/${normalizedPath}?override=true`;
3836
-
3837
- const requestHeaders = {
3838
- 'X-Auth': authToken,
3839
- 'X-Auth-Rest': authRestToken,
3840
- 'upload-length': stats.size.toString(),
3841
- 'upload-offset': '0'
3842
- };
3843
-
3844
- try {
3845
- this.log('debug', `Making pre-upload POST request to ${uploadUrlWithFile}`);
3846
- await axios.post(uploadUrlWithFile, '', {
3847
- headers: requestHeaders,
3848
- timeout: 60000, // 60s
3849
- validateStatus: function (status) {
3850
- return status == 201;
3851
- }
3852
- });
3853
- } catch (error) {
3854
- const errorMessage = error instanceof Error ? error.message : String(error);
3855
-
3856
- if (axios.isAxiosError(error)) {
3857
- const responseData = error.response?.data;
3858
- const responseStatus = error.response?.status;
3859
- const responseHeaders = error.response?.headers;
3860
- const responseText = typeof responseData === 'object' ? JSON.stringify(responseData) : responseData;
3861
-
3862
- this.log('error', 'Pre-upload POST request failed - Full Response Details:', {
3863
- status: responseStatus,
3864
- headers: responseHeaders,
3865
- data: responseText,
3866
- message: errorMessage
3867
- });
3868
- reject(new Error(`Pre-upload request failed: ${errorMessage}`));
3869
- return;
3870
- } else {
3871
- this.log('error', `Pre-upload POST request failed: ${errorMessage}`);
3872
- reject(new Error(`Pre-upload request failed: ${errorMessage}`));
3873
- return;
3874
- }
3875
- }
3876
-
3877
- const upload = new tus.Upload(fileStream, {
3878
- uploadUrl: uploadUrlWithFile,
3879
- retryDelays: [1000, 2000, 4000, 8000, 16000, 20000],
3880
- uploadDataDuringCreation: false,
3881
- parallelUploads: 1,
3882
- chunkSize: 10485760,
3883
- headers: requestHeaders,
3884
- removeFingerprintOnSuccess: true,
3885
- uploadSize: stats.size,
3886
- metadata: {
3887
- filename: path.basename(relativePath)
3888
- },
3889
- onError: (error) => {
3890
- this.log('error', `TUS upload error for ${relativePath}`, { error: error.message });
3891
- reject(new Error(`Upload failed: ${error.message}`));
3892
- },
3893
- onSuccess: () => {
3894
- this.log('info', `TUS upload completed for ${relativePath}`, { url: upload.url });
3895
- resolve({
3896
- url: upload.url,
3897
- filename: relativePath
3898
- });
3899
- }
3900
- });
3901
-
3902
- upload.start();
3903
-
3904
- } catch (error) {
3905
- const errorMessage = error instanceof Error ? error.message : String(error);
3906
- this.log('error', `Error preparing upload for ${filePath}`, { error: errorMessage });
3907
- reject(new Error(`Failed to prepare upload: ${errorMessage}`));
3908
- }
3909
- });
3910
- }
3911
-
3912
- hosting_importWordpressWebsite_validateArchiveFormat(filePath) {
3913
- const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
3914
- const fileName = path.basename(filePath).toLowerCase();
3915
-
3916
- for (const ext of validExtensions) {
3917
- if (fileName.endsWith(`.${ext}`)) {
3918
- return true;
3919
- }
3920
- }
3921
-
3922
- return false;
3923
- }
3924
-
3925
- hosting_importWordpressWebsite_validateRequiredParams(params) {
3926
- const { domain, archivePath, databaseDump } = params;
3927
-
3928
- if (!domain || typeof domain !== 'string') {
3929
- throw new Error('domain is required and must be a string');
3930
- }
3931
-
3932
- if (!archivePath || typeof archivePath !== 'string') {
3933
- throw new Error('archivePath is required and must be a string');
3934
- }
3935
-
3936
- if (!databaseDump || typeof databaseDump !== 'string') {
3937
- throw new Error('databaseDump is required and must be a string');
3938
- }
3939
- }
3940
-
3941
- hosting_importWordpressWebsite_validateArchiveFile(archivePath) {
3942
- if (!fs.existsSync(archivePath)) {
3943
- throw new Error(`Archive file not found: ${archivePath}`);
3944
- }
3945
-
3946
- const archiveStats = fs.statSync(archivePath);
3947
- if (!archiveStats.isFile()) {
3948
- throw new Error(`Archive path is not a file: ${archivePath}`);
3949
- }
3950
-
3951
- if (!this.hosting_importWordpressWebsite_validateArchiveFormat(archivePath)) {
3952
- throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
3953
- }
3954
- }
3955
-
3956
- hosting_importWordpressWebsite_validateDatabaseFile(databaseDump) {
3957
- if (!fs.existsSync(databaseDump)) {
3958
- throw new Error(`Database dump file not found: ${databaseDump}`);
3959
- }
3960
-
3961
- const dbStats = fs.statSync(databaseDump);
3962
- if (!dbStats.isFile()) {
3963
- throw new Error(`Database dump path is not a file: ${databaseDump}`);
3964
- }
3965
-
3966
- if (!databaseDump.toLowerCase().endsWith('.sql')) {
3967
- throw new Error('Database dump must be a .sql file');
3968
- }
3969
- }
3970
-
3971
- async hosting_importWordpressWebsite_checkWebsiteIsEmpty(username, domain) {
3972
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
3973
- const url = new URL(`api/hosting/v1/accounts/${username}/domains/${domain}/is-empty`, baseUrl).toString();
3974
-
3975
- try {
3976
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
3977
- if (!bearerToken) {
3978
- throw new Error('API_TOKEN environment variable not found');
3979
- }
3980
-
3981
- const config = {
3982
- method: 'get',
3983
- url,
3984
- headers: {
3985
- ...this.headers,
3986
- 'Authorization': `Bearer ${bearerToken}`
3987
- },
3988
- timeout: 60000, // 60s
3989
- validateStatus: function (status) {
3990
- return status < 500;
3991
- }
3992
- };
3993
-
3994
- const response = await axios(config);
3995
-
3996
- if (response.status !== 200) {
3997
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
3998
- }
3999
-
4000
- const { is_empty } = response.data;
4001
-
4002
- if (!is_empty) {
4003
- throw new Error('Website is not empty. WordPress import can only be performed on empty sites. Please visit hPanel (https://hpanel.hostinger.com) and remove all existing files from the website before attempting to import.');
4004
- }
4005
-
4006
- this.log('info', `Website ${domain} is empty, proceeding with import`);
4007
- return true;
4008
-
4009
- } catch (error) {
4010
- const errorMessage = error instanceof Error ? error.message : String(error);
4011
- this.log('error', `Failed to check if website is empty: ${errorMessage}`);
4012
-
4013
- if (axios.isAxiosError(error)) {
4014
- const responseData = error.response?.data;
4015
- const responseStatus = error.response?.status;
4016
- this.log('error', 'API Error Details:', {
4017
- status: responseStatus,
4018
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4019
- });
4020
- }
4021
-
4022
- throw error;
4023
- }
4024
- }
4025
-
4026
- async hosting_importWordpressWebsite_extractFiles(username, domain, archivePath, databaseDump) {
4027
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
4028
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/import`, baseUrl).toString();
4029
-
4030
- try {
4031
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
4032
- if (!bearerToken) {
4033
- throw new Error('API_TOKEN environment variable not found');
4034
- }
4035
-
4036
- const config = {
4037
- method: 'post',
4038
- url,
4039
- headers: {
4040
- ...this.headers,
4041
- 'Authorization': `Bearer ${bearerToken}`,
4042
- 'Content-Type': 'application/json'
4043
- },
4044
- data: {
4045
- archive_path: path.basename(archivePath),
4046
- sql_path: path.basename(databaseDump)
4047
- },
4048
- timeout: 60000, // 60s
4049
- validateStatus: function (status) {
4050
- return status < 500;
4051
- }
4052
- };
4053
-
4054
- const response = await axios(config);
4055
-
4056
- this.log('info', `Successfully triggered file extraction for ${domain}`);
4057
- return true;
4058
-
4059
- } catch (error) {
4060
- const errorMessage = error instanceof Error ? error.message : String(error);
4061
- this.log('error', `Failed to trigger file extraction: ${errorMessage}`);
4062
-
4063
- if (axios.isAxiosError(error)) {
4064
- const responseData = error.response?.data;
4065
- const responseStatus = error.response?.status;
4066
- this.log('error', 'API Error Details:', {
4067
- status: responseStatus,
4068
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4069
- });
4070
- }
4071
-
4072
- throw error;
4073
- }
4074
- }
4075
-
4076
- async handleWordpressWebsiteImport(params) {
4077
- const { domain, archivePath, databaseDump } = params;
4078
-
4079
- this.hosting_importWordpressWebsite_validateRequiredParams(params);
4080
- this.hosting_importWordpressWebsite_validateArchiveFile(archivePath);
4081
- this.hosting_importWordpressWebsite_validateDatabaseFile(databaseDump);
4082
-
4083
- // Auto-resolve username from domain
4084
- this.log('info', `Resolving username from domain: ${domain}`);
4085
- const username = await this.resolveUsername(domain);
4086
-
4087
- await this.hosting_importWordpressWebsite_checkWebsiteIsEmpty(username, domain);
4088
-
4089
- const filesToUpload = [{
4090
- absolutePath: archivePath,
4091
- relativePath: path.basename(archivePath),
4092
- type: 'archive'
4093
- }, {
4094
- absolutePath: databaseDump,
4095
- relativePath: path.basename(databaseDump),
4096
- type: 'database'
4097
- }];
4098
-
4099
- let uploadCredentials;
4100
- try {
4101
- uploadCredentials = await this.fetchUploadCredentials(username, domain);
4102
- } catch (error) {
4103
- const errorMessage = error instanceof Error ? error.message : String(error);
4104
- throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
4105
- }
4106
-
4107
- const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
4108
-
4109
- if (!uploadUrl || !authToken || !authRestToken) {
4110
- throw new Error('Invalid upload credentials received from API');
4111
- }
4112
-
4113
- this.log('info', `Starting website archive import to ${uploadUrl}`);
4114
-
4115
- const results = [];
4116
- let successCount = 0;
4117
- let failureCount = 0;
4118
-
4119
- for (const fileInfo of filesToUpload) {
4120
- try {
4121
- this.log('info', `Uploading ${fileInfo.type}: ${fileInfo.absolutePath}`);
4122
-
4123
- const stats = fs.statSync(fileInfo.absolutePath);
4124
- const uploadResult = await this.uploadFile(
4125
- fileInfo.absolutePath,
4126
- fileInfo.relativePath,
4127
- uploadUrl,
4128
- authRestToken,
4129
- authToken
4130
- );
4131
-
4132
- results.push({
4133
- file: fileInfo.absolutePath,
4134
- remotePath: fileInfo.relativePath,
4135
- type: fileInfo.type,
4136
- status: 'success',
4137
- uploadUrl: uploadResult.url,
4138
- size: stats.size
4139
- });
4140
-
4141
- successCount++;
4142
- this.log('info', `Successfully uploaded ${fileInfo.type}: ${fileInfo.relativePath}`);
4143
-
4144
- } catch (error) {
4145
- const errorMessage = error instanceof Error ? error.message : String(error);
4146
- results.push({
4147
- file: fileInfo.absolutePath,
4148
- remotePath: fileInfo.relativePath,
4149
- type: fileInfo.type,
4150
- status: 'error',
4151
- error: errorMessage
4152
- });
4153
-
4154
- failureCount++;
4155
- this.log('error', `Failed to upload ${fileInfo.type} ${fileInfo.absolutePath}: ${errorMessage}`);
4156
- }
4157
- }
4158
-
4159
- const overallStatus = failureCount === 0 ? 'success' : (successCount === 0 ? 'failure' : 'partial');
4160
-
4161
- if (failureCount === 0) {
4162
- try {
4163
- this.log('info', 'All files uploaded successfully, triggering extraction...');
4164
- await this.hosting_importWordpressWebsite_extractFiles(username, domain, archivePath, databaseDump);
4165
- } catch (error) {
4166
- const errorMessage = error instanceof Error ? error.message : String(error);
4167
- this.log('error', `File extraction failed: ${errorMessage}`);
4168
- return {
4169
- status: 'partial',
4170
- summary: {
4171
- total: filesToUpload.length,
4172
- successful: successCount,
4173
- failed: failureCount
4174
- },
4175
- results,
4176
- extractionError: errorMessage
4177
- };
4178
- }
4179
- }
4180
-
4181
- return {
4182
- status: overallStatus,
4183
- summary: {
4184
- total: filesToUpload.length,
4185
- successful: successCount,
4186
- failed: failureCount
4187
- },
4188
- results
4189
- };
4190
- }
4191
-
4192
- hosting_deployWordpressPlugin_generateRandomString(length) {
4193
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
4194
- let result = '';
4195
- for (let i = 0; i < length; i++) {
4196
- result += chars.charAt(Math.floor(Math.random() * chars.length));
4197
- }
4198
- return result;
4199
- }
4200
-
4201
- hosting_deployWordpressPlugin_validateRequiredParams(params) {
4202
- const { domain, slug, pluginPath } = params;
4203
-
4204
- if (!domain || typeof domain !== 'string') {
4205
- throw new Error('domain is required and must be a string');
4206
- }
4207
-
4208
- if (!slug || typeof slug !== 'string') {
4209
- throw new Error('slug is required and must be a string');
4210
- }
4211
-
4212
- if (!pluginPath || typeof pluginPath !== 'string') {
4213
- throw new Error('pluginPath is required and must be a string');
4214
- }
4215
- }
4216
-
4217
- hosting_deployWordpressPlugin_validatePluginDirectory(pluginPath) {
4218
- if (!fs.existsSync(pluginPath)) {
4219
- throw new Error(`Plugin directory not found: ${pluginPath}`);
4220
- }
4221
-
4222
- const pluginStats = fs.statSync(pluginPath);
4223
- if (!pluginStats.isDirectory()) {
4224
- throw new Error(`Plugin path is not a directory: ${pluginPath}`);
4225
- }
4226
- }
4227
-
4228
- hosting_deployWordpressPlugin_scanDirectory(dirPath, basePath = dirPath) {
4229
- const files = [];
4230
-
4231
- const scanDir = (currentPath) => {
4232
- const items = fs.readdirSync(currentPath);
4233
-
4234
- for (const item of items) {
4235
- const itemPath = path.join(currentPath, item);
4236
- const stats = fs.statSync(itemPath);
4237
-
4238
- if (stats.isDirectory()) {
4239
- scanDir(itemPath);
4240
- } else if (stats.isFile()) {
4241
- const relativePath = path.relative(basePath, itemPath);
4242
- files.push({
4243
- absolutePath: itemPath,
4244
- relativePath: relativePath
4245
- });
4246
- }
4247
- }
4248
- };
4249
-
4250
- scanDir(dirPath);
4251
- return files;
4252
- }
4253
-
4254
- async hosting_deployWordpressPlugin_deployPlugin(username, domain, slug, pluginPath) {
4255
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
4256
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/plugins/deploy`, baseUrl).toString();
4257
-
4258
- try {
4259
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
4260
- if (!bearerToken) {
4261
- throw new Error('API_TOKEN environment variable not found');
4262
- }
4263
-
4264
- const config = {
4265
- method: 'post',
4266
- url,
4267
- headers: {
4268
- ...this.headers,
4269
- 'Authorization': `Bearer ${bearerToken}`,
4270
- 'Content-Type': 'application/json'
4271
- },
4272
- data: {
4273
- slug,
4274
- plugin_path: pluginPath
4275
- },
4276
- timeout: 60000, // 60s
4277
- validateStatus: function (status) {
4278
- return status < 500;
4279
- }
4280
- };
4281
-
4282
- const response = await axios(config);
4283
-
4284
- if (response.status !== 200) {
4285
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
4286
- }
4287
-
4288
- this.log('info', `Successfully triggered plugin deployment for ${domain}`);
4289
- return true;
4290
-
4291
- } catch (error) {
4292
- const errorMessage = error instanceof Error ? error.message : String(error);
4293
- this.log('error', `Failed to trigger plugin deployment: ${errorMessage}`);
4294
-
4295
- if (axios.isAxiosError(error)) {
4296
- const responseData = error.response?.data;
4297
- const responseStatus = error.response?.status;
4298
- this.log('error', 'API Error Details:', {
4299
- status: responseStatus,
4300
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4301
- });
4302
- }
4303
-
4304
- throw error;
4305
- }
4306
- }
4307
-
4308
- async handleWordpressPluginDeploy(params) {
4309
- const { domain, slug, pluginPath } = params;
4310
-
4311
- this.hosting_deployWordpressPlugin_validateRequiredParams(params);
4312
- this.hosting_deployWordpressPlugin_validatePluginDirectory(pluginPath);
4313
-
4314
- this.log('info', `Resolving username from domain: ${domain}`);
4315
- const username = await this.resolveUsername(domain);
4316
-
4317
- const randomSuffix = this.hosting_deployWordpressPlugin_generateRandomString(8);
4318
- const uploadDirName = `${slug}-${randomSuffix}`;
4319
-
4320
- this.log('info', `Scanning plugin directory: ${pluginPath}`);
4321
- const pluginFiles = this.hosting_deployWordpressPlugin_scanDirectory(pluginPath);
4322
-
4323
- if (pluginFiles.length === 0) {
4324
- throw new Error(`No files found in plugin directory: ${pluginPath}`);
4325
- }
4326
-
4327
- this.log('info', `Found ${pluginFiles.length} files to upload`);
4328
-
4329
- let uploadCredentials;
4330
- try {
4331
- uploadCredentials = await this.fetchUploadCredentials(username, domain);
4332
- } catch (error) {
4333
- const errorMessage = error instanceof Error ? error.message : String(error);
4334
- throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
4335
- }
4336
-
4337
- const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
4338
-
4339
- if (!uploadUrl || !authToken || !authRestToken) {
4340
- throw new Error('Invalid upload credentials received from API');
4341
- }
4342
-
4343
- this.log('info', `Starting plugin file upload to ${uploadUrl}`);
4344
-
4345
- const results = [];
4346
- let successCount = 0;
4347
- let failureCount = 0;
4348
-
4349
- for (const fileInfo of pluginFiles) {
4350
- try {
4351
- const normalizedRelativePath = this.normalizePath(fileInfo.relativePath);
4352
- const uploadPath = `wp-content/plugins/${uploadDirName}/${normalizedRelativePath}`;
4353
- this.log('info', `Uploading: ${fileInfo.absolutePath} -> ${uploadPath}`);
4354
-
4355
- const stats = fs.statSync(fileInfo.absolutePath);
4356
- const uploadResult = await this.uploadFile(
4357
- fileInfo.absolutePath,
4358
- uploadPath,
4359
- uploadUrl,
4360
- authRestToken,
4361
- authToken
4362
- );
4363
-
4364
- results.push({
4365
- file: fileInfo.absolutePath,
4366
- remotePath: uploadPath,
4367
- status: 'success',
4368
- uploadUrl: uploadResult.url,
4369
- size: stats.size
4370
- });
4371
-
4372
- successCount++;
4373
- this.log('info', `Successfully uploaded: ${uploadPath}`);
4374
-
4375
- } catch (error) {
4376
- const errorMessage = error instanceof Error ? error.message : String(error);
4377
- const normalizedRelativePath = this.normalizePath(fileInfo.relativePath);
4378
- const uploadPath = `wp-content/plugins/${uploadDirName}/${normalizedRelativePath}`;
4379
-
4380
- results.push({
4381
- file: fileInfo.absolutePath,
4382
- remotePath: uploadPath,
4383
- status: 'error',
4384
- error: errorMessage
4385
- });
4386
-
4387
- failureCount++;
4388
- this.log('error', `Failed to upload ${fileInfo.absolutePath}: ${errorMessage}`);
4389
- }
4390
- }
4391
-
4392
- const overallStatus = failureCount === 0 ? 'success' : (successCount === 0 ? 'failure' : 'partial');
4393
-
4394
- if (failureCount === 0) {
4395
- try {
4396
- this.log('info', 'All files uploaded successfully, triggering plugin deployment...');
4397
- await this.hosting_deployWordpressPlugin_deployPlugin(username, domain, slug, uploadDirName);
4398
- } catch (error) {
4399
- const errorMessage = error instanceof Error ? error.message : String(error);
4400
- this.log('error', `Plugin deployment failed: ${errorMessage}`);
4401
- return {
4402
- status: 'partial',
4403
- summary: {
4404
- total: pluginFiles.length,
4405
- successful: successCount,
4406
- failed: failureCount
4407
- },
4408
- results,
4409
- deploymentError: errorMessage
4410
- };
4411
- }
4412
- }
4413
-
4414
- return {
4415
- status: overallStatus,
4416
- summary: {
4417
- total: pluginFiles.length,
4418
- successful: successCount,
4419
- failed: failureCount
4420
- },
4421
- results,
4422
- uploadDirName
4423
- };
4424
- }
4425
-
4426
- hosting_deployWordpressTheme_generateRandomString(length) {
4427
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
4428
- let result = '';
4429
- for (let i = 0; i < length; i++) {
4430
- result += chars.charAt(Math.floor(Math.random() * chars.length));
4431
- }
4432
- return result;
4433
- }
4434
-
4435
- hosting_deployWordpressTheme_validateRequiredParams(params) {
4436
- const { domain, slug, themePath } = params;
4437
-
4438
- if (!domain || typeof domain !== 'string') {
4439
- throw new Error('domain is required and must be a string');
4440
- }
4441
-
4442
- if (!slug || typeof slug !== 'string') {
4443
- throw new Error('slug is required and must be a string');
4444
- }
4445
-
4446
- if (!themePath || typeof themePath !== 'string') {
4447
- throw new Error('themePath is required and must be a string');
4448
- }
4449
- }
4450
-
4451
- hosting_deployWordpressTheme_validateThemeDirectory(themePath) {
4452
- if (!fs.existsSync(themePath)) {
4453
- throw new Error(`Theme directory not found: ${themePath}`);
4454
- }
4455
-
4456
- const themeStats = fs.statSync(themePath);
4457
- if (!themeStats.isDirectory()) {
4458
- throw new Error(`Theme path is not a directory: ${themePath}`);
4459
- }
4460
- }
4461
-
4462
- hosting_deployWordpressTheme_scanDirectory(dirPath, basePath = dirPath) {
4463
- const files = [];
4464
-
4465
- const scanDir = (currentPath) => {
4466
- const items = fs.readdirSync(currentPath);
4467
-
4468
- for (const item of items) {
4469
- const itemPath = path.join(currentPath, item);
4470
- const stats = fs.statSync(itemPath);
4471
-
4472
- if (stats.isDirectory()) {
4473
- scanDir(itemPath);
4474
- } else if (stats.isFile()) {
4475
- const relativePath = path.relative(basePath, itemPath);
4476
- files.push({
4477
- absolutePath: itemPath,
4478
- relativePath: relativePath
4479
- });
4480
- }
4481
- }
4482
- };
4483
-
4484
- scanDir(dirPath);
4485
- return files;
4486
- }
4487
-
4488
- async hosting_deployWordpressTheme_deployTheme(username, domain, slug, themePath, activate = false) {
4489
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
4490
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/themes/deploy`, baseUrl).toString();
4491
-
4492
- try {
4493
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
4494
- if (!bearerToken) {
4495
- throw new Error('API_TOKEN environment variable not found');
4496
- }
4497
-
4498
- const config = {
4499
- method: 'post',
4500
- url,
4501
- headers: {
4502
- ...this.headers,
4503
- 'Authorization': `Bearer ${bearerToken}`,
4504
- 'Content-Type': 'application/json'
4505
- },
4506
- data: {
4507
- slug,
4508
- theme_path: themePath,
4509
- is_activated: activate
4510
- },
4511
- timeout: 60000, // 60s
4512
- validateStatus: function (status) {
4513
- return status < 500;
4514
- }
4515
- };
4516
-
4517
- const response = await axios(config);
4518
-
4519
- if (response.status !== 200) {
4520
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
4521
- }
4522
-
4523
- this.log('info', `Successfully triggered theme deployment for ${domain}`);
4524
- return true;
4525
-
4526
- } catch (error) {
4527
- const errorMessage = error instanceof Error ? error.message : String(error);
4528
- this.log('error', `Failed to trigger theme deployment: ${errorMessage}`);
4529
-
4530
- if (axios.isAxiosError(error)) {
4531
- const responseData = error.response?.data;
4532
- const responseStatus = error.response?.status;
4533
- this.log('error', 'API Error Details:', {
4534
- status: responseStatus,
4535
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4536
- });
4537
- }
4538
-
4539
- throw error;
4540
- }
4541
- }
4542
-
4543
- async handleWordpressThemeDeploy(params) {
4544
- const { domain, slug, themePath, activate = false } = params;
4545
-
4546
- this.hosting_deployWordpressTheme_validateRequiredParams(params);
4547
- this.hosting_deployWordpressTheme_validateThemeDirectory(themePath);
4548
-
4549
- this.log('info', `Resolving username from domain: ${domain}`);
4550
- const username = await this.resolveUsername(domain);
4551
-
4552
- const randomSuffix = this.hosting_deployWordpressTheme_generateRandomString(8);
4553
- const uploadDirName = `${slug}-${randomSuffix}`;
4554
-
4555
- this.log('info', `Scanning theme directory: ${themePath}`);
4556
- const themeFiles = this.hosting_deployWordpressTheme_scanDirectory(themePath);
4557
-
4558
- if (themeFiles.length === 0) {
4559
- throw new Error(`No files found in theme directory: ${themePath}`);
4560
- }
4561
-
4562
- this.log('info', `Found ${themeFiles.length} files to upload`);
4563
-
4564
- let uploadCredentials;
4565
- try {
4566
- uploadCredentials = await this.fetchUploadCredentials(username, domain);
4567
- } catch (error) {
4568
- const errorMessage = error instanceof Error ? error.message : String(error);
4569
- throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
4570
- }
4571
-
4572
- const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
4573
-
4574
- if (!uploadUrl || !authToken || !authRestToken) {
4575
- throw new Error('Invalid upload credentials received from API');
4576
- }
4577
-
4578
- this.log('info', `Starting theme file upload to ${uploadUrl}`);
4579
-
4580
- const results = [];
4581
- let successCount = 0;
4582
- let failureCount = 0;
4583
-
4584
- for (const fileInfo of themeFiles) {
4585
- try {
4586
- const normalizedRelativePath = this.normalizePath(fileInfo.relativePath);
4587
- const uploadPath = `wp-content/themes/${uploadDirName}/${normalizedRelativePath}`;
4588
- this.log('info', `Uploading: ${fileInfo.absolutePath} -> ${uploadPath}`);
4589
-
4590
- const stats = fs.statSync(fileInfo.absolutePath);
4591
- const uploadResult = await this.uploadFile(
4592
- fileInfo.absolutePath,
4593
- uploadPath,
4594
- uploadUrl,
4595
- authRestToken,
4596
- authToken
4597
- );
4598
-
4599
- results.push({
4600
- file: fileInfo.absolutePath,
4601
- remotePath: uploadPath,
4602
- status: 'success',
4603
- uploadUrl: uploadResult.url,
4604
- size: stats.size
4605
- });
4606
-
4607
- successCount++;
4608
- this.log('info', `Successfully uploaded: ${uploadPath}`);
4609
-
4610
- } catch (error) {
4611
- const errorMessage = error instanceof Error ? error.message : String(error);
4612
- const normalizedRelativePath = this.normalizePath(fileInfo.relativePath);
4613
- const uploadPath = `wp-content/themes/${uploadDirName}/${normalizedRelativePath}`;
4614
-
4615
- results.push({
4616
- file: fileInfo.absolutePath,
4617
- remotePath: uploadPath,
4618
- status: 'error',
4619
- error: errorMessage
4620
- });
4621
-
4622
- failureCount++;
4623
- this.log('error', `Failed to upload ${fileInfo.absolutePath}: ${errorMessage}`);
4624
- }
4625
- }
4626
-
4627
- const overallStatus = failureCount === 0 ? 'success' : (successCount === 0 ? 'failure' : 'partial');
4628
-
4629
- if (failureCount === 0) {
4630
- try {
4631
- this.log('info', 'All files uploaded successfully, triggering theme deployment...');
4632
- await this.hosting_deployWordpressTheme_deployTheme(username, domain, slug, uploadDirName, activate);
4633
- } catch (error) {
4634
- const errorMessage = error instanceof Error ? error.message : String(error);
4635
- this.log('error', `Theme deployment failed: ${errorMessage}`);
4636
- return {
4637
- status: 'partial',
4638
- summary: {
4639
- total: themeFiles.length,
4640
- successful: successCount,
4641
- failed: failureCount
4642
- },
4643
- results,
4644
- deploymentError: errorMessage
4645
- };
4646
- }
4647
- }
4648
-
4649
- return {
4650
- status: overallStatus,
4651
- summary: {
4652
- total: themeFiles.length,
4653
- successful: successCount,
4654
- failed: failureCount
4655
- },
4656
- results,
4657
- uploadDirName,
4658
- activated: activate
4659
- };
4660
- }
4661
-
4662
- hosting_deployJsApplication_validateArchiveFormat(filePath) {
4663
- const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
4664
- const fileName = path.basename(filePath).toLowerCase();
4665
-
4666
- for (const ext of validExtensions) {
4667
- if (fileName.endsWith(`.${ext}`)) {
4668
- return true;
4669
- }
4670
- }
4671
-
4672
- return false;
4673
- }
4674
-
4675
- hosting_deployJsApplication_validateRequiredParams(params) {
4676
- const { domain, archivePath, removeArchive } = params;
4677
-
4678
- if (!domain || typeof domain !== 'string') {
4679
- throw new Error('domain is required and must be a string');
4680
- }
4681
-
4682
- if (!archivePath || typeof archivePath !== 'string') {
4683
- throw new Error('archivePath is required and must be a string');
4684
- }
4685
-
4686
- if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
4687
- throw new Error('removeArchive must be a boolean if provided');
4688
- }
4689
- }
4690
-
4691
- hosting_deployJsApplication_removeArchive(archivePath, removeArchive) {
4692
- if (!removeArchive) {
4693
- return false;
4694
- }
4695
-
4696
- try {
4697
- this.log('info', `Removing archive file: ${archivePath}`);
4698
- fs.unlinkSync(archivePath);
4699
- this.log('info', `Successfully removed archive file: ${archivePath}`);
4700
- return true;
4701
- } catch (error) {
4702
- const errorMessage = error instanceof Error ? error.message : String(error);
4703
- this.log('error', `Failed to remove archive file: ${errorMessage}`);
4704
- // Don't fail the entire operation if archive removal fails
4705
- return false;
4706
- }
4707
- }
4708
-
4709
- hosting_deployJsApplication_validateArchiveFile(archivePath) {
4710
- if (!fs.existsSync(archivePath)) {
4711
- throw new Error(`Archive file not found: ${archivePath}`);
4712
- }
4713
-
4714
- const archiveStats = fs.statSync(archivePath);
4715
- if (!archiveStats.isFile()) {
4716
- throw new Error(`Archive path is not a file: ${archivePath}`);
4717
- }
4718
-
4719
- if (!this.hosting_deployJsApplication_validateArchiveFormat(archivePath)) {
4720
- throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
4721
- }
4722
- }
4723
-
4724
- async hosting_deployJsApplication_fetchBuildSettings(username, domain, archivePath) {
4725
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
4726
- const archiveBasename = path.basename(archivePath);
4727
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds/settings/from-archive?archive_path=${encodeURIComponent(archiveBasename)}`, baseUrl).toString();
4728
-
4729
- try {
4730
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
4731
- if (!bearerToken) {
4732
- throw new Error('API_TOKEN environment variable not found');
4733
- }
4734
-
4735
- const config = {
4736
- method: 'get',
4737
- url,
4738
- headers: {
4739
- ...this.headers,
4740
- 'Authorization': `Bearer ${bearerToken}`
4741
- },
4742
- timeout: 60000, // 60s
4743
- validateStatus: function (status) {
4744
- return status < 500;
4745
- }
4746
- };
4747
-
4748
- const response = await axios(config);
4749
-
4750
- if (response.status !== 200) {
4751
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
4752
- }
4753
-
4754
- this.log('info', `Successfully fetched build settings for ${domain}`);
4755
- return response.data;
4756
-
4757
- } catch (error) {
4758
- const errorMessage = error instanceof Error ? error.message : String(error);
4759
- this.log('error', `Failed to fetch build settings: ${errorMessage}`);
4760
-
4761
- if (axios.isAxiosError(error)) {
4762
- const responseData = error.response?.data;
4763
- const responseStatus = error.response?.status;
4764
- this.log('error', 'API Error Details:', {
4765
- status: responseStatus,
4766
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4767
- });
4768
- }
4769
-
4770
- throw error;
4771
- }
4772
- }
4773
-
4774
- async hosting_deployJsApplication_triggerBuild(username, domain, archivePath, buildSettings) {
4775
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
4776
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds`, baseUrl).toString();
4777
-
4778
- try {
4779
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
4780
- if (!bearerToken) {
4781
- throw new Error('API_TOKEN environment variable not found');
4782
- }
4783
-
4784
- const archiveBasename = path.basename(archivePath);
4785
- const buildData = {
4786
- ...buildSettings,
4787
- node_version: buildSettings?.node_version || 20,
4788
- source_type: 'archive',
4789
- source_options: {
4790
- archive_path: archiveBasename
4791
- }
4792
- };
4793
-
4794
- const config = {
4795
- method: 'post',
4796
- url,
4797
- headers: {
4798
- ...this.headers,
4799
- 'Authorization': `Bearer ${bearerToken}`,
4800
- 'Content-Type': 'application/json'
4801
- },
4802
- data: buildData,
4803
- timeout: 60000, // 60s
4804
- validateStatus: function (status) {
4805
- return status < 500;
4806
- }
4807
- };
4808
-
4809
- const response = await axios(config);
4810
-
4811
- if (response.status !== 200) {
4812
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
4813
- }
4814
-
4815
- this.log('info', `Successfully triggered build for ${domain}`);
4816
- return response.data;
4817
-
4818
- } catch (error) {
4819
- const errorMessage = error instanceof Error ? error.message : String(error);
4820
- this.log('error', `Failed to trigger build: ${errorMessage}`);
4821
-
4822
- if (axios.isAxiosError(error)) {
4823
- const responseData = error.response?.data;
4824
- const responseStatus = error.response?.status;
4825
- this.log('error', 'API Error Details:', {
4826
- status: responseStatus,
4827
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
4828
- });
4829
- }
4830
-
4831
- throw error;
4832
- }
4833
- }
4834
-
4835
- async handleJavascriptApplicationDeploy(params) {
4836
- const { domain, archivePath, removeArchive = false } = params;
4837
-
4838
- this.hosting_deployJsApplication_validateRequiredParams(params);
4839
- this.hosting_deployJsApplication_validateArchiveFile(archivePath);
4840
-
4841
- // Auto-resolve username from domain
4842
- this.log('info', `Resolving username from domain: ${domain}`);
4843
- const username = await this.resolveUsername(domain);
4844
-
4845
- // Upload archive file
4846
- this.log('info', `Starting archive upload for ${domain}`);
4847
-
4848
- let uploadCredentials;
4849
- try {
4850
- uploadCredentials = await this.fetchUploadCredentials(username, domain);
4851
- } catch (error) {
4852
- const errorMessage = error instanceof Error ? error.message : String(error);
4853
- throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
4854
- }
4855
-
4856
- const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
4857
-
4858
- if (!uploadUrl || !authToken || !authRestToken) {
4859
- throw new Error('Invalid upload credentials received from API');
4860
- }
4861
-
4862
- const archiveBasename = path.basename(archivePath);
4863
- let uploadResult;
4864
- try {
4865
- const stats = fs.statSync(archivePath);
4866
- uploadResult = await this.uploadFile(
4867
- archivePath,
4868
- archiveBasename,
4869
- uploadUrl,
4870
- authRestToken,
4871
- authToken
4872
- );
4873
-
4874
- this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
4875
- } catch (error) {
4876
- const errorMessage = error instanceof Error ? error.message : String(error);
4877
- throw new Error(`Failed to upload archive: ${errorMessage}`);
4878
- }
4879
-
4880
- // Fetch build settings
4881
- let buildSettings;
4882
- try {
4883
- this.log('info', `Fetching build settings for ${domain}`);
4884
- buildSettings = await this.hosting_deployJsApplication_fetchBuildSettings(username, domain, archivePath);
4885
- } catch (error) {
4886
- const errorMessage = error instanceof Error ? error.message : String(error);
4887
- this.log('error', `Failed to fetch build settings: ${errorMessage}`);
4888
- const archiveRemoved = this.hosting_deployJsApplication_removeArchive(archivePath, removeArchive);
4889
-
4890
- return {
4891
- upload: {
4892
- status: 'success',
4893
- data: {
4894
- filename: uploadResult.filename
4895
- }
4896
- },
4897
- resolveSettings: {
4898
- status: 'error',
4899
- error: errorMessage
4900
- },
4901
- build: {
4902
- status: 'skipped'
4903
- },
4904
- removeArchive: {
4905
- status: archiveRemoved ? 'success' : 'skipped'
4906
- }
4907
- };
4908
- }
4909
-
4910
- // Trigger build
4911
- let buildResult;
4912
- try {
4913
- this.log('info', `Triggering build for ${domain}`);
4914
- buildResult = await this.hosting_deployJsApplication_triggerBuild(username, domain, archivePath, buildSettings);
4915
- } catch (error) {
4916
- const errorMessage = error instanceof Error ? error.message : String(error);
4917
- this.log('error', `Failed to trigger build: ${errorMessage}`);
4918
- const archiveRemoved = this.hosting_deployJsApplication_removeArchive(archivePath, removeArchive);
4919
-
4920
- return {
4921
- upload: {
4922
- status: 'success',
4923
- data: {
4924
- filename: uploadResult.filename
4925
- }
4926
- },
4927
- resolveSettings: {
4928
- status: 'success',
4929
- data: buildSettings
4930
- },
4931
- build: {
4932
- status: 'error',
4933
- error: errorMessage
4934
- },
4935
- removeArchive: {
4936
- status: archiveRemoved ? 'success' : 'skipped'
4937
- }
4938
- };
4939
- }
4940
-
4941
- const archiveRemoved = this.hosting_deployJsApplication_removeArchive(archivePath, removeArchive);
4942
-
4943
- return {
4944
- upload: {
4945
- status: 'success',
4946
- data: {
4947
- filename: uploadResult.filename
4948
- }
4949
- },
4950
- resolveSettings: {
4951
- status: 'success',
4952
- data: buildSettings
4953
- },
4954
- build: {
4955
- status: 'success',
4956
- data: buildResult
4957
- },
4958
- removeArchive: {
4959
- status: archiveRemoved ? 'success' : 'skipped'
4960
- }
4961
- };
4962
- }
4963
-
4964
- hosting_deployStaticWebsite_validateArchiveFormat(filePath) {
4965
- const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
4966
- const fileName = path.basename(filePath).toLowerCase();
4967
-
4968
- for (const ext of validExtensions) {
4969
- if (fileName.endsWith(`.${ext}`)) {
4970
- return true;
4971
- }
4972
- }
4973
-
4974
- return false;
4975
- }
4976
-
4977
- hosting_deployStaticWebsite_validateRequiredParams(params) {
4978
- const { domain, archivePath, removeArchive } = params;
4979
-
4980
- if (!domain || typeof domain !== 'string') {
4981
- throw new Error('domain is required and must be a string');
4982
- }
4983
-
4984
- if (!archivePath || typeof archivePath !== 'string') {
4985
- throw new Error('archivePath is required and must be a string');
4986
- }
4987
-
4988
- if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
4989
- throw new Error('removeArchive must be a boolean if provided');
4990
- }
4991
- }
4992
-
4993
- hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive) {
4994
- if (!removeArchive) {
4995
- return false;
4996
- }
4997
-
4998
- try {
4999
- this.log('info', `Removing archive file: ${archivePath}`);
5000
- fs.unlinkSync(archivePath);
5001
- this.log('info', `Successfully removed archive file: ${archivePath}`);
5002
- return true;
5003
- } catch (error) {
5004
- const errorMessage = error instanceof Error ? error.message : String(error);
5005
- this.log('error', `Failed to remove archive file: ${errorMessage}`);
5006
- return false;
5007
- }
5008
- }
5009
-
5010
- hosting_deployStaticWebsite_validateArchiveFile(archivePath) {
5011
- if (!fs.existsSync(archivePath)) {
5012
- throw new Error(`Archive file not found: ${archivePath}`);
5013
- }
5014
-
5015
- const archiveStats = fs.statSync(archivePath);
5016
- if (!archiveStats.isFile()) {
5017
- throw new Error(`Archive path is not a file: ${archivePath}`);
5018
- }
5019
-
5020
- if (!this.hosting_deployStaticWebsite_validateArchiveFormat(archivePath)) {
5021
- throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
5022
- }
5023
- }
5024
-
5025
- async hosting_deployStaticWebsite_triggerDeploy(username, domain, archivePath) {
5026
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
5027
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/deploy`, baseUrl).toString();
5028
-
5029
- try {
5030
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
5031
- if (!bearerToken) {
5032
- throw new Error('API_TOKEN environment variable not found');
5033
- }
5034
-
5035
- const archiveBasename = path.basename(archivePath);
5036
- const deployData = {
5037
- archive_path: archiveBasename
5038
- };
5039
-
5040
- const config = {
5041
- method: 'post',
5042
- url,
5043
- headers: {
5044
- ...this.headers,
5045
- 'Authorization': `Bearer ${bearerToken}`,
5046
- 'Content-Type': 'application/json'
5047
- },
5048
- data: deployData,
5049
- timeout: 60000,
5050
- validateStatus: function (status) {
5051
- return status < 500;
5052
- }
5053
- };
5054
-
5055
- const response = await axios(config);
5056
-
5057
- if (response.status !== 200) {
5058
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
5059
- }
5060
-
5061
- this.log('info', `Successfully triggered deployment for ${domain}`);
5062
- return response.data;
5063
-
5064
- } catch (error) {
5065
- const errorMessage = error instanceof Error ? error.message : String(error);
5066
- this.log('error', `Failed to trigger deployment: ${errorMessage}`);
5067
-
5068
- if (axios.isAxiosError(error)) {
5069
- const responseData = error.response?.data;
5070
- const responseStatus = error.response?.status;
5071
- this.log('error', 'API Error Details:', {
5072
- status: responseStatus,
5073
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
5074
- });
5075
- }
5076
-
5077
- throw error;
5078
- }
5079
- }
5080
-
5081
- async handleStaticWebsiteDeploy(params) {
5082
- const { domain, archivePath, removeArchive = false } = params;
5083
-
5084
- this.hosting_deployStaticWebsite_validateRequiredParams(params);
5085
- this.hosting_deployStaticWebsite_validateArchiveFile(archivePath);
5086
-
5087
- this.log('info', `Resolving username from domain: ${domain}`);
5088
- const username = await this.resolveUsername(domain);
5089
-
5090
- this.log('info', `Starting archive upload for ${domain}`);
5091
-
5092
- let uploadCredentials;
5093
- try {
5094
- uploadCredentials = await this.fetchUploadCredentials(username, domain);
5095
- } catch (error) {
5096
- const errorMessage = error instanceof Error ? error.message : String(error);
5097
- throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
5098
- }
5099
-
5100
- const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
5101
-
5102
- if (!uploadUrl || !authToken || !authRestToken) {
5103
- throw new Error('Invalid upload credentials received from API');
5104
- }
5105
-
5106
- const archiveBasename = path.basename(archivePath);
5107
- let uploadResult;
5108
- try {
5109
- const stats = fs.statSync(archivePath);
5110
- uploadResult = await this.uploadFile(
5111
- archivePath,
5112
- archiveBasename,
5113
- uploadUrl,
5114
- authRestToken,
5115
- authToken
5116
- );
5117
-
5118
- this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
5119
- } catch (error) {
5120
- const errorMessage = error instanceof Error ? error.message : String(error);
5121
- throw new Error(`Failed to upload archive: ${errorMessage}`);
5122
- }
5123
-
5124
- let deployResult;
5125
- try {
5126
- this.log('info', `Triggering deployment for ${domain}`);
5127
- deployResult = await this.hosting_deployStaticWebsite_triggerDeploy(username, domain, archivePath);
5128
- } catch (error) {
5129
- const errorMessage = error instanceof Error ? error.message : String(error);
5130
- this.log('error', `Failed to trigger deployment: ${errorMessage}`);
5131
- const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
5132
-
5133
- return {
5134
- upload: {
5135
- status: 'success',
5136
- data: {
5137
- filename: uploadResult.filename
5138
- }
5139
- },
5140
- deploy: {
5141
- status: 'error',
5142
- error: errorMessage
5143
- },
5144
- removeArchive: {
5145
- status: archiveRemoved ? 'success' : 'skipped'
5146
- }
5147
- };
5148
- }
5149
-
5150
- const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
5151
-
5152
- return {
5153
- upload: {
5154
- status: 'success',
5155
- data: {
5156
- filename: uploadResult.filename
5157
- }
5158
- },
5159
- deploy: {
5160
- status: 'success',
5161
- data: deployResult
5162
- },
5163
- removeArchive: {
5164
- status: archiveRemoved ? 'success' : 'skipped'
5165
- }
5166
- };
5167
- }
5168
-
5169
- hosting_listJsDeployments_validateRequiredParams(params) {
5170
- const { domain } = params;
5171
-
5172
- if (!domain || typeof domain !== 'string') {
5173
- throw new Error('domain is required and must be a string');
5174
- }
5175
- }
5176
-
5177
- hosting_listJsDeployments_buildQueryParams(params) {
5178
- const { page, perPage, states } = params;
5179
- const queryParams = new URLSearchParams();
5180
-
5181
- if (page !== undefined && page !== null) {
5182
- queryParams.append('page', page.toString());
5183
- }
5184
-
5185
- if (perPage !== undefined && perPage !== null) {
5186
- queryParams.append('per_page', perPage.toString());
5187
- }
5188
-
5189
- if (states && Array.isArray(states) && states.length > 0) {
5190
- states.forEach(state => {
5191
- queryParams.append('states[]', state);
5192
- });
5193
- }
5194
-
5195
- return queryParams.toString();
5196
- }
5197
-
5198
- async hosting_listJsDeployments_fetchDeployments(username, domain, queryParams) {
5199
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
5200
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds`, baseUrl).toString();
5201
-
5202
- const fullUrl = queryParams ? `${url}?${queryParams}` : url;
5203
-
5204
- try {
5205
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
5206
- if (!bearerToken) {
5207
- throw new Error('API_TOKEN environment variable not found');
5208
- }
5209
-
5210
- const config = {
5211
- method: 'get',
5212
- url: fullUrl,
5213
- headers: {
5214
- ...this.headers,
5215
- 'Authorization': `Bearer ${bearerToken}`
5216
- },
5217
- timeout: 60000, // 60s
5218
- validateStatus: function (status) {
5219
- return status < 500;
5220
- }
5221
- };
5222
-
5223
- const response = await axios(config);
5224
-
5225
- if (response.status !== 200) {
5226
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
5227
- }
5228
-
5229
- this.log('info', `Successfully fetched deployments for ${domain}`);
5230
- return response.data;
5231
-
5232
- } catch (error) {
5233
- const errorMessage = error instanceof Error ? error.message : String(error);
5234
- this.log('error', `Failed to fetch deployments: ${errorMessage}`);
5235
-
5236
- if (axios.isAxiosError(error)) {
5237
- const responseData = error.response?.data;
5238
- const responseStatus = error.response?.status;
5239
- this.log('error', 'API Error Details:', {
5240
- status: responseStatus,
5241
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
5242
- });
5243
- }
5244
-
5245
- throw error;
5246
- }
5247
- }
5248
-
5249
- async handleListJavascriptDeployments(params) {
5250
- const { domain, page, perPage, states } = params;
5251
-
5252
- this.hosting_listJsDeployments_validateRequiredParams(params);
5253
-
5254
- // Auto-resolve username from domain
5255
- this.log('info', `Resolving username from domain: ${domain}`);
5256
- const username = await this.resolveUsername(domain);
5257
-
5258
- // Build query parameters
5259
- const queryParams = this.hosting_listJsDeployments_buildQueryParams(params);
5260
-
5261
- // Fetch deployments
5262
- let deployments;
5263
- try {
5264
- this.log('info', `Fetching deployments for ${domain}`);
5265
- deployments = await this.hosting_listJsDeployments_fetchDeployments(username, domain, queryParams);
5266
- } catch (error) {
5267
- const errorMessage = error instanceof Error ? error.message : String(error);
5268
- this.log('error', `Failed to fetch deployments: ${errorMessage}`);
5269
- throw error;
5270
- }
5271
-
5272
- return {
5273
- status: 'success',
5274
- domain,
5275
- username,
5276
- queryParams: {
5277
- page,
5278
- perPage,
5279
- states
5280
- },
5281
- deployments
5282
- };
5283
- }
5284
-
5285
- hosting_showJsDeploymentLogs_validateRequiredParams(params) {
5286
- const { domain, buildUuid, fromLine } = params;
5287
-
5288
- if (!domain || typeof domain !== 'string') {
5289
- throw new Error('domain is required and must be a string');
5290
- }
5291
-
5292
- if (!buildUuid || typeof buildUuid !== 'string') {
5293
- throw new Error('buildUuid is required and must be a string');
5294
- }
5295
-
5296
- if (fromLine !== undefined && (typeof fromLine !== 'number' || !Number.isInteger(fromLine) || fromLine < 0)) {
5297
- throw new Error('fromLine must be a non-negative integer when provided');
5298
- }
5299
- }
5300
-
5301
- hosting_showJsDeploymentLogs_buildQueryParams(params) {
5302
- const { fromLine } = params;
5303
- const queryParams = new URLSearchParams();
5304
-
5305
- const line = (typeof fromLine === 'number' && Number.isInteger(fromLine) && fromLine >= 0) ? fromLine : 0;
5306
- queryParams.append('from_line', line.toString());
5307
-
5308
- return queryParams.toString();
5309
- }
5310
-
5311
- async hosting_showJsDeploymentLogs_fetchLogs(username, domain, buildUuid, queryParams) {
5312
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
5313
- const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds/${buildUuid}/logs`, baseUrl).toString();
5314
-
5315
- const fullUrl = queryParams ? `${url}?${queryParams}` : url;
5316
-
5317
- try {
5318
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
5319
- if (!bearerToken) {
5320
- throw new Error('API_TOKEN environment variable not found');
5321
- }
5322
-
5323
- const config = {
5324
- method: 'get',
5325
- url: fullUrl,
5326
- headers: {
5327
- ...this.headers,
5328
- 'Authorization': `Bearer ${bearerToken}`
5329
- },
5330
- timeout: 60000,
5331
- validateStatus: function (status) {
5332
- return status < 500;
5333
- }
5334
- };
5335
-
5336
- const response = await axios(config);
5337
-
5338
- if (response.status !== 200) {
5339
- throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
5340
- }
5341
-
5342
- this.log('info', `Successfully fetched logs for ${domain} build ${buildUuid}`);
5343
- return response.data;
5344
-
5345
- } catch (error) {
5346
- const errorMessage = error instanceof Error ? error.message : String(error);
5347
- this.log('error', `Failed to fetch logs: ${errorMessage}`);
5348
-
5349
- if (axios.isAxiosError(error)) {
5350
- const responseData = error.response?.data;
5351
- const responseStatus = error.response?.status;
5352
- this.log('error', 'API Error Details:', {
5353
- status: responseStatus,
5354
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
5355
- });
5356
- }
5357
-
5358
- throw error;
5359
- }
5360
- }
5361
-
5362
- async handleShowJsDeploymentLogs(params) {
5363
- const { domain, buildUuid, fromLine } = params;
5364
-
5365
- this.hosting_showJsDeploymentLogs_validateRequiredParams(params);
5366
-
5367
- this.log('info', `Resolving username from domain: ${domain}`);
5368
- const username = await this.resolveUsername(domain);
5369
-
5370
- const queryParams = this.hosting_showJsDeploymentLogs_buildQueryParams(params);
5371
-
5372
- let logs;
5373
- try {
5374
- this.log('info', `Fetching logs for ${domain}, build ${buildUuid}`);
5375
- logs = await this.hosting_showJsDeploymentLogs_fetchLogs(username, domain, buildUuid, queryParams);
5376
- } catch (error) {
5377
- const errorMessage = error instanceof Error ? error.message : String(error);
5378
- this.log('error', `Failed to fetch logs: ${errorMessage}`);
5379
- throw error;
5380
- }
5381
-
5382
- const effectiveFromLine = (typeof fromLine === 'number' && Number.isInteger(fromLine) && fromLine >= 0) ? fromLine : 0;
5383
-
5384
- return {
5385
- domain,
5386
- username,
5387
- buildUuid,
5388
- fromLine: effectiveFromLine,
5389
- logs
5390
- };
5391
- }
5392
-
5393
- /**
5394
- * Execute an API call for a tool
5395
- */
5396
- async executeApiCall(tool, params) {
5397
- // Get method and path from tool
5398
- const method = tool.method;
5399
- let path = tool.path;
5400
-
5401
- // Clone params to avoid modifying the original
5402
- const requestParams = { ...params };
5403
-
5404
- // Replace path parameters with values from params
5405
- Object.entries(requestParams).forEach(([key, value]) => {
5406
- const placeholder = `{${key}}`;
5407
- if (path.includes(placeholder)) {
5408
- path = path.replace(placeholder, encodeURIComponent(String(value)));
5409
- delete requestParams[key]; // Remove used parameter
5410
- }
5411
- });
5412
-
5413
- // Build the full URL
5414
- const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
5415
- const cleanPath = path.startsWith("/") ? path.slice(1) : path;
5416
- const url = new URL(cleanPath, baseUrl).toString();
5417
-
5418
- this.log('debug', `API Request: ${method} ${url}`);
5419
-
5420
- try {
5421
- // Configure the request
5422
- const config = {
5423
- method: method.toLowerCase(),
5424
- url,
5425
- headers: { ...this.headers },
5426
- timeout: 60000, // 60s
5427
- validateStatus: function (status) {
5428
- return status < 500; // Resolve only if the status code is less than 500
5429
- }
5430
- };
5431
-
5432
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN']; // APITOKEN for backwards compatibility
5433
- if (bearerToken) {
5434
- config.headers['Authorization'] = `Bearer ${bearerToken}`;
5435
- } else {
5436
- this.log('error', `Bearer Token environment variable not found: API_TOKEN`);
5437
- }
5438
-
5439
- // Add parameters based on request method
5440
- if (["GET", "DELETE"].includes(method)) {
5441
- // For GET/DELETE, send params as query string
5442
- config.params = { ...(config.params || {}), ...requestParams };
5443
- } else {
5444
- // For POST/PUT/PATCH, send params as JSON body
5445
- config.data = requestParams;
5446
- config.headers["Content-Type"] = "application/json";
5447
- }
5448
-
5449
- this.log('debug', "Request config:", {
5450
- url: config.url,
5451
- method: config.method,
5452
- params: config.params,
5453
- headers: Object.keys(config.headers)
5454
- });
5455
-
5456
- // Execute the request
5457
- const response = await axios(config);
5458
- this.log('debug', `Response status: ${response.status}`);
5459
-
5460
- return response.data;
5461
-
5462
- } catch (error) {
5463
- const errorMessage = error instanceof Error ? error.message : String(error);
5464
- this.log('error', `API request failed: ${errorMessage}`);
5465
-
5466
- if (axios.isAxiosError(error)) {
5467
- const responseData = error.response?.data;
5468
- const responseStatus = error.response?.status;
5469
-
5470
- this.log('error', 'API Error Details:', {
5471
- status: responseStatus,
5472
- data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
5473
- });
5474
-
5475
- // Rethrow with more context for better error handling
5476
- const detailedError = new Error(`API request failed with status ${responseStatus}: ${errorMessage}`);
5477
- detailedError.response = error.response;
5478
- throw detailedError;
5479
- }
5480
-
5481
- throw error;
5482
- }
5483
- }
5484
-
5485
- /**
5486
- * Log messages with appropriate level
5487
- * Only sends to MCP if we're connected
5488
- */
5489
- log(level, message, data) {
5490
- // Always log to stderr for visibility
5491
- console.error(`[${level.toUpperCase()}] ${message}${data ? ': ' + JSON.stringify(data) : ''}`);
5492
-
5493
- // Only try to send via MCP if we're in debug mode or it's important
5494
- if (this.debug || level !== 'debug') {
5495
- try {
5496
- // Only send if server exists and is connected
5497
- if (this.server && this.server.isConnected) {
5498
- this.server.sendLoggingMessage({
5499
- level,
5500
- data: `[MCP Server] ${message}${data ? ': ' + JSON.stringify(data) : ''}`
5501
- });
5502
- }
5503
- } catch (e) {
5504
- // If logging fails, log to stderr
5505
- console.error('Failed to send log via MCP:', e.message);
5506
- }
5507
- }
5508
- }
5509
-
5510
- /**
5511
- * Create and configure Express app with shared middleware
5512
- */
5513
- createApp() {
5514
- const app = express();
5515
- app.use(express.json());
5516
- app.use(cors());
5517
- return app;
5518
- }
5519
-
5520
- /**
5521
- * Start the server with HTTP streaming transport
5522
- */
5523
- async startHttp(host, port) {
5524
- try {
5525
- const app = this.createApp();
5526
-
5527
- // Create HTTP transport with session management
5528
- const httpTransport = new StreamableHTTPServerTransport({
5529
- sessionIdGenerator: () => {
5530
- // Generate a simple session ID
5531
- return `session-${Date.now()}-${Math.random().toString(36).substring(7)}`;
5532
- },
5533
- });
5534
-
5535
- // Set up CORS for all routes
5536
- app.options("*", (req, res) => {
5537
- res.header("Access-Control-Allow-Origin", "*");
5538
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
5539
- res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, x-session-id");
5540
- res.sendStatus(200);
5541
- });
5542
-
5543
- // Health check endpoint
5544
- app.get("/health", (req, res) => {
5545
- res.status(200).json({ status: "ok", transport: "http" });
5546
- });
5547
-
5548
- // Set up the HTTP transport endpoint
5549
- app.post("/", async (req, res) => {
5550
- res.header("Access-Control-Allow-Origin", "*");
5551
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
5552
- res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, x-session-id");
5553
- await httpTransport.handleRequest(req, res, req.body);
5554
- });
5555
-
5556
- // Start the server
5557
- const server = app.listen(port, host, () => {
5558
- this.log('info', `MCP Server with HTTP streaming transport started successfully with ${this.tools.size} tools`);
5559
- this.log('info', `Listening on http://${host}:${port}`);
5560
- });
5561
-
5562
- // Connect the MCP server to the transport
5563
- await this.server.connect(httpTransport);
5564
-
5565
- } catch (error) {
5566
- console.error("Failed to start MCP server:", error);
5567
- process.exit(1);
5568
- }
5569
- }
5570
-
5571
- /**
5572
- * Start the server
5573
- */
5574
- async startStdio() {
5575
- try {
5576
- // Create stdio transport
5577
- const transport = new StdioServerTransport();
5578
- console.error("MCP Server starting on stdio transport");
5579
-
5580
- // Connect to the transport
5581
- await this.server.connect(transport);
5582
-
5583
- // Now we can safely log via MCP
5584
- console.error(`Registered ${this.tools.size} tools`);
5585
- this.log('info', `MCP Server with stdio transport started successfully with ${this.tools.size} tools`);
5586
- } catch (error) {
5587
- console.error("Failed to start MCP server:", error);
5588
- process.exit(1);
5589
- }
5590
- }
5591
- }
5592
-
5593
- // Start the server
5594
- async function main() {
5595
- try {
5596
- const argv = minimist(process.argv.slice(2), {
5597
- string: ['host'],
5598
- boolean: ['stdio', 'http', 'help'],
5599
- default: {
5600
- host: '127.0.0.1',
5601
- port: 8100,
5602
- stdio: true,
5603
- }
5604
- });
5605
-
5606
- // Show help if requested
5607
- if (argv.help) {
5608
- console.log(`
5609
- Hostinger API MCP Server
5610
- Usage: hostinger-api-mcp [options]
5611
- Options:
5612
- --http Use HTTP streaming transport
5613
- --stdio Use standard input/output transport (default)
5614
- --host <host> Host to bind to (default: 127.0.0.1)
5615
- --port <port> Port to bind to (default: 8100)
5616
- --help Show this help message
5617
- Environment Variables:
5618
- API_TOKEN Your Hostinger API token (required)
5619
- DEBUG Enable debug logging (true/false)
5620
- `);
5621
- process.exit(0);
5622
- }
5623
-
5624
- const server = new MCPServer();
5625
- if (argv.http) {
5626
- await server.startHttp(argv.host, argv.port);
5627
- } else {
5628
- await server.startStdio();
5629
- }
5630
- } catch (error) {
5631
- console.error("Failed to start server:", error);
5632
- process.exit(1);
5633
- }
5634
- }
5635
-
5636
- main();
3648
+ export default tools;