hostinger-api-mcp 2.0.0 → 2.2.0
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/CHANGELOG.md +432 -0
- package/README.md +406 -406
- package/gemini-extension.json +1 -1
- package/package.json +4 -3
- package/server.json +2 -2
- package/skills/headless/SKILL.md +1 -1
- package/skills/headless/references/DATABASE.md +14 -14
- package/skills/headless/references/DEPLOYMENT.md +5 -5
- package/skills/headless/references/SETUP.md +8 -8
- package/skills/headless/references/STORE.md +4 -4
- package/skills/headless/references/WORDPRESS.md +5 -5
- package/src/core/instructions.js +1 -1
- package/src/core/instructions.ts +1 -1
- package/src/core/runtime.js +87 -12
- package/src/core/runtime.ts +93 -12
- package/src/core/skills.js +54 -0
- package/src/core/skills.ts +69 -0
- package/src/core/tools/agency-hosting.js +44 -44
- package/src/core/tools/agency-hosting.ts +44 -44
- package/src/core/tools/all.js +405 -405
- package/src/core/tools/all.ts +405 -405
- package/src/core/tools/billing.js +9 -9
- package/src/core/tools/billing.ts +9 -9
- package/src/core/tools/dns.js +8 -8
- package/src/core/tools/dns.ts +8 -8
- package/src/core/tools/domains.js +41 -41
- package/src/core/tools/domains.ts +41 -41
- package/src/core/tools/ecommerce.js +29 -29
- package/src/core/tools/ecommerce.ts +29 -29
- package/src/core/tools/horizons.js +6 -6
- package/src/core/tools/horizons.ts +6 -6
- package/src/core/tools/hosting.js +76 -76
- package/src/core/tools/hosting.ts +76 -76
- package/src/core/tools/mail.js +38 -38
- package/src/core/tools/mail.ts +38 -38
- package/src/core/tools/reach.js +52 -52
- package/src/core/tools/reach.ts +52 -52
- package/src/core/tools/vps.js +64 -64
- package/src/core/tools/vps.ts +64 -64
- package/src/core/tools/wordpress.js +38 -38
- package/src/core/tools/wordpress.ts +38 -38
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/all.js +2 -1
- package/src/servers/all.ts +2 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
- package/types.d.ts +405 -405
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hostinger-api-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"mcpName": "io.github.hostinger/hostinger-api-mcp",
|
|
5
5
|
"description": "MCP server for Hostinger API",
|
|
6
6
|
"repository": {
|
|
@@ -38,13 +38,14 @@
|
|
|
38
38
|
"start:ts": "npx tsc && node dist/src/servers/all.js"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
42
42
|
"axios": "^1.8.0",
|
|
43
43
|
"cors": "^2.8.5",
|
|
44
44
|
"dotenv": "^18.0.1",
|
|
45
45
|
"express": "^5.2.1",
|
|
46
46
|
"minimist": "^1.2.8",
|
|
47
|
-
"tus-js-client": "^4.1.0"
|
|
47
|
+
"tus-js-client": "^4.1.0",
|
|
48
|
+
"zod": "^4.1.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@types/node": "^26.1.1",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/hostinger/api-mcp-server",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.2.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@hostinger/mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.2.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/skills/headless/SKILL.md
CHANGED
|
@@ -52,5 +52,5 @@ Run non-interactively wherever possible. The exceptions that must involve the us
|
|
|
52
52
|
|
|
53
53
|
The inputSchema returned by search is authoritative for request shapes — read it before calling. Two live sources supersede anything written here when they disagree:
|
|
54
54
|
|
|
55
|
-
- The ecommerce operation `
|
|
55
|
+
- The ecommerce operation `ecommerce_miscellaneous_custom-storefront-setup-instructions` returns the current storefront integration guide from the server — execute it at the start of any store run.
|
|
56
56
|
- The Hostinger API reference at https://developers.hostinger.com describes every endpoint behind the operations.
|
|
@@ -4,8 +4,8 @@ Use this when the app needs MySQL, or when a deploy "succeeded" but the app cann
|
|
|
4
4
|
|
|
5
5
|
## 1. Create the database
|
|
6
6
|
|
|
7
|
-
1. `
|
|
8
|
-
2. Read the full, prefixed `name` and `user` back from `
|
|
7
|
+
1. `hosting_databases_create` on the site's `username` with a database name, a user and a password you generate — name and user are prefixed with the account username automatically.
|
|
8
|
+
2. Read the full, prefixed `name` and `user` back from `hosting_databases_list`; every other database operation wants the full name.
|
|
9
9
|
3. Do not print the password to the user once it is stored, and never commit it.
|
|
10
10
|
|
|
11
11
|
## 2. Which host the app connects to
|
|
@@ -14,13 +14,13 @@ Use this when the app needs MySQL, or when a deploy "succeeded" but the app cann
|
|
|
14
14
|
| --- | --- | --- | --- |
|
|
15
15
|
| PHP on the website | `localhost` or `127.0.0.1` | 3306 | `localhost` uses the MySQL socket; both are granted |
|
|
16
16
|
| Node.js on the website | `127.0.0.1` | 3306 | `localhost` may resolve to IPv6 `::1`, and not every database user has that grant |
|
|
17
|
-
| Outside Hostinger (a laptop, another server) | the `host` from `
|
|
17
|
+
| Outside Hostinger (a laptop, another server) | the `host` from `hosting_databases_list` (`srvNNNN.hstgr.io`) | 3306 | needs `hosting_databases_create-remote-connection` for that client's IP first — never `%`, it opens the database to every address on the internet |
|
|
18
18
|
|
|
19
19
|
Do not put the remote `host` into the deployed app — it has no grant for connections that come from the server itself.
|
|
20
20
|
|
|
21
21
|
## 3. Hand credentials to the app
|
|
22
22
|
|
|
23
|
-
- **Node.js:** environment variables via `
|
|
23
|
+
- **Node.js:** environment variables via `hosting_nodejs_replace-environment-variables`. Conventional keys: `DB_HOST=127.0.0.1`, `DB_PORT=3306`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, plus `DATABASE_URL=mysql://USER:PASSWORD@127.0.0.1:3306/NAME` for ORMs that want one string — URL-encode `USER` and `PASSWORD` there (`encodeURIComponent`), or a password containing `@`, `:`, `/` or `#` breaks the URL. The call is a **full replace**: list the current keys with `hosting_nodejs_list-environment-variables` (values come back masked as `********` — never copy those), then send the complete set with real values. Saving restarts the process; frameworks that bake variables into the build (Next.js, `NEXT_PUBLIC_*`) need a new `hosting_nodejs_start-build` afterwards.
|
|
24
24
|
- **PHP:** a config file on the server that the app reads (its own `config.php`, WordPress's `wp-config.php`). Ship it with the deploy, keep it out of git.
|
|
25
25
|
- **Never** in client-side JavaScript, in a committed `.env`, or in chat replies.
|
|
26
26
|
|
|
@@ -28,22 +28,22 @@ Do not put the remote `host` into the deployed app — it has no grant for conne
|
|
|
28
28
|
|
|
29
29
|
| You called | Poll this until | Typical wait |
|
|
30
30
|
| --- | --- | --- |
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `agency-
|
|
31
|
+
| `hosting_websites_create` | `hosting_websites_list` lists the domain | up to a few minutes |
|
|
32
|
+
| `hosting_deploy-js-application` | `hosting_list-js-deployments` shows the build finished | minutes |
|
|
33
|
+
| `hosting_nodejs_start-build` | `hosting_nodejs_build` state is `completed` or `failed` | minutes |
|
|
34
|
+
| `wordpress_installations_install`, plugin/theme/core jobs | `wordpress_installations_list` lists the install | 1–2 minutes |
|
|
35
|
+
| `hosting_databases_repair`, `hosting_websites_delete` | nothing to poll — runs in the background | minutes |
|
|
36
|
+
| `agency-hosting_website-setups_create` | `agency-hosting_website-setups_status` with `setup_uuid` is `completed` | minutes |
|
|
37
37
|
|
|
38
38
|
Poll with backoff (a few seconds, then tens of seconds). A queued response is not a failure, and re-sending the write does not speed it up.
|
|
39
39
|
|
|
40
40
|
## 5. When the app is up but broken
|
|
41
41
|
|
|
42
|
-
- Build failed: `
|
|
43
|
-
- Build passed, app errors: `
|
|
42
|
+
- Build failed: `hosting_nodejs_build-logs` for the raw log, `hosting_nodejs_analyse-failed-build` for a diagnosis.
|
|
43
|
+
- Build passed, app errors: `hosting_nodejs_runtime-logs` with a `period` such as `1h`; entries before `last_deployed_at` belong to the previous deploy.
|
|
44
44
|
- `Access denied for user 'x'@'::1'`: the app used `localhost` from Node.js — set `DB_HOST` to `127.0.0.1`.
|
|
45
|
-
- PHP limits (memory, upload size): `
|
|
45
|
+
- PHP limits (memory, upload size): `hosting_php_get`, then `hosting_php_update-options`.
|
|
46
46
|
|
|
47
47
|
## Agency Plan websites (`agency-hosting_*`)
|
|
48
48
|
|
|
49
|
-
`agency-
|
|
49
|
+
`agency-hosting_databases_create-website` creates the database and its single user; the user is granted on `localhost`, so the PHP app connects to `localhost`. Deploys are synchronous there — `agency-hosting_deploy-php-application` returns when the site is live. Other jobs (SSL, backups) show up in `agency-hosting_websites_list-processes`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Match the deploy method to what the project actually is — this is the single most common failure point.
|
|
4
4
|
|
|
5
|
-
## Static site → `
|
|
5
|
+
## Static site → `hosting_deploy-static-website`
|
|
6
6
|
|
|
7
7
|
For pre-built files only: plain HTML/CSS/JS, or the **build output** of a framework (run the build locally first).
|
|
8
8
|
|
|
@@ -10,15 +10,15 @@ For pre-built files only: plain HTML/CSS/JS, or the **build output** of a framew
|
|
|
10
10
|
- Name it `name_YYYYMMDD_HHMMSS.zip`; pass `removeArchive: true` to clean up after upload.
|
|
11
11
|
- Deployment is effectively immediate — verify right after.
|
|
12
12
|
|
|
13
|
-
## Node.js app → `
|
|
13
|
+
## Node.js app → `hosting_deploy-js-application`
|
|
14
14
|
|
|
15
15
|
For anything that needs a server or a server-side build (Express, Next.js, NestJS, SSR frameworks).
|
|
16
16
|
|
|
17
17
|
- Archive the **source**, not the output: exclude `node_modules/`, `dist/`, `.next/`, `build/`, and everything matched by `.gitignore`. The install and build run on Hostinger.
|
|
18
18
|
- Hard cap: **50 MB** archive.
|
|
19
|
-
- The upload starts a build. Track it with `
|
|
19
|
+
- The upload starts a build. Track it with `hosting_list-js-deployments`; on failure pull `hosting_show-js-deployment-logs`, fix, and redeploy. Poll with backoff — builds take minutes, not seconds.
|
|
20
20
|
|
|
21
|
-
## WordPress → `
|
|
21
|
+
## WordPress → `hosting_import-wordpress-website` / plugin & theme operations
|
|
22
22
|
|
|
23
23
|
Only when the user explicitly wants a WordPress *site* migrated or themed. Site imports take an archive plus a `.sql` dump and can run for several minutes. (WordPress as a headless content backend behind an agent-built frontend is a different flow — that's `WORDPRESS.md`, installed fresh via the API, not imported here.)
|
|
24
24
|
|
|
@@ -29,6 +29,6 @@ curl -s -o /dev/null -w "%{http_code}\n" https://YOURDOMAIN/
|
|
|
29
29
|
curl -s https://YOURDOMAIN/ | grep -o "SOME_REAL_HEADLINE_TEXT"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Replace the grep target with copy you actually rendered — a 200 alone can be a placeholder page. A freshly created site may serve a default page for a short while; if content doesn't match, clear the cache (`
|
|
32
|
+
Replace the grep target with copy you actually rendered — a 200 alone can be a placeholder page. A freshly created site may serve a default page for a short while; if content doesn't match, clear the cache (`hosting_cache_clear-website`) and retry before assuming the deploy failed. For store runs, also run the checkout verification in `STORE.md`.
|
|
33
33
|
|
|
34
34
|
When the site is live, report the URL and remind the user the site is managed from hPanel (https://hpanel.hostinger.com).
|
|
@@ -6,24 +6,24 @@ Everything here uses the **hosting** MCP operations. If they're missing, ask the
|
|
|
6
6
|
|
|
7
7
|
A website can only be created on an active hosting plan.
|
|
8
8
|
|
|
9
|
-
1. `
|
|
10
|
-
2. Otherwise `
|
|
9
|
+
1. `hosting_websites_list` — if it returns websites, the account has a working plan; note any existing `order_id` and `username` for step 2.
|
|
10
|
+
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`.
|
|
11
11
|
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.
|
|
12
12
|
|
|
13
13
|
Never purchase a plan, domain, or any paid item without the user explicitly approving that specific purchase.
|
|
14
14
|
|
|
15
15
|
## 2. Choose the domain
|
|
16
16
|
|
|
17
|
-
- **Default: free subdomain.** `
|
|
18
|
-
- **User-owned domain:** `
|
|
19
|
-
- **New domain purchase:** only on explicit request — check availability with `
|
|
17
|
+
- **Default: free subdomain.** `hosting_domains_generate-free-subdomain` returns a `*.hostingersite.com` domain. No verification needed. Tell the user a custom domain can be connected later.
|
|
18
|
+
- **User-owned domain:** `hosting_domains_verify-ownership` first. If not accessible, relay the TXT record it returns, remind them propagation can take ~10 minutes, and re-verify before continuing.
|
|
19
|
+
- **New domain purchase:** only on explicit request — check availability with `domains_availability_check`, state the price, and get an explicit yes before `domains_portfolio_purchase` (needs the domains product group).
|
|
20
20
|
|
|
21
21
|
## 3. Create the website and wait for it
|
|
22
22
|
|
|
23
23
|
Generating a subdomain does **not** create a website — deploying straight to it fails with `No website found for domain`. The working sequence:
|
|
24
24
|
|
|
25
|
-
1. `
|
|
26
|
-
2. **Poll** `
|
|
25
|
+
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`).
|
|
26
|
+
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.
|
|
27
27
|
3. Note the site's `username` — deployment and database operations are keyed on it.
|
|
28
28
|
|
|
29
29
|
If the domain already has a website (an `iterate` run, or the user pointed at an existing site), skip creation entirely.
|
|
@@ -31,4 +31,4 @@ If the domain already has a website (an `iterate` run, or the user pointed at an
|
|
|
31
31
|
## 4. Optional extras (only when the run needs them)
|
|
32
32
|
|
|
33
33
|
- **Database:** when the app needs MySQL, follow `DATABASE.md` — creating it, which host each runtime connects to (Node.js must use `127.0.0.1`), and handing the credentials to the app without hard-coding them.
|
|
34
|
-
- **DNS records:** the DNS operations (`
|
|
34
|
+
- **DNS records:** the DNS operations (`dns_records_update` etc.) for custom-domain records; take a snapshot (`dns_snapshots_list` context) before destructive changes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Store — Hostinger Ecommerce with a custom storefront
|
|
2
2
|
|
|
3
|
-
**Primary source:** execute `
|
|
3
|
+
**Primary source:** execute `ecommerce_miscellaneous_custom-storefront-setup-instructions` at the start of every store run — it returns the current, server-maintained integration guide, and it supersedes this file wherever they disagree. This file carries the essentials so the run can be planned before that call, and a fallback when the ecommerce operations aren't enabled.
|
|
4
4
|
|
|
5
5
|
## Mental model
|
|
6
6
|
|
|
@@ -17,9 +17,9 @@ Two API surfaces — don't mix them:
|
|
|
17
17
|
|
|
18
18
|
## Backend setup (management tools)
|
|
19
19
|
|
|
20
|
-
1. Resolve the `custom` sales channel: `
|
|
21
|
-
2. For checkout to work the store needs all three: ≥ 1 product, ≥ 1 payment method, ≥ 1 shipping zone. Payment, shipping, and currency are store-level — check `
|
|
22
|
-
3. After deploy, point the channel at the live site: `
|
|
20
|
+
1. Resolve the `custom` sales channel: `ecommerce_stores_list` → `ecommerce_sales-channels_list`. If a `custom` channel already exists, confirm with the user that it's the one to use; otherwise add one to the existing store with `ecommerce_sales-channels_create`, or create a store with `ecommerce_stores_create` (with a `custom` sales channel) when no suitable store exists. Set the channel `url` to the storefront's public domain at creation when it's already known.
|
|
21
|
+
2. For checkout to work the store needs all three: ≥ 1 product, ≥ 1 payment method, ≥ 1 shipping zone. Payment, shipping, and currency are store-level — check `ecommerce_stores_metadata` (`has_payment_methods` and `has_shipping` must be `true`; the same response carries `default_currency` with `code`, `decimal_digits`, `template` — read it here rather than guessing). Products are **per sales channel** — verify by listing products for the resolved channel on the Storefront API and confirming the list is non-empty. Seed what's missing with `ecommerce_products_create-physical` / `ecommerce_products_create-digital`, `ecommerce_payments_enable-manual-method`, and `ecommerce_shipping_set-store` (price `0` = free shipping) — confirming with the user before writing to a store that already has real data.
|
|
22
|
+
3. After deploy, point the channel at the live site: `ecommerce_sales-channels_update { store_id, sales_channel_id, url }`.
|
|
23
23
|
|
|
24
24
|
If the ecommerce operations aren't available, ask the user to enable the Ecommerce product group in the Hostinger Connector — or to provide the `sales_channel_id` directly, after which the public endpoints suffice for the frontend work.
|
|
25
25
|
|
|
@@ -20,11 +20,11 @@ Two surfaces, like the store recipe:
|
|
|
20
20
|
|
|
21
21
|
## Setup (management tools)
|
|
22
22
|
|
|
23
|
-
1. **Check for an existing installation first**: `
|
|
24
|
-
2. **Choose where WordPress lives.** Default: a dedicated subdomain of the site, e.g. `cms.<domain>` — create it with `
|
|
25
|
-
3. **Install**: `
|
|
26
|
-
4. **Hand the owner their editor**: mint a one-click wp-admin link with `
|
|
27
|
-
5. **Caching** (recommended before finishing): enable the object cache with `
|
|
23
|
+
1. **Check for an existing installation first**: `wordpress_installations_list` filtered by the site's username/domain. Reuse a valid install when the user agrees — never overwrite one silently.
|
|
24
|
+
2. **Choose where WordPress lives.** Default: a dedicated subdomain of the site, e.g. `cms.<domain>` — create it with `hosting_domains_create-website-subdomain` so the main domain stays free for the frontend. A separate free-subdomain website (per `SETUP.md`) also works when the plan allows another website.
|
|
25
|
+
3. **Install**: `wordpress_installations_install` on that domain. The call only queues the job — **poll** `wordpress_installations_list` until the installation appears (typically 1–2 minutes). Don't proceed on the queued response alone.
|
|
26
|
+
4. **Hand the owner their editor**: mint a one-click wp-admin link with `wordpress_login_create-links` and show it to the user. Content authoring happens in wp-admin — the skill does not seed posts (there is no anonymous write path, by design). The fresh install ships with a sample post, which is enough to build and verify the frontend against.
|
|
27
|
+
5. **Caching** (recommended before finishing): enable the object cache with `wordpress_object-cache_toggle-memcached`; after config changes, purge with `wordpress_litespeed-cache_purge-lite-speed`.
|
|
28
28
|
|
|
29
29
|
## Frontend contract
|
|
30
30
|
|
package/src/core/instructions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from
|
|
2
|
+
export default "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list until the domain appears (a few minutes).\n- hosting_nodejs_start-build -> hosting_nodejs_build; logs via hosting_nodejs_build-logs; on failure hosting_nodejs_analyse-failed-build.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
package/src/core/instructions.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Auto-generated server instructions
|
|
2
|
-
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from
|
|
2
|
+
const instructions: string = "Hostinger API notes for agents. Find operations with search; the inputSchema it returns is authoritative for request shapes. Run one with execute, or chain several with multi-execute.\n\nDatabases (web hosting, hosting_* operations)\n- MySQL runs on the same server as the website. From the deployed app connect to host 127.0.0.1, port 3306. PHP may also use localhost (Unix socket). Node.js should use 127.0.0.1: localhost can resolve to IPv6 (::1) and not every database user has that grant.\n- The `host` from hosting_databases_list (srvNNNN.hstgr.io) is only for connections from outside Hostinger, after hosting_databases_create-remote-connection. Never use it from the app.\n- Agency Plan (agency-hosting_* operations) database users are created for localhost; the PHP app connects there.\n\nAsynchronous operations\n- A success response often means \"queued\", not \"done\". Poll the matching read operation with backoff (seconds, then minutes). Never re-send the write.\n- hosting_websites_create -> poll hosting_websites_list until the domain appears (a few minutes).\n- hosting_nodejs_start-build -> hosting_nodejs_build; logs via hosting_nodejs_build-logs; on failure hosting_nodejs_analyse-failed-build.\n- wordpress_installations_install and WordPress plugin/theme/core jobs -> poll wordpress_installations_list.\n- agency-hosting_website-setups_create -> poll agency-hosting_website-setups_status with setup_uuid; other Agency Plan jobs -> agency-hosting_websites_list-processes.\n- Website deletion, database repair and domain contact changes finish in the background.\n\nCredentials\n- Never write database passwords or API tokens into source code, client-side JavaScript or committed .env files.\n- Node.js apps read them from environment variables: set the full set with hosting_nodejs_replace-environment-variables (full replace; values are masked on read). Build-time variables (Next.js) need a new build afterwards.\n- PHP apps read them from a config file on the server, outside the repository.\n- Generate strong passwords and do not echo them back once stored.\n\nFor end-to-end site builds install the headless skill: npx skills add hostinger/api-mcp-server";
|
|
3
3
|
export default instructions;
|
package/src/core/runtime.js
CHANGED
|
@@ -10,9 +10,13 @@ import { config as dotenvConfig } from "dotenv";
|
|
|
10
10
|
import {
|
|
11
11
|
ListToolsRequestSchema,
|
|
12
12
|
CallToolRequestSchema,
|
|
13
|
+
ListResourcesRequestSchema,
|
|
14
|
+
ListResourceTemplatesRequestSchema,
|
|
15
|
+
ReadResourceRequestSchema,
|
|
13
16
|
ErrorCode,
|
|
14
17
|
McpError,
|
|
15
18
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
19
|
+
import { z } from "zod";
|
|
16
20
|
import { OAuthProvider, getEnvToken } from "./oauth.js";
|
|
17
21
|
import * as tus from "tus-js-client";
|
|
18
22
|
import fs from "fs";
|
|
@@ -43,17 +47,33 @@ const SECURITY_SCHEMES = {
|
|
|
43
47
|
}
|
|
44
48
|
};
|
|
45
49
|
|
|
50
|
+
// MCP Skills extension (io.modelcontextprotocol/skills). The SDK has no built-in
|
|
51
|
+
// support yet, so the two methods are registered as custom requests.
|
|
52
|
+
const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
|
|
53
|
+
const SKILLS_CACHE = { ttlMs: 300000, cacheScope: "public" };
|
|
54
|
+
const ListSkillsRequestSchema = z.object({
|
|
55
|
+
method: z.literal("skills/list"),
|
|
56
|
+
params: z.object({ cursor: z.string().optional() }).passthrough().optional(),
|
|
57
|
+
});
|
|
58
|
+
const GetSkillRequestSchema = z.object({
|
|
59
|
+
method: z.literal("skills/get"),
|
|
60
|
+
// Lenient on purpose: a missing or non-string uri must surface as InvalidParams
|
|
61
|
+
// from the handler, not as a zod parse failure (InternalError).
|
|
62
|
+
params: z.object({ uri: z.unknown().optional() }).passthrough().optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
46
65
|
/**
|
|
47
66
|
* MCP Server for Hostinger API
|
|
48
67
|
* Generated from OpenAPI spec version 1.54.2
|
|
49
68
|
*/
|
|
50
69
|
class MCPServer {
|
|
51
|
-
constructor({ name, version, tools, instructions }) {
|
|
70
|
+
constructor({ name, version, tools, instructions, skills = [] }) {
|
|
52
71
|
// Initialize class properties
|
|
53
72
|
this.name = name;
|
|
54
73
|
this.version = version;
|
|
55
74
|
this.toolList = tools;
|
|
56
75
|
this.instructions = instructions;
|
|
76
|
+
this.skills = skills;
|
|
57
77
|
this.server = null;
|
|
58
78
|
this.operations = new Map();
|
|
59
79
|
this.debug = process.env.DEBUG === "true";
|
|
@@ -73,6 +93,7 @@ class MCPServer {
|
|
|
73
93
|
{
|
|
74
94
|
capabilities: {
|
|
75
95
|
tools: {}, // Enable tools capability
|
|
96
|
+
...(this.skills.length > 0 ? { resources: {}, extensions: { [SKILLS_EXTENSION]: {} } } : {}),
|
|
76
97
|
},
|
|
77
98
|
instructions: this.instructions,
|
|
78
99
|
}
|
|
@@ -186,6 +207,60 @@ class MCPServer {
|
|
|
186
207
|
return { isError: true, content: [{ type: "text", text: errorText }] };
|
|
187
208
|
}
|
|
188
209
|
});
|
|
210
|
+
|
|
211
|
+
if (this.skills.length > 0) {
|
|
212
|
+
this.setupSkillHandlers();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Serve the bundled agent skills over the MCP Skills extension: skills/list and
|
|
218
|
+
* skills/get return the manifest entries, and every listed file is a skill://
|
|
219
|
+
* resource readable with resources/read.
|
|
220
|
+
*/
|
|
221
|
+
setupSkillHandlers() {
|
|
222
|
+
const entry = (skill) => ({
|
|
223
|
+
uri: skill.uri,
|
|
224
|
+
frontmatter: skill.frontmatter,
|
|
225
|
+
resources: skill.resources.map(({ uri, digest, size }) => ({ uri, digest, size })),
|
|
226
|
+
});
|
|
227
|
+
const skillsByUri = new Map(this.skills.map((skill) => [skill.uri, skill]));
|
|
228
|
+
const files = new Map(this.skills.flatMap((skill) => skill.resources.map((file) => [file.uri, file])));
|
|
229
|
+
|
|
230
|
+
this.server.setRequestHandler(ListSkillsRequestSchema, async () => ({
|
|
231
|
+
resultType: "complete",
|
|
232
|
+
skills: this.skills.map(entry),
|
|
233
|
+
...SKILLS_CACHE,
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
this.server.setRequestHandler(GetSkillRequestSchema, async (request) => {
|
|
237
|
+
const uri = request.params?.uri;
|
|
238
|
+
const skill = typeof uri === "string" ? skillsByUri.get(uri) : undefined;
|
|
239
|
+
if (!skill) {
|
|
240
|
+
throw new McpError(ErrorCode.InvalidParams, typeof uri === "string" ? `Unknown skill: ${uri}` : "Missing or invalid skill uri");
|
|
241
|
+
}
|
|
242
|
+
return { resultType: "complete", skill: entry(skill), ...SKILLS_CACHE };
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
246
|
+
resources: Array.from(files.values()).map((file) => ({
|
|
247
|
+
uri: file.uri,
|
|
248
|
+
name: file.uri.slice("skill://".length),
|
|
249
|
+
mimeType: file.mimeType,
|
|
250
|
+
size: file.size,
|
|
251
|
+
})),
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
// resources is advertised, so answer templates/list too (skills have none).
|
|
255
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
|
|
256
|
+
|
|
257
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
258
|
+
const file = files.get(request.params.uri);
|
|
259
|
+
if (!file) {
|
|
260
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
|
|
261
|
+
}
|
|
262
|
+
return { contents: [{ uri: file.uri, mimeType: file.mimeType, text: file.text }] };
|
|
263
|
+
});
|
|
189
264
|
}
|
|
190
265
|
|
|
191
266
|
/**
|
|
@@ -218,23 +293,23 @@ class MCPServer {
|
|
|
218
293
|
|
|
219
294
|
async executeCustomTool(tool, params) {
|
|
220
295
|
switch (tool.name) {
|
|
221
|
-
case 'agency-
|
|
296
|
+
case 'agency-hosting_deploy-node-static-website':
|
|
222
297
|
return await this.handleNodeStaticDeploy(params);
|
|
223
|
-
case 'agency-
|
|
298
|
+
case 'agency-hosting_deploy-php-application':
|
|
224
299
|
return await this.handlePhpAppDeploy(params);
|
|
225
|
-
case '
|
|
300
|
+
case 'hosting_import-wordpress-website':
|
|
226
301
|
return await this.handleWordpressWebsiteImport(params);
|
|
227
|
-
case '
|
|
302
|
+
case 'hosting_deploy-wordpress-plugin':
|
|
228
303
|
return await this.handleWordpressPluginDeploy(params);
|
|
229
|
-
case '
|
|
304
|
+
case 'hosting_deploy-wordpress-theme':
|
|
230
305
|
return await this.handleWordpressThemeDeploy(params);
|
|
231
|
-
case '
|
|
306
|
+
case 'hosting_deploy-js-application':
|
|
232
307
|
return await this.handleJavascriptApplicationDeploy(params);
|
|
233
|
-
case '
|
|
308
|
+
case 'hosting_deploy-static-website':
|
|
234
309
|
return await this.handleStaticWebsiteDeploy(params);
|
|
235
|
-
case '
|
|
310
|
+
case 'hosting_list-js-deployments':
|
|
236
311
|
return await this.handleListJavascriptDeployments(params);
|
|
237
|
-
case '
|
|
312
|
+
case 'hosting_show-js-deployment-logs':
|
|
238
313
|
return await this.handleShowJsDeploymentLogs(params);
|
|
239
314
|
default:
|
|
240
315
|
throw new Error(`Unknown custom tool: ${tool.name}`);
|
|
@@ -2636,7 +2711,7 @@ class MCPServer {
|
|
|
2636
2711
|
}
|
|
2637
2712
|
}
|
|
2638
2713
|
|
|
2639
|
-
export async function startServer({ name, version, tools, instructions }) {
|
|
2714
|
+
export async function startServer({ name, version, tools, instructions, skills }) {
|
|
2640
2715
|
const argv = minimist(process.argv.slice(2), {
|
|
2641
2716
|
string: ['host'],
|
|
2642
2717
|
boolean: ['stdio', 'http', 'help', 'version', 'login', 'logout'],
|
|
@@ -2694,7 +2769,7 @@ export async function startServer({ name, version, tools, instructions }) {
|
|
|
2694
2769
|
}
|
|
2695
2770
|
}
|
|
2696
2771
|
|
|
2697
|
-
const server = new MCPServer({ name, version, tools, instructions });
|
|
2772
|
+
const server = new MCPServer({ name, version, tools, instructions, skills });
|
|
2698
2773
|
if (argv.http) {
|
|
2699
2774
|
await server.startHttp(argv.host, argv.port);
|
|
2700
2775
|
} else {
|
package/src/core/runtime.ts
CHANGED
|
@@ -10,10 +10,14 @@ import { config as dotenvConfig } from "dotenv";
|
|
|
10
10
|
import {
|
|
11
11
|
ListToolsRequestSchema,
|
|
12
12
|
CallToolRequestSchema,
|
|
13
|
+
ListResourcesRequestSchema,
|
|
14
|
+
ListResourceTemplatesRequestSchema,
|
|
15
|
+
ReadResourceRequestSchema,
|
|
13
16
|
ErrorCode,
|
|
14
17
|
McpError,
|
|
15
18
|
Tool,
|
|
16
19
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
+
import { z } from "zod";
|
|
17
21
|
import { OAuthProvider, getEnvToken } from "./oauth.js";
|
|
18
22
|
import axios,{ AxiosRequestConfig, AxiosError, AxiosResponse } from "axios";
|
|
19
23
|
import * as tus from "tus-js-client";
|
|
@@ -70,6 +74,24 @@ const SECURITY_SCHEMES: Record<string, SecurityScheme> = {
|
|
|
70
74
|
}
|
|
71
75
|
};
|
|
72
76
|
|
|
77
|
+
// MCP Skills extension (io.modelcontextprotocol/skills). The SDK has no built-in
|
|
78
|
+
// support yet, so the two methods are registered as custom requests.
|
|
79
|
+
const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
|
|
80
|
+
const SKILLS_CACHE = { ttlMs: 300000, cacheScope: "public" };
|
|
81
|
+
const ListSkillsRequestSchema = z.object({
|
|
82
|
+
method: z.literal("skills/list"),
|
|
83
|
+
params: z.object({ cursor: z.string().optional() }).passthrough().optional(),
|
|
84
|
+
});
|
|
85
|
+
const GetSkillRequestSchema = z.object({
|
|
86
|
+
method: z.literal("skills/get"),
|
|
87
|
+
// Lenient on purpose: a missing or non-string uri must surface as InvalidParams
|
|
88
|
+
// from the handler, not as a zod parse failure (InternalError).
|
|
89
|
+
params: z.object({ uri: z.unknown().optional() }).passthrough().optional(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
interface SkillFile { uri: string; digest: string; size: number; mimeType: string; text: string }
|
|
93
|
+
interface Skill { uri: string; frontmatter: Record<string, unknown>; resources: SkillFile[] }
|
|
94
|
+
|
|
73
95
|
/**
|
|
74
96
|
* MCP Server for Hostinger API
|
|
75
97
|
* Generated from OpenAPI spec version 1.54.2
|
|
@@ -81,17 +103,19 @@ class MCPServer {
|
|
|
81
103
|
private server: Server;
|
|
82
104
|
private operations: Map<string, OpenApiTool> = new Map();
|
|
83
105
|
private instructions?: string;
|
|
106
|
+
private skills: Skill[];
|
|
84
107
|
private debug: boolean;
|
|
85
108
|
private baseUrl: string;
|
|
86
109
|
private headers: Record<string, string>;
|
|
87
110
|
private oauth: OAuthProvider;
|
|
88
111
|
|
|
89
|
-
constructor({ name, version, tools, instructions }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string }) {
|
|
112
|
+
constructor({ name, version, tools, instructions, skills = [] }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string; skills?: Skill[] }) {
|
|
90
113
|
// Initialize class properties
|
|
91
114
|
this.name = name;
|
|
92
115
|
this.version = version;
|
|
93
116
|
this.toolList = tools;
|
|
94
117
|
this.instructions = instructions;
|
|
118
|
+
this.skills = skills;
|
|
95
119
|
this.debug = process.env.DEBUG === "true";
|
|
96
120
|
this.baseUrl = process.env.API_BASE_URL || "https://developers.hostinger.com";
|
|
97
121
|
this.headers = this.parseHeaders(process.env.API_HEADERS || "");
|
|
@@ -109,6 +133,7 @@ class MCPServer {
|
|
|
109
133
|
{
|
|
110
134
|
capabilities: {
|
|
111
135
|
tools: {}, // Enable tools capability
|
|
136
|
+
...(this.skills.length > 0 ? { resources: {}, extensions: { [SKILLS_EXTENSION]: {} } } : {}),
|
|
112
137
|
},
|
|
113
138
|
instructions: this.instructions,
|
|
114
139
|
}
|
|
@@ -217,6 +242,62 @@ class MCPServer {
|
|
|
217
242
|
return { isError: true, content: [{ type: "text", text: errorText }] };
|
|
218
243
|
}
|
|
219
244
|
});
|
|
245
|
+
|
|
246
|
+
if (this.skills.length > 0) {
|
|
247
|
+
this.setupSkillHandlers();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Serve the bundled agent skills over the MCP Skills extension: skills/list and
|
|
253
|
+
* skills/get return the manifest entries, and every listed file is a skill://
|
|
254
|
+
* resource readable with resources/read.
|
|
255
|
+
*/
|
|
256
|
+
private setupSkillHandlers(): void {
|
|
257
|
+
const entry = (skill: Skill) => ({
|
|
258
|
+
uri: skill.uri,
|
|
259
|
+
frontmatter: skill.frontmatter,
|
|
260
|
+
resources: skill.resources.map(({ uri, digest, size }) => ({ uri, digest, size })),
|
|
261
|
+
});
|
|
262
|
+
const skillsByUri = new Map<string, Skill>(this.skills.map((skill) => [skill.uri, skill]));
|
|
263
|
+
const files = new Map<string, SkillFile>(this.skills.flatMap((skill) => skill.resources.map((file) => [file.uri, file] as [string, SkillFile])));
|
|
264
|
+
|
|
265
|
+
// Widened return types below satisfy setRequestHandler's inferred result type
|
|
266
|
+
// for these two custom (non-SDK) schemas.
|
|
267
|
+
this.server.setRequestHandler(ListSkillsRequestSchema, async (): Promise<Record<string, unknown>> => ({
|
|
268
|
+
resultType: "complete",
|
|
269
|
+
skills: this.skills.map(entry),
|
|
270
|
+
...SKILLS_CACHE,
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
this.server.setRequestHandler(GetSkillRequestSchema, async (request): Promise<Record<string, unknown>> => {
|
|
274
|
+
const uri = request.params?.uri;
|
|
275
|
+
const skill = typeof uri === "string" ? skillsByUri.get(uri) : undefined;
|
|
276
|
+
if (!skill) {
|
|
277
|
+
throw new McpError(ErrorCode.InvalidParams, typeof uri === "string" ? `Unknown skill: ${uri}` : "Missing or invalid skill uri");
|
|
278
|
+
}
|
|
279
|
+
return { resultType: "complete", skill: entry(skill), ...SKILLS_CACHE };
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
283
|
+
resources: Array.from(files.values()).map((file) => ({
|
|
284
|
+
uri: file.uri,
|
|
285
|
+
name: file.uri.slice("skill://".length),
|
|
286
|
+
mimeType: file.mimeType,
|
|
287
|
+
size: file.size,
|
|
288
|
+
})),
|
|
289
|
+
}));
|
|
290
|
+
|
|
291
|
+
// resources is advertised, so answer templates/list too (skills have none).
|
|
292
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
|
|
293
|
+
|
|
294
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
295
|
+
const file = files.get(request.params.uri);
|
|
296
|
+
if (!file) {
|
|
297
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
|
|
298
|
+
}
|
|
299
|
+
return { contents: [{ uri: file.uri, mimeType: file.mimeType, text: file.text }] };
|
|
300
|
+
});
|
|
220
301
|
}
|
|
221
302
|
|
|
222
303
|
private async executeOperation(operation: unknown, params: unknown): Promise<any> {
|
|
@@ -242,23 +323,23 @@ class MCPServer {
|
|
|
242
323
|
|
|
243
324
|
private async executeCustomTool(tool: OpenApiTool, params: Record<string, any>): Promise<any> {
|
|
244
325
|
switch (tool.name) {
|
|
245
|
-
case 'agency-
|
|
326
|
+
case 'agency-hosting_deploy-node-static-website':
|
|
246
327
|
return await this.handleNodeStaticDeploy(params);
|
|
247
|
-
case 'agency-
|
|
328
|
+
case 'agency-hosting_deploy-php-application':
|
|
248
329
|
return await this.handlePhpAppDeploy(params);
|
|
249
|
-
case '
|
|
330
|
+
case 'hosting_import-wordpress-website':
|
|
250
331
|
return await this.handleWordpressWebsiteImport(params);
|
|
251
|
-
case '
|
|
332
|
+
case 'hosting_deploy-wordpress-plugin':
|
|
252
333
|
return await this.handleWordpressPluginDeploy(params);
|
|
253
|
-
case '
|
|
334
|
+
case 'hosting_deploy-wordpress-theme':
|
|
254
335
|
return await this.handleWordpressThemeDeploy(params);
|
|
255
|
-
case '
|
|
336
|
+
case 'hosting_deploy-js-application':
|
|
256
337
|
return await this.handleJavascriptApplicationDeploy(params);
|
|
257
|
-
case '
|
|
338
|
+
case 'hosting_deploy-static-website':
|
|
258
339
|
return await this.handleStaticWebsiteDeploy(params);
|
|
259
|
-
case '
|
|
340
|
+
case 'hosting_list-js-deployments':
|
|
260
341
|
return await this.handleListJavascriptDeployments(params);
|
|
261
|
-
case '
|
|
342
|
+
case 'hosting_show-js-deployment-logs':
|
|
262
343
|
return await this.handleShowJsDeploymentLogs(params);
|
|
263
344
|
default:
|
|
264
345
|
throw new Error(`Unknown custom tool: ${tool.name}`);
|
|
@@ -2681,7 +2762,7 @@ class MCPServer {
|
|
|
2681
2762
|
}
|
|
2682
2763
|
}
|
|
2683
2764
|
|
|
2684
|
-
export async function startServer({ name, version, tools, instructions }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string }): Promise<void> {
|
|
2765
|
+
export async function startServer({ name, version, tools, instructions, skills }: { name: string; version: string; tools: OpenApiTool[]; instructions?: string; skills?: Skill[] }): Promise<void> {
|
|
2685
2766
|
const argv = minimist(process.argv.slice(2), {
|
|
2686
2767
|
string: ['host'],
|
|
2687
2768
|
boolean: ['stdio', 'http', 'help', 'version', 'login', 'logout'],
|
|
@@ -2739,7 +2820,7 @@ export async function startServer({ name, version, tools, instructions }: { name
|
|
|
2739
2820
|
}
|
|
2740
2821
|
}
|
|
2741
2822
|
|
|
2742
|
-
const server = new MCPServer({ name, version, tools, instructions });
|
|
2823
|
+
const server = new MCPServer({ name, version, tools, instructions, skills });
|
|
2743
2824
|
if (argv.http) {
|
|
2744
2825
|
await server.startHttp(argv.host, argv.port);
|
|
2745
2826
|
} else {
|