hostinger-api-mcp 1.61.1 → 1.61.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,14 @@ 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
+ ## v1.61.2 — 2026-09-21
8
+
9
+ ### Also in this release
10
+
11
+ - Dependencies: `package-lock.json`
12
+ - Request handling and authentication: `src/core/runtime.js`, `src/core/runtime.ts`
13
+ - Other files: `.gitignore`
14
+
7
15
  ## v1.61.1 — 2026-09-17
8
16
 
9
17
  ### Also in this release
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "1.61.1",
3
+ "version": "1.61.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": "1.61.1",
3
+ "version": "1.61.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": "1.61.1",
9
+ "version": "1.61.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@hostinger/mcp",
14
- "version": "1.61.1",
14
+ "version": "1.61.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -10,6 +10,8 @@ import { config as dotenvConfig } from "dotenv";
10
10
  import {
11
11
  ListToolsRequestSchema,
12
12
  CallToolRequestSchema,
13
+ ErrorCode,
14
+ McpError,
13
15
  } from "@modelcontextprotocol/sdk/types.js";
14
16
  import { OAuthProvider, getEnvToken } from "./oauth.js";
15
17
  import * as tus from "tus-js-client";
@@ -19,6 +21,19 @@ import path from "path";
19
21
  // Load environment variables
20
22
  dotenvConfig({ quiet: true });
21
23
 
24
+ /**
25
+ * An upstream API call that came back as a failure. Carries the status and the
26
+ * API's own response body so the CallTool handler can hand both to the model.
27
+ */
28
+ class ApiExecutionError extends Error {
29
+ constructor(status, data, message) {
30
+ super(message || `API request failed with status ${status}`);
31
+ this.name = 'ApiExecutionError';
32
+ this.status = status;
33
+ this.responseData = data;
34
+ }
35
+ }
36
+
22
37
  const SECURITY_SCHEMES = {
23
38
  "apiToken": {
24
39
  "type": "http",
@@ -157,13 +172,17 @@ class MCPServer {
157
172
  }
158
173
  }
159
174
 
175
+ // An unknown tool is a protocol error, not a tool execution error: the
176
+ // model cannot self-correct its way out of a name the server does not
177
+ // have. -32602 is what the spec's example and the SDK's own McpServer
178
+ // use; a plain Error would go out as -32603 instead.
160
179
  if (!toolName) {
161
- throw new Error(`Tool not found: ${name}`);
180
+ throw new McpError(ErrorCode.InvalidParams, `Tool not found: ${name}`);
162
181
  }
163
182
 
164
183
  toolDetails = this.toolList.find(t => t.name === toolName);
165
184
  if (!toolDetails) {
166
- throw new Error(`Tool details not found for ID: ${toolName}`);
185
+ throw new McpError(ErrorCode.InternalError, `Tool details not found for ID: ${toolName}`);
167
186
  }
168
187
 
169
188
  try {
@@ -189,10 +208,32 @@ class MCPServer {
189
208
 
190
209
  } catch (error) {
191
210
  const errorMessage = error instanceof Error ? error.message : String(error);
192
- const response = error.response;
193
211
  this.log('error', `Error executing tool ${name}: ${errorMessage}`);
194
212
 
195
- throw error;
213
+ // MCP reports tool execution failures (API errors, bad input, business
214
+ // logic) inside the result with isError: true. Throwing here would turn
215
+ // them into JSON-RPC protocol errors instead, which the spec reserves
216
+ // for unknown tools and malformed requests — those are still thrown,
217
+ // above, before this try. The status stays in the text so the model can
218
+ // tell a retryable 429/503 from a 403 it should not retry.
219
+ let errorText = errorMessage;
220
+ if (error instanceof ApiExecutionError) {
221
+ const payload = error.responseData;
222
+ const body = payload === null || payload === undefined || payload === ''
223
+ ? errorMessage
224
+ : typeof payload === 'object' ? JSON.stringify(payload) : String(payload);
225
+ errorText = error.status ? `HTTP ${error.status}: ${body}` : body;
226
+ }
227
+
228
+ return {
229
+ isError: true,
230
+ content: [
231
+ {
232
+ type: "text",
233
+ text: errorText
234
+ }
235
+ ]
236
+ };
196
237
  }
197
238
  });
