hostinger-api-mcp 3.20.1 → 3.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/core/oauth.js +50 -15
- package/src/core/oauth.ts +38 -11
- package/src/core/runtime.js +115 -94
- package/src/core/runtime.ts +115 -94
- package/src/core/tools/all.js +3 -3
- package/src/core/tools/all.ts +3 -3
- package/src/core/tools/hosting.js +3 -3
- package/src/core/tools/hosting.ts +3 -3
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
- package/types.d.ts +3 -3
package/src/core/runtime.ts
CHANGED
|
@@ -380,6 +380,93 @@ class MCPServer {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
// Directories that hold credentials, the server's own token store (hostinger-mcp) among them.
|
|
384
|
+
private credentialDirectories = ['.ssh', '.aws', '.gnupg', '.config', '.kube', '.docker', '.azure', 'hostinger-mcp'];
|
|
385
|
+
|
|
386
|
+
// System directories. /var as a whole stays deployable: sites live in /var/www, and macOS keeps its temp dir there.
|
|
387
|
+
private systemDirectories = ['/etc', '/root', '/usr', '/bin', '/sbin', '/boot', '/proc', '/sys', '/dev', '/var/lib', '/var/log', '/var/db', '/var/root', '/private/etc', '/private/var/lib', '/private/var/log', '/private/var/db', '/private/var/root', '/System', '/Library'];
|
|
388
|
+
|
|
389
|
+
// A deploy publishes what it reads, and the path comes from the model, so prompt-injected arguments could
|
|
390
|
+
// publish any readable file. Refused, before anything is read or sent: the home directory and every directory
|
|
391
|
+
// above it (they contain the credentials), other users' home directories, system directories, a path in a
|
|
392
|
+
// credential directory, and a secret file. Symlinks are resolved first.
|
|
393
|
+
// os is imported by deploy.template: the JS generator dedupes import blocks by their whole text, and this block
|
|
394
|
+
// must stay identical to agency-hosting/shared's, or tus and fs are imported twice.
|
|
395
|
+
private assertDeployablePath(localPath: string): void {
|
|
396
|
+
let real: string;
|
|
397
|
+
try {
|
|
398
|
+
real = fs.realpathSync(localPath);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
throw new Error('Path not found: ' + localPath);
|
|
401
|
+
}
|
|
402
|
+
let home = os.homedir();
|
|
403
|
+
try {
|
|
404
|
+
home = fs.realpathSync(home);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
// a missing home directory has nothing in it to protect
|
|
407
|
+
}
|
|
408
|
+
const inside = (dir: string, file: string): boolean => {
|
|
409
|
+
const relative = path.relative(dir, file);
|
|
410
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
411
|
+
};
|
|
412
|
+
// A home in the filesystem root (HOME=/ in a container run as a user without a passwd entry) exempts nothing.
|
|
413
|
+
const inHome = path.dirname(home) !== home && inside(home, real);
|
|
414
|
+
// The directory that holds the home directories (/Users, /home); not when home sits in the root (/root).
|
|
415
|
+
const homes = path.dirname(home);
|
|
416
|
+
const otherHome = !inHome && path.dirname(homes) !== homes && inside(homes, real);
|
|
417
|
+
const inSystem = !inHome && this.systemDirectories.some((dir: string) => inside(dir, real));
|
|
418
|
+
const inCredentials = real.split(path.sep).some((segment: string) => this.credentialDirectories.includes(segment.toLowerCase()));
|
|
419
|
+
if (inside(real, home) || otherHome || inSystem || inCredentials || (fs.statSync(real).isFile() && this.isSecretFile(path.basename(real)))) {
|
|
420
|
+
throw new Error('Refusing to deploy ' + localPath + ': it is, or contains, a home, system or credentials location. Pass the project directory or archive.');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Env files (examples such as .env.example are kept), credentials and private keys: a deploy would serve them
|
|
425
|
+
// publicly. Matches the CLI's isSecretFile, plus .npmrc and the Hostinger credential files.
|
|
426
|
+
private isSecretFile(name: string): boolean {
|
|
427
|
+
if (['.env', '.envrc', '.netrc', '.git-credentials', '.pgpass', '.npmrc', '.hostinger.yaml', 'credentials.json'].includes(name)) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
if (['id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519'].some((key) => name.startsWith(key))) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
if (name.endsWith('.pem') || name.endsWith('.key')) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
return name.startsWith('.env.') && !['.env.example', '.env.sample', '.env.template', '.env.dist'].includes(name);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The files to ship, as sorted forward-slash paths relative to dir, and the secret files left out.
|
|
440
|
+
// node_modules, .git and OS junk are skipped; symlinks are not followed. .gitignore is not read:
|
|
441
|
+
// keep build output out of the directory.
|
|
442
|
+
// exclude is a file that is uploaded on its own (the database dump) and must not also travel inside
|
|
443
|
+
// the archive, where extracting it would leave a second, downloadable copy.
|
|
444
|
+
private listDeployFiles(dir: string, exclude?: string): { files: string[]; skipped: string[] } {
|
|
445
|
+
const files: string[] = [];
|
|
446
|
+
const skipped: string[] = [];
|
|
447
|
+
const walk = (relativeDir: string): void => {
|
|
448
|
+
for (const entry of fs.readdirSync(path.join(dir, relativeDir), { withFileTypes: true })) {
|
|
449
|
+
if (entry.isSymbolicLink()) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const relativePath = relativeDir ? relativeDir + '/' + entry.name : entry.name;
|
|
453
|
+
if (entry.isDirectory()) {
|
|
454
|
+
if (entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
455
|
+
walk(relativePath);
|
|
456
|
+
}
|
|
457
|
+
} else if (entry.isFile() && entry.name !== '.DS_Store' && path.resolve(dir, relativePath) !== exclude) {
|
|
458
|
+
if (this.isSecretFile(entry.name)) {
|
|
459
|
+
skipped.push(relativePath);
|
|
460
|
+
} else {
|
|
461
|
+
files.push(relativePath);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
walk('');
|
|
467
|
+
return { files: files.sort(), skipped: skipped.sort() };
|
|
468
|
+
}
|
|
469
|
+
|
|
383
470
|
private async resolveUsername(domain: string): Promise<string> {
|
|
384
471
|
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
385
472
|
const url = new URL(`api/hosting/v1/websites?domain=${encodeURIComponent(domain)}`, baseUrl).toString();
|
|
@@ -718,43 +805,6 @@ class MCPServer {
|
|
|
718
805
|
return slug;
|
|
719
806
|
}
|
|
720
807
|
|
|
721
|
-
// .env and .env.* hold secrets, and one in a document root can be fetched over HTTP; the platform
|
|
722
|
-
// takes variables from its own settings, so they are never packed. Examples are kept.
|
|
723
|
-
private hosting_deploy_isEnvFile(name: string): boolean {
|
|
724
|
-
return name === '.env' || (name.startsWith('.env.') && !['.env.example', '.env.sample', '.env.template', '.env.dist'].includes(name));
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// The files to ship, as sorted forward-slash paths relative to dir, and the .env files left out.
|
|
728
|
-
// node_modules, .git and OS junk are skipped; symlinks are not followed. .gitignore is not read:
|
|
729
|
-
// keep build output out of the directory.
|
|
730
|
-
// exclude is a file that is uploaded on its own (the database dump) and must not also travel inside
|
|
731
|
-
// the archive, where extracting it would leave a second, downloadable copy.
|
|
732
|
-
private hosting_deploy_listFiles(dir: string, exclude?: string): { files: string[]; skipped: string[] } {
|
|
733
|
-
const files: string[] = [];
|
|
734
|
-
const skipped: string[] = [];
|
|
735
|
-
const walk = (relativeDir: string): void => {
|
|
736
|
-
for (const entry of fs.readdirSync(path.join(dir, relativeDir), { withFileTypes: true })) {
|
|
737
|
-
if (entry.isSymbolicLink()) {
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
const relativePath = relativeDir ? relativeDir + '/' + entry.name : entry.name;
|
|
741
|
-
if (entry.isDirectory()) {
|
|
742
|
-
if (entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
743
|
-
walk(relativePath);
|
|
744
|
-
}
|
|
745
|
-
} else if (entry.isFile() && entry.name !== '.DS_Store' && path.resolve(dir, relativePath) !== exclude) {
|
|
746
|
-
if (this.hosting_deploy_isEnvFile(entry.name)) {
|
|
747
|
-
skipped.push(relativePath);
|
|
748
|
-
} else {
|
|
749
|
-
files.push(relativePath);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
};
|
|
754
|
-
walk('');
|
|
755
|
-
return { files: files.sort(), skipped: skipped.sort() };
|
|
756
|
-
}
|
|
757
|
-
|
|
758
808
|
// Adds what was left out to a result, so it is never silent.
|
|
759
809
|
private hosting_deploy_withSkipped(result: any, skipped: string[]): any {
|
|
760
810
|
if (skipped.length === 0) {
|
|
@@ -763,7 +813,7 @@ class MCPServer {
|
|
|
763
813
|
return {
|
|
764
814
|
...result,
|
|
765
815
|
skippedFiles: skipped,
|
|
766
|
-
skippedNote: 'These
|
|
816
|
+
skippedNote: 'These files hold secrets (env files, keys, credentials) and were not uploaded. Set environment values with hosting_nodejs_replace-environment-variables.',
|
|
767
817
|
};
|
|
768
818
|
}
|
|
769
819
|
|
|
@@ -964,7 +1014,7 @@ class MCPServer {
|
|
|
964
1014
|
return { archivePath: resolved, skipped: [] };
|
|
965
1015
|
}
|
|
966
1016
|
|
|
967
|
-
const { files, skipped } = this.
|
|
1017
|
+
const { files, skipped } = this.listDeployFiles(resolved, exclude);
|
|
968
1018
|
if (files.length === 0) {
|
|
969
1019
|
throw new Error('No files to deploy in ' + resolved);
|
|
970
1020
|
}
|
|
@@ -1015,7 +1065,7 @@ class MCPServer {
|
|
|
1015
1065
|
const isPlugin = type === 'wordpress-plugin';
|
|
1016
1066
|
const directory = path.resolve(params.path);
|
|
1017
1067
|
const slug = params.slug !== undefined ? params.slug : this.hosting_deploy_slug(directory);
|
|
1018
|
-
const { files, skipped } = this.
|
|
1068
|
+
const { files, skipped } = this.listDeployFiles(directory);
|
|
1019
1069
|
if (files.length === 0) {
|
|
1020
1070
|
throw new Error('No files found in directory: ' + directory);
|
|
1021
1071
|
}
|
|
@@ -1274,6 +1324,10 @@ class MCPServer {
|
|
|
1274
1324
|
|
|
1275
1325
|
if (git === undefined) {
|
|
1276
1326
|
// Local checks first, so a bad path fails before any request is made.
|
|
1327
|
+
this.assertDeployablePath(params.path);
|
|
1328
|
+
if (params.databaseDump !== undefined) {
|
|
1329
|
+
this.assertDeployablePath(params.databaseDump);
|
|
1330
|
+
}
|
|
1277
1331
|
const type = this.hosting_deploy_resolveType(params);
|
|
1278
1332
|
const website = await this.findWebsite(domain);
|
|
1279
1333
|
return this.hosting_deploy_fromPath(website.username, website.domain, type, params);
|
|
@@ -1550,6 +1604,7 @@ class MCPServer {
|
|
|
1550
1604
|
}
|
|
1551
1605
|
|
|
1552
1606
|
private hosting_importWordpressWebsite_validateArchiveFile(archivePath: string): void {
|
|
1607
|
+
this.assertDeployablePath(archivePath);
|
|
1553
1608
|
if (!fs.existsSync(archivePath)) {
|
|
1554
1609
|
throw new Error(`Archive file not found: ${archivePath}`);
|
|
1555
1610
|
}
|
|
@@ -1565,6 +1620,7 @@ class MCPServer {
|
|
|
1565
1620
|
}
|
|
1566
1621
|
|
|
1567
1622
|
private hosting_importWordpressWebsite_validateDatabaseFile(databaseDump: string): void {
|
|
1623
|
+
this.assertDeployablePath(databaseDump);
|
|
1568
1624
|
if (!fs.existsSync(databaseDump)) {
|
|
1569
1625
|
throw new Error(`Database dump file not found: ${databaseDump}`);
|
|
1570
1626
|
}
|
|
@@ -1820,6 +1876,7 @@ class MCPServer {
|
|
|
1820
1876
|
}
|
|
1821
1877
|
|
|
1822
1878
|
private hosting_deployWordpressPlugin_validatePluginDirectory(pluginPath: string): void {
|
|
1879
|
+
this.assertDeployablePath(pluginPath);
|
|
1823
1880
|
if (!fs.existsSync(pluginPath)) {
|
|
1824
1881
|
throw new Error(`Plugin directory not found: ${pluginPath}`);
|
|
1825
1882
|
}
|
|
@@ -1830,32 +1887,6 @@ class MCPServer {
|
|
|
1830
1887
|
}
|
|
1831
1888
|
}
|
|
1832
1889
|
|
|
1833
|
-
private hosting_deployWordpressPlugin_scanDirectory(dirPath: string, basePath: string = dirPath): Array<{absolutePath: string, relativePath: string}> {
|
|
1834
|
-
const files: Array<{absolutePath: string, relativePath: string}> = [];
|
|
1835
|
-
|
|
1836
|
-
const scanDir = (currentPath: string) => {
|
|
1837
|
-
const items = fs.readdirSync(currentPath);
|
|
1838
|
-
|
|
1839
|
-
for (const item of items) {
|
|
1840
|
-
const itemPath = path.join(currentPath, item);
|
|
1841
|
-
const stats = fs.statSync(itemPath);
|
|
1842
|
-
|
|
1843
|
-
if (stats.isDirectory()) {
|
|
1844
|
-
scanDir(itemPath);
|
|
1845
|
-
} else if (stats.isFile()) {
|
|
1846
|
-
const relativePath = path.relative(basePath, itemPath);
|
|
1847
|
-
files.push({
|
|
1848
|
-
absolutePath: itemPath,
|
|
1849
|
-
relativePath: relativePath
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
};
|
|
1854
|
-
|
|
1855
|
-
scanDir(dirPath);
|
|
1856
|
-
return files;
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
1890
|
private async hosting_deployWordpressPlugin_deployPlugin(username: string, domain: string, slug: string, pluginPath: string): Promise<boolean> {
|
|
1860
1891
|
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
1861
1892
|
const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/plugins/deploy`, baseUrl).toString();
|
|
@@ -1920,7 +1951,8 @@ class MCPServer {
|
|
|
1920
1951
|
const uploadDirName = `${slug}-${randomSuffix}`;
|
|
1921
1952
|
|
|
1922
1953
|
this.log('info', `Scanning plugin directory: ${pluginPath}`);
|
|
1923
|
-
const
|
|
1954
|
+
const { files, skipped } = this.listDeployFiles(pluginPath);
|
|
1955
|
+
const pluginFiles = files.map((relativePath: string) => ({ absolutePath: path.join(pluginPath, relativePath), relativePath }));
|
|
1924
1956
|
|
|
1925
1957
|
if (pluginFiles.length === 0) {
|
|
1926
1958
|
throw new Error(`No files found in plugin directory: ${pluginPath}`);
|
|
@@ -2008,6 +2040,7 @@ class MCPServer {
|
|
|
2008
2040
|
failed: failureCount
|
|
2009
2041
|
},
|
|
2010
2042
|
results,
|
|
2043
|
+
...(skipped.length > 0 ? { skippedFiles: skipped } : {}),
|
|
2011
2044
|
deploymentError: errorMessage
|
|
2012
2045
|
};
|
|
2013
2046
|
}
|
|
@@ -2021,6 +2054,7 @@ class MCPServer {
|
|
|
2021
2054
|
failed: failureCount
|
|
2022
2055
|
},
|
|
2023
2056
|
results,
|
|
2057
|
+
...(skipped.length > 0 ? { skippedFiles: skipped } : {}),
|
|
2024
2058
|
uploadDirName
|
|
2025
2059
|
};
|
|
2026
2060
|
}
|
|
@@ -2051,6 +2085,7 @@ class MCPServer {
|
|
|
2051
2085
|
}
|
|
2052
2086
|
|
|
2053
2087
|
private hosting_deployWordpressTheme_validateThemeDirectory(themePath: string): void {
|
|
2088
|
+
this.assertDeployablePath(themePath);
|
|
2054
2089
|
if (!fs.existsSync(themePath)) {
|
|
2055
2090
|
throw new Error(`Theme directory not found: ${themePath}`);
|
|
2056
2091
|
}
|
|
@@ -2061,32 +2096,6 @@ class MCPServer {
|
|
|
2061
2096
|
}
|
|
2062
2097
|
}
|
|
2063
2098
|
|
|
2064
|
-
private hosting_deployWordpressTheme_scanDirectory(dirPath: string, basePath: string = dirPath): Array<{absolutePath: string, relativePath: string}> {
|
|
2065
|
-
const files: Array<{absolutePath: string, relativePath: string}> = [];
|
|
2066
|
-
|
|
2067
|
-
const scanDir = (currentPath: string) => {
|
|
2068
|
-
const items = fs.readdirSync(currentPath);
|
|
2069
|
-
|
|
2070
|
-
for (const item of items) {
|
|
2071
|
-
const itemPath = path.join(currentPath, item);
|
|
2072
|
-
const stats = fs.statSync(itemPath);
|
|
2073
|
-
|
|
2074
|
-
if (stats.isDirectory()) {
|
|
2075
|
-
scanDir(itemPath);
|
|
2076
|
-
} else if (stats.isFile()) {
|
|
2077
|
-
const relativePath = path.relative(basePath, itemPath);
|
|
2078
|
-
files.push({
|
|
2079
|
-
absolutePath: itemPath,
|
|
2080
|
-
relativePath: relativePath
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
};
|
|
2085
|
-
|
|
2086
|
-
scanDir(dirPath);
|
|
2087
|
-
return files;
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
2099
|
private async hosting_deployWordpressTheme_deployTheme(username: string, domain: string, slug: string, themePath: string, activate: boolean = false): Promise<boolean> {
|
|
2091
2100
|
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
2092
2101
|
const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/themes/deploy`, baseUrl).toString();
|
|
@@ -2152,7 +2161,8 @@ class MCPServer {
|
|
|
2152
2161
|
const uploadDirName = `${slug}-${randomSuffix}`;
|
|
2153
2162
|
|
|
2154
2163
|
this.log('info', `Scanning theme directory: ${themePath}`);
|
|
2155
|
-
const
|
|
2164
|
+
const { files, skipped } = this.listDeployFiles(themePath);
|
|
2165
|
+
const themeFiles = files.map((relativePath: string) => ({ absolutePath: path.join(themePath, relativePath), relativePath }));
|
|
2156
2166
|
|
|
2157
2167
|
if (themeFiles.length === 0) {
|
|
2158
2168
|
throw new Error(`No files found in theme directory: ${themePath}`);
|
|
@@ -2240,6 +2250,7 @@ class MCPServer {
|
|
|
2240
2250
|
failed: failureCount
|
|
2241
2251
|
},
|
|
2242
2252
|
results,
|
|
2253
|
+
...(skipped.length > 0 ? { skippedFiles: skipped } : {}),
|
|
2243
2254
|
deploymentError: errorMessage
|
|
2244
2255
|
};
|
|
2245
2256
|
}
|
|
@@ -2253,6 +2264,7 @@ class MCPServer {
|
|
|
2253
2264
|
failed: failureCount
|
|
2254
2265
|
},
|
|
2255
2266
|
results,
|
|
2267
|
+
...(skipped.length > 0 ? { skippedFiles: skipped } : {}),
|
|
2256
2268
|
uploadDirName,
|
|
2257
2269
|
activated: activate
|
|
2258
2270
|
};
|
|
@@ -2306,6 +2318,7 @@ class MCPServer {
|
|
|
2306
2318
|
}
|
|
2307
2319
|
|
|
2308
2320
|
private hosting_deployJsApplication_validateArchiveFile(archivePath: string): void {
|
|
2321
|
+
this.assertDeployablePath(archivePath);
|
|
2309
2322
|
if (!fs.existsSync(archivePath)) {
|
|
2310
2323
|
throw new Error(`Archive file not found: ${archivePath}`);
|
|
2311
2324
|
}
|
|
@@ -2601,6 +2614,7 @@ class MCPServer {
|
|
|
2601
2614
|
}
|
|
2602
2615
|
|
|
2603
2616
|
private hosting_deployStaticWebsite_validateArchiveFile(archivePath: string): void {
|
|
2617
|
+
this.assertDeployablePath(archivePath);
|
|
2604
2618
|
if (!fs.existsSync(archivePath)) {
|
|
2605
2619
|
throw new Error(`Archive file not found: ${archivePath}`);
|
|
2606
2620
|
}
|
|
@@ -3052,6 +3066,7 @@ class MCPServer {
|
|
|
3052
3066
|
}
|
|
3053
3067
|
|
|
3054
3068
|
private agencyHosting_validateArchiveFile(archivePath: string): void {
|
|
3069
|
+
this.assertDeployablePath(archivePath);
|
|
3055
3070
|
if (!fs.existsSync(archivePath)) {
|
|
3056
3071
|
throw new Error(`Archive file not found: ${archivePath}`);
|
|
3057
3072
|
}
|
|
@@ -3060,6 +3075,12 @@ class MCPServer {
|
|
|
3060
3075
|
if (!archiveStats.isFile()) {
|
|
3061
3076
|
throw new Error(`Archive path is not a file: ${archivePath}`);
|
|
3062
3077
|
}
|
|
3078
|
+
|
|
3079
|
+
// Only an archive is uploaded (and, with removeArchive, deleted afterwards), never any other file.
|
|
3080
|
+
const name = path.basename(archivePath).toLowerCase();
|
|
3081
|
+
if (!['.zip', '.tar', '.tar.gz', '.tgz', '.7z', '.gz', '.gzip'].some((extension) => name.endsWith(extension))) {
|
|
3082
|
+
throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
|
|
3083
|
+
}
|
|
3063
3084
|
}
|
|
3064
3085
|
|
|
3065
3086
|
private agencyHosting_removeArchive(archivePath: string, removeArchive: boolean): boolean {
|
package/src/core/tools/all.js
CHANGED
|
@@ -72,7 +72,7 @@ export default [
|
|
|
72
72
|
},
|
|
73
73
|
"path": {
|
|
74
74
|
"type": "string",
|
|
75
|
-
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and .env
|
|
75
|
+
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and secret files (.env, keys, credentials), but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
|
|
76
76
|
},
|
|
77
77
|
"git": {
|
|
78
78
|
"type": "object",
|
|
@@ -496,7 +496,7 @@ export default [
|
|
|
496
496
|
},
|
|
497
497
|
"pluginPath": {
|
|
498
498
|
"type": "string",
|
|
499
|
-
"description": "Absolute or relative path to the plugin directory containing all plugin files"
|
|
499
|
+
"description": "Absolute or relative path to the plugin directory containing all plugin files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
500
500
|
}
|
|
501
501
|
},
|
|
502
502
|
"required": [
|
|
@@ -538,7 +538,7 @@ export default [
|
|
|
538
538
|
},
|
|
539
539
|
"themePath": {
|
|
540
540
|
"type": "string",
|
|
541
|
-
"description": "Absolute or relative path to the theme directory containing all theme files"
|
|
541
|
+
"description": "Absolute or relative path to the theme directory containing all theme files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
542
542
|
},
|
|
543
543
|
"activate": {
|
|
544
544
|
"type": "boolean",
|
package/src/core/tools/all.ts
CHANGED
|
@@ -89,7 +89,7 @@ const tools: OpenApiTool[] = [
|
|
|
89
89
|
},
|
|
90
90
|
"path": {
|
|
91
91
|
"type": "string",
|
|
92
|
-
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and .env
|
|
92
|
+
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and secret files (.env, keys, credentials), but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
|
|
93
93
|
},
|
|
94
94
|
"git": {
|
|
95
95
|
"type": "object",
|
|
@@ -513,7 +513,7 @@ const tools: OpenApiTool[] = [
|
|
|
513
513
|
},
|
|
514
514
|
"pluginPath": {
|
|
515
515
|
"type": "string",
|
|
516
|
-
"description": "Absolute or relative path to the plugin directory containing all plugin files"
|
|
516
|
+
"description": "Absolute or relative path to the plugin directory containing all plugin files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
517
517
|
}
|
|
518
518
|
},
|
|
519
519
|
"required": [
|
|
@@ -555,7 +555,7 @@ const tools: OpenApiTool[] = [
|
|
|
555
555
|
},
|
|
556
556
|
"themePath": {
|
|
557
557
|
"type": "string",
|
|
558
|
-
"description": "Absolute or relative path to the theme directory containing all theme files"
|
|
558
|
+
"description": "Absolute or relative path to the theme directory containing all theme files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
559
559
|
},
|
|
560
560
|
"activate": {
|
|
561
561
|
"type": "boolean",
|
|
@@ -72,7 +72,7 @@ export default [
|
|
|
72
72
|
},
|
|
73
73
|
"path": {
|
|
74
74
|
"type": "string",
|
|
75
|
-
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and .env
|
|
75
|
+
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and secret files (.env, keys, credentials), but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
|
|
76
76
|
},
|
|
77
77
|
"git": {
|
|
78
78
|
"type": "object",
|
|
@@ -416,7 +416,7 @@ export default [
|
|
|
416
416
|
},
|
|
417
417
|
"pluginPath": {
|
|
418
418
|
"type": "string",
|
|
419
|
-
"description": "Absolute or relative path to the plugin directory containing all plugin files"
|
|
419
|
+
"description": "Absolute or relative path to the plugin directory containing all plugin files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
420
420
|
}
|
|
421
421
|
},
|
|
422
422
|
"required": [
|
|
@@ -458,7 +458,7 @@ export default [
|
|
|
458
458
|
},
|
|
459
459
|
"themePath": {
|
|
460
460
|
"type": "string",
|
|
461
|
-
"description": "Absolute or relative path to the theme directory containing all theme files"
|
|
461
|
+
"description": "Absolute or relative path to the theme directory containing all theme files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
462
462
|
},
|
|
463
463
|
"activate": {
|
|
464
464
|
"type": "boolean",
|
|
@@ -89,7 +89,7 @@ const tools: OpenApiTool[] = [
|
|
|
89
89
|
},
|
|
90
90
|
"path": {
|
|
91
91
|
"type": "string",
|
|
92
|
-
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and .env
|
|
92
|
+
"description": "Local directory (preferred; it is archived for you, skipping node_modules, .git and secret files (.env, keys, credentials), but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
|
|
93
93
|
},
|
|
94
94
|
"git": {
|
|
95
95
|
"type": "object",
|
|
@@ -433,7 +433,7 @@ const tools: OpenApiTool[] = [
|
|
|
433
433
|
},
|
|
434
434
|
"pluginPath": {
|
|
435
435
|
"type": "string",
|
|
436
|
-
"description": "Absolute or relative path to the plugin directory containing all plugin files"
|
|
436
|
+
"description": "Absolute or relative path to the plugin directory containing all plugin files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
437
437
|
}
|
|
438
438
|
},
|
|
439
439
|
"required": [
|
|
@@ -475,7 +475,7 @@ const tools: OpenApiTool[] = [
|
|
|
475
475
|
},
|
|
476
476
|
"themePath": {
|
|
477
477
|
"type": "string",
|
|
478
|
-
"description": "Absolute or relative path to the theme directory containing all theme files"
|
|
478
|
+
"description": "Absolute or relative path to the theme directory containing all theme files. node_modules, .git, symlinks and secret files (.env, keys, credentials) are not uploaded."
|
|
479
479
|
},
|
|
480
480
|
"activate": {
|
|
481
481
|
"type": "boolean",
|
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/agency-hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.agency-hosting.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.4', tools, instructions });
|
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/agency-hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.agency-hosting.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.
|
|
7
|
+
startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.4', tools, instructions });
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
import { startServer } from '../core/runtime.js';
|
|
4
4
|
import tools from '../core/tools/agent-readiness.js';
|
|
5
5
|
|
|
6
|
-
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.4', tools });
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
import { startServer } from '../core/runtime.js';
|
|
4
4
|
import tools from '../core/tools/agent-readiness.js';
|
|
5
5
|
|
|
6
|
-
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.
|
|
6
|
+
startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.4', tools });
|
package/src/servers/all.js
CHANGED
|
@@ -5,4 +5,4 @@ import tools from '../core/tools/all.js';
|
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
import skills from '../core/skills.js';
|
|
7
7
|
|
|
8
|
-
startServer({ name: 'hostinger-api-mcp', version: '3.20.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.20.4', tools, instructions, skills });
|
package/src/servers/all.ts
CHANGED
|
@@ -5,4 +5,4 @@ import tools from '../core/tools/all.js';
|
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
import skills from '../core/skills.js';
|
|
7
7
|
|
|
8
|
-
startServer({ name: 'hostinger-api-mcp', version: '3.20.
|
|
8
|
+
startServer({ name: 'hostinger-api-mcp', version: '3.20.4', tools, instructions, skills });
|
package/src/servers/billing.js
CHANGED
package/src/servers/billing.ts
CHANGED
package/src/servers/dns.js
CHANGED
package/src/servers/dns.ts
CHANGED
package/src/servers/domains.js
CHANGED
package/src/servers/domains.ts
CHANGED
package/src/servers/ecommerce.js
CHANGED
package/src/servers/ecommerce.ts
CHANGED
package/src/servers/horizons.js
CHANGED
package/src/servers/horizons.ts
CHANGED
package/src/servers/hosting.js
CHANGED
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-hosting-mcp', version: '3.20.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.20.4', tools, instructions });
|
package/src/servers/hosting.ts
CHANGED
|
@@ -4,4 +4,4 @@ import { startServer } from '../core/runtime.js';
|
|
|
4
4
|
import tools from '../core/tools/hosting.js';
|
|
5
5
|
import instructions from '../core/instructions.js';
|
|
6
6
|
|
|
7
|
-
startServer({ name: 'hostinger-hosting-mcp', version: '3.20.
|
|
7
|
+
startServer({ name: 'hostinger-hosting-mcp', version: '3.20.4', tools, instructions });
|
package/src/servers/mail.js
CHANGED
package/src/servers/mail.ts
CHANGED
package/src/servers/reach.js
CHANGED
package/src/servers/reach.ts
CHANGED
package/src/servers/vps.js
CHANGED
package/src/servers/vps.ts
CHANGED