Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hostinger-connector",
"displayName": "Hostinger Connector",
"version": "0.3.0",
"version": "0.3.1",
"description": "MCP plugin to deploy and manage Hostinger websites, WordPress, domains, DNS, VPS, and subscriptions from inside Cursor.",
"author": "Hostinger",
"license": "MIT",
Expand Down
48 changes: 0 additions & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,51 +36,3 @@ jobs:

- name: Check hook script is executable
run: test -x scripts/check-no-token-leak.sh

# The MCP server ships new tools regularly. This is advisory: it tells us the
# checked-in catalog has fallen behind, without failing PRs that didn't cause it.
catalog-drift:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Regenerate the catalog from the latest published server
run: node scripts/sync-mcp-tools.mjs

- name: Report drift
run: |
if git diff --quiet -- scripts/mcp-tools.json; then
echo "Catalog is up to date with hostinger-api-mcp@latest."
else
echo "::warning::scripts/mcp-tools.json is behind hostinger-api-mcp@latest."
echo "Run 'node scripts/sync-mcp-tools.mjs' and commit the result."
git --no-pager diff --stat -- scripts/mcp-tools.json
fi

skills-drift:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Regenerate skills from the latest published server
run: node scripts/sync-skills.mjs

- name: Report drift
run: |
if [ -z "$(git status --porcelain -- skills)" ]; then
echo "Skills are up to date with hostinger-api-mcp@latest."
else
echo "::warning::skills/ is behind hostinger-api-mcp@latest."
echo "Run 'node scripts/sync-skills.mjs' and commit the result."
git status --short -- skills
fi
107 changes: 107 additions & 0 deletions .github/workflows/mcp-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: mcp-sync

# Opens (or refreshes) one PR that brings skills/ and the operation catalog up
# to a published @hostinger/mcp release. Every run rebuilds from main, so
# several releases between merges collapse into one PR and one bump. If the
# release renamed operations the hand-written rules/agents/commands use, CI's
# check-tool-names fails on the PR; fix those names on the PR branch.

on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
inputs:
version:
description: "@hostinger/mcp version (default: latest)"
required: false
type: string

concurrency:
group: mcp-sync
cancel-in-progress: false

permissions:
contents: read

jobs:
sync:
runs-on: ubuntu-latest
steps:
# PRs opened with GITHUB_TOKEN don't trigger ci.yml, so use the App.
- name: Get GitHub token
uses: actions/create-github-app-token@v2
id: token
with:
app-id: ${{ secrets.HOSTINGERBOT_APP_ID }}
private-key: ${{ secrets.HOSTINGERBOT_PRIVATE_KEY }}

- uses: actions/checkout@v4
with:
ref: main

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Resolve version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
V=$(npm view "@hostinger/mcp@${INPUT_VERSION:-latest}" version)
if ! [[ "$V" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::Unresolvable @hostinger/mcp version '${INPUT_VERSION:-latest}'"; exit 1; fi
echo "version=$V" >> "$GITHUB_OUTPUT"

- name: Sync skills and operation catalog
env:
V: ${{ steps.version.outputs.version }}
run: |
node scripts/sync-skills.mjs "$V"
node scripts/sync-mcp-tools.mjs "$V"
# mcp-tools.json records the source version; a release that changes no
# operation would otherwise open a PR for that one line.
# shellcheck disable=SC2143
if [ -z "$(git diff -U0 -- scripts/mcp-tools.json | grep '^[-+] ' | grep -v '"version":')" ]; then
git checkout -- scripts/mcp-tools.json
fi

# No early exit on an empty diff: create-pull-request then closes a stale
# chore/mcp-sync PR, which is what we want once main has caught up.
- name: Bump version and CHANGELOG
id: bump
env:
V: ${{ steps.version.outputs.version }}
run: |
git add -A
if git diff --cached --quiet; then
echo "Already in sync with @hostinger/mcp $V."
exit 0
fi
CHANGES=$(git diff --cached --name-only | sed -E \
-e 's#^skills/([^/]+)/.*#\1#' \
-e 's#^scripts/mcp-tools\.json$#operation catalog#' | sort -u | paste -sd, - | sed 's/,/, /g')
OLD=$(jq -r .version .cursor-plugin/plugin.json)
IFS=. read -r MAJOR MINOR PATCH <<< "$OLD"
NEW="$MAJOR.$MINOR.$((PATCH + 1))"
jq --arg v "$NEW" '.version = $v' .cursor-plugin/plugin.json > plugin.tmp
mv plugin.tmp .cursor-plugin/plugin.json
sed -i "s/plugin;cursor;$OLD/plugin;cursor;$NEW/g" mcp.json
ENTRY="## [$NEW] - $(date -u +%F)\n\n### Changed\n\n- Synced with \`@hostinger/mcp\` $V: $CHANGES\n"
awk -v e="$ENTRY" 'NR==2 { print; printf "%s\n", e; next } { print }' CHANGELOG.md > CHANGELOG.tmp
mv CHANGELOG.tmp CHANGELOG.md
echo "new=$NEW" >> "$GITHUB_OUTPUT"

- name: Open or update the sync PR
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.token.outputs.token }}
branch: chore/mcp-sync
base: main
delete-branch: true
sign-commits: true
add-paths: .
commit-message: "chore: sync with @hostinger/mcp ${{ steps.version.outputs.version }}"
title: "chore: sync with @hostinger/mcp ${{ steps.version.outputs.version }}"
body: |
Synced with `@hostinger/mcp` ${{ steps.version.outputs.version }}; version bumped to ${{ steps.bump.outputs.new }}.
If CI's check-tool-names fails, the release renamed operations the hand-written rules/agents/commands use — fix them on this branch before merging.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [0.3.1] - 2026-10-02

