hostinger-api-mcp 3.29.0 → 3.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +9 -40
  3. package/gemini-extension.json +1 -1
  4. package/package.json +1 -1
  5. package/server.json +2 -2
  6. package/skills/headless/VERSION +2 -2
  7. package/skills/headless/connect/SKILL.md +13 -6
  8. package/skills/headless/connect/tools/antigravity.md +2 -2
  9. package/skills/headless/connect/tools/claude-code.md +2 -2
  10. package/skills/headless/connect/tools/codex.md +4 -4
  11. package/skills/headless/connect/tools/cursor.md +5 -5
  12. package/skills/headless/connect/tools/devin.md +4 -4
  13. package/skills/headless/connect/tools/vscode.md +4 -4
  14. package/skills/headless/steps/03-provision/ecommerce/SKILL.md +1 -1
  15. package/skills/headless/steps/08-deploy/SKILL.md +1 -1
  16. package/skills/maintain-wordpress/SKILL.md +1 -1
  17. package/skills/troubleshoot-website/SKILL.md +1 -1
  18. package/src/core/runtime.js +96 -0
  19. package/src/core/runtime.ts +96 -0
  20. package/src/core/skills.js +33 -33
  21. package/src/core/skills.ts +33 -33
  22. package/src/core/tools/all.js +78 -4
  23. package/src/core/tools/all.ts +78 -4
  24. package/src/core/tools/hosting.js +78 -4
  25. package/src/core/tools/hosting.ts +78 -4
  26. package/src/servers/agency-hosting.js +1 -1
  27. package/src/servers/agency-hosting.ts +1 -1
  28. package/src/servers/agent-readiness.js +1 -1
  29. package/src/servers/agent-readiness.ts +1 -1
  30. package/src/servers/all.js +1 -1
  31. package/src/servers/all.ts +1 -1
  32. package/src/servers/billing.js +1 -1
  33. package/src/servers/billing.ts +1 -1
  34. package/src/servers/dns.js +1 -1
  35. package/src/servers/dns.ts +1 -1
  36. package/src/servers/domains.js +1 -1
  37. package/src/servers/domains.ts +1 -1
  38. package/src/servers/ecommerce.js +1 -1
  39. package/src/servers/ecommerce.ts +1 -1
  40. package/src/servers/gpu.js +1 -1
  41. package/src/servers/gpu.ts +1 -1
  42. package/src/servers/horizons.js +1 -1
  43. package/src/servers/horizons.ts +1 -1
  44. package/src/servers/hosting.js +1 -1
  45. package/src/servers/hosting.ts +1 -1
  46. package/src/servers/mail.js +1 -1
  47. package/src/servers/mail.ts +1 -1
  48. package/src/servers/reach.js +1 -1
  49. package/src/servers/reach.ts +1 -1
  50. package/src/servers/vps.js +1 -1
  51. package/src/servers/vps.ts +1 -1
  52. package/src/servers/wordpress.js +1 -1
  53. package/src/servers/wordpress.ts +1 -1
  54. package/types.d.ts +29 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ Generated at release time from the diff between the previous and current
4
4
  build. Tool changes are listed individually; everything else is summarised
5
5
  by area.
6
6
 
7
+ ## v3.30.1 — 2026-11-05
8
+
9
+ ### Also in this release
10
+
11
+ - Bundled agent skills: 10 files
12
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
13
+
14
+ ## v3.30.0 — 2026-11-05
15
+
16
+ ### Operations
17
+
18
+ - Added `hosting_php_update` — Update website PHP version, extensions and options
19
+ - Hid 4 operations from search (they still run through `execute`): `hosting_php_reset-extensions`, `hosting_php_update-extensions`, `hosting_php_update-options`, `hosting_php_update-version`
20
+
21
+ ### Also in this release
22
+
23
+ - Documentation: `README.md`
24
+ - Bundled agent skills: `skills/headless/VERSION`, `skills/maintain-wordpress/SKILL.md`, `skills/troubleshoot-website/SKILL.md`
25
+ - Request handling and authentication: `src/core/runtime.js`, `src/core/runtime.ts`
26
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
27
+ - Build and packaging: `types.d.ts`
28
+
7
29
  ## v3.29.0 — 2026-11-05
8
30
 
9
31
  ### Also in this release
package/README.md CHANGED
@@ -69,7 +69,7 @@ pnpm update -g @hostinger/mcp
69
69
 
70
70
  This package installs the following MCP server commands:
71
71
 
72
- - `hostinger-api-mcp` — unified server over every operation (412 total)
72
+ - `hostinger-api-mcp` — unified server over every operation (409 total)
73
73
  - `hostinger-agency-hosting-mcp` — 43 operations for agency-hosting