198
239
  }
@@ -2474,6 +2515,13 @@ class MCPServer {
2474
2515
  this.log('debug', `Retry response status: ${response.status}`);
2475
2516
  }
2476
2517
 
2518
+ // validateStatus resolves everything under 500, so a 4xx arrives here as
2519
+ // an ordinary response. Without this it would be returned to the model as
2520
+ // a successful tool result whose body happens to be the API's error.
2521
+ if (response.status >= 400) {
2522
+ throw new ApiExecutionError(response.status, response.data);
2523
+ }
2524
+
2477
2525
  return response.data;
2478
2526
 
2479
2527
  } catch (error) {
@@ -2489,10 +2537,14 @@ class MCPServer {
2489
2537
  data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
2490
2538
  });
2491
2539
 
2492
- // Rethrow with more context for better error handling
2493
- const detailedError = new Error(`API request failed with status ${responseStatus}: ${errorMessage}`);
2494
- detailedError.response = error.response;
2495
- throw detailedError;
2540
+ // Structured so the CallTool handler can report this as an MCP tool
2541
+ // execution error carrying the API's own payload. Status is 0 for a
2542
+ // transport failure (DNS, ECONNREFUSED, timeout) — there is no response.
2543
+ throw new ApiExecutionError(
2544
+ responseStatus ?? 0,
2545
+ responseData,
2546
+ `API request failed with status ${responseStatus}: ${errorMessage}`
2547
+ );
2496
2548
  }
2497
2549
 
2498
2550
  throw error;
@@ -10,6 +10,8 @@ import { config as dotenvConfig } from "dotenv";
10
10
  import {
11
11
  ListToolsRequestSchema,
12
12
  CallToolRequestSchema,
13
+ ErrorCode,
14
+ McpError,
13
15
  Tool,
14
16
  } from "@modelcontextprotocol/sdk/types.js";
15
17
  import { OAuthProvider, getEnvToken } from "./oauth.js";
@@ -42,6 +44,22 @@ interface SecurityScheme {
42
44
  scheme?: string;
43
45
  }
44
46
 
47
+ /**
48
+ * An upstream API call that came back as a failure. Carries the status and the
49
+ * API's own response body so the CallTool handler can hand both to the model.
50
+ */
51
+ class ApiExecutionError extends Error {
52
+ public readonly status: number;
53
+ public readonly responseData: any;
54
+
55
+ constructor(status: number, data: any, message?: string) {
56
+ super(message || `API request failed with status ${status}`);
57
+ this.name = 'ApiExecutionError';
58
+ this.status = status;
59
+ this.responseData = data;
60
+ }
61
+ }
62
+
45
63
  const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
46
64
  "apiToken": {
47
65
  "type": "http",
@@ -188,13 +206,17 @@ class MCPServer {
188
206
  }
189
207
  }
190
208
 
209
+ // An unknown tool is a protocol error, not a tool execution error: the
210
+ // model cannot self-correct its way out of a name the server does not
211
+ // have. -32602 is what the spec's example and the SDK's own McpServer
212
+ // use; a plain Error would go out as -32603 instead.
191
213
  if (!toolName) {
192
- throw new Error(`Tool not found: ${name}`)
214
+ throw new McpError(ErrorCode.InvalidParams, `Tool not found: ${name}`);
193
215
  }
194
216
 
195
217
  toolDetails = this.toolList.find(t => t.name === toolName);
196
218
  if (!toolDetails) {
197
- throw new Error(`Tool details not found for ID: ${toolName}`);
219
+ throw new McpError(ErrorCode.InternalError, `Tool details not found for ID: ${toolName}`);
198
220
  }
199
221
 
