hostinger-api-mcp 3.19.0 → 3.19.2

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 CHANGED
@@ -4,6 +4,20 @@ 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.19.2 — 2026-10-28
8
+
9
+ ### Also in this release
10
+
11
+ - Bundled agent skills: `skills/headless/steps/09-done/SKILL.md`
12
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
13
+
14
+ ## v3.19.1 — 2026-10-28
15
+
16
+ ### Also in this release
17
+
18
+ - Bundled agent skills: `skills/headless/SKILL.md`, `skills/headless/connect/SKILL.md`, `skills/headless/run-log.mjs`
19
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
20
+
7
21
  ## v3.19.0 — 2026-10-28
8
22
 
9
23
  No changes to the published artifact.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "3.19.0",
3
+ "version": "3.19.2",
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.19.0",
3
+ "version": "3.19.2",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/hostinger/api-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.19.0",
9
+ "version": "3.19.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@hostinger/mcp",
14
- "version": "3.19.0",
14
+ "version": "3.19.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -170,7 +170,7 @@ took from it.
170
170
  | File (under `~/.hostinger/<domain>/`) | Written by | Contains |
171
171
  |---|---|---|
172
172
  | `state.json` | every stage | tool, next stage, Hostinger connector version, timestamps |
173
- | `connect.json` | connect | tool, account/plan readiness |
173
+ | `connect.json` | connect | tool, MCP version, sign-in method, plan; shape in `connect/SKILL.md` |
174
174
  | `brief.md` | refine | refined brief, audience, tone, pages, must-haves |
175
175
  | `site.json` | provision | chosen backend, username, install id, all URLs |
176
176
  | `content-model.md` | content model | entities, fields, pages, navigation |
@@ -87,7 +87,28 @@ Credentials live in `~/.config/hostinger-mcp/credentials.json` (macOS/Linux) or
87
87
  If the user's tool isn't listed yet, connect it the generic way (register the Hostinger MCP server
88
88
  for that tool, or drive `npx --yes --package=@hostinger/mcp@latest hostinger-api-mcp` over stdio) and continue.
89
89
 
90
- ## Phase 3 - Continue to Headless Hosting steps
90
+ ## Phase 3 - Write `connect.json`
91
+
92
+ Once the tool is connected, write `~/.hostinger/<domain>/connect.json` (create the folder if it is
93
+ missing) in exactly this shape:
94
+
95
+ ```json
96
+ { "tool": "claude-code", "mcp": "hostinger-api-mcp 3.8.0", "auth": "oauth_cached",
97
+ "order_id": null, "plan": null, "connected_at": "2026-10-28T10:46:12Z" }
98
+ ```
99
+
100
+ - `tool`: the `<tool>` from the Phase 2 route (`claude-code`, `codex`, `cursor`, `vscode`,
101
+ `antigravity`, `devin`, or a short lowercase name for an unlisted tool), the same value later
102
+ passed to `run-start --tool`.
103
+ - `mcp`: one string, `hostinger-api-mcp <version>`, with the `version` from the `mcp_ok` event.
104
+ Never an object.
105
+ - `auth`: the `method` from the `logged_in` event (`api_token`, `oauth_cached` or `oauth`).
106
+ - `order_id`, `plan`: the active hosting plan's order id and name when you looked them up, else `null`.
107
+ - `connected_at`: the current time in UTC, ISO 8601 to the second.
108
+
109
+ Extra keys are allowed, but keep these six as above: the build report reads `tool` and `mcp`.
110
+
111
+ ## Phase 4 - Continue to Headless Hosting steps
91
112
 
92
113
  After evrything is verified and you have access to the Hostinger MCP Server and the skills then you can go back to `headless/SKILL.md` and follow the steps section to start the process.
93
114
 
@@ -100,6 +100,15 @@ function readEvents(ws) {
100
100
  .map((line) => JSON.parse(line));
101
101
  }
102
102
 
