hostinger-api-mcp 2.1.0 → 2.2.0

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,15 @@ 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
+ ## v2.2.0 — 2026-09-28
8
+
9
+ ### Also in this release
10
+
11
+ - Dependencies: `package-lock.json`, `package.json`
12
+ - Request handling and authentication: `src/core/runtime.js`, `src/core/runtime.ts`
13
+ - Server entry points: `src/servers/all.js`, `src/servers/all.ts`
14
+ - Other files: `src/core/skills.js`, `src/core/skills.ts`
15
+
7
16
  ## v2.1.0 — 2026-09-28
8
17
 
9
18
  ### Operations
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
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": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
@@ -38,13 +38,14 @@
38
38
  "start:ts": "npx tsc && node dist/src/servers/all.js"
39
39
  },
40
40
  "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.10.0",
41
+ "@modelcontextprotocol/sdk": "^1.30.0",
42
42
  "axios": "^1.8.0",
43
43
  "cors": "^2.8.5",
44
44
  "dotenv": "^18.0.1",
45
45
  "express": "^5.2.1",
46
46
  "minimist": "^1.2.8",
47
- "tus-js-client": "^4.1.0"
47
+ "tus-js-client": "^4.1.0",
48
+ "zod": "^4.1.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/node": "^26.1.1",
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": "2.1.0",
9
+ "version": "2.2.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@hostinger/mcp",
14
- "version": "2.1.0",
14
+ "version": "2.2.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -10,9 +10,13 @@ import { config as dotenvConfig } from "dotenv";
10
10
  import {
11
11
  ListToolsRequestSchema,
12
12
  CallToolRequestSchema,
13
+ ListResourcesRequestSchema,
14
+ ListResourceTemplatesRequestSchema,
15
+ ReadResourceRequestSchema,
13
16
  ErrorCode,
14
17
  McpError,
15
18
  } from "@modelcontextprotocol/sdk/types.js";
19
+ import { z } from "zod";
16
20
  import { OAuthProvider, getEnvToken } from "./oauth.js";
17
21
  import * as tus from "tus-js-client";
18
22
  import fs from "fs";
@@ -43,17 +47,33 @@ const SECURITY_SCHEMES = {
43
47
  }
44
48
  };
45
49
 
50
+ // MCP Skills extension (io.modelcontextprotocol/skills). The SDK has no built-in
51
+ // support yet, so the two methods are registered as custom requests.
52
+ const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
53
+ const SKILLS_CACHE = { ttlMs: 300000, cacheScope: "public" };
54
+ const ListSkillsRequestSchema = z.object({
55
+ method: z.literal("skills/list"),
56
+ params: z.object({ cursor: z.string().optional() }).passthrough().optional(),
57
+ });
58
+ const GetSkillRequestSchema = z.object({
59
+ method: z.literal("skills/get"),
60
+ // Lenient on purpose: a missing or non-string uri must surface as InvalidParams
61
+ // from the handler, not as a zod parse failure (InternalError).
62
+ params: z.object({ uri: z.unknown().optional() }).passthrough().optional(),
63
+ });
64
+
46
65
  /**
47
66
  * MCP Server for Hostinger API
48
67
  * Generated from OpenAPI spec version 1.54.2
49
68
  */
50
69
  class MCPServer {
51
- constructor({ name, version, tools, instructions }) {
70
+ constructor({ name, version, tools, instructions, skills = [] }) {
52
71
  // Initialize class properties
53
72
  this.name = name;
54
73
  this.version = version;
55
74
  this.toolList = tools;
56
75
  this.instructions = instructions;
76
+ this.skills = skills;
57
77
  this.server = null;
58
78
  this.operations = new Map();
59
79
  this.debug = process.env.DEBUG === "true";
@@ -73,6 +93,7 @@ class MCPServer {
73
93
  {
74
94
  capabilities: {
75
95
  tools: {}, // Enable tools capability
96
+ ...(this.skills.length > 0 ? { resources: {}, extensions: { [SKILLS_EXTENSION]: {} } } : {}),
76
97
  },
77
98
  instructions: this.instructions,
78
99
  }
@@ -186,6 +207,60 @@ class MCPServer {
186
207
  return { isError: true, content: [{ type: "text", text: errorText }] };
187
208
  }
188
209
  });
210
+
211
+ if (this.skills.length > 0) {
212
+ this.setupSkillHandlers();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Serve the bundled agent skills over the MCP Skills extension: skills/list and
218
+ * skills/get return the manifest entries, and every listed file is a skill://
219
+ * resource readable with resources/read.
220
+ */
221
+ setupSkillHandlers() {
222
+ const entry = (skill) => ({
223
+ uri: skill.uri,
224
+ frontmatter: skill.frontmatter,
225
+ resources: skill.resources.map(({ uri, digest, size }) => ({ uri, digest, size })),
226
+ });
227
+ const skillsByUri = new Map(this.skills.map((skill) => [skill.uri, skill]));
228
+ const files = new Map(this.skills.flatMap((skill) => skill.resources.map((file) => [file.uri, file])));
229
+
230
+ this.server.setRequestHandler(ListSkillsRequestSchema, async () => ({
231
+ resultType: "complete",
232
+ skills: this.skills.map(entry),
233
+ ...SKILLS_CACHE,
234
+ }));
235
+
236
+ this.server.setRequestHandler(GetSkillRequestSchema, async (request) => {
237
+ const uri = request.params?.uri;
238
+ const skill = typeof uri === "string" ? skillsByUri.get(uri) : undefined;
239
+ if (!skill) {
240
+ throw new McpError(ErrorCode.InvalidParams, typeof uri === "string" ? `Unknown skill: ${uri}` : "Missing or invalid skill uri");
241
+ }
242
+ return { resultType: "complete", skill: entry(skill), ...SKILLS_CACHE };
243
+ });
244
+
245
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
246
+ resources: Array.from(files.values()).map((file) => ({
247
+ uri: file.uri,
248
+ name: file.uri.slice("skill://".length),
249
+ mimeType: file.mimeType,
250
+ size: file.size,
251
+ })),
252
+ }));
253
+
254
+ // resources is advertised, so answer templates/list too (skills have none).
255
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
256
+
257
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
258
+ const file = files.get(request.params.uri);
259
+ if (!file) {
260
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
261
+ }
262
+ return { contents: [{ uri: file.uri, mimeType: file.mimeType, text: file.text }] };
263
+ });
189
264
  }
190
265
 