200
222
  try {
@@ -221,8 +243,31 @@ class MCPServer {
221
243
  } catch (error) {
222
244
  const errorMessage = error instanceof Error ? error.message : String(error);
223
245
  this.log('error', `Error executing tool ${toolName}: ${errorMessage}`);
224
-
225
- throw error;
246
+
247
+ // MCP reports tool execution failures (API errors, bad input, business
248
+ // logic) inside the result with isError: true. Throwing here would turn
249
+ // them into JSON-RPC protocol errors instead, which the spec reserves
250
+ // for unknown tools and malformed requests — those are still thrown,
251
+ // above, before this try. The status stays in the text so the model can
252
+ // tell a retryable 429/503 from a 403 it should not retry.
253
+ let errorText: string = errorMessage;
254
+ if (error instanceof ApiExecutionError) {
255
+ const payload = error.responseData;
256
+ const body = payload === null || payload === undefined || payload === ''
257
+ ? errorMessage
258
+ : typeof payload === 'object' ? JSON.stringify(payload) : String(payload);
259
+ errorText = error.status ? `HTTP ${error.status}: ${body}` : body;
260
+ }
261
+
262
+ return {
263
+ isError: true,
264
+ content: [
265
+ {
266
+ type: "text",
267
+ text: errorText
268
+ }
269
+ ]
270
+ };
226
271
  }
227
272
  });
228
273
  }
@@ -2524,6 +2569,13 @@ class MCPServer {
2524
2569
  this.log('debug', `Retry response status: ${response.status}`);
2525
2570
  }
2526
2571
 
2572
+ // validateStatus resolves everything under 500, so a 4xx arrives here as
2573
+ // an ordinary response. Without this it would be returned to the model as
2574
+ // a successful tool result whose body happens to be the API's error.
2575
+ if (response.status >= 400) {
2576
+ throw new ApiExecutionError(response.status, response.data);
2577
+ }
2578
+
2527
2579
  return response.data;
2528
2580
 
2529
2581
  } catch (error) {
@@ -2540,10 +2592,14 @@ class MCPServer {
2540
2592
  data: typeof responseData === 'object' ? JSON.stringify(responseData) : String(responseData)
2541
2593
  });
2542
2594
 
2543
- // Rethrow with more context for better error handling
2544
- const detailedError = new Error(`API request failed with status ${responseStatus}: ${errorMessage}`);
2545
- (detailedError as any).response = axiosError.response;
2546
- throw detailedError;
2595
+ // Structured so the CallTool handler can report this as an MCP tool
2596
+ // execution error carrying the API's own payload. Status is 0 for a
2597
+ // transport failure (DNS, ECONNREFUSED, timeout) — there is no response.
2598
+ throw new ApiExecutionError(
2599
+ responseStatus ?? 0,
2600
+ responseData,
2601
+ `API request failed with status ${responseStatus}: ${errorMessage}`
2602
+ );
2547
2603
  }
2548
2604
 
2549
2605
  throw error;
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/agency-hosting.js';
5
5
 
6
- startServer({ name: 'hostinger-agency-hosting-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '1.61.2', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/agency-hosting.js';
5
5
 
6
- startServer({ name: 'hostinger-agency-hosting-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-agency-hosting-mcp', version: '1.61.2', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/all.js';
5
5
 
6
- startServer({ name: 'hostinger-api-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-api-mcp', version: '1.61.2', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/all.js';
5
5
 
6
- startServer({ name: 'hostinger-api-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-api-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-ecommerce-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-horizons-mcp', version: '1.61.2', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/hosting.js';
5
5
 
6
- startServer({ name: 'hostinger-hosting-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-hosting-mcp', version: '1.61.2', tools });
@@ -3,4 +3,4 @@
3
3
  import { startServer } from '../core/runtime.js';
4
4
  import tools from '../core/tools/hosting.js';
5
5
 
6
- startServer({ name: 'hostinger-hosting-mcp', version: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-hosting-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-mail-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '1.61.2', 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: '1.61.1', tools });
6
+ startServer({ name: 'hostinger-wordpress-mcp', version: '1.61.2', tools });