hostinger-api-mcp 3.20.0 → 3.20.3

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.
@@ -98,7 +98,7 @@ interface Skill { uri: string; frontmatter: Record<string, unknown>; resources:
98
98
 
99
99
  /**
100
100
  * MCP Server for Hostinger API
101
- * Generated from OpenAPI spec version 2.5.0
101
+ * Generated from OpenAPI spec version 2.5.1
102
102
  */
103
103
  class MCPServer {
104
104
  private readonly name: string;
@@ -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 .env files were not uploaded. Set their values with hosting_nodejs_replace-environment-variables.',
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.hosting_deploy_listFiles(resolved, exclude);
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.hosting_deploy_listFiles(directory);
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 pluginFiles = this.hosting_deployWordpressPlugin_scanDirectory(pluginPath);
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 themeFiles = this.hosting_deployWordpressTheme_scanDirectory(themePath);
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 {
@@ -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 files, but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
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",
@@ -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 files, but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
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 files, but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
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 files, but not reading .gitignore, so keep build output out) or archive (zip, tar, tar.gz, tgz, 7z, gz, gzip). Not together with git."
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.0', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.3', 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.0', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '3.20.3', 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.0', tools });
6
+ startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.3', 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.0', tools });
6
+ startServer({ name: 'hostinger-agent-readiness-mcp', version: '3.20.3', tools });
@@ -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.0', tools, instructions, skills });
8
+ startServer({ name: 'hostinger-api-mcp', version: '3.20.3', tools, instructions, skills });
@@ -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.0', tools, instructions, skills });
8
+ startServer({ name: 'hostinger-api-mcp', version: '3.20.3', tools, instructions, skills });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/billing.js';
5
5
 
6
- startServer({ name: 'hostinger-billing-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/billing.js';
5
5
 
6
- startServer({ name: 'hostinger-billing-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/dns.js';
5
5
 
6
- startServer({ name: 'hostinger-dns-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/dns.js';
5
5
 
6
- startServer({ name: 'hostinger-dns-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/domains.js';
5
5
 
6
- startServer({ name: 'hostinger-domains-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/domains.js';
5
5
 
6
- startServer({ name: 'hostinger-domains-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/ecommerce.js';
5
5
 
6
- startServer({ name: 'hostinger-ecommerce-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/ecommerce.js';
5
5
 
6
- startServer({ name: 'hostinger-ecommerce-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/horizons.js';
5
5
 
6
- startServer({ name: 'hostinger-horizons-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/horizons.js';
5
5
 
6
- startServer({ name: 'hostinger-horizons-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '3.20.3', tools });
@@ -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.0', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '3.20.3', tools, instructions });
@@ -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.0', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '3.20.3', tools, instructions });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/mail.js';
5
5
 
6
- startServer({ name: 'hostinger-mail-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/mail.js';
5
5
 
6
- startServer({ name: 'hostinger-mail-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/reach.js';
5
5
 
6
- startServer({ name: 'hostinger-reach-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/reach.js';
5
5
 
6
- startServer({ name: 'hostinger-reach-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '3.20.3', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/vps.js';
5
5
 
6
- startServer({ name: 'hostinger-vps-mcp', version: '3.20.0', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '3.20.3', tools });