### Changed

- MCP servers run from `@hostinger/mcp` instead of the legacy `hostinger-api-mcp` alias. Same servers and binaries.
- Skills and the operation catalog synced with `@hostinger/mcp` 2.7.0.
- New MCP server releases now arrive as an automated sync PR; the advisory `catalog-drift` and `skills-drift` jobs are gone.

## [0.3.0] - 2026-09-30

Catches the plugin up with `hostinger-api-mcp` 2.5.0, which renamed every operation after the CLI commands (2.1.0) and consolidated each server to `search`, `execute` and `multi-execute` (2.0.0).
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Official Cursor plugin for [Hostinger](https://hostinger.com/) — deploy and manage Hostinger websites, WordPress, Agency Plan sites, domains, DNS, VPS, and subscriptions without leaving Cursor.

The plugin wires the official [`hostinger-api-mcp`](https://www.npmjs.com/package/hostinger-api-mcp) servers into Cursor, plus a set of skills, rules, an agent, and a command so the agent can take real actions on your Hostinger account.
The plugin wires the official [`@hostinger/mcp`](https://www.npmjs.com/package/@hostinger/mcp) servers into Cursor, plus a set of skills, rules, an agent, and a command so the agent can take real actions on your Hostinger account.

---

Expand Down Expand Up @@ -50,8 +50,8 @@ Access tokens refresh automatically, and the credentials are shared across every
To sign in ahead of time, or to sign out:

```bash
npx --package=hostinger-api-mcp@latest hostinger-hosting-mcp --login
npx --package=hostinger-api-mcp@latest hostinger-hosting-mcp --logout
npx --package=@hostinger/mcp@latest hostinger-hosting-mcp --login
npx --package=@hostinger/mcp@latest hostinger-hosting-mcp --logout
```

### API token (optional)
Expand Down Expand Up @@ -89,7 +89,7 @@ Each product area runs as its own MCP server, so you can disable areas you don't
| `hostinger-ecommerce` | `hostinger-ecommerce-mcp` | 29 | Stores, products, sales channels, shipping |
| `hostinger-vps` | `hostinger-vps-mcp` | 64 | Virtual machines, firewalls, snapshots, backups, SSH keys, metrics |

`hostinger-api-mcp` also publishes `hostinger-mail-mcp` and `hostinger-horizons-mcp`, which the plugin doesn't wire up.
`@hostinger/mcp` also publishes `hostinger-mail-mcp` and `hostinger-horizons-mcp`, which the plugin doesn't wire up.

Operation names follow the API — `hosting_websites_list`, `dns_records_list`, `billing_subscriptions_list` — and the prefix tells you which server owns the operation. For the full catalog, see [`scripts/mcp-tools.json`](scripts/mcp-tools.json) or [hostinger/api-mcp-server](https://github.com/hostinger/api-mcp-server).

Expand Down Expand Up @@ -159,14 +159,14 @@ node scripts/sync-skills.mjs 2.5.0
node scripts/sync-skills.mjs ../public-api-generator/mcp/assets/skills
```

`scripts/mcp-tools.json` is a checked-in snapshot of the published server's operation catalog, so `check-tool-names.mjs` runs offline in CI. `skills/` is generated the same way and is replaced wholesale on every sync. CI runs advisory jobs that flag when either has fallen behind `hostinger-api-mcp@latest`.
`scripts/mcp-tools.json` is a checked-in snapshot of the published server's operation catalog, so `check-tool-names.mjs` runs offline in CI. `skills/` is generated the same way and is replaced wholesale on every sync. The `mcp-sync` workflow (daily, or on demand from the Actions tab) refreshes both from the latest `@hostinger/mcp` release and opens a `chore/mcp-sync` PR.

---

## Links

- MCP server source: https://github.com/hostinger/api-mcp-server
- MCP server on npm: https://www.npmjs.com/package/hostinger-api-mcp
- MCP server on npm: https://www.npmjs.com/package/@hostinger/mcp
- Hostinger VS Code extension: https://open-vsx.org/extension/hostinger/hostinger-connector
- Hostinger API docs: https://developers.hostinger.com
- Hostinger: https://hostinger.com/
Expand Down
36 changes: 18 additions & 18 deletions mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,48 @@
"mcpServers": {
"hostinger-hosting": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-hosting-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-hosting-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-wordpress": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-wordpress-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-wordpress-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-agency-hosting": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-agency-hosting-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-agency-hosting-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-domains": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-domains-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-domains-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-dns": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-dns-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-dns-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-billing": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-billing-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-billing-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-reach": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-reach-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-reach-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-ecommerce": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-ecommerce-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
},
"hostinger-vps": {
"command": "npx",
"args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-vps-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.0" }
"args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-vps-mcp"],
"env": { "USER_AGENT": "plugin;cursor;0.3.1" }
}
}
}
4 changes: 2 additions & 2 deletions scripts/mcp-tools.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package": "hostinger-api-mcp",
"version": "2.5.0",
"package": "@hostinger/mcp",
"version": "2.7.0",
"total": 403,
"groups": {
"agency-hosting": [
Expand Down
6 changes: 3 additions & 3 deletions scripts/sync-mcp-tools.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* Regenerate scripts/mcp-tools.json from a published hostinger-api-mcp tarball.
* Regenerate scripts/mcp-tools.json from a published @hostinger/mcp tarball.
*
* node scripts/sync-mcp-tools.mjs # latest
* node scripts/sync-mcp-tools.mjs 1.29.0 # a specific version
Expand All @@ -16,7 +16,7 @@ import path from "node:path";
import process from "node:process";

const version = process.argv[2] ?? "latest";
const spec = `hostinger-api-mcp@${version}`;
const spec = `@hostinger/mcp@${version}`;
const outFile = path.join(import.meta.dirname, "mcp-tools.json");

const work = mkdtempSync(path.join(tmpdir(), "hostinger-mcp-tools-"));
Expand Down Expand Up @@ -53,7 +53,7 @@ try {
const total = Object.values(groups).reduce((n, g) => n + g.length, 0);
writeFileSync(
outFile,
`${JSON.stringify({ package: "hostinger-api-mcp", version: resolvedVersion, total, groups }, null, 2)}\n`,
`${JSON.stringify({ package: "@hostinger/mcp", version: resolvedVersion, total, groups }, null, 2)}\n`,
);

console.log(`Wrote ${path.relative(process.cwd(), outFile)}`);
Expand Down
4 changes: 2 additions & 2 deletions scripts/sync-skills.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function resolveSkillsRoot() {
return { root, label: root };
}

const spec = `hostinger-api-mcp@${source}`;
const spec = `@hostinger/mcp@${source}`;
const packed = execFileSync("npm", ["pack", spec, "--silent", "--pack-destination", work], {
encoding: "utf8",
})
Expand All @@ -27,7 +27,7 @@ function resolveSkillsRoot() {

const pkgRoot = path.join(work, "package");
const { version } = JSON.parse(readFileSync(path.join(pkgRoot, "package.json"), "utf8"));
return { root: path.join(pkgRoot, "skills"), label: `hostinger-api-mcp@${version}` };
return { root: path.join(pkgRoot, "skills"), label: `@hostinger/mcp@${version}` };
}

function skillName(skillFile) {
Expand Down
25 changes: 25 additions & 0 deletions skills/deploy-to-hosting/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,31 @@ Archive rules (name archives `name_YYYYMMDD_HHMMSS.zip`):
- **Node.js source:** no `node_modules/`, no build output (`dist/`, `.next/`, `build/`), no `.env*`, nothing matched by `.gitignore`; 50 MB at most. `git archive --format=zip -o app_20260101_120000.zip HEAD` produces exactly the committed files — mention that uncommitted changes are left out.
- Agency deploys are synchronous: the site is live when the call returns.

The operations in the table read the archive from this machine, so only the local `hostinger-api-mcp` server has them. On the hosted server (`mcp.hostinger.com`) `search` does not find them — upload the files yourself as below.

### Without the local deploy operations

1. Get upload credentials: `hosting_files_generate-upload-url` (`username`, `domain`); Agency: `agency-hosting_files_generate-upload-url` (`website_uid`). Both return `url`, `auth_key` and `rest_auth_key`, which authenticate the upload instead of the API token.
2. Upload each file with TUS, where `DEST` is its path in the website's storage:

```bash
SIZE=$(wc -c < "$FILE" | tr -d ' ')
curl -sS -X POST "$URL/$DEST?override=true" -H "X-Auth: $AUTH_KEY" -H "X-Auth-Rest: $REST_AUTH_KEY" -H "Tus-Resumable: 1.0.0" -H "Upload-Length: $SIZE" -H "Upload-Offset: 0"
curl -sS -X PATCH "$URL/$DEST?override=true" -H "X-Auth: $AUTH_KEY" -H "X-Auth-Rest: $REST_AUTH_KEY" -H "Tus-Resumable: 1.0.0" -H "Content-Type: application/offset+octet-stream" -H "Upload-Offset: 0" --data-binary "@$FILE"
```

The first call returns `201`, the second `204` with an `Upload-Offset` header equal to the file size. `override=true` makes a retry safe.
3. Deploy from the uploaded files (`RANDOM8` is any fresh 8-character string, e.g. `$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)`):

| Project | `DEST` | Then |
| --- | --- | --- |
| Static or PHP | `site.zip` | `hosting_websites_deploy-static-site-archive` with `archive_path: "site.zip"` |
| Node.js | `app.zip` | `hosting_nodejs_build-settings-from-archive` with `archive_path: "app.zip"`, then `hosting_nodejs_start-build` with those settings, `source_type: "archive"` and `source_options.archive_path: "app.zip"` |
| WordPress plugin | every file, as `wp-content/plugins/SLUG-RANDOM8/<path in the plugin>` | `wordpress_plugins_deploy` with `slug` and `plugin_path: "SLUG-RANDOM8"` |
| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/<path in the theme>` | `wordpress_themes_deploy` with `slug`, `theme_path: "SLUG-RANDOM8"` and optional `is_activated` |
| Agency, extracted as-is | `.h5g/site.zip` | `agency-hosting_files_import-website-from-archive` with `archive_name: "site.zip"` |
| Agency `node-static` | `.h5g/RANDOM8/app.zip` | `agency-hosting_websites_build-nodejs-assets` with `archive_path: ".h5g/RANDOM8"` — the directory, not the file |

## 3. Node.js builds

`hosting_deploy-js-application` uploads the archive to the document root, detects settings from `package.json` and starts a build. Track it with `hosting_nodejs_list-builds` and `hosting_nodejs_build` (by `uuid`), polling every 10–20 s — builds take minutes.
Expand Down
2 changes: 1 addition & 1 deletion skills/hostinger-headless/references/DATABASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Do not put the remote `host` into the deployed app — it has no grant for conne

| You called | Poll this until | Typical wait |
| --- | --- | --- |
| `hosting_websites_create` | `hosting_websites_list` lists the domain | up to a few minutes |
| `hosting_websites_create` | `hosting_websites_list-setups` with the domain reports `status: completed` | up to a few minutes |
| `hosting_deploy-js-application` | `hosting_list-js-deployments` shows the build finished | minutes |
| `hosting_nodejs_start-build` | `hosting_nodejs_build` state is `completed` or `failed` | minutes |
| `wordpress_installations_install`, plugin/theme/core jobs | `wordpress_installations_list` lists the install | 1–2 minutes |
Expand Down
2 changes: 2 additions & 0 deletions skills/hostinger-headless/references/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Match the deploy method to what the project actually is — this is the single most common failure point.

The deploy operations below read the archive from this machine, so only the local `hostinger-api-mcp` server has them. On the hosted server (`mcp.hostinger.com`) `search` does not find them: upload the files and deploy from the upload as described in the `deploy-to-hosting` skill ("Without the local deploy operations"). Agency Plan sites deploy through the Agency operations listed there as well.

## Static site → `hosting_deploy-static-website`

For pre-built files only: plain HTML/CSS/JS, or the **build output** of a framework (run the build locally first).
Expand Down
Loading
Loading