hostinger-api-mcp 0.1.17 → 0.1.18

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/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,11 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "MCP server for Hostinger API",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/hostinger/api-mcp-server.git"
8
8
  },
9
- "mcpName": "io.github.hostinger/hostinger-api-mcp",
10
9
  "license": "MIT",
11
10
  "keywords": [
12
11
  "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.8.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.15",
3425
+ version: "0.1.18",
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.15';
3450
+ headers['User-Agent'] = 'hostinger-mcp-server/0.1.18';
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.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.8.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.17",
3447
+ version: "0.1.18",
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.17';
3472
+ headers['User-Agent'] = 'hostinger-mcp-server/0.1.18';
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
  */
package/server.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
3
- "name": "io.github.hostinger/hostinger-api-mcp",
4
- "title": "Hostinger API MCP",
5
- "description": "MCP server for Hostinger API",
6
- "repository": {
7
- "url": "https://github.com/hostinger/api-mcp-server",
8
- "source": "github"
9
- },
10
- "version": "0.1.17",
11
- "packages": [
12
- {
13
- "registryType": "npm",
14
- "identifier": "hostinger-api-mcp",
15
- "version": "0.1.17",
16
- "transport": {
17
- "type": "stdio"
18
- },
19
- "environmentVariables": [
20
- {
21
- "name": "API_TOKEN",
22
- "description": "Hostinger API token",
23
- "isRequired": true,
24
- "isSecret": true
25
- }
26
- ]
27
- }
28
- ]
29
- }