hostinger-api-mcp 0.1.43 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/README.md +40 -2
- package/package.json +1 -1
- package/src/core/oauth-error.html +90 -0
- package/src/core/oauth-success.html +77 -0
- package/src/core/oauth.js +392 -0
- package/src/core/oauth.ts +362 -0
- package/src/core/runtime.js +66 -54
- package/src/core/runtime.ts +70 -52
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
package/src/core/runtime.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
CallToolRequestSchema,
|
|
13
13
|
Tool,
|
|
14
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import { OAuthProvider } 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";
|
|
@@ -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. API_TOKEN env var takes precedence; otherwise the
|
|
116
|
+
* 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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1950
|
-
|
|
1927
|
+
const envToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
|
|
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
|
-
|
|
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,44 @@ 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
|
|
2115
|
+
--http Use HTTP streaming transport (requires API_TOKEN env var)
|
|
2125
2116
|
--stdio Use standard input/output transport (default)
|
|
2126
2117
|
--host <host> Host to bind to (default: 127.0.0.1)
|
|
2127
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
|
|
2128
2121
|
--help Show this help message
|
|
2129
2122
|
Environment Variables:
|
|
2130
|
-
API_TOKEN
|
|
2123
|
+
API_TOKEN Hostinger API token (overrides OAuth when set)
|
|
2124
|
+
OAUTH_ISSUER OAuth server base URL (default: https://auth.hostinger.com)
|
|
2131
2125
|
DEBUG Enable debug logging (true/false)
|
|
2132
2126
|
`);
|
|
2133
2127
|
process.exit(0);
|
|
2134
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
if (argv.login) {
|
|
2131
|
+
const provider = new OAuthProvider();
|
|
2132
|
+
console.error('[OAuth] Starting sign-in flow...');
|
|
2133
|
+
await provider.login();
|
|
2134
|
+
console.error('[OAuth] Sign-in successful. Credentials stored.');
|
|
2135
|
+
process.exit(0);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
if (argv.logout) {
|
|
2139
|
+
const provider = new OAuthProvider();
|
|
2140
|
+
await provider.logout();
|
|
2141
|
+
console.error('[OAuth] Signed out. Stored credentials revoked and cleared.');
|
|
2142
|
+
process.exit(0);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
if (argv.http) {
|
|
2146
|
+
const envToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
|
|
2147
|
+
if (!envToken) {
|
|
2148
|
+
console.error('[Error] HTTP transport requires the API_TOKEN environment variable. OAuth sign-in is only supported in stdio mode.');
|
|
2149
|
+
process.exit(1);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2135
2153
|
const server = new MCPServer({ name, version, tools });
|
|
2136
2154
|
if (argv.http) {
|
|
2137
2155
|
await server.startHttp(argv.host, argv.port);
|
package/src/servers/all.js
CHANGED
package/src/servers/all.ts
CHANGED
package/src/servers/billing.js
CHANGED
package/src/servers/billing.ts
CHANGED
package/src/servers/dns.js
CHANGED
package/src/servers/dns.ts
CHANGED
package/src/servers/domains.js
CHANGED
package/src/servers/domains.ts
CHANGED
package/src/servers/hosting.js
CHANGED
package/src/servers/hosting.ts
CHANGED
package/src/servers/reach.js
CHANGED
package/src/servers/reach.ts
CHANGED
package/src/servers/vps.js
CHANGED
package/src/servers/vps.ts
CHANGED