74
74
  - `hostinger-agent-readiness-mcp` — 4 operations for agent-readiness
75
75
  - `hostinger-billing-mcp` — 10 operations for billing
@@ -78,7 +78,7 @@ This package installs the following MCP server commands:
78
78
  - `hostinger-ecommerce-mcp` — 30 operations for ecommerce
79
79
  - `hostinger-gpu-mcp` — 5 operations for gpu
80
80
  - `hostinger-horizons-mcp` — 7 operations for horizons
81
- - `hostinger-hosting-mcp` — 57 operations for hosting
81
+ - `hostinger-hosting-mcp` — 54 operations for hosting
82
82
  - `hostinger-mail-mcp` — 39 operations for mail
83
83
  - `hostinger-reach-mcp` — 71 operations for reach
84
84
  - `hostinger-vps-mcp` — 68 operations for vps
@@ -1960,6 +1960,13 @@ Clear a website's server cache (the hosting server's page cache) and Hostinger C
1960
1960
  - **Method**: `custom`
1961
1961
  - **Path**: `custom`
1962
1962
 
1963
+ #### hosting_php_update
1964
+
1965
+ Change a website's PHP version, extensions and php.ini options in one call. Pass only what to change, at least one of version, resetExtensions, extensions and options. Steps run in the order version, resetExtensions, extensions, options, and the call stops at the first failure. applied lists the steps that ran. When a step fails, the call returns an error whose text is the same JSON: failed names the step and why, and skipped lists the ones not attempted; a multi-execute batch stops there too. WARNING: changing the version resets the website's extensions (PHP 8.0 and later) and its options to their defaults, so pass the extensions and options to keep in the same call; they are applied after the version. Option values above the plan limit are capped silently: read the applied values back with hosting_php_get, which also lists the available versions, extensions and options. Works for Shared and Cloud hosting websites.
1966
+
1967
+ - **Method**: `custom`
1968
+ - **Path**: `custom`
1969
+
1963
1970
  #### hosting_websites_list-domains
1964
1971
 
1965
1972
  List the extra domains of a website: its parked (alias) domains, which show the same site under another domain name, and its subdomains (e.g. blog.example.com), with each one's root directory. parked items have domain (a domain name or an IP address) and type (domain or ip); subdomains items have domain (the full name), subdomain (the prefix) and root_directory. A list that cannot be read comes back as { error } and the other is still returned. To add or remove one use hosting_websites_add-domain or hosting_websites_remove-domain. Works for Shared and Cloud hosting websites.
@@ -2441,15 +2448,6 @@ or specific order IDs for more targeted results.
2441
2448
  - **Method**: `GET`
2442
2449
  - **Path**: `/api/hosting/v1/orders`
2443
2450
 
2444
- #### hosting_php_reset-extensions
2445
-
2446
- Resets all PHP extensions of the website to their default state.
2447
-
2448
- Use it to recover from extension conflicts or restore the original configuration.
2449
-
2450
- - **Method**: `PATCH`
2451
- - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/php/extensions/reset`
2452
-
2453
2451
  #### hosting_php_get
2454
2452
 
2455
2453
  Returns the full PHP configuration for the website: current version, available versions
@@ -2470,35 +2468,6 @@ Use it to debug PHP issues or inspect the complete PHP environment of the websit
2470
2468
  - **Method**: `GET`
2471
2469
  - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/php/php-info`
2472
2470
 
2473
- #### hosting_php_update-extensions
2474
-
2475
- Enables or disables PHP extensions (modules) for the website.
2476
-
2477
- Use the Get PHP details endpoint to check the current extension states before changing them.
2478
-
2479
- - **Method**: `PATCH`
2480
- - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/php/extensions`
2481
-
2482
- #### hosting_php_update-options
2483
-
2484
- Updates PHP options for the website (e.g. `memory_limit`, `max_execution_time`, `upload_max_filesize`).
2485
- Only provide the options you want to change, inside the `options` object.
2486
-
2487
- Values above the account plan limit are silently capped to that limit, so the request can succeed
2488
- with a smaller applied value. Call the Get PHP details endpoint afterwards to read the applied value.
2489
-
2490
- - **Method**: `PATCH`
2491
- - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/php/options`
2492
-
2493
- #### hosting_php_update-version
2494
-
2495
- Changes the PHP version of the website.
2496
-
2497
- Use the Get PHP details endpoint to see the versions available for the website.
2498
-
2499
- - **Method**: `PATCH`
2500
- - **Path**: `/api/hosting/v1/accounts/{username}/websites/{domain}/php/version`
2501
-
2502
2471
  #### hosting_redirects_list-website
