From d533ea7e96372145ce5f1fccbb3e7434b05f504b Mon Sep 17 00:00:00 2001 From: Aurimas Butkus Date: Fri, 2 Oct 2026 12:44:11 +0300 Subject: [PATCH 1/4] feat: run MCP servers from @hostinger/mcp --- README.md | 12 ++++----- mcp.json | 18 ++++++------- scripts/mcp-tools.json | 4 +-- scripts/sync-mcp-tools.mjs | 6 ++--- scripts/sync-skills.mjs | 4 +-- skills/deploy-to-hosting/SKILL.md | 25 +++++++++++++++++++ .../hostinger-headless/references/DATABASE.md | 2 +- .../references/DEPLOYMENT.md | 2 ++ skills/hostinger-headless/references/SETUP.md | 5 ++-- skills/migrate-to-hosting/SKILL.md | 8 +++--- 10 files changed, 58 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 383cd4e..23179c5 100644 --- a/README.md +++ b/README.md @@ -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. --- @@ -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) @@ -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). @@ -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. CI runs advisory jobs that flag when either has fallen behind `@hostinger/mcp@latest`. --- ## 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/ diff --git a/mcp.json b/mcp.json index 316805f..2d8bd70 100644 --- a/mcp.json +++ b/mcp.json @@ -2,47 +2,47 @@ "mcpServers": { "hostinger-hosting": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-hosting-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-hosting-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-wordpress": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-wordpress-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-wordpress-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-agency-hosting": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-agency-hosting-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-agency-hosting-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-domains": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-domains-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-domains-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-dns": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-dns-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-dns-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-billing": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-billing-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-billing-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-reach": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-reach-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-reach-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-ecommerce": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-ecommerce-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } }, "hostinger-vps": { "command": "npx", - "args": ["--yes", "--package=hostinger-api-mcp@latest", "hostinger-vps-mcp"], + "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-vps-mcp"], "env": { "USER_AGENT": "plugin;cursor;0.3.0" } } } diff --git a/scripts/mcp-tools.json b/scripts/mcp-tools.json index d28bedc..874222a 100644 --- a/scripts/mcp-tools.json +++ b/scripts/mcp-tools.json @@ -1,6 +1,6 @@ { - "package": "hostinger-api-mcp", - "version": "2.5.0", + "package": "@hostinger/mcp", + "version": "2.7.0", "total": 403, "groups": { "agency-hosting": [ diff --git a/scripts/sync-mcp-tools.mjs b/scripts/sync-mcp-tools.mjs index 20bfef9..b5a5d8a 100644 --- a/scripts/sync-mcp-tools.mjs +++ b/scripts/sync-mcp-tools.mjs @@ -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 @@ -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-")); @@ -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)}`); diff --git a/scripts/sync-skills.mjs b/scripts/sync-skills.mjs index 292d70a..46fcf27 100755 --- a/scripts/sync-skills.mjs +++ b/scripts/sync-skills.mjs @@ -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", }) @@ -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) { diff --git a/skills/deploy-to-hosting/SKILL.md b/skills/deploy-to-hosting/SKILL.md index eb13470..96442d7 100644 --- a/skills/deploy-to-hosting/SKILL.md +++ b/skills/deploy-to-hosting/SKILL.md @@ -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/` | `wordpress_plugins_deploy` with `slug` and `plugin_path: "SLUG-RANDOM8"` | +| WordPress theme | every file, as `wp-content/themes/SLUG-RANDOM8/` | `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. diff --git a/skills/hostinger-headless/references/DATABASE.md b/skills/hostinger-headless/references/DATABASE.md index e66d085..4e70a41 100644 --- a/skills/hostinger-headless/references/DATABASE.md +++ b/skills/hostinger-headless/references/DATABASE.md @@ -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 | diff --git a/skills/hostinger-headless/references/DEPLOYMENT.md b/skills/hostinger-headless/references/DEPLOYMENT.md index 31a8882..e3926e3 100644 --- a/skills/hostinger-headless/references/DEPLOYMENT.md +++ b/skills/hostinger-headless/references/DEPLOYMENT.md @@ -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). diff --git a/skills/hostinger-headless/references/SETUP.md b/skills/hostinger-headless/references/SETUP.md index 0d0d0aa..632ab67 100644 --- a/skills/hostinger-headless/references/SETUP.md +++ b/skills/hostinger-headless/references/SETUP.md @@ -8,7 +8,8 @@ A website can only be created on an active hosting plan. 1. `hosting_websites_list` — if it returns websites, the account has a working plan; note any existing `order_id` and `username` for step 2. 2. Otherwise `hosting_orders_list` — look for an order in a usable state. A fresh order that has never hosted a website still works; note its `order_id`. -3. If there is no usable order: **stop and tell the user** that an active Hostinger hosting plan is required to deploy, link https://www.hostinger.com/web-hosting, and offer to continue building the site locally in the meantime. When they confirm the purchase, re-run this check. +3. Agency Plan orders are not in that list: check `agency-hosting_orders_list` too. An active Agency order is a usable plan — create the website with `agency-hosting_website-setups_create` and poll `agency-hosting_website-setups_status` until `completed` (it returns the `website_uid`), then deploy with the Agency operations in the `deploy-to-hosting` skill. +4. If there is no usable order: **stop and tell the user** that an active Hostinger hosting plan is required to deploy, link https://www.hostinger.com/web-hosting, and offer to continue building the site locally in the meantime. When they confirm the purchase, re-run this check. Never purchase a plan, domain, or any paid item without the user explicitly approving that specific purchase. @@ -23,7 +24,7 @@ Never purchase a plan, domain, or any paid item without the user explicitly appr Generating a subdomain does **not** create a website — deploying straight to it fails with `No website found for domain`. The working sequence: 1. `hosting_websites_create { domain, order_id }` — `datacenter_code` is required only for the first website on a brand-new plan (pick the first entry from `hosting_datacenters_list`). -2. **Poll** `hosting_websites_list` filtered by the domain until the site appears. Creation takes up to a few minutes — poll with backoff, don't fail fast. +2. **Poll** `hosting_websites_list-setups` filtered by the domain every 10–15 s until `status` is `completed`. The site shows up in `hosting_websites_list` before its setup finishes, and uploads, deploys and database calls return 404 or 409 until then. Creation takes up to a few minutes — don't fail fast. 3. Note the site's `username` — deployment and database operations are keyed on it. If the domain already has a website (an `iterate` run, or the user pointed at an existing site), skip creation entirely. diff --git a/skills/migrate-to-hosting/SKILL.md b/skills/migrate-to-hosting/SKILL.md index 12a76e5..d18f365 100644 --- a/skills/migrate-to-hosting/SKILL.md +++ b/skills/migrate-to-hosting/SKILL.md @@ -41,9 +41,11 @@ Create the Hostinger website on the domain being migrated, not on a free subdoma ## 3. Import -**WordPress, Shared and Cloud:** `hosting_import-wordpress-website` with `domain`, `archivePath` and `databaseDump` uploads both, extracts the files and imports the database; large sites take several minutes. Then `wordpress_installations_detect` with `username` and poll `wordpress_installations_list` with `domain` until the install appears with `is_valid: true`. +`hosting_import-wordpress-website`, `hosting_deploy-static-website` and `agency-hosting_deploy-php-application` read files from this machine and exist only in the local `hostinger-api-mcp` server. On the hosted server (`mcp.hostinger.com`), upload the files first as described in the `deploy-to-hosting` skill ("Without the local deploy operations") and use the operation named in brackets below. -**Static, or PHP without a database:** `hosting_deploy-static-website` (Agency: `agency-hosting_deploy-php-application`). +**WordPress, Shared and Cloud:** `hosting_import-wordpress-website` with `domain`, `archivePath` and `databaseDump` uploads both, extracts the files and imports the database; large sites take several minutes. [Upload `site.zip` and `dump.sql`, then `wordpress_installations_import-website` with `archive_path` and `sql_path`.] Then `wordpress_installations_detect` with `username` and poll `wordpress_installations_list` with `domain` until the install appears with `is_valid: true`. + +**Static, or PHP without a database:** `hosting_deploy-static-website` [upload, then `hosting_websites_deploy-static-site-archive`]; Agency: `agency-hosting_deploy-php-application` [upload to `.h5g/`, then `agency-hosting_files_import-website-from-archive`]. **PHP with a database, Shared and Cloud:** @@ -53,7 +55,7 @@ Create the Hostinger website on the domain being migrated, not on a free subdoma **Node.js:** deploy with the `deploy-to-hosting` skill. When a dump must be imported, create the database with `hosting_databases_create` rather than `hosting_databases_setup-website` — the latter never reveals the password needed for the import — and pass the credentials through environment variables. -**Agency:** files with `agency-hosting_deploy-php-application`; the database with `agency-hosting_databases_create-website`, imported through phpMyAdmin in hPanel (no import operation exists). For WordPress, set the new credentials in `wp-config.php` before archiving. +**Agency:** files with `agency-hosting_deploy-php-application` [or upload plus `agency-hosting_files_import-website-from-archive`]; the database with `agency-hosting_databases_create-website`, imported through phpMyAdmin in hPanel (no import operation exists). For WordPress, set the new credentials in `wp-config.php` before archiving. ## 4. Test before DNS moves From 82ee2769421d5bebe77f05a3dbd42453daf1a2d4 Mon Sep 17 00:00:00 2001 From: Aurimas Butkus Date: Fri, 2 Oct 2026 12:45:59 +0300 Subject: [PATCH 2/4] ci: open a sync PR for each MCP server release --- .github/workflows/ci.yml | 48 ---------------- .github/workflows/mcp-sync.yml | 100 +++++++++++++++++++++++++++++++++ README.md | 2 +- 3 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/mcp-sync.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e1a40..fe24ee3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/mcp-sync.yml b/.github/workflows/mcp-sync.yml new file mode 100644 index 0000000..0c97be7 --- /dev/null +++ b/.github/workflows/mcp-sync.yml @@ -0,0 +1,100 @@ +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="${INPUT_VERSION:-$(npm view @hostinger/mcp version)}" + 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" + + # 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. diff --git a/README.md b/README.md index 23179c5..2636243 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ 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/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. --- From 221bb4c2cc71de726ffef099176b5f2af5fd5ca4 Mon Sep 17 00:00:00 2001 From: Aurimas Butkus Date: Fri, 2 Oct 2026 12:48:15 +0300 Subject: [PATCH 3/4] chore: release 0.3.1 --- .cursor-plugin/plugin.json | 2 +- CHANGELOG.md | 8 ++++++++ mcp.json | 18 +++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 4762d5b..1078c84 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -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", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf6400..468a348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/mcp.json b/mcp.json index 2d8bd70..b614f3c 100644 --- a/mcp.json +++ b/mcp.json @@ -3,47 +3,47 @@ "hostinger-hosting": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-hosting-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-wordpress": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-wordpress-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-agency-hosting": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-agency-hosting-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-domains": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-domains-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-dns": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-dns-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-billing": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-billing-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-reach": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-reach-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-ecommerce": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-ecommerce-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } }, "hostinger-vps": { "command": "npx", "args": ["--yes", "--package=@hostinger/mcp@latest", "hostinger-vps-mcp"], - "env": { "USER_AGENT": "plugin;cursor;0.3.0" } + "env": { "USER_AGENT": "plugin;cursor;0.3.1" } } } } From d7171b183768286e73b993c16340d4de580303b5 Mon Sep 17 00:00:00 2001 From: Aurimas Butkus Date: Fri, 2 Oct 2026 12:59:28 +0300 Subject: [PATCH 4/4] fix: skip sync PRs for version-only catalog changes --- .github/workflows/mcp-sync.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mcp-sync.yml b/.github/workflows/mcp-sync.yml index 0c97be7..23ba165 100644 --- a/.github/workflows/mcp-sync.yml +++ b/.github/workflows/mcp-sync.yml @@ -48,7 +48,8 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} run: | - V="${INPUT_VERSION:-$(npm view @hostinger/mcp version)}" + 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 @@ -57,6 +58,12 @@ jobs: 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.