191
266
  /**
@@ -2636,7 +2711,7 @@ class MCPServer {
2636
2711
  }
2637
2712
  }
2638
2713
 
2639
- export async function startServer({ name, version, tools, instructions }) {
2714
+ export async function startServer({ name, version, tools, instructions, skills }) {
2640
2715
  const argv = minimist(process.argv.slice(2), {
2641
2716
  string: ['host'],
2642
2717
  boolean: ['stdio', 'http', 'help', 'version', 'login', 'logout'],
@@ -2694,7 +2769,7 @@ export async function startServer({ name, version, tools, instructions }) {
2694
2769
  }
2695
2770
  }
2696
2771
 
2697
- const server = new MCPServer({ name, version, tools, instructions });
2772
+ const server = new MCPServer({ name, version, tools, instructions, skills });
2698
2773
  if (argv.http) {
2699
2774
  await server.startHttp(argv.host, argv.port);
2700
2775
  } else {
@@ -10,10 +10,14 @@ import { config as dotenvConfig } from "dotenv";
10
10
  import {
11
11
  ListToolsRequestSchema,
12
12
  CallToolRequestSchema,
13
+ ListResourcesRequestSchema,
14
+ ListResourceTemplatesRequestSchema,
15
+ ReadResourceRequestSchema,
13
16
  ErrorCode,
14
17
  McpError,
15
18
  Tool,
16
19
  } from "@modelcontextprotocol/sdk/types.js";
20
+ import { z } from "zod";
17
21
  import { OAuthProvider, getEnvToken } from "./oauth.js";
18
22
  import axios,{ AxiosRequestConfig, AxiosError, AxiosResponse } from "axios";
19
23
  import * as tus from "tus-js-client";
@@ -70,6 +74,24 @@ const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
70
74
  }
71
75
  };
72
76
 
77
+ // MCP Skills extension (io.modelcontextprotocol/skills). The SDK has no built-in
78
+ // support yet, so the two methods are registered as custom requests.
79
+ const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
80
+ const SKILLS_CACHE = { ttlMs: 300000, cacheScope: "public" };
81
+ const ListSkillsRequestSchema = z.object({
82
+ method: z.literal("skills/list"),
83
+ params: z.object({ cursor: z.string().optional() }).passthrough().optional(),
84
+ });
85
+ const GetSkillRequestSchema = z.object({
86
+ method: z.literal("skills/get"),
87
+ // Lenient on purpose: a missing or non-string uri must surface as InvalidParams
88
+ // from the handler, not as a zod parse failure (InternalError).
89
+ params: z.object({ uri: z.unknown().optional() }).passthrough().optional(),
90
+ });
91
+
92
+ interface SkillFile { uri: string; digest: string; size: number; mimeType: string; text: string }
93
+ interface Skill { uri: string; frontmatter: Record<string, unknown>; resources: SkillFile[] }
94
+
73
95
  /**
74
96
  * MCP Server for Hostinger API
75
97
  * Generated from OpenAPI spec version 1.54.2
@@ -81,17 +103,19 @@ class MCPServer {
81
103
  private server: Server;
82
104
  private operations: Map<string, OpenApiTool> = new Map();
83
105
  private instructions?: string;
106
+ private skills: Skill[];
84
107
  private debug: boolean;
85
108
  private baseUrl: string;
86
109
  private headers: Record<string, string>;
87
110
  private oauth: OAuthProvider;
88
111
 
89
- constructor({ name, version, tools, instructions }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string }) {
112
+ constructor({ name, version, tools, instructions, skills = [] }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string; skills?: Skill[] }) {
90
113
  // Initialize class properties
91
114
  this.name = name;
92
115
  this.version = version;
93
116
  this.toolList = tools;
94
117
  this.instructions = instructions;
118
+ this.skills = skills;
95
119
  this.debug = process.env.DEBUG === "true";
96
120
  this.baseUrl = process.env.API_BASE_URL || "https://developers.hostinger.com";
97
121
  this.headers = this.parseHeaders(process.env.API_HEADERS || "");
@@ -109,6 +133,7 @@ class MCPServer {
109
133
  {
110
134
  capabilities: {
111
135
  tools: {}, // Enable tools capability
136
+ ...(this.skills.length > 0 ? { resources: {}, extensions: { [SKILLS_EXTENSION]: {} } } : {}),
112
137
  },
113
138
  instructions: this.instructions,
114
139
  }
@@ -217,6 +242,62 @@ class MCPServer {
217
242
  return { isError: true, content: [{ type: "text", text: errorText }] };
218
243
  }
219
244
  });
245
+
246
+ if (this.skills.length > 0) {
247
+ this.setupSkillHandlers();
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Serve the bundled agent skills over the MCP Skills extension: skills/list and
253
+ * skills/get return the manifest entries, and every listed file is a skill://
254
+ * resource readable with resources/read.
255
+ */
256
+ private setupSkillHandlers(): void {
257
+ const entry = (skill: Skill) => ({
258
+ uri: skill.uri,
259
+ frontmatter: skill.frontmatter,
260
+ resources: skill.resources.map(({ uri, digest, size }) => ({ uri, digest, size })),
261
+ });
262
+ const skillsByUri = new Map<string, Skill>(this.skills.map((skill) => [skill.uri, skill]));
263
+ const files = new Map<string, SkillFile>(this.skills.flatMap((skill) => skill.resources.map((file) => [file.uri, file] as [string, SkillFile])));
264
+
265
+ // Widened return types below satisfy setRequestHandler's inferred result type
266
+ // for these two custom (non-SDK) schemas.
267
+ this.server.setRequestHandler(ListSkillsRequestSchema, async (): Promise<Record<string, unknown>> => ({
268
+ resultType: "complete",
269
+ skills: this.skills.map(entry),
270
+ ...SKILLS_CACHE,
271
+ }));
272
+
273
+ this.server.setRequestHandler(GetSkillRequestSchema, async (request): Promise<Record<string, unknown>> => {
274
+ const uri = request.params?.uri;
275
+ const skill = typeof uri === "string" ? skillsByUri.get(uri) : undefined;
276
+ if (!skill) {
277
+ throw new McpError(ErrorCode.InvalidParams, typeof uri === "string" ? `Unknown skill: ${uri}` : "Missing or invalid skill uri");
278
+ }
279
+ return { resultType: "complete", skill: entry(skill), ...SKILLS_CACHE };
280
+ });
281
+
282
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
283
+ resources: Array.from(files.values()).map((file) => ({
284
+ uri: file.uri,
285
+ name: file.uri.slice("skill://".length),
286
+ mimeType: file.mimeType,
287
+ size: file.size,
288
+ })),
289
+ }));
290
+
291
+ // resources is advertised, so answer templates/list too (skills have none).
292
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
293
+
294
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
295
+ const file = files.get(request.params.uri);
296
+ if (!file) {
297
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
298
+ }
299
+ return { contents: [{ uri: file.uri, mimeType: file.mimeType, text: file.text }] };
300
+ });
220
301
  }
221
302
 
222
303
  private async executeOperation(operation: unknown, params: unknown): Promise<any> {
@@ -2681,7 +2762,7 @@ class MCPServer {
2681
2762
  }
2682
2763
  }
2683
2764
 