2503
2472
 
2504
2473
  Returns a paginated list of redirects configured for the selected website.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.29.0",
3
+ "version": "3.30.1",
4
4
  "description": "Hostinger API — manage domains, DNS, hosting, VPS, billing and more from Gemini CLI.",
5
5
  "mcpServers": {
6
6
  "hostinger": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.29.0",
3
+ "version": "3.30.1",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  "url": "https://github.com/hostinger/api-mcp-server",
18
18
  "source": "github"
19
19
  },
20
- "version": "3.29.0",
20
+ "version": "3.30.1",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "@hostinger/mcp",
25
- "version": "3.29.0",
25
+ "version": "3.30.1",
26
26
  "transport": {
27
27
  "type": "stdio"
28
28
  },
@@ -1,3 +1,3 @@
1
- version=3.29.0
2
- commit=c4c3f98adbe2
1
+ version=3.30.1
2
+ commit=4d02050e3406
3
3
  built=2026-11-05
@@ -107,8 +107,8 @@ hostinger-api-mcp --login
107
107
  hostinger-api-mcp --logout
108
108
 
109
109
  # Without a global install:
110
- npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --login
111
- npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --logout
110
+ npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp --login
111
+ npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp --logout
112
112
  ```
113
113
 
114
114
  Credentials live in `~/.config/hostinger-mcp/credentials.json` (macOS/Linux) or `%APPDATA%\hostinger-mcp\credentials.json` (Windows) and are shared by every Hostinger MCP binary. OAuth works only when the tool runs the server over its default local (stdio) transport, which is what every registration below uses; `--http` needs `HOSTINGER_API_TOKEN`.
@@ -127,8 +127,8 @@ one group says nothing about the other:
127
127
 
128
128
  | Group | Operations | Comes from | Needed by |
129
129
  |---|---|---|---|
130
- | **Hosting** | `hosting_*` (websites, deploy, build report), `wordpress_*` | `hostinger` (`npx -y @hostinger/mcp@latest`), or the scoped servers such as `hostinger-hosting` + `hostinger-wordpress` | every build |
131
- | **Ecommerce** | `ecommerce_*` (stores, products, shipping, payments) | **only** `hostinger-ecommerce` (`npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp`), or the Ecommerce MCP in the Hostinger Connector. Neither `hostinger` nor the scoped hosting servers include it | Ecommerce backends (chosen later, in refine) |
130
+ | **Hosting** | `hosting_*` (websites, deploy, build report), `wordpress_*` | `hostinger` (`npx -y --prefix="$HOME" @hostinger/mcp@latest`), or the scoped servers such as `hostinger-hosting` + `hostinger-wordpress` | every build |
131
+ | **Ecommerce** | `ecommerce_*` (stores, products, shipping, payments) | **only** `hostinger-ecommerce` (`npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp`), or the Ecommerce MCP in the Hostinger Connector. Neither `hostinger` nor the scoped hosting servers include it | Ecommerce backends (chosen later, in refine) |
132
132
 
133
133
  Check each group on its own, right here at connect, before anything else:
134
134
 
@@ -148,8 +148,8 @@ Most coding tools load MCP servers only when they start, so a server registered
148
148
  until the tool restarts. When either tool group above is missing:
149
149
 
150
150
  1. **Register the missing servers** in the tool's MCP config, as the tool route below shows: `hostinger`
151
- (`npx -y @hostinger/mcp@latest`) if the hosting group is missing, and `hostinger-ecommerce`
152
- (`npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp`) if the ecommerce group is missing.
151
+ (`npx -y --prefix="$HOME" @hostinger/mcp@latest`) if the hosting group is missing, and `hostinger-ecommerce`
152
+ (`npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp`) if the ecommerce group is missing.
153
153
  Skip a server that is already registered under its name. When neither group is there, register
154
154
  both: the backend isn't chosen until refine, and one restart then covers every backend.
155
155
  2. **Write `connect.json`** (Phase 3) before you stop. The skill folder from Phase 1 stays in place,
@@ -163,6 +163,13 @@ until the tool restarts. When either tool group above is missing:
163
163
 
164
164
  If a tool loads new servers without a restart (VS Code), use them right away and skip steps 3–4.
165
165
 
166
+ **Every `npx` command and registration keeps `--prefix` set to the user's home folder.** The tool
167
+ starts the server, and you run commands, in the user's project, often a cloned repository or
168
+ template. Without `--prefix`, npm reads the `.npmrc` there, and whoever wrote it can point npx at
169
+ another registry or run their own code. In shell commands write `--prefix="$HOME"` (the shell expands
170
+ it when you register). In JSON or TOML configs replace `<home>` with the home folder's absolute path
171
+ (`echo $HOME`, e.g. `/Users/me` or `C:\\Users\\me`). Never drop the flag or leave `<home>` in place.
172
+
166
173
  | Tool | Route |
167
174
  |-------------|---------------------------------|
168
175
  | Claude Code | `connect/tools/claude-code.md` |
@@ -26,11 +26,11 @@ no tokens to handle:
26
26
  "mcpServers": {
27
27
  "hostinger": {
28
28
  "command": "npx",
29
- "args": ["-y", "@hostinger/mcp@latest"]
29
+ "args": ["-y", "--prefix=<home>", "@hostinger/mcp@latest"]
30
30
  },
31
31
  "hostinger-ecommerce": {
32
32
  "command": "npx",
33
- "args": ["--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
33
+ "args": ["--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
34
34
  }
35
35
  }
36
36
  }
@@ -25,8 +25,8 @@ in, so they reuse the cached sign-in (`~/.config/hostinger-mcp/credentials.json`
25
25
  `HOSTINGER_API_TOKEN`) and there are no tokens to handle:
26
26
 
27
27
  ```bash