103
+ // connect.json from older runs may hold mcp as an object or only mcp_version; the report needs strings.
104
+ function readConnect(ws) {
105
+ const connect = readJson(join(ws.dir, 'connect.json'), {});
106
+ const mcp = connect.mcp ?? connect.mcp_version ?? null;
107
+ let version = mcp;
108
+ if (mcp !== null && typeof mcp !== 'string') version = mcp.name || mcp.version ? [mcp.name, mcp.version].filter(Boolean).join(' ') : JSON.stringify(mcp);
109
+ return { tool: typeof connect.tool === 'string' ? connect.tool : null, mcp: version };
110
+ }
111
+
103
112
  function runStart(ws, flags) {
104
113
  const state = loadState(ws);
105
114
  // A previous run that never reached its report (closed window, crash) is closed out first, so
@@ -111,7 +120,7 @@ function runStart(ws, flags) {
111
120
  const fresh = loadState(ws);
112
121
  const previous = fresh.run ?? {};
113
122
  const startedAt = now();
114
- const connect = readJson(join(ws.dir, 'connect.json'), {});
123
+ const connect = readConnect(ws);
115
124
  // Run ids are the start time; a second run in the same second gets a suffix.
116
125
  const taken = new Set([
117
126
  ...(fresh.history ?? []).map((h) => h.run),
@@ -268,7 +277,7 @@ function buildReport(ws, { outcome, tokens } = {}) {
268
277
  const run = currentRun(ws, state);
269
278
  const site = readJson(join(ws.dir, 'site.json'), {});
270
279
  const deploy = readJson(join(ws.dir, 'deploy.json'), {});
271
- const connect = readJson(join(ws.dir, 'connect.json'), {});
280
+ const connect = readConnect(ws);
272
281
  const history = (state.history ?? []).filter((h) => h.run === run.id);
273
282
  // Lines logged before events were checked on write may not match the schema.
274
283
  const dropped = [];
@@ -291,7 +300,7 @@ function buildReport(ws, { outcome, tokens } = {}) {
291
300
  const stillOpen = resolvedOutcome !== 'abandoned' && history.some((h) => !h.ended_at);
292
301
  const endedAt = stillOpen ? now() : lastActivity;
293
302
  const stages = history.map((h) => ({ ...h, minutes: minutesBetween(h.started_at, h.ended_at ?? endedAt) }));
294
- const gaps = [...loggingGaps(stages, run, resolvedOutcome), ...dropped];
303
+ const gaps = [...loggingGaps(stages, run, resolvedOutcome, site.backend), ...dropped];
295
304
  const strip = ({ ts, run: _run, type: _type, ...rest }) => ({ ...rest, logged_at: ts });
296
305
  const liveStage = stages.find((s) => s.stage === 'deploy' && s.result?.startsWith('ok'));
297
306
  if (!OUTCOMES.includes(resolvedOutcome)) fail(`unknown outcome "${resolvedOutcome}"; one of ${OUTCOMES.join(', ')}`);
@@ -316,7 +325,7 @@ function buildReport(ws, { outcome, tokens } = {}) {
316
325
  model: run.model ?? null,
317
326
  os: process.platform,
318
327
  node: process.version,
319
- mcp: connect.mcp ?? null,
328
+ mcp: connect.mcp,
320
329
  backend: site.backend ?? null,
321
330
  frontend: frontendStack(ws.dir),
322
331
  outcome: resolvedOutcome,
@@ -400,15 +409,21 @@ function reportRejected(ws, [runId]) {
400
409
 
401
410
  // Gaps are printed as warnings by `report`, so guesses never pass as measurements.
402
411
  const EXPECTED_FOR_BUILD = ['check_state', 'refine', 'provision', 'content_model', 'design', 'frontend', 'validate', 'deploy'];
403
- function loggingGaps(stages, run, outcome) {
412
+ // Stages a backend skips on purpose: webapp has no CMS, so no content model.
413
+ const SKIPPED_BY_BACKEND = { webapp: ['content_model'] };
414
+ function loggingGaps(stages, run, outcome, backend) {
404
415
  const gaps = [];
405
416
  for (const s of stages) {
406
- if (!s.ended_at) gaps.push({ stage: s.stage, phase: s.phase, problem: 'started but never ended' });
407
- else if (s.start_estimated) gaps.push({ stage: s.stage, phase: s.phase, problem: 'start time estimated, not measured' });
408
- else if (Date.parse(s.ended_at) - Date.parse(s.started_at) < 5000) gaps.push({ stage: s.stage, phase: s.phase, problem: 'logged with no duration; probably started and ended in one go after the work' });
417
+ // A skipped stage did no work, so it has no duration to measure.
418
+ const problem = s.result === 'skipped' ? null
419
+ : !s.ended_at ? 'started but never ended'
420
+ : s.start_estimated ? 'start time estimated, not measured'
421
+ : Date.parse(s.ended_at) === Date.parse(s.started_at) ? 'logged with no duration; probably started and ended in one go after the work'
422
+ : null;
423
+ if (problem) gaps.push({ stage: s.stage, phase: s.phase, problem });
409
424
  }
410
425
  if (run.kind === 'build' && outcome === 'live') {
411
- for (const stage of EXPECTED_FOR_BUILD) {
426
+ for (const stage of EXPECTED_FOR_BUILD.filter((name) => !SKIPPED_BY_BACKEND[backend]?.includes(name))) {
412
427
  if (!stages.some((s) => s.stage === stage)) gaps.push({ stage, phase: null, problem: 'never logged, though the site went live' });
413
428
  }
414
429
  }
@@ -9,29 +9,11 @@ Verify the live site, then hand over the URLs (success page, main site, WordPres
9
9
  summary of what was built, and what the user can do next. **On a successful run, open the success page
10
10
  for the user** (see *Open the success page*).
11
11
 
12
- ## Verify the live site (brief)
13
-
14
- On a run that deployed this session, re-confirm what you're about to hand over actually works (deploy
15
- already checked this — this is a quick re-confirm, not a full re-run):
16
-
17
- - **Main site:** `GET https://<domain>/` → 200 (Next.js SSR: `x-powered-by: Next.js`; Astro SSG: static
18
- HTML with real content).
19
- - **WordPress admin:** `GET {urls.wp_admin}` → reachable (a login page, 200/302), not 404.
20
- - **Backend API:** `GET {urls.rest_api}/posts?per_page=1` → 200 (WordPress); for a **WooCommerce** store
21
- (`site.json → woocommerce.enabled`) also `GET {urls.store_api}/products?per_page=1` → 200; or the
22
- storefront API for the sales channel → 200 (Ecommerce).
23
- - **Contact form** (`site.json → contact_form.enabled`): validate already sent a test submission, so
24
- don't send another here. In the handover, tell the owner the contact page is live and enquiries email
25
- the **WordPress admin address** (via `wp_mail`) — and that for the most reliable delivery that address
26
- should be a Hostinger mailbox on the domain. If validate reported `mail_failed`, say so plainly instead
27
- of claiming the form sends.
28
-
29
- If a check fails, say so in the outcome paragraph and don't claim the site is live.
30
-
31
12
  ## Final report
32
13
 
33
14
  Every run ends with this report, whether it finishes here or stops at an earlier stage (a dummy or
34
- missing step, a failed provision, the user pausing). Build it from `state.json`, `site.json`,
15
+ missing step, a failed provision, the user pausing). On a run that deployed this session, run the
16
+ checks in *Verify the live site* below first. Build the report from `state.json`, `site.json`,
35
17
  `deploy.json` and what you actually did and checked in this run. Report only what you verified. Don't
36
18
  claim a stage passed if you didn't check it.
37
19
 
@@ -41,19 +23,23 @@ Write it in this order:
41
23
  early, name the stage it stopped at, why, and what the user will see right now (e.g. "the site
42
24
  root returns 403 until the frontend is deployed").
43
25
 
44
- 2. **Steps completed** — a table with one row per stage that ran:
26
+ 2. **Steps completed.** This table is required on every run, including early stops: never replace
27
+ it with prose. Give one row per stage that ran, using the stage numbers and names from the
28
+ orchestrator's flow table. Leave out stages that didn't run and name them in the outcome
29
+ paragraph instead. A finished WordPress blog build looks like this:
45
30
 
46
31
  | Stage | Result |
47
32
  |---|---|
48
- | connect | How the connector was reached, MCP version, how sign-in happened. |
49
- | 1 check state | New build / resumed at `<stage>` / adopted an existing site. |
50
- | 2 refine | What the brief is and which backend it chose (say if a sample brief was used). |
51
- | 3 provision | Plan and order used, how long setup took, which verification checks passed. |
52
- | 4 content model | What was created (counts of pages, posts, terms, menus, media, SEO) and how it was verified. |
53
- | … | One row for each further stage that ran (design, frontend, validate, deploy, done). |
54
-
55
- Stages that didn't run are left out of the table and named in the outcome paragraph instead.
56
- Use the stage numbers and names from the orchestrator's flow table.
33
+ | connect | Hostinger connector reached over MCP (`@hostinger/mcp` 1.9.2); signed in through the browser login. |
34
+ | 1 check state | New build for `harbourlight.blog`; no earlier workspace. |
35
+ | 2 refine | Personal travel blog about coastal towns in the UK; backend: WordPress. |
36
+ | 3 provision | Business plan, order 41875230; setup took 6 min; domain, SSL and WordPress checks passed. |
37
+ | 4 content model | 5 pages, 8 posts, 6 categories, 1 menu, 14 images (Hostinger AI), SEO titles on every page; counts re-read from the REST API. |
38
+ | 5 design | "Salt & Ink" direction: serif headings, sea-blue accents; `design.md` and logo written, logo check passed. |
39
+ | 6 frontend | Next.js SSR with home, blog, post, category and contact pages wired to the REST API; local build passed. |
40
+ | 7 validate | Instance setup completed; REST API returned the 8 pushed posts; no preview (nobody signing off). |
41
+ | 8 deploy | Source uploaded, build finished in 3 min; site root returns 200. |
42
+ | 9 done | Main site, wp-admin and REST API re-checked live: all returned 200. |
57
43
 
58
44
  3. **Links** — every URL that exists at this point. Build the hPanel URLs from `site.json`
59
45
  (`order_id`, `username`, `domain`) and the onboarding slug; take the rest from `site.json` /
@@ -92,6 +78,25 @@ Write it in this order:
92
78
  Never include passwords, JWTs or other secrets in the report. For wp-admin access, give the URL and
93
79
  offer a one-click login link (`wordpress_login_create-links`), not credentials.
94
80
 
81
+ ## Verify the live site (brief)
82
+
83
+ On a run that deployed this session, re-confirm what you're about to hand over actually works (deploy
84
+ already checked this — this is a quick re-confirm, not a full re-run):
85
+
86
+ - **Main site:** `GET https://<domain>/` → 200 (Next.js SSR: `x-powered-by: Next.js`; Astro SSG: static
87
+ HTML with real content).
88
+ - **WordPress admin:** `GET {urls.wp_admin}` → reachable (a login page, 200/302), not 404.
89
+ - **Backend API:** `GET {urls.rest_api}/posts?per_page=1` → 200 (WordPress); for a **WooCommerce** store
90
+ (`site.json → woocommerce.enabled`) also `GET {urls.store_api}/products?per_page=1` → 200; or the
91
+ storefront API for the sales channel → 200 (Ecommerce).
92
+ - **Contact form** (`site.json → contact_form.enabled`): validate already sent a test submission, so
93
+ don't send another here. In the handover, tell the owner the contact page is live and enquiries email
94
+ the **WordPress admin address** (via `wp_mail`) — and that for the most reliable delivery that address
95
+ should be a Hostinger mailbox on the domain. If validate reported `mail_failed`, say so plainly instead
96
+ of claiming the form sends.
97
+
98
+ If a check fails, say so in the outcome paragraph and don't claim the site is live.
99
+
95
100
  ## Open the success page
96
101
 
97
102
  On a **successful** run (the deploy succeeded and the live checks passed), open the success page in the