hostinger-api-mcp 0.1.17 → 0.1.19
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/.github/workflows/build-release.yaml +3 -0
- package/README.md +13 -0
- package/package.json +2 -2
- package/server.js +243 -3
- package/server.json +6 -6
- package/server.ts +243 -3
- package/types.d.ts +21 -0
package/README.md
CHANGED
|
@@ -197,6 +197,19 @@ Deploy a JavaScript application from an archive file to a hosting server. IMPORT
|
|
|
197
197
|
- `archivePath`: Absolute or relative path to the application archive file. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip. If user provides directory path, create archive from it before proceeding. IMPORTANT: the archive must ONLY contain application source files, not the build output, skip node_modules directory. (required)
|
|
198
198
|
- `removeArchive`: Whether to remove the archive file after successful deployment (default: false)
|
|
199
199
|
|
|
200
|
+
### hosting_deployStaticWebsite
|
|
201
|
+
|
|
202
|
+
Deploy a static website from an archive file to a hosting server. IMPORTANT: This tool only works for static websites with no build process. The archive must contain pre-built static files (HTML, CSS, JavaScript, images, etc.) ready to be served. If the website has a package.json file or requires a build command, use hosting_deployJsApplication instead. The archive will be extracted and deployed directly without any build steps. The username will be automatically resolved from the domain.
|
|
203
|
+
|
|
204
|
+
- **Method**: ``
|
|
205
|
+
- **Path**: ``
|
|
206
|
+
|
|
207
|
+
**Parameters**:
|
|
208
|
+
|
|
209
|
+
- `domain`: Domain name associated with the hosting account (e.g., example.com) (required)
|
|
210
|
+
- `archivePath`: Absolute or relative path to the static website archive file. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip. If user provides directory path, create archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mystaticwebsite_20250115_143022.zip) (required)
|
|
211
|
+
- `removeArchive`: Whether to remove the archive file after successful deployment (default: false)
|
|
212
|
+
|
|
200
213
|
### hosting_listJsDeployments
|
|
201
214
|
|
|
202
215
|
List javascript application deployments for checking their status. Use this tool when customer asks for the status of the deployment. This tool retrieves a paginated list of Node.js application deployments for a domain with optional filtering by deployment states.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hostinger-api-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"mcpName": "io.github.hostinger/api-mcp-server",
|
|
4
5
|
"description": "MCP server for Hostinger API",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "https://github.com/hostinger/api-mcp-server.git"
|
|
8
9
|
},
|
|
9
|
-
"mcpName": "io.github.hostinger/hostinger-api-mcp",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"keywords": [
|
|
12
12
|
"hostinger",
|
package/server.js
CHANGED
|
@@ -160,6 +160,39 @@ const TOOLS = [
|
|
|
160
160
|
"templateFileTS": "deploy-javascript-app.template.ts",
|
|
161
161
|
"handlerMethod": "handleJavascriptApplicationDeploy"
|
|
162
162
|
},
|
|
163
|
+
{
|
|
164
|
+
"name": "hosting_deployStaticWebsite",
|
|
165
|
+
"topic": "hosting",
|
|
166
|
+
"description": "Deploy a static website from an archive file to a hosting server. IMPORTANT: This tool only works for static websites with no build process. The archive must contain pre-built static files (HTML, CSS, JavaScript, images, etc.) ready to be served. If the website has a package.json file or requires a build command, use hosting_deployJsApplication instead. The archive will be extracted and deployed directly without any build steps. The username will be automatically resolved from the domain.",
|
|
167
|
+
"method": "",
|
|
168
|
+
"path": "",
|
|
169
|
+
"inputSchema": {
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"domain": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"description": "Domain name associated with the hosting account (e.g., example.com)"
|
|
175
|
+
},
|
|
176
|
+
"archivePath": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"description": "Absolute or relative path to the static website archive file. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip. If user provides directory path, create archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mystaticwebsite_20250115_143022.zip)"
|
|
179
|
+
},
|
|
180
|
+
"removeArchive": {
|
|
181
|
+
"type": "boolean",
|
|
182
|
+
"description": "Whether to remove the archive file after successful deployment (default: false)"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"required": [
|
|
186
|
+
"domain",
|
|
187
|
+
"archivePath"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"security": [],
|
|
191
|
+
"custom": true,
|
|
192
|
+
"templateFile": "deploy-static-website.template.js",
|
|
193
|
+
"templateFileTS": "deploy-static-website.template.ts",
|
|
194
|
+
"handlerMethod": "handleStaticWebsiteDeploy"
|
|
195
|
+
},
|
|
163
196
|
{
|
|
164
197
|
"name": "hosting_listJsDeployments",
|
|
165
198
|
"topic": "hosting",
|
|
@@ -3371,7 +3404,7 @@ const SECURITY_SCHEMES = {
|
|
|
3371
3404
|
|
|
3372
3405
|
/**
|
|
3373
3406
|
* MCP Server for Hostinger API
|
|
3374
|
-
* Generated from OpenAPI spec version 0.
|
|
3407
|
+
* Generated from OpenAPI spec version 0.9.0
|
|
3375
3408
|
*/
|
|
3376
3409
|
class MCPServer {
|
|
3377
3410
|
constructor() {
|
|
@@ -3389,7 +3422,7 @@ class MCPServer {
|
|
|
3389
3422
|
this.server = new Server(
|
|
3390
3423
|
{
|
|
3391
3424
|
name: "hostinger-api-mcp",
|
|
3392
|
-
version: "0.1.
|
|
3425
|
+
version: "0.1.19",
|
|
3393
3426
|
},
|
|
3394
3427
|
{
|
|
3395
3428
|
capabilities: {
|
|
@@ -3414,7 +3447,7 @@ class MCPServer {
|
|
|
3414
3447
|
});
|
|
3415
3448
|
}
|
|
3416
3449
|
|
|
3417
|
-
headers['User-Agent'] = 'hostinger-mcp-server/0.1.
|
|
3450
|
+
headers['User-Agent'] = 'hostinger-mcp-server/0.1.19';
|
|
3418
3451
|
|
|
3419
3452
|
return headers;
|
|
3420
3453
|
}
|
|
@@ -3520,6 +3553,8 @@ class MCPServer {
|
|
|
3520
3553
|
return await this.handleWordpressThemeDeploy(params);
|
|
3521
3554
|
case 'hosting_deployJsApplication':
|
|
3522
3555
|
return await this.handleJavascriptApplicationDeploy(params);
|
|
3556
|
+
case 'hosting_deployStaticWebsite':
|
|
3557
|
+
return await this.handleStaticWebsiteDeploy(params);
|
|
3523
3558
|
case 'hosting_listJsDeployments':
|
|
3524
3559
|
return await this.handleListJavascriptDeployments(params);
|
|
3525
3560
|
case 'hosting_showJsDeploymentLogs':
|
|
@@ -4782,6 +4817,211 @@ class MCPServer {
|
|
|
4782
4817
|
};
|
|
4783
4818
|
}
|
|
4784
4819
|
|
|
4820
|
+
hosting_deployStaticWebsite_validateArchiveFormat(filePath) {
|
|
4821
|
+
const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
|
|
4822
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
4823
|
+
|
|
4824
|
+
for (const ext of validExtensions) {
|
|
4825
|
+
if (fileName.endsWith(`.${ext}`)) {
|
|
4826
|
+
return true;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
return false;
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
hosting_deployStaticWebsite_validateRequiredParams(params) {
|
|
4834
|
+
const { domain, archivePath, removeArchive } = params;
|
|
4835
|
+
|
|
4836
|
+
if (!domain || typeof domain !== 'string') {
|
|
4837
|
+
throw new Error('domain is required and must be a string');
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4840
|
+
if (!archivePath || typeof archivePath !== 'string') {
|
|
4841
|
+
throw new Error('archivePath is required and must be a string');
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
|
|
4845
|
+
throw new Error('removeArchive must be a boolean if provided');
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive) {
|
|
4850
|
+
if (!removeArchive) {
|
|
4851
|
+
return false;
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
try {
|
|
4855
|
+
this.log('info', `Removing archive file: ${archivePath}`);
|
|
4856
|
+
fs.unlinkSync(archivePath);
|
|
4857
|
+
this.log('info', `Successfully removed archive file: ${archivePath}`);
|
|
4858
|
+
return true;
|
|
4859
|
+
} catch (error) {
|
|
4860
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4861
|
+
this.log('error', `Failed to remove archive file: ${errorMessage}`);
|
|
4862
|
+
return false;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
hosting_deployStaticWebsite_validateArchiveFile(archivePath) {
|
|
4867
|
+
if (!fs.existsSync(archivePath)) {
|
|
4868
|
+
throw new Error(`Archive file not found: ${archivePath}`);
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
const archiveStats = fs.statSync(archivePath);
|
|
4872
|
+
if (!archiveStats.isFile()) {
|
|
4873
|
+
throw new Error(`Archive path is not a file: ${archivePath}`);
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
if (!this.hosting_deployStaticWebsite_validateArchiveFormat(archivePath)) {
|
|
4877
|
+
throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
async hosting_deployStaticWebsite_triggerDeploy(username, domain, archivePath) {
|
|
4882
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
4883
|
+
const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/deploy`, baseUrl).toString();
|
|
4884
|
+
|
|
4885
|
+
try {
|
|
4886
|
+
const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
|
|
4887
|
+
if (!bearerToken) {
|
|
4888
|
+
throw new Error('API_TOKEN environment variable not found');
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
const archiveBasename = path.basename(archivePath);
|
|
4892
|
+
const deployData = {
|
|
4893
|
+
archive_path: archiveBasename
|
|
4894
|
+
};
|
|
4895
|
+
|
|
4896
|
+
const config = {
|
|
4897
|
+
method: 'post',
|
|
4898
|
+
url,
|
|
4899
|
+
headers: {
|
|
4900
|
+
...this.headers,
|
|
4901
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
4902
|
+
'Content-Type': 'application/json'
|
|
4903
|
+
},
|
|
4904
|
+
data: deployData,
|
|
4905
|
+
timeout: 60000,
|
|
4906
|
+
validateStatus: function (status) {
|
|
4907
|
+
return status < 500;
|
|
4908
|
+
}
|
|
4909
|
+
};
|
|
4910
|
+
|
|
4911
|
+
const response = await axios(config);
|
|
4912
|
+
|
|
4913
|
+
if (response.status !== 200) {
|
|
4914
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
this.log('info', `Successfully triggered deployment for ${domain}`);
|
|
4918
|
+
return response.data;
|
|
4919
|
+
|
|
4920
|
+
} catch (error) {
|
|
4921
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4922
|
+
this.log('error', `Failed to trigger deployment: ${errorMessage}`);
|
|
4923
|
+
|
|
4924
|
+
if (axios.isAxiosError(error)) {
|
|
4925
|
+
const responseData = error.response?.data;
|
|
4926
|
+
const responseStatus = error.response?.status;
|
|
4927
|
+
this.log('error', 'API Error Details:', {
|
|
4928
|
+
status: responseStatus,
|
|
4929
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
throw error;
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
async handleStaticWebsiteDeploy(params) {
|
|
4938
|
+
const { domain, archivePath, removeArchive = false } = params;
|
|
4939
|
+
|
|
4940
|
+
this.hosting_deployStaticWebsite_validateRequiredParams(params);
|
|
4941
|
+
this.hosting_deployStaticWebsite_validateArchiveFile(archivePath);
|
|
4942
|
+
|
|
4943
|
+
this.log('info', `Resolving username from domain: ${domain}`);
|
|
4944
|
+
const username = await this.resolveUsername(domain);
|
|
4945
|
+
|
|
4946
|
+
this.log('info', `Starting archive upload for ${domain}`);
|
|
4947
|
+
|
|
4948
|
+
let uploadCredentials;
|
|
4949
|
+
try {
|
|
4950
|
+
uploadCredentials = await this.fetchUploadCredentials(username, domain);
|
|
4951
|
+
} catch (error) {
|
|
4952
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4953
|
+
throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
|
|
4957
|
+
|
|
4958
|
+
if (!uploadUrl || !authToken || !authRestToken) {
|
|
4959
|
+
throw new Error('Invalid upload credentials received from API');
|
|
4960
|
+
}
|
|
4961
|
+
|
|
4962
|
+
const archiveBasename = path.basename(archivePath);
|
|
4963
|
+
let uploadResult;
|
|
4964
|
+
try {
|
|
4965
|
+
const stats = fs.statSync(archivePath);
|
|
4966
|
+
uploadResult = await this.uploadFile(
|
|
4967
|
+
archivePath,
|
|
4968
|
+
archiveBasename,
|
|
4969
|
+
uploadUrl,
|
|
4970
|
+
authRestToken,
|
|
4971
|
+
authToken
|
|
4972
|
+
);
|
|
4973
|
+
|
|
4974
|
+
this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
|
|
4975
|
+
} catch (error) {
|
|
4976
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4977
|
+
throw new Error(`Failed to upload archive: ${errorMessage}`);
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4980
|
+
let deployResult;
|
|
4981
|
+
try {
|
|
4982
|
+
this.log('info', `Triggering deployment for ${domain}`);
|
|
4983
|
+
deployResult = await this.hosting_deployStaticWebsite_triggerDeploy(username, domain, archivePath);
|
|
4984
|
+
} catch (error) {
|
|
4985
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4986
|
+
this.log('error', `Failed to trigger deployment: ${errorMessage}`);
|
|
4987
|
+
const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
|
|
4988
|
+
|
|
4989
|
+
return {
|
|
4990
|
+
upload: {
|
|
4991
|
+
status: 'success',
|
|
4992
|
+
data: {
|
|
4993
|
+
filename: uploadResult.filename
|
|
4994
|
+
}
|
|
4995
|
+
},
|
|
4996
|
+
deploy: {
|
|
4997
|
+
status: 'error',
|
|
4998
|
+
error: errorMessage
|
|
4999
|
+
},
|
|
5000
|
+
removeArchive: {
|
|
5001
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
5002
|
+
}
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
5005
|
+
|
|
5006
|
+
const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
|
|
5007
|
+
|
|
5008
|
+
return {
|
|
5009
|
+
upload: {
|
|
5010
|
+
status: 'success',
|
|
5011
|
+
data: {
|
|
5012
|
+
filename: uploadResult.filename
|
|
5013
|
+
}
|
|
5014
|
+
},
|
|
5015
|
+
deploy: {
|
|
5016
|
+
status: 'success',
|
|
5017
|
+
data: deployResult
|
|
5018
|
+
},
|
|
5019
|
+
removeArchive: {
|
|
5020
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
5021
|
+
}
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
5024
|
+
|
|
4785
5025
|
hosting_listJsDeployments_validateRequiredParams(params) {
|
|
4786
5026
|
const { domain } = params;
|
|
4787
5027
|
|
package/server.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
|
|
3
|
-
"name": "io.github.hostinger/
|
|
4
|
-
"title": "Hostinger API MCP",
|
|
3
|
+
"name": "io.github.hostinger/api-mcp-server",
|
|
5
4
|
"description": "MCP server for Hostinger API",
|
|
6
5
|
"repository": {
|
|
7
6
|
"url": "https://github.com/hostinger/api-mcp-server",
|
|
8
7
|
"source": "github"
|
|
9
8
|
},
|
|
10
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.19",
|
|
11
10
|
"packages": [
|
|
12
11
|
{
|
|
13
12
|
"registryType": "npm",
|
|
14
13
|
"identifier": "hostinger-api-mcp",
|
|
15
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.19",
|
|
16
15
|
"transport": {
|
|
17
16
|
"type": "stdio"
|
|
18
17
|
},
|
|
@@ -21,9 +20,10 @@
|
|
|
21
20
|
"name": "API_TOKEN",
|
|
22
21
|
"description": "Hostinger API token",
|
|
23
22
|
"isRequired": true,
|
|
24
|
-
"isSecret": true
|
|
23
|
+
"isSecret": true,
|
|
24
|
+
"format": "string"
|
|
25
25
|
}
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
|
-
}
|
|
29
|
+
}
|
package/server.ts
CHANGED
|
@@ -178,6 +178,39 @@ const TOOLS: OpenApiTool[] = [
|
|
|
178
178
|
"templateFileTS": "deploy-javascript-app.template.ts",
|
|
179
179
|
"handlerMethod": "handleJavascriptApplicationDeploy"
|
|
180
180
|
},
|
|
181
|
+
{
|
|
182
|
+
"name": "hosting_deployStaticWebsite",
|
|
183
|
+
"topic": "hosting",
|
|
184
|
+
"description": "Deploy a static website from an archive file to a hosting server. IMPORTANT: This tool only works for static websites with no build process. The archive must contain pre-built static files (HTML, CSS, JavaScript, images, etc.) ready to be served. If the website has a package.json file or requires a build command, use hosting_deployJsApplication instead. The archive will be extracted and deployed directly without any build steps. The username will be automatically resolved from the domain.",
|
|
185
|
+
"method": "",
|
|
186
|
+
"path": "",
|
|
187
|
+
"inputSchema": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"properties": {
|
|
190
|
+
"domain": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Domain name associated with the hosting account (e.g., example.com)"
|
|
193
|
+
},
|
|
194
|
+
"archivePath": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "Absolute or relative path to the static website archive file. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip. If user provides directory path, create archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mystaticwebsite_20250115_143022.zip)"
|
|
197
|
+
},
|
|
198
|
+
"removeArchive": {
|
|
199
|
+
"type": "boolean",
|
|
200
|
+
"description": "Whether to remove the archive file after successful deployment (default: false)"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"required": [
|
|
204
|
+
"domain",
|
|
205
|
+
"archivePath"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"security": [],
|
|
209
|
+
"custom": true,
|
|
210
|
+
"templateFile": "deploy-static-website.template.js",
|
|
211
|
+
"templateFileTS": "deploy-static-website.template.ts",
|
|
212
|
+
"handlerMethod": "handleStaticWebsiteDeploy"
|
|
213
|
+
},
|
|
181
214
|
{
|
|
182
215
|
"name": "hosting_listJsDeployments",
|
|
183
216
|
"topic": "hosting",
|
|
@@ -3389,7 +3422,7 @@ const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
|
|
|
3389
3422
|
|
|
3390
3423
|
/**
|
|
3391
3424
|
* MCP Server for Hostinger API
|
|
3392
|
-
* Generated from OpenAPI spec version 0.
|
|
3425
|
+
* Generated from OpenAPI spec version 0.9.0
|
|
3393
3426
|
*/
|
|
3394
3427
|
class MCPServer {
|
|
3395
3428
|
private server: Server;
|
|
@@ -3411,7 +3444,7 @@ class MCPServer {
|
|
|
3411
3444
|
this.server = new Server(
|
|
3412
3445
|
{
|
|
3413
3446
|
name: "hostinger-api-mcp",
|
|
3414
|
-
version: "0.1.
|
|
3447
|
+
version: "0.1.19",
|
|
3415
3448
|
},
|
|
3416
3449
|
{
|
|
3417
3450
|
capabilities: {
|
|
@@ -3436,7 +3469,7 @@ class MCPServer {
|
|
|
3436
3469
|
});
|
|
3437
3470
|
}
|
|
3438
3471
|
|
|
3439
|
-
headers['User-Agent'] = 'hostinger-mcp-server/0.1.
|
|
3472
|
+
headers['User-Agent'] = 'hostinger-mcp-server/0.1.19';
|
|
3440
3473
|
|
|
3441
3474
|
return headers;
|
|
3442
3475
|
}
|
|
@@ -3541,6 +3574,8 @@ class MCPServer {
|
|
|
3541
3574
|
return await this.handleWordpressThemeDeploy(params);
|
|
3542
3575
|
case 'hosting_deployJsApplication':
|
|
3543
3576
|
return await this.handleJavascriptApplicationDeploy(params);
|
|
3577
|
+
case 'hosting_deployStaticWebsite':
|
|
3578
|
+
return await this.handleStaticWebsiteDeploy(params);
|
|
3544
3579
|
case 'hosting_listJsDeployments':
|
|
3545
3580
|
return await this.handleListJavascriptDeployments(params);
|
|
3546
3581
|
case 'hosting_showJsDeploymentLogs':
|
|
@@ -4809,6 +4844,211 @@ class MCPServer {
|
|
|
4809
4844
|
};
|
|
4810
4845
|
}
|
|
4811
4846
|
|
|
4847
|
+
private hosting_deployStaticWebsite_validateArchiveFormat(filePath: string): boolean {
|
|
4848
|
+
const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
|
|
4849
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
4850
|
+
|
|
4851
|
+
for (const ext of validExtensions) {
|
|
4852
|
+
if (fileName.endsWith(`.${ext}`)) {
|
|
4853
|
+
return true;
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
return false;
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
private hosting_deployStaticWebsite_validateRequiredParams(params: Record<string, any>): void {
|
|
4861
|
+
const { domain, archivePath, removeArchive } = params;
|
|
4862
|
+
|
|
4863
|
+
if (!domain || typeof domain !== 'string') {
|
|
4864
|
+
throw new Error('domain is required and must be a string');
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
if (!archivePath || typeof archivePath !== 'string') {
|
|
4868
|
+
throw new Error('archivePath is required and must be a string');
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
|
|
4872
|
+
throw new Error('removeArchive must be a boolean if provided');
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
private hosting_deployStaticWebsite_removeArchive(archivePath: string, removeArchive: boolean): boolean {
|
|
4877
|
+
if (!removeArchive) {
|
|
4878
|
+
return false;
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
try {
|
|
4882
|
+
this.log('info', `Removing archive file: ${archivePath}`);
|
|
4883
|
+
fs.unlinkSync(archivePath);
|
|
4884
|
+
this.log('info', `Successfully removed archive file: ${archivePath}`);
|
|
4885
|
+
return true;
|
|
4886
|
+
} catch (error) {
|
|
4887
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4888
|
+
this.log('error', `Failed to remove archive file: ${errorMessage}`);
|
|
4889
|
+
return false;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
private hosting_deployStaticWebsite_validateArchiveFile(archivePath: string): void {
|
|
4894
|
+
if (!fs.existsSync(archivePath)) {
|
|
4895
|
+
throw new Error(`Archive file not found: ${archivePath}`);
|
|
4896
|
+
}
|
|
4897
|
+
|
|
4898
|
+
const archiveStats = fs.statSync(archivePath);
|
|
4899
|
+
if (!archiveStats.isFile()) {
|
|
4900
|
+
throw new Error(`Archive path is not a file: ${archivePath}`);
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
if (!this.hosting_deployStaticWebsite_validateArchiveFormat(archivePath)) {
|
|
4904
|
+
throw new Error('Invalid archive format. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip');
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
|
|
4908
|
+
private async hosting_deployStaticWebsite_triggerDeploy(username: string, domain: string, archivePath: string): Promise<any> {
|
|
4909
|
+
const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
|
|
4910
|
+
const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/deploy`, baseUrl).toString();
|
|
4911
|
+
|
|
4912
|
+
try {
|
|
4913
|
+
const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN'];
|
|
4914
|
+
if (!bearerToken) {
|
|
4915
|
+
throw new Error('API_TOKEN environment variable not found');
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4918
|
+
const archiveBasename = path.basename(archivePath);
|
|
4919
|
+
const deployData = {
|
|
4920
|
+
archive_path: archiveBasename
|
|
4921
|
+
};
|
|
4922
|
+
|
|
4923
|
+
const config: AxiosRequestConfig = {
|
|
4924
|
+
method: 'post',
|
|
4925
|
+
url,
|
|
4926
|
+
headers: {
|
|
4927
|
+
...this.headers,
|
|
4928
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
4929
|
+
'Content-Type': 'application/json'
|
|
4930
|
+
},
|
|
4931
|
+
data: deployData,
|
|
4932
|
+
timeout: 60000,
|
|
4933
|
+
validateStatus: function (status: number): boolean {
|
|
4934
|
+
return status < 500;
|
|
4935
|
+
}
|
|
4936
|
+
};
|
|
4937
|
+
|
|
4938
|
+
const response = await axios(config);
|
|
4939
|
+
|
|
4940
|
+
if (response.status !== 200) {
|
|
4941
|
+
throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
|
|
4942
|
+
}
|
|
4943
|
+
|
|
4944
|
+
this.log('info', `Successfully triggered deployment for ${domain}`);
|
|
4945
|
+
return response.data;
|
|
4946
|
+
|
|
4947
|
+
} catch (error) {
|
|
4948
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4949
|
+
this.log('error', `Failed to trigger deployment: ${errorMessage}`);
|
|
4950
|
+
|
|
4951
|
+
if (axios.isAxiosError(error)) {
|
|
4952
|
+
const responseData = error.response?.data;
|
|
4953
|
+
const responseStatus = error.response?.status;
|
|
4954
|
+
this.log('error', 'API Error Details:', {
|
|
4955
|
+
status: responseStatus,
|
|
4956
|
+
data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
throw error;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
private async handleStaticWebsiteDeploy(params: Record<string, any>): Promise<any> {
|
|
4965
|
+
const { domain, archivePath, removeArchive = false } = params;
|
|
4966
|
+
|
|
4967
|
+
this.hosting_deployStaticWebsite_validateRequiredParams(params);
|
|
4968
|
+
this.hosting_deployStaticWebsite_validateArchiveFile(archivePath);
|
|
4969
|
+
|
|
4970
|
+
this.log('info', `Resolving username from domain: ${domain}`);
|
|
4971
|
+
const username = await this.resolveUsername(domain);
|
|
4972
|
+
|
|
4973
|
+
this.log('info', `Starting archive upload for ${domain}`);
|
|
4974
|
+
|
|
4975
|
+
let uploadCredentials: any;
|
|
4976
|
+
try {
|
|
4977
|
+
uploadCredentials = await this.fetchUploadCredentials(username, domain);
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4980
|
+
throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
|
|
4984
|
+
|
|
4985
|
+
if (!uploadUrl || !authToken || !authRestToken) {
|
|
4986
|
+
throw new Error('Invalid upload credentials received from API');
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
const archiveBasename = path.basename(archivePath);
|
|
4990
|
+
let uploadResult: any;
|
|
4991
|
+
try {
|
|
4992
|
+
const stats = fs.statSync(archivePath);
|
|
4993
|
+
uploadResult = await this.uploadFile(
|
|
4994
|
+
archivePath,
|
|
4995
|
+
archiveBasename,
|
|
4996
|
+
uploadUrl,
|
|
4997
|
+
authRestToken,
|
|
4998
|
+
authToken
|
|
4999
|
+
);
|
|
5000
|
+
|
|
5001
|
+
this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
|
|
5002
|
+
} catch (error) {
|
|
5003
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5004
|
+
throw new Error(`Failed to upload archive: ${errorMessage}`);
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
let deployResult: any;
|
|
5008
|
+
try {
|
|
5009
|
+
this.log('info', `Triggering deployment for ${domain}`);
|
|
5010
|
+
deployResult = await this.hosting_deployStaticWebsite_triggerDeploy(username, domain, archivePath);
|
|
5011
|
+
} catch (error) {
|
|
5012
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5013
|
+
this.log('error', `Failed to trigger deployment: ${errorMessage}`);
|
|
5014
|
+
const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
|
|
5015
|
+
|
|
5016
|
+
return {
|
|
5017
|
+
upload: {
|
|
5018
|
+
status: 'success',
|
|
5019
|
+
data: {
|
|
5020
|
+
filename: uploadResult.filename
|
|
5021
|
+
}
|
|
5022
|
+
},
|
|
5023
|
+
deploy: {
|
|
5024
|
+
status: 'error',
|
|
5025
|
+
error: errorMessage
|
|
5026
|
+
},
|
|
5027
|
+
removeArchive: {
|
|
5028
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
const archiveRemoved = this.hosting_deployStaticWebsite_removeArchive(archivePath, removeArchive);
|
|
5034
|
+
|
|
5035
|
+
return {
|
|
5036
|
+
upload: {
|
|
5037
|
+
status: 'success',
|
|
5038
|
+
data: {
|
|
5039
|
+
filename: uploadResult.filename
|
|
5040
|
+
}
|
|
5041
|
+
},
|
|
5042
|
+
deploy: {
|
|
5043
|
+
status: 'success',
|
|
5044
|
+
data: deployResult
|
|
5045
|
+
},
|
|
5046
|
+
removeArchive: {
|
|
5047
|
+
status: archiveRemoved ? 'success' : 'skipped'
|
|
5048
|
+
}
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
|
|
4812
5052
|
private hosting_listJsDeployments_validateRequiredParams(params: Record<string, any>): void {
|
|
4813
5053
|
const { domain } = params;
|
|
4814
5054
|
|
package/types.d.ts
CHANGED
|
@@ -92,6 +92,27 @@ export interface APITools {
|
|
|
92
92
|
response: any; // Response structure will depend on the API
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Deploy a static website from an archive file to a hosting server. IMPORTANT: This tool only works for static websites with no build process. The archive must contain pre-built static files (HTML, CSS, JavaScript, images, etc.) ready to be served. If the website has a package.json file or requires a build command, use hosting_deployJsApplication instead. The archive will be extracted and deployed directly without any build steps. The username will be automatically resolved from the domain.
|
|
97
|
+
*/
|
|
98
|
+
"hosting_deployStaticWebsite": {
|
|
99
|
+
params: {
|
|
100
|
+
/**
|
|
101
|
+
* Domain name associated with the hosting account (e.g., example.com)
|
|
102
|
+
*/
|
|
103
|
+
domain: string;
|
|
104
|
+
/**
|
|
105
|
+
* Absolute or relative path to the static website archive file. Supported formats: zip, tar, tar.gz, tgz, 7z, gz, gzip. If user provides directory path, create archive from it before proceeding using EXACTLY this naming pattern: directoryname_YYYYMMDD_HHMMSS.zip (e.g., mystaticwebsite_20250115_143022.zip)
|
|
106
|
+
*/
|
|
107
|
+
archivePath: string;
|
|
108
|
+
/**
|
|
109
|
+
* Whether to remove the archive file after successful deployment (default: false)
|
|
110
|
+
*/
|
|
111
|
+
removeArchive?: boolean;
|
|
112
|
+
};
|
|
113
|
+
response: any; // Response structure will depend on the API
|
|
114
|
+
};
|
|
115
|
+
|
|
95
116
|
/**
|
|
96
117
|
* List javascript application deployments for checking their status. Use this tool when customer asks for the status of the deployment. This tool retrieves a paginated list of Node.js application deployments for a domain with optional filtering by deployment states.
|
|
97
118
|
*/
|