28
- claude mcp add hostinger --scope user -- npx -y @hostinger/mcp@latest # hosting group missing
29
- claude mcp add hostinger-ecommerce --scope user -- npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp # ecommerce group missing
28
+ claude mcp add hostinger --scope user -- npx -y --prefix="$HOME" @hostinger/mcp@latest # hosting group missing
29
+ claude mcp add hostinger-ecommerce --scope user -- npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp # ecommerce group missing
30
30
  ```
31
31
 
32
32
  Claude Code loads MCP servers only at startup, so follow *register, restart, resume* in
@@ -19,8 +19,8 @@ sign-in (`~/.config/hostinger-mcp/credentials.json`, or `HOSTINGER_API_TOKEN`) a
19
19
  to handle:
20
20
 
21
21
  ```bash
22
- codex mcp add hostinger -- npx -y @hostinger/mcp@latest
23
- codex mcp add hostinger-ecommerce -- npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
22
+ codex mcp add hostinger -- npx -y --prefix="$HOME" @hostinger/mcp@latest
23
+ codex mcp add hostinger-ecommerce -- npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
24
24
  ```
25
25
 
26
26
  or add them to `~/.codex/config.toml`:
@@ -28,11 +28,11 @@ or add them to `~/.codex/config.toml`:
28
28
  ```toml
29
29
  [mcp_servers.hostinger]
30
30
  command = "npx"
31
- args = ["-y", "@hostinger/mcp@latest"]
31
+ args = ["-y", "--prefix=<home>", "@hostinger/mcp@latest"]
32
32
 
33
33
  [mcp_servers.hostinger-ecommerce]
34
34
  command = "npx"