2684
- export async function startServer({ name, version, tools, instructions }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string }): Promise<void> {
2765
+ export async function startServer({ name, version, tools, instructions, skills }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string; skills?: Skill[] }): Promise<void> {
2685
2766
  const argv = minimist(process.argv.slice(2), {
2686
2767
  string: ['host'],
2687
2768
  boolean: ['stdio', 'http', 'help', 'version', 'login', 'logout'],
@@ -2739,7 +2820,7 @@ export async function startServer({ name, version, tools, instructions }: { name
2739
2820
  }
2740
2821
  }
2741
2822
 
2742
- const server = new MCPServer({ name, version, tools, instructions });
2823
+ const server = new MCPServer({ name, version, tools, instructions, skills });
2743
2824
  if (argv.http) {
2744
2825
  await server.startHttp(argv.host, argv.port);
2745
2826
  } else {
@@ -0,0 +1,54 @@
1
+ // Auto-generated agent skills served over the MCP Skills extension
2
+ export default [
3
+ {
4
+ "uri": "skill://hostinger-headless/SKILL.md",
5
+ "frontmatter": {
6
+ "name": "hostinger-headless",
7
+ "description": "Build, connect, or iterate on a website hosted on Hostinger — provision hosting and a domain, optionally seed an ecommerce store with a real hosted checkout or a WordPress content backend (headless CMS/blog), build the frontend, deploy, and verify. Requires an authenticated Hostinger MCP session (see entry/skill.md). Triggers: build me a site on Hostinger, deploy this to Hostinger, connect this project to Hostinger, add a store to my Hostinger site, add a blog to my Hostinger site, update my Hostinger site."
8
+ },
9
+ "resources": [
10
+ {
11
+ "uri": "skill://hostinger-headless/SKILL.md",
12
+ "digest": "sha256:7ed8a33109f571b56646627d21ba58cf7cbf8c2818c9bfacf645a341a8847b1f",
13
+ "size": 7200,
14
+ "mimeType": "text/markdown",
15
+ "text": "---\nname: hostinger-headless\ndescription: \"Build, connect, or iterate on a website hosted on Hostinger — provision hosting and a domain, optionally seed an ecommerce store with a real hosted checkout or a WordPress content backend (headless CMS/blog), build the frontend, deploy, and verify. Requires an authenticated Hostinger MCP session (see entry/skill.md). Triggers: build me a site on Hostinger, deploy this to Hostinger, connect this project to Hostinger, add a store to my Hostinger site, add a blog to my Hostinger site, update my Hostinger site.\"\n---\n\n# Hostinger Headless\n\nThis skill turns a prompt into a live website on Hostinger. Its job is to own the full run: check the account, provision hosting and a domain, seed the backend (an ecommerce store when the intent calls for selling), build the frontend, deploy it, and verify the result.\n\nThe frontend is built ad-hoc to the user's intent — there is no template library. The backend pieces are real Hostinger products: web hosting (static or Node.js), domains and DNS, Hostinger Ecommerce with its public Storefront API and hosted checkout, and WordPress as a headless CMS/blog backend read through the public WP REST API.\n\n## Preconditions\n\n1. An authenticated Hostinger MCP session — the entry skill's bootstrap handled this. If MCP tools fail with auth errors, send the user back through `entry/skill.md`.\n2. The Hostinger MCP operations for **hosting** (plus **ecommerce** when the run involves a store, and **wordpress** when it involves a content backend). Operation availability varies per user — product groups can be toggled in the Hostinger Connector. When search cannot find a needed operation, ask the user to enable that product group (or configure the scoped binary, e.g. `hostinger-hosting-mcp`) rather than improvising around it.\n3. An active hosting plan (checked in Setup §1). Hostinger hosting is a paid product: if the account has no usable plan, inform the user plainly that a subscription is required, link them to https://www.hostinger.com/web-hosting, and pause until they confirm the purchase. Do not treat this as an error — it is a normal step for new accounts. Everything that doesn't need hosting (planning, building the frontend locally) can proceed while they decide.\n\n## Resolving the operation\n\nResolve by intent first, disk second — never let an empty directory override what the user is asking for. Check `iterate` first (it's decided by an unambiguous on-disk signal):\n\n- `iterate` — a `.hostinger/site.json` is present in the project: the site is already deployed through this skill and the user wants changes. Reuse the recorded domain/site details; apply only the delta the new intent needs (edit frontend → redeploy; add a store → run `references/STORE.md` then redeploy). Never re-provision an existing site.\n- `connect` — a frontend project already on disk (or brought in as a zip/export/URL) that is not yet on Hostinger, with language like \"deploy this / host this on Hostinger / connect this project\". Emptiness of the CWD at trigger time is not a create signal when a design is brought in from elsewhere.\n- `create` — a new site from a prompt with nothing brought in: \"build me a store / portfolio / site…\".\n\nIf signals conflict, ask the user — don't guess.\n\n## The run\n\n1. **Discovery** — from the prompt (and the project on disk for connect/iterate), infer: does this run need a store (any buy/sell/product intent → yes)? Does it need owner-managed content — a blog, news, or anything the owner edits without a redeploy (→ WordPress backend; static copy that rarely changes does not qualify)? What brand, copy, and structure does the site need? What stack fits — plain static HTML/CSS/JS for simple sites (default), a static-output framework build, or a Node.js app when SSR or server code is genuinely required? Prefer static: it deploys fastest and has no build-failure surface. Ask the user only what you cannot infer.\n2. **Setup** (`references/SETUP.md`) — plan check, then provision the website on a free subdomain (or the user's own domain) and wait until it's ready.\n3. **Store** (`references/STORE.md`, only when commerce is needed) — resolve or create the store and its `custom` sales channel, seed products/shipping/payment, and note the `sales_channel_id` for the frontend.\n4. **Content backend** (`references/WORDPRESS.md`, only when owner-managed content is needed) — install WordPress on a dedicated subdomain, wait until it's ready, and hand the owner a wp-admin login link. The frontend reads it through the public WP REST API.\n5. **Build** — create or wire the frontend. For store runs, follow the frontend contract in `references/STORE.md` (catalog fetched at runtime from the public Storefront API, cart in `localStorage`, client-side checkout — never embed an API token in the site). For content runs, follow the frontend contract in `references/WORDPRESS.md` (posts fetched at runtime from the WP REST API, rendered HTML bodies, graceful empty states). When the app needs MySQL, follow `references/DATABASE.md` (which host each runtime connects to, credentials via env vars or a server-side config file, never in the frontend).\n6. **Deploy** (`references/DEPLOYMENT.md`) — archive and deploy via the matching hosting operation, polling build logs for Node.js runs.\n7. **Verify** — curl the live URL for a 200 and a piece of real page copy; for store runs, confirm a checkout POST returns a redirect URL; for content runs, confirm the WP REST API returns 200 and the frontend renders posts. Show the user the live URL and where to manage things (hPanel: https://hpanel.hostinger.com, the store dashboard for commerce, wp-admin for content).\n8. **Record** — write `.hostinger/site.json` into the project: `{ \"domain\", \"username\", \"type\": \"static\"|\"nodejs\", \"sales_channel_id\"?, \"store_id\"?, \"cms_domain\"? }`. This is what makes future runs resolve as `iterate`.\n\nRun non-interactively wherever possible. The exceptions that must involve the user: the paid-plan confirmation, anything that costs money (a domain purchase, a new subscription), and picking between genuinely equal options the prompt doesn't decide.\n\n## Paths\n\n| What | Path |\n| --- | --- |\n| Plan check + website/domain provisioning | `references/SETUP.md` |\n| Store: seed the backend + the frontend API contract | `references/STORE.md` |\n| WordPress: headless CMS/blog backend + the frontend read contract | `references/WORDPRESS.md` |\n| Deploy: static vs Node.js, archive rules, logs, verify | `references/DEPLOYMENT.md` |\n| Database + runtime: MySQL host per runtime, env vars, what to poll, runtime logs | `references/DATABASE.md` |\n\n## Where the how comes from\n\nThe inputSchema returned by search is authoritative for request shapes — read it before calling. Two live sources supersede anything written here when they disagree:\n\n- The ecommerce operation `ecommerce_miscellaneous_custom-storefront-setup-instructions` returns the current storefront integration guide from the server — execute it at the start of any store run.\n- The Hostinger API reference at https://developers.hostinger.com describes every endpoint behind the operations.\n"
16
+ },
17
+ {
18
+ "uri": "skill://hostinger-headless/references/DATABASE.md",
19
+ "digest": "sha256:e0800ac848244ead5b9a1bfb0a39d223b336c2a179ac13ece0089d135c418d76",
20
+ "size": 4299,
21
+ "mimeType": "text/markdown",
22
+ "text": "# Database and runtime — MySQL hosts, env vars, and what to poll\n\nUse this when the app needs MySQL, or when a deploy \"succeeded\" but the app cannot reach its database. Everything here uses the **hosting** MCP operations; Agency Plan differences are at the end.\n\n## 1. Create the database\n\n1. `hosting_databases_create` on the site's `username` with a database name, a user and a password you generate — name and user are prefixed with the account username automatically.\n2. Read the full, prefixed `name` and `user` back from `hosting_databases_list`; every other database operation wants the full name.\n3. Do not print the password to the user once it is stored, and never commit it.\n\n## 2. Which host the app connects to\n\n| Where the code runs | Host | Port | Why |\n| --- | --- | --- | --- |\n| PHP on the website | `localhost` or `127.0.0.1` | 3306 | `localhost` uses the MySQL socket; both are granted |\n| Node.js on the website | `127.0.0.1` | 3306 | `localhost` may resolve to IPv6 `::1`, and not every database user has that grant |\n| Outside Hostinger (a laptop, another server) | the `host` from `hosting_databases_list` (`srvNNNN.hstgr.io`) | 3306 | needs `hosting_databases_create-remote-connection` for that client's IP first — never `%`, it opens the database to every address on the internet |\n\nDo not put the remote `host` into the deployed app — it has no grant for connections that come from the server itself.\n\n## 3. Hand credentials to the app\n\n- **Node.js:** environment variables via `hosting_nodejs_replace-environment-variables`. Conventional keys: `DB_HOST=127.0.0.1`, `DB_PORT=3306`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, plus `DATABASE_URL=mysql://USER:PASSWORD@127.0.0.1:3306/NAME` for ORMs that want one string — URL-encode `USER` and `PASSWORD` there (`encodeURIComponent`), or a password containing `@`, `:`, `/` or `#` breaks the URL. The call is a **full replace**: list the current keys with `hosting_nodejs_list-environment-variables` (values come back masked as `********` — never copy those), then send the complete set with real values. Saving restarts the process; frameworks that bake variables into the build (Next.js, `NEXT_PUBLIC_*`) need a new `hosting_nodejs_start-build` afterwards.\n- **PHP:** a config file on the server that the app reads (its own `config.php`, WordPress's `wp-config.php`). Ship it with the deploy, keep it out of git.\n- **Never** in client-side JavaScript, in a committed `.env`, or in chat replies.\n\n## 4. What is asynchronous, and what to poll\n\n| You called | Poll this until | Typical wait |\n| --- | --- | --- |\n| `hosting_websites_create` | `hosting_websites_list` lists the domain | up to a few minutes |\n| `hosting_deploy-js-application` | `hosting_list-js-deployments` shows the build finished | minutes |\n| `hosting_nodejs_start-build` | `hosting_nodejs_build` state is `completed` or `failed` | minutes |\n| `wordpress_installations_install`, plugin/theme/core jobs | `wordpress_installations_list` lists the install | 1–2 minutes |\n| `hosting_databases_repair`, `hosting_websites_delete` | nothing to poll — runs in the background | minutes |\n| `agency-hosting_website-setups_create` | `agency-hosting_website-setups_status` with `setup_uuid` is `completed` | minutes |\n\nPoll with backoff (a few seconds, then tens of seconds). A queued response is not a failure, and re-sending the write does not speed it up.\n\n## 5. When the app is up but broken\n\n- Build failed: `hosting_nodejs_build-logs` for the raw log, `hosting_nodejs_analyse-failed-build` for a diagnosis.\n- Build passed, app errors: `hosting_nodejs_runtime-logs` with a `period` such as `1h`; entries before `last_deployed_at` belong to the previous deploy.\n- `Access denied for user 'x'@'::1'`: the app used `localhost` from Node.js — set `DB_HOST` to `127.0.0.1`.\n- PHP limits (memory, upload size): `hosting_php_get`, then `hosting_php_update-options`.\n\n## Agency Plan websites (`agency-hosting_*`)\n\n`agency-hosting_databases_create-website` creates the database and its single user; the user is granted on `localhost`, so the PHP app connects to `localhost`. Deploys are synchronous there — `agency-hosting_deploy-php-application` returns when the site is live. Other jobs (SSL, backups) show up in `agency-hosting_websites_list-processes`.\n"
23
+ },
24
+ {
25
+ "uri": "skill://hostinger-headless/references/DEPLOYMENT.md",
26
+ "digest": "sha256:1369632f23a6934ca47ca942cba20882681bc679fe833757ce30099c51257ea2",
27
+ "size": 2170,
28
+ "mimeType": "text/markdown",
29
+ "text": "# Deployment — getting the build live\n\nMatch the deploy method to what the project actually is — this is the single most common failure point.\n\n## Static site → `hosting_deploy-static-website`\n\nFor pre-built files only: plain HTML/CSS/JS, or the **build output** of a framework (run the build locally first).\n\n- The archive (zip/tar) must have `index.html` at its **root** — not nested inside a folder.\n- Name it `name_YYYYMMDD_HHMMSS.zip`; pass `removeArchive: true` to clean up after upload.\n- Deployment is effectively immediate — verify right after.\n\n## Node.js app → `hosting_deploy-js-application`\n\nFor anything that needs a server or a server-side build (Express, Next.js, NestJS, SSR frameworks).\n\n- Archive the **source**, not the output: exclude `node_modules/`, `dist/`, `.next/`, `build/`, and everything matched by `.gitignore`. The install and build run on Hostinger.\n- Hard cap: **50 MB** archive.\n- The upload starts a build. Track it with `hosting_list-js-deployments`; on failure pull `hosting_show-js-deployment-logs`, fix, and redeploy. Poll with backoff — builds take minutes, not seconds.\n\n## WordPress → `hosting_import-wordpress-website` / plugin & theme operations\n\nOnly when the user explicitly wants a WordPress *site* migrated or themed. Site imports take an archive plus a `.sql` dump and can run for several minutes. (WordPress as a headless content backend behind an agent-built frontend is a different flow — that's `WORDPRESS.md`, installed fresh via the API, not imported here.)\n\n## Verify (every deploy)\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://YOURDOMAIN/\ncurl -s https://YOURDOMAIN/ | grep -o \"SOME_REAL_HEADLINE_TEXT\"\n```\n\nReplace the grep target with copy you actually rendered — a 200 alone can be a placeholder page. A freshly created site may serve a default page for a short while; if content doesn't match, clear the cache (`hosting_cache_clear-website`) and retry before assuming the deploy failed. For store runs, also run the checkout verification in `STORE.md`.\n\nWhen the site is live, report the URL and remind the user the site is managed from hPanel (https://hpanel.hostinger.com).\n"
30
+ },
31
+ {
32
+ "uri": "skill://hostinger-headless/references/SETUP.md",
33
+ "digest": "sha256:52cc927dd67c4f4b6686dff55e1fd36b61350a1a41a3e5ced364a769dcd6f6fd",
34
+ "size": 2860,
35
+ "mimeType": "text/markdown",
36
+ "text": "# Setup — plan check and website provisioning\n\nEverything here uses the **hosting** MCP operations. If they're missing, ask the user to enable the Websites product group in the Hostinger Connector (or use the `hostinger-hosting-mcp` scoped binary).\n\n## 1. Plan check (gate — run this first)\n\nA website can only be created on an active hosting plan.\n\n1. `hosting_websites_list` — if it returns websites, the account has a working plan; note any existing `order_id` and `username` for step 2.\n2. Otherwise `hosting_orders_list` — look for an order in a usable state. A fresh order that has never hosted a website still works; note its `order_id`.\n3. If there is no usable order: **stop and tell the user** that an active Hostinger hosting plan is required to deploy, link https://www.hostinger.com/web-hosting, and offer to continue building the site locally in the meantime. When they confirm the purchase, re-run this check.\n\nNever purchase a plan, domain, or any paid item without the user explicitly approving that specific purchase.\n\n## 2. Choose the domain\n\n- **Default: free subdomain.** `hosting_domains_generate-free-subdomain` returns a `*.hostingersite.com` domain. No verification needed. Tell the user a custom domain can be connected later.\n- **User-owned domain:** `hosting_domains_verify-ownership` first. If not accessible, relay the TXT record it returns, remind them propagation can take ~10 minutes, and re-verify before continuing.\n- **New domain purchase:** only on explicit request — check availability with `domains_availability_check`, state the price, and get an explicit yes before `domains_portfolio_purchase` (needs the domains product group).\n\n## 3. Create the website and wait for it\n\nGenerating a subdomain does **not** create a website — deploying straight to it fails with `No website found for domain`. The working sequence:\n\n1. `hosting_websites_create { domain, order_id }` — `datacenter_code` is required only for the first website on a brand-new plan (pick the first entry from `hosting_datacenters_list`).\n2. **Poll** `hosting_websites_list` filtered by the domain until the site appears. Creation takes up to a few minutes — poll with backoff, don't fail fast.\n3. Note the site's `username` — deployment and database operations are keyed on it.\n\nIf the domain already has a website (an `iterate` run, or the user pointed at an existing site), skip creation entirely.\n\n## 4. Optional extras (only when the run needs them)\n\n- **Database:** when the app needs MySQL, follow `DATABASE.md` — creating it, which host each runtime connects to (Node.js must use `127.0.0.1`), and handing the credentials to the app without hard-coding them.\n- **DNS records:** the DNS operations (`dns_records_update` etc.) for custom-domain records; take a snapshot (`dns_snapshots_list` context) before destructive changes.\n"
37
+ },
38
+ {
39
+ "uri": "skill://hostinger-headless/references/STORE.md",
40
+ "digest": "sha256:ca5bf13b3968b49ae53717c0e52e16d301774b2ea166a687e246bd6089f155e2",
41
+ "size": 5470,
42
+ "mimeType": "text/markdown",
43
+ "text": "# Store — Hostinger Ecommerce with a custom storefront\n\n**Primary source:** execute `ecommerce_miscellaneous_custom-storefront-setup-instructions` at the start of every store run — it returns the current, server-maintained integration guide, and it supersedes this file wherever they disagree. This file carries the essentials so the run can be planned before that call, and a fallback when the ecommerce operations aren't enabled.\n\n## Mental model\n\nTwo API surfaces — don't mix them:\n\n| Surface | Base URL | Auth | Use for |\n| --- | --- | --- | --- |\n| Storefront Core V2 | `https://api-ecommerce.hostinger.com/v2` | none (public, open CORS) | read products/variants, checkout — called from the browser |\n| Management (MCP tools) | Hostinger API | authenticated | create stores/products/shipping/payments |\n\n- A **store** (`store_*`) has **sales channels** (`scha_*`). The Storefront API is keyed on the **`sales_channel_id`** of a `custom`-type channel — not the store id. Managed channels (e.g. `quick-link`) cannot be used here, and only `custom` channels are API-creatable.\n- **Prices are integers in minor units** (`2900` = €29.00 → divide by `10^decimal_digits`) and live on **variants**, not products.\n- **Security boundary:** storefront reads and checkout are public and run client-side — **never embed a Hostinger API token in the storefront**. Only the management/hosting side (MCP tools) is authenticated.\n\n## Backend setup (management tools)\n\n1. Resolve the `custom` sales channel: `ecommerce_stores_list` → `ecommerce_sales-channels_list`. If a `custom` channel already exists, confirm with the user that it's the one to use; otherwise add one to the existing store with `ecommerce_sales-channels_create`, or create a store with `ecommerce_stores_create` (with a `custom` sales channel) when no suitable store exists. Set the channel `url` to the storefront's public domain at creation when it's already known.\n2. For checkout to work the store needs all three: ≥ 1 product, ≥ 1 payment method, ≥ 1 shipping zone. Payment, shipping, and currency are store-level — check `ecommerce_stores_metadata` (`has_payment_methods` and `has_shipping` must be `true`; the same response carries `default_currency` with `code`, `decimal_digits`, `template` — read it here rather than guessing). Products are **per sales channel** — verify by listing products for the resolved channel on the Storefront API and confirming the list is non-empty. Seed what's missing with `ecommerce_products_create-physical` / `ecommerce_products_create-digital`, `ecommerce_payments_enable-manual-method`, and `ecommerce_shipping_set-store` (price `0` = free shipping) — confirming with the user before writing to a store that already has real data.\n3. After deploy, point the channel at the live site: `ecommerce_sales-channels_update { store_id, sales_channel_id, url }`.\n\nIf the ecommerce operations aren't available, ask the user to enable the Ecommerce product group in the Hostinger Connector — or to provide the `sales_channel_id` directly, after which the public endpoints suffice for the frontend work.\n\n## Frontend contract\n\nThe OpenAPI schema for the Storefront API is at `https://api-ecommerce.hostinger.com/v2/docs.json` — fetch it for exact endpoint shapes. Non-obvious rules the schema won't spell out:\n\n- **Fetch the catalog at runtime, not build time** — client-side fetch on page load (CORS is open). Baking products into a static build makes the site stale on the first catalog edit. (SSR / Node.js hosting may fetch it server-side per request.)\n- Each variant has a **`prices[]` array** (one entry per currency) — read `prices[0].amount` / `prices[0].sale_amount` (minor units) and `prices[0].currency` (which carries `decimal_digits` and the `template` whose `$1` placeholder you replace when formatting). There is **no** top-level `amount` on a variant.\n- Product detail resolves by **product id** only; a slug returns 404 (map slug→id from the list endpoint). `limit` max is 100 on list endpoints (else 400).\n- Filter variants with `product_ids[]=...`; the unfiltered variant list is eventually-consistent right after catalog edits.\n- Cart lives in the site (e.g. `localStorage`): persist only `variant_id` + `quantity`, resolve display data from the live catalog — never a saved copy — then send everything in one checkout call:\n\n```\nPOST /channels/{sales_channel_id}/checkout\n{ \"items\":[{ \"variant_id\":\"variant_01...\", \"quantity\":1 }],\n \"success_url\":\"https://YOURDOMAIN/checkout/success/\",\n \"cancel_url\":\"https://YOURDOMAIN/checkout/cancel/\",\n \"locale\":\"en\" }\n```\n\n`success_url`/`cancel_url` are required — build them from `window.location.origin` and make sure both pages exist. The response is `{ url, cart_token }`; redirect the browser to `url` (Hostinger's hosted checkout).\n\nA multi-step cart alternative exists (`POST /channels/{sales_channel_id}/carts`, `PATCH /carts/{cart_id}`, `POST /carts/{cart_id}/line-items`, `PUT /carts/{cart_id}/shipping-method`, `POST /carts/{cart_id}/complete`) — the single checkout call suffices for a simple buy flow.\n\n## Verify\n\n- A `POST` to the checkout endpoint returns a `url` — test it directly with curl before calling the run done.\n- The `success_url`/`cancel_url` pages return 200 (`curl -s -o /dev/null -w \"%{http_code}\" https://YOURDOMAIN/checkout/success/`).\n- Open the live site, add to cart, and confirm the redirect to `https://checkout.hostinger.com`.\n"
44
+ },
45
+ {
46
+ "uri": "skill://hostinger-headless/references/WORDPRESS.md",
47
+ "digest": "sha256:d9e80f800fbf4c53a95972762f9fe177430860715d849ea520ae5d47e0ef8f04",
48
+ "size": 4569,
49
+ "mimeType": "text/markdown",
50
+ "text": "# WordPress — headless CMS and blog backend\n\nUse WordPress when the site has **owner-managed content**: a blog, news, articles, or any content the owner must edit without a developer or a redeploy. The frontend stays agent-built and is deployed like any other run; WordPress runs separately as the content backend, and the owner writes in wp-admin.\n\n**When NOT to use this:** static copy that rarely changes (an about page, a services list, a brochure site) — bake that into the frontend. Installing WordPress for content the owner will never edit adds a moving part for nothing. Any buy/sell intent is `STORE.md`, not this file.\n\nEverything in Setup uses the **hosting** and **wordpress** MCP operation groups; ask the user to enable them in the Hostinger Connector if operations are missing.\n\n## Mental model\n\nTwo surfaces, like the store recipe:\n\n| Surface | Base URL | Auth | Use for |\n| --- | --- | --- | --- |\n| WP REST API | `https://CMS_DOMAIN/wp-json/wp/v2` | none for published content (open CORS) | frontend reads: posts, pages, media, categories |\n| Management (MCP tools + wp-admin) | Hostinger API / wp-admin | authenticated | install WP, plugins, cache; the owner writes content |\n\n- Published content is publicly readable and the REST API allows cross-origin browser reads — a static frontend on another domain fetches it directly at runtime.\n- Drafts, previews, and **writes need authentication** (WP application passwords). Never put credentials of any kind in the frontend; a public site only needs the anonymous read path.\n\n## Setup (management tools)\n\n1. **Check for an existing installation first**: `wordpress_installations_list` filtered by the site's username/domain. Reuse a valid install when the user agrees — never overwrite one silently.\n2. **Choose where WordPress lives.** Default: a dedicated subdomain of the site, e.g. `cms.<domain>` — create it with `hosting_domains_create-website-subdomain` so the main domain stays free for the frontend. A separate free-subdomain website (per `SETUP.md`) also works when the plan allows another website.\n3. **Install**: `wordpress_installations_install` on that domain. The call only queues the job — **poll** `wordpress_installations_list` until the installation appears (typically 1–2 minutes). Don't proceed on the queued response alone.\n4. **Hand the owner their editor**: mint a one-click wp-admin link with `wordpress_login_create-links` and show it to the user. Content authoring happens in wp-admin — the skill does not seed posts (there is no anonymous write path, by design). The fresh install ships with a sample post, which is enough to build and verify the frontend against.\n5. **Caching** (recommended before finishing): enable the object cache with `wordpress_object-cache_toggle-memcached`; after config changes, purge with `wordpress_litespeed-cache_purge-lite-speed`.\n\n## Frontend contract\n\nAll paths relative to `https://CMS_DOMAIN/wp-json/wp/v2`. Non-obvious rules:\n\n- **Fetch at runtime, not build time** — content changes whenever the owner publishes; baking it into a static build defeats the purpose. Client-side fetch on page load is fine (CORS is open).\n- List posts with `/posts?per_page=10&_embed` — `_embed` inlines featured images (`_embedded['wp:featuredmedia'][0].source_url`), authors, and terms; without it you get IDs that need extra requests.\n- Post bodies are **rendered HTML**: use `title.rendered` / `content.rendered` / `excerpt.rendered` and render them as HTML — do not treat them as plain text and do not try to restyle their inner markup beyond CSS.\n- Single post by slug: `/posts?slug=my-post` — returns an **array** (take the first element); there is no direct slug path.\n- Pagination comes from the `X-WP-Total` / `X-WP-TotalPages` response headers, not the body.\n- Only **published** content is returned anonymously. An empty list is a normal state — the frontend must render it gracefully, not error.\n- SEO is the frontend's job: page titles, meta tags, and the sitemap come from the frontend build, not from WordPress plugins.\n\n## Verify\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" \"https://CMS_DOMAIN/wp-json/wp/v2/posts?per_page=1\"\n```\n\nExpect 200 with a JSON array. Then confirm the deployed frontend renders the sample post (or a clean empty state), and show the user two links: the live site and the wp-admin login for writing content.\n\n## Record\n\nAdd `\"cms_domain\"` to `.hostinger/site.json` (see `SKILL.md` §Record) so iterate runs know a WordPress backend exists and reuse it instead of installing again.\n"
51
+ }
52
+ ]
53
+ }
54
+ ];
@@ -0,0 +1,69 @@
1
+ // Auto-generated agent skills served over the MCP Skills extension
2
+ export interface SkillFile {
3
+ uri: string;
4
+ digest: string;
5
+ size: number;
6
+ mimeType: string;
7
+ text: string;
8
+ }
9
+
10
+ export interface Skill {
11
+ uri: string;
12
+ frontmatter: Record<string, unknown>;
13
+ resources: SkillFile[];
14
+ }
15
+
16
+ const skills: Skill[] = [
17
+ {
18
+ "uri": "skill://hostinger-headless/SKILL.md",
19
+ "frontmatter": {
20
+ "name": "hostinger-headless",
21
+ "description": "Build, connect, or iterate on a website hosted on Hostinger — provision hosting and a domain, optionally seed an ecommerce store with a real hosted checkout or a WordPress content backend (headless CMS/blog), build the frontend, deploy, and verify. Requires an authenticated Hostinger MCP session (see entry/skill.md). Triggers: build me a site on Hostinger, deploy this to Hostinger, connect this project to Hostinger, add a store to my Hostinger site, add a blog to my Hostinger site, update my Hostinger site."
22
+ },
23
+ "resources": [
24
+ {
25
+ "uri": "skill://hostinger-headless/SKILL.md",
26
+ "digest": "sha256:7ed8a33109f571b56646627d21ba58cf7cbf8c2818c9bfacf645a341a8847b1f",
27
+ "size": 7200,
28
+ "mimeType": "text/markdown",
29
+ "text": "---\nname: hostinger-headless\ndescription: \"Build, connect, or iterate on a website hosted on Hostinger — provision hosting and a domain, optionally seed an ecommerce store with a real hosted checkout or a WordPress content backend (headless CMS/blog), build the frontend, deploy, and verify. Requires an authenticated Hostinger MCP session (see entry/skill.md). Triggers: build me a site on Hostinger, deploy this to Hostinger, connect this project to Hostinger, add a store to my Hostinger site, add a blog to my Hostinger site, update my Hostinger site.\"\n---\n\n# Hostinger Headless\n\nThis skill turns a prompt into a live website on Hostinger. Its job is to own the full run: check the account, provision hosting and a domain, seed the backend (an ecommerce store when the intent calls for selling), build the frontend, deploy it, and verify the result.\n\nThe frontend is built ad-hoc to the user's intent — there is no template library. The backend pieces are real Hostinger products: web hosting (static or Node.js), domains and DNS, Hostinger Ecommerce with its public Storefront API and hosted checkout, and WordPress as a headless CMS/blog backend read through the public WP REST API.\n\n## Preconditions\n\n1. An authenticated Hostinger MCP session — the entry skill's bootstrap handled this. If MCP tools fail with auth errors, send the user back through `entry/skill.md`.\n2. The Hostinger MCP operations for **hosting** (plus **ecommerce** when the run involves a store, and **wordpress** when it involves a content backend). Operation availability varies per user — product groups can be toggled in the Hostinger Connector. When search cannot find a needed operation, ask the user to enable that product group (or configure the scoped binary, e.g. `hostinger-hosting-mcp`) rather than improvising around it.\n3. An active hosting plan (checked in Setup §1). Hostinger hosting is a paid product: if the account has no usable plan, inform the user plainly that a subscription is required, link them to https://www.hostinger.com/web-hosting, and pause until they confirm the purchase. Do not treat this as an error — it is a normal step for new accounts. Everything that doesn't need hosting (planning, building the frontend locally) can proceed while they decide.\n\n## Resolving the operation\n\nResolve by intent first, disk second — never let an empty directory override what the user is asking for. Check `iterate` first (it's decided by an unambiguous on-disk signal):\n\n- `iterate` — a `.hostinger/site.json` is present in the project: the site is already deployed through this skill and the user wants changes. Reuse the recorded domain/site details; apply only the delta the new intent needs (edit frontend → redeploy; add a store → run `references/STORE.md` then redeploy). Never re-provision an existing site.\n- `connect` — a frontend project already on disk (or brought in as a zip/export/URL) that is not yet on Hostinger, with language like \"deploy this / host this on Hostinger / connect this project\". Emptiness of the CWD at trigger time is not a create signal when a design is brought in from elsewhere.\n- `create` — a new site from a prompt with nothing brought in: \"build me a store / portfolio / site…\".\n\nIf signals conflict, ask the user — don't guess.\n\n## The run\n\n1. **Discovery** — from the prompt (and the project on disk for connect/iterate), infer: does this run need a store (any buy/sell/product intent → yes)? Does it need owner-managed content — a blog, news, or anything the owner edits without a redeploy (→ WordPress backend; static copy that rarely changes does not qualify)? What brand, copy, and structure does the site need? What stack fits — plain static HTML/CSS/JS for simple sites (default), a static-output framework build, or a Node.js app when SSR or server code is genuinely required? Prefer static: it deploys fastest and has no build-failure surface. Ask the user only what you cannot infer.\n2. **Setup** (`references/SETUP.md`) — plan check, then provision the website on a free subdomain (or the user's own domain) and wait until it's ready.\n3. **Store** (`references/STORE.md`, only when commerce is needed) — resolve or create the store and its `custom` sales channel, seed products/shipping/payment, and note the `sales_channel_id` for the frontend.\n4. **Content backend** (`references/WORDPRESS.md`, only when owner-managed content is needed) — install WordPress on a dedicated subdomain, wait until it's ready, and hand the owner a wp-admin login link. The frontend reads it through the public WP REST API.\n5. **Build** — create or wire the frontend. For store runs, follow the frontend contract in `references/STORE.md` (catalog fetched at runtime from the public Storefront API, cart in `localStorage`, client-side checkout — never embed an API token in the site). For content runs, follow the frontend contract in `references/WORDPRESS.md` (posts fetched at runtime from the WP REST API, rendered HTML bodies, graceful empty states). When the app needs MySQL, follow `references/DATABASE.md` (which host each runtime connects to, credentials via env vars or a server-side config file, never in the frontend).\n6. **Deploy** (`references/DEPLOYMENT.md`) — archive and deploy via the matching hosting operation, polling build logs for Node.js runs.\n7. **Verify** — curl the live URL for a 200 and a piece of real page copy; for store runs, confirm a checkout POST returns a redirect URL; for content runs, confirm the WP REST API returns 200 and the frontend renders posts. Show the user the live URL and where to manage things (hPanel: https://hpanel.hostinger.com, the store dashboard for commerce, wp-admin for content).\n8. **Record** — write `.hostinger/site.json` into the project: `{ \"domain\", \"username\", \"type\": \"static\"|\"nodejs\", \"sales_channel_id\"?, \"store_id\"?, \"cms_domain\"? }`. This is what makes future runs resolve as `iterate`.\n\nRun non-interactively wherever possible. The exceptions that must involve the user: the paid-plan confirmation, anything that costs money (a domain purchase, a new subscription), and picking between genuinely equal options the prompt doesn't decide.\n\n## Paths\n\n| What | Path |\n| --- | --- |\n| Plan check + website/domain provisioning | `references/SETUP.md` |\n| Store: seed the backend + the frontend API contract | `references/STORE.md` |\n| WordPress: headless CMS/blog backend + the frontend read contract | `references/WORDPRESS.md` |\n| Deploy: static vs Node.js, archive rules, logs, verify | `references/DEPLOYMENT.md` |\n| Database + runtime: MySQL host per runtime, env vars, what to poll, runtime logs | `references/DATABASE.md` |\n\n## Where the how comes from\n\nThe inputSchema returned by search is authoritative for request shapes — read it before calling. Two live sources supersede anything written here when they disagree:\n\n- The ecommerce operation `ecommerce_miscellaneous_custom-storefront-setup-instructions` returns the current storefront integration guide from the server — execute it at the start of any store run.\n- The Hostinger API reference at https://developers.hostinger.com describes every endpoint behind the operations.\n"
30
+ },
31
+ {
32
+ "uri": "skill://hostinger-headless/references/DATABASE.md",
33
+ "digest": "sha256:e0800ac848244ead5b9a1bfb0a39d223b336c2a179ac13ece0089d135c418d76",
34
+ "size": 4299,
35
+ "mimeType": "text/markdown",
36
+ "text": "# Database and runtime — MySQL hosts, env vars, and what to poll\n\nUse this when the app needs MySQL, or when a deploy \"succeeded\" but the app cannot reach its database. Everything here uses the **hosting** MCP operations; Agency Plan differences are at the end.\n\n## 1. Create the database\n\n1. `hosting_databases_create` on the site's `username` with a database name, a user and a password you generate — name and user are prefixed with the account username automatically.\n2. Read the full, prefixed `name` and `user` back from `hosting_databases_list`; every other database operation wants the full name.\n3. Do not print the password to the user once it is stored, and never commit it.\n\n## 2. Which host the app connects to\n\n| Where the code runs | Host | Port | Why |\n| --- | --- | --- | --- |\n| PHP on the website | `localhost` or `127.0.0.1` | 3306 | `localhost` uses the MySQL socket; both are granted |\n| Node.js on the website | `127.0.0.1` | 3306 | `localhost` may resolve to IPv6 `::1`, and not every database user has that grant |\n| Outside Hostinger (a laptop, another server) | the `host` from `hosting_databases_list` (`srvNNNN.hstgr.io`) | 3306 | needs `hosting_databases_create-remote-connection` for that client's IP first — never `%`, it opens the database to every address on the internet |\n\nDo not put the remote `host` into the deployed app — it has no grant for connections that come from the server itself.\n\n## 3. Hand credentials to the app\n\n- **Node.js:** environment variables via `hosting_nodejs_replace-environment-variables`. Conventional keys: `DB_HOST=127.0.0.1`, `DB_PORT=3306`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, plus `DATABASE_URL=mysql://USER:PASSWORD@127.0.0.1:3306/NAME` for ORMs that want one string — URL-encode `USER` and `PASSWORD` there (`encodeURIComponent`), or a password containing `@`, `:`, `/` or `#` breaks the URL. The call is a **full replace**: list the current keys with `hosting_nodejs_list-environment-variables` (values come back masked as `********` — never copy those), then send the complete set with real values. Saving restarts the process; frameworks that bake variables into the build (Next.js, `NEXT_PUBLIC_*`) need a new `hosting_nodejs_start-build` afterwards.\n- **PHP:** a config file on the server that the app reads (its own `config.php`, WordPress's `wp-config.php`). Ship it with the deploy, keep it out of git.\n- **Never** in client-side JavaScript, in a committed `.env`, or in chat replies.\n\n## 4. What is asynchronous, and what to poll\n\n| You called | Poll this until | Typical wait |\n| --- | --- | --- |\n| `hosting_websites_create` | `hosting_websites_list` lists the domain | up to a few minutes |\n| `hosting_deploy-js-application` | `hosting_list-js-deployments` shows the build finished | minutes |\n| `hosting_nodejs_start-build` | `hosting_nodejs_build` state is `completed` or `failed` | minutes |\n| `wordpress_installations_install`, plugin/theme/core jobs | `wordpress_installations_list` lists the install | 1–2 minutes |\n| `hosting_databases_repair`, `hosting_websites_delete` | nothing to poll — runs in the background | minutes |\n| `agency-hosting_website-setups_create` | `agency-hosting_website-setups_status` with `setup_uuid` is `completed` | minutes |\n\nPoll with backoff (a few seconds, then tens of seconds). A queued response is not a failure, and re-sending the write does not speed it up.\n\n## 5. When the app is up but broken\n\n- Build failed: `hosting_nodejs_build-logs` for the raw log, `hosting_nodejs_analyse-failed-build` for a diagnosis.\n- Build passed, app errors: `hosting_nodejs_runtime-logs` with a `period` such as `1h`; entries before `last_deployed_at` belong to the previous deploy.\n- `Access denied for user 'x'@'::1'`: the app used `localhost` from Node.js — set `DB_HOST` to `127.0.0.1`.\n- PHP limits (memory, upload size): `hosting_php_get`, then `hosting_php_update-options`.\n\n## Agency Plan websites (`agency-hosting_*`)\n\n`agency-hosting_databases_create-website` creates the database and its single user; the user is granted on `localhost`, so the PHP app connects to `localhost`. Deploys are synchronous there — `agency-hosting_deploy-php-application` returns when the site is live. Other jobs (SSL, backups) show up in `agency-hosting_websites_list-processes`.\n"
37
+ },
38
+ {
39
+ "uri": "skill://hostinger-headless/references/DEPLOYMENT.md",
40
+ "digest": "sha256:1369632f23a6934ca47ca942cba20882681bc679fe833757ce30099c51257ea2",
41
+ "size": 2170,
42
+ "mimeType": "text/markdown",
43
+ "text": "# Deployment — getting the build live\n\nMatch the deploy method to what the project actually is — this is the single most common failure point.\n\n## Static site → `hosting_deploy-static-website`\n\nFor pre-built files only: plain HTML/CSS/JS, or the **build output** of a framework (run the build locally first).\n\n- The archive (zip/tar) must have `index.html` at its **root** — not nested inside a folder.\n- Name it `name_YYYYMMDD_HHMMSS.zip`; pass `removeArchive: true` to clean up after upload.\n- Deployment is effectively immediate — verify right after.\n\n## Node.js app → `hosting_deploy-js-application`\n\nFor anything that needs a server or a server-side build (Express, Next.js, NestJS, SSR frameworks).\n\n- Archive the **source**, not the output: exclude `node_modules/`, `dist/`, `.next/`, `build/`, and everything matched by `.gitignore`. The install and build run on Hostinger.\n- Hard cap: **50 MB** archive.\n- The upload starts a build. Track it with `hosting_list-js-deployments`; on failure pull `hosting_show-js-deployment-logs`, fix, and redeploy. Poll with backoff — builds take minutes, not seconds.\n\n## WordPress → `hosting_import-wordpress-website` / plugin & theme operations\n\nOnly when the user explicitly wants a WordPress *site* migrated or themed. Site imports take an archive plus a `.sql` dump and can run for several minutes. (WordPress as a headless content backend behind an agent-built frontend is a different flow — that's `WORDPRESS.md`, installed fresh via the API, not imported here.)\n\n## Verify (every deploy)\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" https://YOURDOMAIN/\ncurl -s https://YOURDOMAIN/ | grep -o \"SOME_REAL_HEADLINE_TEXT\"\n```\n\nReplace the grep target with copy you actually rendered — a 200 alone can be a placeholder page. A freshly created site may serve a default page for a short while; if content doesn't match, clear the cache (`hosting_cache_clear-website`) and retry before assuming the deploy failed. For store runs, also run the checkout verification in `STORE.md`.\n\nWhen the site is live, report the URL and remind the user the site is managed from hPanel (https://hpanel.hostinger.com).\n"
44
+ },
45
+ {
46
+ "uri": "skill://hostinger-headless/references/SETUP.md",
47
+ "digest": "sha256:52cc927dd67c4f4b6686dff55e1fd36b61350a1a41a3e5ced364a769dcd6f6fd",
48
+ "size": 2860,
49
+ "mimeType": "text/markdown",
50
+ "text": "# Setup — plan check and website provisioning\n\nEverything here uses the **hosting** MCP operations. If they're missing, ask the user to enable the Websites product group in the Hostinger Connector (or use the `hostinger-hosting-mcp` scoped binary).\n\n## 1. Plan check (gate — run this first)\n\nA website can only be created on an active hosting plan.\n\n1. `hosting_websites_list` — if it returns websites, the account has a working plan; note any existing `order_id` and `username` for step 2.\n2. Otherwise `hosting_orders_list` — look for an order in a usable state. A fresh order that has never hosted a website still works; note its `order_id`.\n3. If there is no usable order: **stop and tell the user** that an active Hostinger hosting plan is required to deploy, link https://www.hostinger.com/web-hosting, and offer to continue building the site locally in the meantime. When they confirm the purchase, re-run this check.\n\nNever purchase a plan, domain, or any paid item without the user explicitly approving that specific purchase.\n\n## 2. Choose the domain\n\n- **Default: free subdomain.** `hosting_domains_generate-free-subdomain` returns a `*.hostingersite.com` domain. No verification needed. Tell the user a custom domain can be connected later.\n- **User-owned domain:** `hosting_domains_verify-ownership` first. If not accessible, relay the TXT record it returns, remind them propagation can take ~10 minutes, and re-verify before continuing.\n- **New domain purchase:** only on explicit request — check availability with `domains_availability_check`, state the price, and get an explicit yes before `domains_portfolio_purchase` (needs the domains product group).\n\n## 3. Create the website and wait for it\n\nGenerating a subdomain does **not** create a website — deploying straight to it fails with `No website found for domain`. The working sequence:\n\n1. `hosting_websites_create { domain, order_id }` — `datacenter_code` is required only for the first website on a brand-new plan (pick the first entry from `hosting_datacenters_list`).\n2. **Poll** `hosting_websites_list` filtered by the domain until the site appears. Creation takes up to a few minutes — poll with backoff, don't fail fast.\n3. Note the site's `username` — deployment and database operations are keyed on it.\n\nIf the domain already has a website (an `iterate` run, or the user pointed at an existing site), skip creation entirely.\n\n## 4. Optional extras (only when the run needs them)\n\n- **Database:** when the app needs MySQL, follow `DATABASE.md` — creating it, which host each runtime connects to (Node.js must use `127.0.0.1`), and handing the credentials to the app without hard-coding them.\n- **DNS records:** the DNS operations (`dns_records_update` etc.) for custom-domain records; take a snapshot (`dns_snapshots_list` context) before destructive changes.\n"
51
+ },
52
+ {
53
+ "uri": "skill://hostinger-headless/references/STORE.md",
54
+ "digest": "sha256:ca5bf13b3968b49ae53717c0e52e16d301774b2ea166a687e246bd6089f155e2",
55
+ "size": 5470,
56
+ "mimeType": "text/markdown",
57
+ "text": "# Store — Hostinger Ecommerce with a custom storefront\n\n**Primary source:** execute `ecommerce_miscellaneous_custom-storefront-setup-instructions` at the start of every store run — it returns the current, server-maintained integration guide, and it supersedes this file wherever they disagree. This file carries the essentials so the run can be planned before that call, and a fallback when the ecommerce operations aren't enabled.\n\n## Mental model\n\nTwo API surfaces — don't mix them:\n\n| Surface | Base URL | Auth | Use for |\n| --- | --- | --- | --- |\n| Storefront Core V2 | `https://api-ecommerce.hostinger.com/v2` | none (public, open CORS) | read products/variants, checkout — called from the browser |\n| Management (MCP tools) | Hostinger API | authenticated | create stores/products/shipping/payments |\n\n- A **store** (`store_*`) has **sales channels** (`scha_*`). The Storefront API is keyed on the **`sales_channel_id`** of a `custom`-type channel — not the store id. Managed channels (e.g. `quick-link`) cannot be used here, and only `custom` channels are API-creatable.\n- **Prices are integers in minor units** (`2900` = €29.00 → divide by `10^decimal_digits`) and live on **variants**, not products.\n- **Security boundary:** storefront reads and checkout are public and run client-side — **never embed a Hostinger API token in the storefront**. Only the management/hosting side (MCP tools) is authenticated.\n\n## Backend setup (management tools)\n\n1. Resolve the `custom` sales channel: `ecommerce_stores_list` → `ecommerce_sales-channels_list`. If a `custom` channel already exists, confirm with the user that it's the one to use; otherwise add one to the existing store with `ecommerce_sales-channels_create`, or create a store with `ecommerce_stores_create` (with a `custom` sales channel) when no suitable store exists. Set the channel `url` to the storefront's public domain at creation when it's already known.\n2. For checkout to work the store needs all three: ≥ 1 product, ≥ 1 payment method, ≥ 1 shipping zone. Payment, shipping, and currency are store-level — check `ecommerce_stores_metadata` (`has_payment_methods` and `has_shipping` must be `true`; the same response carries `default_currency` with `code`, `decimal_digits`, `template` — read it here rather than guessing). Products are **per sales channel** — verify by listing products for the resolved channel on the Storefront API and confirming the list is non-empty. Seed what's missing with `ecommerce_products_create-physical` / `ecommerce_products_create-digital`, `ecommerce_payments_enable-manual-method`, and `ecommerce_shipping_set-store` (price `0` = free shipping) — confirming with the user before writing to a store that already has real data.\n3. After deploy, point the channel at the live site: `ecommerce_sales-channels_update { store_id, sales_channel_id, url }`.\n\nIf the ecommerce operations aren't available, ask the user to enable the Ecommerce product group in the Hostinger Connector — or to provide the `sales_channel_id` directly, after which the public endpoints suffice for the frontend work.\n\n## Frontend contract\n\nThe OpenAPI schema for the Storefront API is at `https://api-ecommerce.hostinger.com/v2/docs.json` — fetch it for exact endpoint shapes. Non-obvious rules the schema won't spell out:\n\n- **Fetch the catalog at runtime, not build time** — client-side fetch on page load (CORS is open). Baking products into a static build makes the site stale on the first catalog edit. (SSR / Node.js hosting may fetch it server-side per request.)\n- Each variant has a **`prices[]` array** (one entry per currency) — read `prices[0].amount` / `prices[0].sale_amount` (minor units) and `prices[0].currency` (which carries `decimal_digits` and the `template` whose `$1` placeholder you replace when formatting). There is **no** top-level `amount` on a variant.\n- Product detail resolves by **product id** only; a slug returns 404 (map slug→id from the list endpoint). `limit` max is 100 on list endpoints (else 400).\n- Filter variants with `product_ids[]=...`; the unfiltered variant list is eventually-consistent right after catalog edits.\n- Cart lives in the site (e.g. `localStorage`): persist only `variant_id` + `quantity`, resolve display data from the live catalog — never a saved copy — then send everything in one checkout call:\n\n```\nPOST /channels/{sales_channel_id}/checkout\n{ \"items\":[{ \"variant_id\":\"variant_01...\", \"quantity\":1 }],\n \"success_url\":\"https://YOURDOMAIN/checkout/success/\",\n \"cancel_url\":\"https://YOURDOMAIN/checkout/cancel/\",\n \"locale\":\"en\" }\n```\n\n`success_url`/`cancel_url` are required — build them from `window.location.origin` and make sure both pages exist. The response is `{ url, cart_token }`; redirect the browser to `url` (Hostinger's hosted checkout).\n\nA multi-step cart alternative exists (`POST /channels/{sales_channel_id}/carts`, `PATCH /carts/{cart_id}`, `POST /carts/{cart_id}/line-items`, `PUT /carts/{cart_id}/shipping-method`, `POST /carts/{cart_id}/complete`) — the single checkout call suffices for a simple buy flow.\n\n## Verify\n\n- A `POST` to the checkout endpoint returns a `url` — test it directly with curl before calling the run done.\n- The `success_url`/`cancel_url` pages return 200 (`curl -s -o /dev/null -w \"%{http_code}\" https://YOURDOMAIN/checkout/success/`).\n- Open the live site, add to cart, and confirm the redirect to `https://checkout.hostinger.com`.\n"
58
+ },
59
+ {
60
+ "uri": "skill://hostinger-headless/references/WORDPRESS.md",
61
+ "digest": "sha256:d9e80f800fbf4c53a95972762f9fe177430860715d849ea520ae5d47e0ef8f04",
62
+ "size": 4569,
63
+ "mimeType": "text/markdown",
64
+ "text": "# WordPress — headless CMS and blog backend\n\nUse WordPress when the site has **owner-managed content**: a blog, news, articles, or any content the owner must edit without a developer or a redeploy. The frontend stays agent-built and is deployed like any other run; WordPress runs separately as the content backend, and the owner writes in wp-admin.\n\n**When NOT to use this:** static copy that rarely changes (an about page, a services list, a brochure site) — bake that into the frontend. Installing WordPress for content the owner will never edit adds a moving part for nothing. Any buy/sell intent is `STORE.md`, not this file.\n\nEverything in Setup uses the **hosting** and **wordpress** MCP operation groups; ask the user to enable them in the Hostinger Connector if operations are missing.\n\n## Mental model\n\nTwo surfaces, like the store recipe:\n\n| Surface | Base URL | Auth | Use for |\n| --- | --- | --- | --- |\n| WP REST API | `https://CMS_DOMAIN/wp-json/wp/v2` | none for published content (open CORS) | frontend reads: posts, pages, media, categories |\n| Management (MCP tools + wp-admin) | Hostinger API / wp-admin | authenticated | install WP, plugins, cache; the owner writes content |\n\n- Published content is publicly readable and the REST API allows cross-origin browser reads — a static frontend on another domain fetches it directly at runtime.\n- Drafts, previews, and **writes need authentication** (WP application passwords). Never put credentials of any kind in the frontend; a public site only needs the anonymous read path.\n\n## Setup (management tools)\n\n1. **Check for an existing installation first**: `wordpress_installations_list` filtered by the site's username/domain. Reuse a valid install when the user agrees — never overwrite one silently.\n2. **Choose where WordPress lives.** Default: a dedicated subdomain of the site, e.g. `cms.<domain>` — create it with `hosting_domains_create-website-subdomain` so the main domain stays free for the frontend. A separate free-subdomain website (per `SETUP.md`) also works when the plan allows another website.\n3. **Install**: `wordpress_installations_install` on that domain. The call only queues the job — **poll** `wordpress_installations_list` until the installation appears (typically 1–2 minutes). Don't proceed on the queued response alone.\n4. **Hand the owner their editor**: mint a one-click wp-admin link with `wordpress_login_create-links` and show it to the user. Content authoring happens in wp-admin — the skill does not seed posts (there is no anonymous write path, by design). The fresh install ships with a sample post, which is enough to build and verify the frontend against.\n5. **Caching** (recommended before finishing): enable the object cache with `wordpress_object-cache_toggle-memcached`; after config changes, purge with `wordpress_litespeed-cache_purge-lite-speed`.\n\n## Frontend contract\n\nAll paths relative to `https://CMS_DOMAIN/wp-json/wp/v2`. Non-obvious rules:\n\n- **Fetch at runtime, not build time** — content changes whenever the owner publishes; baking it into a static build defeats the purpose. Client-side fetch on page load is fine (CORS is open).\n- List posts with `/posts?per_page=10&_embed` — `_embed` inlines featured images (`_embedded['wp:featuredmedia'][0].source_url`), authors, and terms; without it you get IDs that need extra requests.\n- Post bodies are **rendered HTML**: use `title.rendered` / `content.rendered` / `excerpt.rendered` and render them as HTML — do not treat them as plain text and do not try to restyle their inner markup beyond CSS.\n- Single post by slug: `/posts?slug=my-post` — returns an **array** (take the first element); there is no direct slug path.\n- Pagination comes from the `X-WP-Total` / `X-WP-TotalPages` response headers, not the body.\n- Only **published** content is returned anonymously. An empty list is a normal state — the frontend must render it gracefully, not error.\n- SEO is the frontend's job: page titles, meta tags, and the sitemap come from the frontend build, not from WordPress plugins.\n\n## Verify\n\n```bash\ncurl -s -o /dev/null -w \"%{http_code}\\n\" \"https://CMS_DOMAIN/wp-json/wp/v2/posts?per_page=1\"\n```\n\nExpect 200 with a JSON array. Then confirm the deployed frontend renders the sample post (or a clean empty state), and show the user two links: the live site and the wp-admin login for writing content.\n\n## Record\n\nAdd `\"cms_domain\"` to `.hostinger/site.json` (see `SKILL.md` §Record) so iterate runs know a WordPress backend exists and reuse it instead of installing again.\n"
65
+ }
66
+ ]
67
+ }
68
+ ];
69
+ export default skills;
@@ -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.js';
6
6
 
7
- startServer({ name: 'hostinger-agency-hosting-mcp', version: '2.1.0', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '2.2.0', 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.js';
6
6
 
7
- startServer({ name: 'hostinger-agency-hosting-mcp', version: '2.1.0', tools, instructions });
7
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '2.2.0', tools, instructions });
@@ -3,5 +3,6 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/all.js';
5
5
  import instructions from '../core/instructions.js';
6
+ import skills from '../core/skills.js';
6
7
 
7
- startServer({ name: 'hostinger-api-mcp', version: '2.1.0', tools, instructions });
8
+ startServer({ name: 'hostinger-api-mcp', version: '2.2.0', tools, instructions, skills });
@@ -3,5 +3,6 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/all.js';
5
5
  import instructions from '../core/instructions.js';
6
+ import skills from '../core/skills.js';
6
7
 
7
- startServer({ name: 'hostinger-api-mcp', version: '2.1.0', tools, instructions });
8
+ startServer({ name: 'hostinger-api-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '2.2.0', 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: '2.1.0', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '2.2.0', 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: '2.1.0', tools, instructions });
7
+ startServer({ name: 'hostinger-hosting-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '2.2.0', 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: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '2.2.0', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/wordpress.js';
5
5
 
6
- startServer({ name: 'hostinger-wordpress-mcp', version: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '2.2.0', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/wordpress.js';
5
5
 
6
- startServer({ name: 'hostinger-wordpress-mcp', version: '2.1.0', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '2.2.0', tools });