hostinger-api-mcp 0.1.43 → 0.2.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.
@@ -12,6 +12,7 @@ import {
12
12
  CallToolRequestSchema,
13
13
  Tool,
14
14
  } from "@modelcontextprotocol/sdk/types.js";
15
+ import { OAuthProvider, getEnvToken } from "./oauth.js";
15
16
  import axios,{ AxiosRequestConfig, AxiosError, AxiosResponse } from "axios";
16
17
  import * as tus from "tus-js-client";
17
18
  import fs from "fs";
@@ -46,7 +47,7 @@ const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
46
47
 
47
48
  /**
48
49
  * MCP Server for Hostinger API
49
- * Generated from OpenAPI spec version 0.11.7
50
+ * Generated from OpenAPI spec version 0.12.0
50
51
  */
51
52
  class MCPServer {
52
53
  private readonly name: string;
@@ -57,6 +58,7 @@ class MCPServer {
57
58
  private debug: boolean;
58
59
  private baseUrl: string;
59
60
  private headers: Record<string, string>;
61
+ private oauth: OAuthProvider;
60
62
 
61
63
  constructor({ name, version, tools }: { name: string; version: string; tools: OpenApiTool[] }) {
62
64
  // Initialize class properties
@@ -66,6 +68,7 @@ class MCPServer {
66
68
  this.debug = process.env.DEBUG === "true";
67
69
  this.baseUrl = process.env.API_BASE_URL || "https://developers.hostinger.com";
68
70
  this.headers = this.parseHeaders(process.env.API_HEADERS || "");
71
+ this.oauth = new OAuthProvider();
69
72
 
70
73
  // Initialize tools map - do this before creating server
71
74
  this.initializeTools();
@@ -108,6 +111,14 @@ class MCPServer {
108
111
  return headers;
109
112
  }
110
113
 
114
+ /**
115
+ * Resolve a bearer token. HOSTINGER_API_TOKEN / API_TOKEN env vars take
116
+ * precedence; otherwise the OAuth provider handles login/refresh transparently.
117
+ */
118
+ private async getAuthToken(): Promise<string> {
119
+ return await this.oauth.getAccessToken();
120
+ }
121
+
111
122
  /**
112
123
  * Initialize tools map from OpenAPI spec
113
124
  * This runs before the server is connected, so don't log here
@@ -228,10 +239,7 @@ class MCPServer {
228
239
  const url = new URL(`api/hosting/v1/websites?domain=${encodeURIComponent(domain)}`, baseUrl).toString();
229
240
 
230
241
  try {
231
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
232
- if (!bearerToken) {
233
- throw new Error('API_TOKEN environment variable not found');
234
- }
242
+ const bearerToken = await this.getAuthToken();
235
243
 
236
244
  const config: AxiosRequestConfig = {
237
245
  method: 'get',
@@ -287,10 +295,7 @@ class MCPServer {
287
295
  const url = new URL('api/hosting/v1/files/upload-urls', baseUrl).toString();
288
296
 
289
297
  try {
290
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
291
- if (!bearerToken) {
292
- throw new Error('API_TOKEN environment variable not found');
293
- }
298
+ const bearerToken = await this.getAuthToken();
294
299
 
295
300
  const config: AxiosRequestConfig = {
296
301
  method: 'post',
@@ -490,10 +495,7 @@ class MCPServer {
490
495
  const url = new URL(`api/hosting/v1/accounts/${username}/domains/${domain}/is-empty`, baseUrl).toString();
491
496
 
492
497
  try {
493
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
494
- if (!bearerToken) {
495
- throw new Error('API_TOKEN environment variable not found');
496
- }
498
+ const bearerToken = await this.getAuthToken();
497
499
 
498
500
  const config: AxiosRequestConfig = {
499
501
  method: 'get',
@@ -545,10 +547,7 @@ class MCPServer {
545
547
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/import`, baseUrl).toString();
546
548
 
547
549
  try {
548
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
549
- if (!bearerToken) {
550
- throw new Error('API_TOKEN environment variable not found');
551
- }
550
+ const bearerToken = await this.getAuthToken();
552
551
 
553
552
  const config: AxiosRequestConfig = {
554
553
  method: 'post',
@@ -773,10 +772,7 @@ class MCPServer {
773
772
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/plugins/deploy`, baseUrl).toString();
774
773
 
775
774
  try {
776
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
777
- if (!bearerToken) {
778
- throw new Error('API_TOKEN environment variable not found');
779
- }
775
+ const bearerToken = await this.getAuthToken();
780
776
 
781
777
  const config: AxiosRequestConfig = {
782
778
  method: 'post',
@@ -1007,10 +1003,7 @@ class MCPServer {
1007
1003
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/themes/deploy`, baseUrl).toString();
1008
1004
 
1009
1005
  try {
1010
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1011
- if (!bearerToken) {
1012
- throw new Error('API_TOKEN environment variable not found');
1013
- }
1006
+ const bearerToken = await this.getAuthToken();
1014
1007
 
1015
1008
  const config: AxiosRequestConfig = {
1016
1009
  method: 'post',
@@ -1244,10 +1237,7 @@ class MCPServer {
1244
1237
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds/settings/from-archive?archive_path=${encodeURIComponent(archiveBasename)}`, baseUrl).toString();
1245
1238
 
1246
1239
  try {
1247
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1248
- if (!bearerToken) {
1249
- throw new Error('API_TOKEN environment variable not found');
1250
- }
1240
+ const bearerToken = await this.getAuthToken();
1251
1241
 
1252
1242
  const config: AxiosRequestConfig = {
1253
1243
  method: 'get',
@@ -1293,10 +1283,7 @@ class MCPServer {
1293
1283
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/nodejs/builds`, baseUrl).toString();
1294
1284
 
1295
1285
  try {
1296
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1297
- if (!bearerToken) {
1298
- throw new Error('API_TOKEN environment variable not found');
1299
- }
1286
+ const bearerToken = await this.getAuthToken();
1300
1287
 
1301
1288
  const archiveBasename = path.basename(archivePath);
1302
1289
  const buildData = {
@@ -1544,10 +1531,7 @@ class MCPServer {
1544
1531
  const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/deploy`, baseUrl).toString();
1545
1532
 
1546
1533
  try {
1547
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1548
- if (!bearerToken) {
1549
- throw new Error('API_TOKEN environment variable not found');
1550
- }
1534
+ const bearerToken = await this.getAuthToken();
1551
1535
 
1552
1536
  const archiveBasename = path.basename(archivePath);
1553
1537
  const deployData = {
@@ -1719,10 +1703,7 @@ class MCPServer {
1719
1703
  const fullUrl = queryParams ? `${url}?${queryParams}` : url;
1720
1704
 
1721
1705
  try {
1722
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1723
- if (!bearerToken) {
1724
- throw new Error('API_TOKEN environment variable not found');
1725
- }
1706
+ const bearerToken = await this.getAuthToken();
1726
1707
 
1727
1708
  const config: AxiosRequestConfig = {
1728
1709
  method: 'get',
@@ -1832,10 +1813,7 @@ class MCPServer {
1832
1813
  const fullUrl = queryParams ? `${url}?${queryParams}` : url;
1833
1814
 
1834
1815
  try {
1835
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
1836
- if (!bearerToken) {
1837
- throw new Error('API_TOKEN environment variable not found');
1838
- }
1816
+ const bearerToken = await this.getAuthToken();
1839
1817
 
1840
1818
  const config: AxiosRequestConfig = {
1841
1819
  method: 'get',
@@ -1946,11 +1924,10 @@ class MCPServer {
1946
1924
  }
1947
1925
  };
1948
1926
 
1949
- const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN']; // APITOKEN for backwards compatibility
1950
- if (bearerToken && config.headers) {
1927
+ const envToken = getEnvToken();
1928
+ let bearerToken = await this.getAuthToken();
1929
+ if (config.headers) {
1951
1930
  config.headers['Authorization'] = `Bearer ${bearerToken}`;
1952
- } else {
1953
- this.log('error', `Bearer Token environment variable not found: API_TOKEN`);
1954
1931
  }
1955
1932
 
1956
1933
  // Add parameters based on request method
@@ -1973,9 +1950,23 @@ class MCPServer {
1973
1950
  });
1974
1951
 
1975
1952
  // Execute the request
1976
- const response = await axios(config);
1953
+ let response = await axios(config);
1977
1954
  this.log('debug', `Response status: ${response.status}`);
1978
1955
 
1956
+ // Reactive token recovery: a 401 means the bearer was rejected even
1957
+ // though our local expiry said it was fine (e.g. revoked, account
1958
+ // changed, clock skew). Force a re-auth via refresh-or-login and retry
1959
+ // once. Skipped for the env-token path — nothing to refresh.
1960
+ if (response.status === 401 && !envToken) {
1961
+ this.log('info', 'API returned 401; reauthenticating and retrying once');
1962
+ bearerToken = await this.oauth.reauthenticate();
1963
+ if (config.headers) {
1964
+ config.headers['Authorization'] = `Bearer ${bearerToken}`;
1965
+ }
1966
+ response = await axios(config);
1967
+ this.log('debug', `Retry response status: ${response.status}`);
1968
+ }
1969
+
1979
1970
  return response.data;
1980
1971
 
1981
1972
  } catch (error) {
@@ -2113,7 +2104,7 @@ class MCPServer {
2113
2104
  export async function startServer({ name, version, tools }: { name: string; version: string; tools: OpenApiTool[] }): Promise<void> {
2114
2105
  const argv = minimist(process.argv.slice(2), {
2115
2106
  string: ['host'],
2116
- boolean: ['stdio', 'http', 'help'],
2107
+ boolean: ['stdio', 'http', 'help', 'login', 'logout'],
2117
2108
  default: { host: '127.0.0.1', port: 8100, stdio: true }
2118
2109
  });
2119
2110
  if (argv.help) {
@@ -2121,17 +2112,45 @@ export async function startServer({ name, version, tools }: { name: string; vers
2121
2112
  ${name}
2122
2113
  Usage: ${name} [options]
2123
2114
  Options:
2124
- --http Use HTTP streaming transport
2125
- --stdio Use standard input/output transport (default)
2126
- --host <host> Host to bind to (default: 127.0.0.1)
2127
- --port <port> Port to bind to (default: 8100)
2128
- --help Show this help message
2115
+ --http Use HTTP streaming transport (requires HOSTINGER_API_TOKEN env var)
2116
+ --stdio Use standard input/output transport (default)
2117
+ --host <host> Host to bind to (default: 127.0.0.1)
2118
+ --port <port> Port to bind to (default: 8100)
2119
+ --login Run OAuth sign-in flow and exit
2120
+ --logout Revoke stored OAuth credentials and exit
2121
+ --help Show this help message
2129
2122
  Environment Variables:
2130
- API_TOKEN Your Hostinger API token (required)
2131
- DEBUG Enable debug logging (true/false)
2123
+ HOSTINGER_API_TOKEN Hostinger API token (overrides OAuth when set)
2124
+ API_TOKEN Deprecated alias for HOSTINGER_API_TOKEN (will be removed in a future version)
2125
+ OAUTH_ISSUER OAuth server base URL (default: https://auth.hostinger.com)
2126
+ DEBUG Enable debug logging (true/false)
2132
2127
  `);
2133
2128
  process.exit(0);
2134
2129
  }
2130
+
2131
+ if (argv.login) {
2132
+ const provider = new OAuthProvider();
2133
+ console.error('[OAuth] Starting sign-in flow...');
2134
+ await provider.login();
2135
+ console.error('[OAuth] Sign-in successful. Credentials stored.');
2136
+ process.exit(0);
2137
+ }
2138
+
2139
+ if (argv.logout) {
2140
+ const provider = new OAuthProvider();
2141
+ await provider.logout();
2142
+ console.error('[OAuth] Signed out. Stored credentials revoked and cleared.');
2143
+ process.exit(0);
2144
+ }
2145
+
2146
+ if (argv.http) {
2147
+ const envToken = getEnvToken();
2148
+ if (!envToken) {
2149
+ console.error('[Error] HTTP transport requires the HOSTINGER_API_TOKEN environment variable. OAuth sign-in is only supported in stdio mode.');
2150
+ process.exit(1);
2151
+ }
2152
+ }
2153
+
2135
2154
  const server = new MCPServer({ name, version, tools });
2136
2155
  if (argv.http) {
2137
2156
  await server.startHttp(argv.host, argv.port);
@@ -1570,6 +1570,10 @@ export default [
1570
1570
  "type": "string",
1571
1571
  "description": "surname parameter"
1572
1572
  },
1573
+ "phone": {
1574
+ "type": "string",
1575
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
1576
+ },
1573
1577
  "note": {
1574
1578
  "type": "string",
1575
1579
  "description": "note parameter"
@@ -1787,6 +1791,10 @@ export default [
1787
1791
  "type": "string",
1788
1792
  "description": "surname parameter"
1789
1793
  },
1794
+ "phone": {
1795
+ "type": "string",
1796
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
1797
+ },
1790
1798
  "note": {
1791
1799
  "type": "string",
1792
1800
  "description": "note parameter"
@@ -1580,6 +1580,10 @@ const tools: OpenApiTool[] = [
1580
1580
  "type": "string",
1581
1581
  "description": "surname parameter"
1582
1582
  },
1583
+ "phone": {
1584
+ "type": "string",
1585
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
1586
+ },
1583
1587
  "note": {
1584
1588
  "type": "string",
1585
1589
  "description": "note parameter"
@@ -1797,6 +1801,10 @@ const tools: OpenApiTool[] = [
1797
1801
  "type": "string",
1798
1802
  "description": "surname parameter"
1799
1803
  },
1804
+ "phone": {
1805
+ "type": "string",
1806
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
1807
+ },
1800
1808
  "note": {
1801
1809
  "type": "string",
1802
1810
  "description": "note parameter"
@@ -95,6 +95,10 @@ export default [
95
95
  "type": "string",
96
96
  "description": "surname parameter"
97
97
  },
98
+ "phone": {
99
+ "type": "string",
100
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
101
+ },
98
102
  "note": {
99
103
  "type": "string",
100
104
  "description": "note parameter"
@@ -312,6 +316,10 @@ export default [
312
316
  "type": "string",
313
317
  "description": "surname parameter"
314
318
  },
319
+ "phone": {
320
+ "type": "string",
321
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
322
+ },
315
323
  "note": {
316
324
  "type": "string",
317
325
  "description": "note parameter"
@@ -105,6 +105,10 @@ const tools: OpenApiTool[] = [
105
105
  "type": "string",
106
106
  "description": "surname parameter"
107
107
  },
108
+ "phone": {
109
+ "type": "string",
110
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
111
+ },
108
112
  "note": {
109
113
  "type": "string",
110
114
  "description": "note parameter"
@@ -322,6 +326,10 @@ const tools: OpenApiTool[] = [
322
326
  "type": "string",
323
327
  "description": "surname parameter"
324
328
  },
329
+ "phone": {
330
+ "type": "string",
331
+ "description": "Phone number in E.164 format (leading \"+\" then 7-15 digits)"
332
+ },
325
333
  "note": {
326
334
  "type": "string",
327
335
  "description": "note parameter"
@@ -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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-api-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-api-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-billing-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-dns-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-domains-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-hosting-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-hosting-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-reach-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '0.2.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: '0.1.43', tools });
6
+ startServer({ name: 'hostinger-vps-mcp', version: '0.2.2', tools });
package/types.d.ts CHANGED
@@ -1019,6 +1019,10 @@ the contact will be created with a pending status and a confirmation email will
1019
1019
  * surname parameter
1020
1020
  */
1021
1021
  surname?: string;
1022
+ /**
1023
+ * Phone number in E.164 format (leading "+" then 7-15 digits)
1024
+ */
1025
+ phone?: string;
1022
1026
  /**
1023
1027
  * note parameter
1024
1028
  */
@@ -1129,6 +1133,10 @@ and a confirmation email will be sent.
1129
1133
  * surname parameter
1130
1134
  */
1131
1135
  surname?: string;
1136
+ /**
1137
+ * Phone number in E.164 format (leading "+" then 7-15 digits)
1138
+ */
1139
+ phone?: string;
1132
1140
  /**
1133
1141
  * note parameter
1134
1142
  */