35
- args = ["--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
35
+ args = ["--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
36
36
  ```
37
37
 
38
38
  Codex loads MCP servers only when it starts, so follow *register, restart, resume* in
@@ -19,7 +19,7 @@ The Hostinger MCP is the npm package `@hostinger/mcp`. You do **not** need a glo
19
19
 
20
20
  ```bash
21
21
  # Verify the package/binary is reachable
22
- npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --version
22
+ npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp --version
23
23
  ```
24
24
 
25
25
  ### Option B — global install
@@ -49,11 +49,11 @@ OAuth can run:
49
49
  "mcpServers": {
50
50
  "hostinger": {
51
51
  "command": "npx",
52
- "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-api-mcp"]
52
+ "args": ["--yes", "--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-api-mcp"]
53
53
  },
54
54
  "hostinger-ecommerce": {
55
55
  "command": "npx",
56
- "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
56
+ "args": ["--yes", "--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
57
57
  }
58
58
  }
59
59
  }
@@ -74,7 +74,7 @@ If the connect bootstrap already emitted `logged_in` / `success`, credentials ar
74
74
  Otherwise run the login command. It registers a dynamic OAuth client (once per machine), **opens the user's browser** to Hostinger's authorization page, listens on a localhost callback, stores tokens, and exits:
75
75
 
76
76
  ```bash
77
- npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --login
77
+ npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp --login
78
78
  ```
79
79
 
80
80
  (With a global install: `hostinger-api-mcp --login`.)
@@ -95,7 +95,7 @@ They are shared across every Hostinger MCP binary. Expired access tokens refresh
95
95
  To sign out later:
96
96
 
97
97
  ```bash
98
- npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp --logout
98
+ npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp --logout
99
99
  ```
100
100
 
101
101
  **API token (optional, CI / scripts):** set `HOSTINGER_API_TOKEN` in the environment before launching Cursor. When present it always wins and no OAuth code runs. Do not commit the token.
@@ -21,18 +21,18 @@ tokens to handle.
21
21
  - **Devin CLI:**
22
22
 
23
23
  ```bash
24
- devin mcp add hostinger -- npx -y @hostinger/mcp@latest
25
- devin mcp add hostinger-ecommerce -- npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
24
+ devin mcp add hostinger -- npx -y --prefix="$HOME" @hostinger/mcp@latest
25
+ devin mcp add hostinger-ecommerce -- npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
26
26
  ```
27
27
 
28
28
  - **Devin app:** Settings → MCP Marketplace → **Add Your Own**, transport **STDIO**, once per server:
29
29
 
30
30
  ```json
31
- { "command": "npx", "args": ["-y", "@hostinger/mcp@latest"] }
31
+ { "command": "npx", "args": ["-y", "--prefix=<home>", "@hostinger/mcp@latest"] }
32
32
  ```
33
33
 
34
34
  ```json
35
- { "command": "npx", "args": ["--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"] }
35
+ { "command": "npx", "args": ["--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"] }
36
36
  ```
37
37
 
38
38
  If the tools still aren't in this session, follow *register, restart, resume* in `connect/SKILL.md`:
@@ -26,12 +26,12 @@ no tokens to handle. Add them to `.vscode/mcp.json` (workspace) or your user `se
26
26
  "hostinger": {
27
27
  "type": "stdio",
28
28
  "command": "npx",
29
- "args": ["-y", "@hostinger/mcp@latest"]
29
+ "args": ["-y", "--prefix=<home>", "@hostinger/mcp@latest"]
30
30
  },
31
31
  "hostinger-ecommerce": {
32
32
  "type": "stdio",
33
33
  "command": "npx",
34
- "args": ["--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
34
+ "args": ["--prefix=<home>", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"]
35
35
  }
36
36
  }
37
37
  }
@@ -40,8 +40,8 @@ no tokens to handle. Add them to `.vscode/mcp.json` (workspace) or your user `se
40
40
  Or add them in two commands:
41
41
 
42
42
  ```bash
43
- code --add-mcp '{"name":"hostinger","command":"npx","args":["-y","@hostinger/mcp@latest"]}'
44
- code --add-mcp '{"name":"hostinger-ecommerce","command":"npx","args":["--package=@hostinger/mcp@latest","hostinger-ecommerce-mcp"]}'
43
+ code --add-mcp '{"name":"hostinger","command":"npx","args":["-y","--prefix=<home>","@hostinger/mcp@latest"]}'
44
+ code --add-mcp '{"name":"hostinger-ecommerce","command":"npx","args":["--prefix=<home>","--package=@hostinger/mcp@latest","hostinger-ecommerce-mcp"]}'
45
45
  ```
46
46
 
47
47
  VS Code picks up new servers and their tools become available right away, with no editor restart.
@@ -22,7 +22,7 @@ Provision a **Hostinger Ecommerce** backend for `<domain>` and prove it is ready
22
22
  > aren't (for example, a tool connected before connect registered both servers), register it now. The
23
23
  > command for each tool is in its `connect/tools/<tool>.md`; for Claude Code:
24
24
  > ```bash
25
- > claude mcp add hostinger-ecommerce --scope user -- npx --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
25
+ > claude mcp add hostinger-ecommerce --scope user -- npx --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-ecommerce-mcp
26
26
  > ```
27
27
  > (Or enable the **Ecommerce** MCP in the Hostinger Connector.) A newly added MCP server only loads
28
28
  > after a restart, so pause the run with *register, restart, resume* from `connect/SKILL.md`: save
@@ -15,7 +15,7 @@ This is an **instance-barrier** step: run it only after `provision.status == "co
15
15
  > **Connected to the hosted server (`mcp.hostinger.com`)?** Its `hosting_deploy` deploys Git
16
16
  > repositories only and cannot read a local `path`. Run this step through the local
17
17
  > `hostinger-api-mcp` server instead (see the connect step:
18
- > `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp`).
18
+ > `npx --yes --prefix="$HOME" --package=@hostinger/mcp@latest hostinger-api-mcp`).
19
19
 
20
20
  > **With a shell and the Hostinger CLI installed**, the same deploy is one command, with the build
21
21
  > settings passed exactly as the table below says. Run it from the project, since it links the current directory:
@@ -65,7 +65,7 @@ When a site breaks: stop the run, deactivate the plugin updated last with `wordp
65
65
 
66
66
  - Page cache: `wordpress_litespeed-cache_show-lite-speed-status`; purge after design or content changes.
67
67
  - Object cache: `wordpress_object-cache_show-memcached-status`, then `wordpress_object-cache_toggle-memcached` with `enabled: true` — the usual quick speed-up.
68
- - PHP version: `hosting_php_get` is large (around 25 KB), so read it only when the user asks about PHP. PHP 8.x is markedly faster than 7.x; confirm plugin compatibility before `hosting_php_update-version`.
68
+ - PHP version: `hosting_php_get` is large (around 25 KB), so read it only when the user asks about PHP. PHP 8.x is markedly faster than 7.x; confirm plugin compatibility before switching with `hosting_php_update` (`version`). The switch resets extensions and options to their defaults, so pass the custom ones from `hosting_php_get` in the same call.
69
69
  - wp-admin: `wordpress_login_create-links` returns temporary one-click login links. Give them to the user only; never store or reuse them.
70
70
  - Hostinger's own plugins update with `wordpress_plugins_update-hostinger` (`slug`).
71
71
 
@@ -68,7 +68,7 @@ Batch `hosting_get-deployment` (the latest build with its log, failure analysis
68
68
 
69
69
  ### PHP
70
70
 
71
- `hosting_php_get` returns every extension and option (around 25 KB), so call it only for PHP problems. `hosting_php_update-version` switches versions. `hosting_php_update-options` silently caps values at the plan maximum (`max` in `hosting_php_get`), so read the applied value back. Agency: `agency-hosting_php_list-options-for-website`, then `agency-hosting_php_replace-website-options` — a full replace, so send every custom option that should stay.
71
+ `hosting_php_get` returns every extension and option (around 25 KB), so call it only for PHP problems. `hosting_php_update` changes the version, extensions and options in one call. Switching the version resets the extensions and options to their defaults, so pass the ones to keep in the same call. Option values are silently capped at the plan maximum (`max` in `hosting_php_get`), so read the applied value back. Agency: `agency-hosting_php_list-options-for-website`, then `agency-hosting_php_replace-website-options` — a full replace, so send every custom option that should stay.
72
72
 
73
73
  ### Agency Plan sites
74
74
 
@@ -304,6 +304,8 @@ class MCPServer {
304
304
  return await this.handleHostingWebsitesUpdateSettings(params);
305
305
  case 'hosting_cache_clear':
306
306
  return await this.handleHostingCacheClear(params);
307
+ case 'hosting_php_update':
308
+ return await this.handleHostingPhpUpdate(params);
307
309
  case 'hosting_websites_list-domains':
308
310
  return await this.handleHostingWebsitesListDomains(params);
309
311
  case 'hosting_websites_add-domain':
@@ -1605,6 +1607,100 @@ class MCPServer {
1605
1607
  return { domain, cleared: scope, directory, response };
1606
1608
  }
1607
1609
 
1610
+ // Application order, and how each step maps to its operation.
1611
+ hosting_phpUpdate_steps = [
1612
+ { setting: 'version', run: (target, value) => this.callOperation('hosting_php_update-version', { ...target, version: value }) },
1613
+ { setting: 'resetExtensions', run: (target) => this.callOperation('hosting_php_reset-extensions', target) },
1614
+ { setting: 'extensions', run: (target, value) => this.callOperation('hosting_php_update-extensions', { ...target, ...this.hosting_phpUpdate_extensionLists(value) }) },
1615
+ { setting: 'options', run: (target, value) => this.callOperation('hosting_php_update-options', { ...target, options: value }) },
1616
+ ];
1617
+
1618
+ // The enable and disable lists to send: only the non-empty ones.
1619
+ hosting_phpUpdate_extensionLists(extensions) {
1620
+ const lists = {};
1621
+ for (const key of ['enable', 'disable']) {
1622
+ if (Array.isArray(extensions[key]) && extensions[key].length > 0) {
1623
+ lists[key] = extensions[key];
1624
+ }
1625
+ }
1626
+ return lists;
1627
+ }
1628
+
1629
+ hosting_phpUpdate_validate(params) {
1630
+ // Models send null for fields they leave unset.
1631
+ const given = (name) => params[name] !== undefined && params[name] !== null;
1632
+
1633
+ if (given('version') && (typeof params.version !== 'string' || params.version.trim() === '')) {
1634
+ throw new Error('version must be a non-empty string when provided');
1635
+ }
1636
+ if (given('resetExtensions') && typeof params.resetExtensions !== 'boolean') {
1637
+ throw new Error('resetExtensions must be a boolean when provided');
1638
+ }
1639
+ if (given('extensions')) {
1640
+ const extensions = params.extensions;
1641
+ const list = (value) => value === undefined || value === null || (Array.isArray(value) && value.every((name) => typeof name === 'string' && name !== ''));
1642
+ const valid = typeof extensions === 'object' && !Array.isArray(extensions)
1643
+ && list(extensions.enable) && list(extensions.disable)
1644
+ && Object.keys(this.hosting_phpUpdate_extensionLists(extensions)).length > 0;
1645
+ if (!valid) {
1646
+ throw new Error('extensions must be an object with a non-empty enable or disable list of extension names');
1647
+ }
1648
+ }
1649
+ if (given('options')) {
1650
+ const options = params.options;
1651
+ const valid = typeof options === 'object' && !Array.isArray(options) && Object.keys(options).length > 0
1652
+ && Object.values(options).every(value => typeof value === 'string' || (typeof value === 'number' && Number.isInteger(value)));
1653
+ if (!valid) {
1654
+ throw new Error('options must be a non-empty object of option names to string or integer values');
1655
+ }
1656
+ }
1657
+ }
1658
+
1659
+ async handleHostingPhpUpdate(params) {
1660
+ const { domain: requestedDomain } = params;
1661
+ this.hosting_phpUpdate_validate(params);
1662
+
1663
+ // resetExtensions: false asks for nothing, like null.
1664
+ const requested = this.hosting_phpUpdate_steps.filter(step => step.setting === 'resetExtensions'
1665
+ ? params.resetExtensions === true
1666
+ : params[step.setting] !== undefined && params[step.setting] !== null);
1667
+ if (requested.length === 0) {
1668
+ throw new Error('Pass at least one of version, resetExtensions, extensions, options');
1669
+ }
1670
+ if (!requestedDomain || typeof requestedDomain !== 'string') {
1671
+ throw new Error('domain is required and must be a string');
1672
+ }
1673
+
1674
+ const { username, domain } = await this.findWebsite(requestedDomain);
1675
+ const target = { username, domain };
1676
+
1677
+ const applied = {};
1678
+ let failed = null;
1679
+ const skipped = [];
1680
+ for (const step of requested) {
1681
+ const value = params[step.setting];
1682
+ if (failed) {
1683
+ skipped.push(step.setting);
1684
+ continue;
1685
+ }
1686
+ try {
1687
+ await step.run(target, value);
1688
+ // What was sent: for extensions, only the non-empty lists.
1689
+ applied[step.setting] = step.setting === 'extensions' ? this.hosting_phpUpdate_extensionLists(value) : value;
1690
+ } catch (error) {
1691
+ failed = { setting: step.setting, error: this.formatToolError(error) };
1692
+ }
1693
+ }
1694
+
1695
+ const result = { domain, applied, failed, skipped };
1696
+ // A failed step is a tool error carrying the same JSON, so execute reports it and a
1697
+ // multi-execute batch stops at this step.
1698
+ if (failed) {
1699
+ throw new Error(JSON.stringify(result));
1700
+ }
1701
+ return result;
1702
+ }
1703
+
1608
1704
  // A 404 means the domain is not a website on the account, which fails the whole call; any other
1609
1705
  // failure only fails its own list.
1610
1706
  async hosting_websitesListDomains_section(request, pick) {
@@ -336,6 +336,8 @@ class MCPServer {
336
336
  return await this.handleHostingWebsitesUpdateSettings(params);
337
337
  case 'hosting_cache_clear':
338
338
  return await this.handleHostingCacheClear(params);
339
+ case 'hosting_php_update':
340
+ return await this.handleHostingPhpUpdate(params);
339
341
  case 'hosting_websites_list-domains':
340
342
  return await this.handleHostingWebsitesListDomains(params);
341
343
  case 'hosting_websites_add-domain':
@@ -1643,6 +1645,100 @@ class MCPServer {
1643
1645
  return { domain, cleared: scope, directory, response };
1644
1646
  }
1645
1647
 
1648
+ // Application order, and how each step maps to its operation.
1649
+ private hosting_phpUpdate_steps: Array<{ setting: string; run: (target: { username: string; domain: string }, value: any) => Promise<any> }> = [
1650
+ { setting: 'version', run: (target, value) => this.callOperation('hosting_php_update-version', { ...target, version: value }) },
1651
+ { setting: 'resetExtensions', run: (target) => this.callOperation('hosting_php_reset-extensions', target) },
1652
+ { setting: 'extensions', run: (target, value) => this.callOperation('hosting_php_update-extensions', { ...target, ...this.hosting_phpUpdate_extensionLists(value) }) },
1653
+ { setting: 'options', run: (target, value) => this.callOperation('hosting_php_update-options', { ...target, options: value }) },
1654
+ ];
1655
+
1656
+ // The enable and disable lists to send: only the non-empty ones.
1657
+ private hosting_phpUpdate_extensionLists(extensions: Record<string, any>): Record<string, string[]> {
1658
+ const lists: Record<string, string[]> = {};
1659
+ for (const key of ['enable', 'disable']) {
1660
+ if (Array.isArray(extensions[key]) && extensions[key].length > 0) {
1661
+ lists[key] = extensions[key];
1662
+ }
1663
+ }
1664
+ return lists;
1665
+ }
1666
+
1667
+ private hosting_phpUpdate_validate(params: Record<string, any>): void {
1668
+ // Models send null for fields they leave unset.
1669
+ const given = (name: string) => params[name] !== undefined && params[name] !== null;
1670
+
1671
+ if (given('version') && (typeof params.version !== 'string' || params.version.trim() === '')) {
1672
+ throw new Error('version must be a non-empty string when provided');
1673
+ }
1674
+ if (given('resetExtensions') && typeof params.resetExtensions !== 'boolean') {
1675
+ throw new Error('resetExtensions must be a boolean when provided');
1676
+ }
1677
+ if (given('extensions')) {
1678
+ const extensions = params.extensions;
1679
+ const list = (value: any) => value === undefined || value === null || (Array.isArray(value) && value.every((name: any) => typeof name === 'string' && name !== ''));
1680
+ const valid = typeof extensions === 'object' && !Array.isArray(extensions)
1681
+ && list(extensions.enable) && list(extensions.disable)
1682
+ && Object.keys(this.hosting_phpUpdate_extensionLists(extensions)).length > 0;
1683
+ if (!valid) {
1684
+ throw new Error('extensions must be an object with a non-empty enable or disable list of extension names');
1685
+ }
1686
+ }
1687
+ if (given('options')) {
1688
+ const options = params.options;
1689
+ const valid = typeof options === 'object' && !Array.isArray(options) && Object.keys(options).length > 0
1690
+ && Object.values(options).every(value => typeof value === 'string' || (typeof value === 'number' && Number.isInteger(value)));
1691
+ if (!valid) {
1692
+ throw new Error('options must be a non-empty object of option names to string or integer values');
1693
+ }
1694
+ }
1695
+ }
1696
+
1697
+ private async handleHostingPhpUpdate(params: Record<string, any>): Promise<any> {
1698
+ const { domain: requestedDomain } = params;
1699
+ this.hosting_phpUpdate_validate(params);
1700
+
1701
+ // resetExtensions: false asks for nothing, like null.
1702
+ const requested = this.hosting_phpUpdate_steps.filter(step => step.setting === 'resetExtensions'
1703
+ ? params.resetExtensions === true
1704
+ : params[step.setting] !== undefined && params[step.setting] !== null);
1705
+ if (requested.length === 0) {
1706
+ throw new Error('Pass at least one of version, resetExtensions, extensions, options');
1707
+ }
1708
+ if (!requestedDomain || typeof requestedDomain !== 'string') {
1709
+ throw new Error('domain is required and must be a string');
1710
+ }
1711
+
1712
+ const { username, domain } = await this.findWebsite(requestedDomain);
1713
+ const target = { username, domain };
1714
+
1715
+ const applied: Record<string, any> = {};
1716
+ let failed: { setting: string; error: string } | null = null;
1717
+ const skipped: string[] = [];
1718
+ for (const step of requested) {
1719
+ const value = params[step.setting];
1720
+ if (failed) {
1721
+ skipped.push(step.setting);
1722
+ continue;
1723
+ }
1724
+ try {
1725
+ await step.run(target, value);
1726
+ // What was sent: for extensions, only the non-empty lists.
1727
+ applied[step.setting] = step.setting === 'extensions' ? this.hosting_phpUpdate_extensionLists(value) : value;
1728
+ } catch (error) {
1729
+ failed = { setting: step.setting, error: this.formatToolError(error) };
1730
+ }
1731
+ }
1732
+
1733
+ const result = { domain, applied, failed, skipped };
1734
+ // A failed step is a tool error carrying the same JSON, so execute reports it and a
1735
+ // multi-execute batch stops at this step.
1736
+ if (failed) {
1737
+ throw new Error(JSON.stringify(result));
1738
+ }
1739
+ return result;
1740
+ }
1741
+
1646
1742
  // A 404 means the domain is not a website on the account, which fails the whole call; any other
1647
1743
  // failure only fails its own list.
1648
1744
  private async hosting_websitesListDomains_section(request: Promise<any>, pick: (item: any) => any): Promise<any> {