impreza-mcp 0.53.0 → 0.54.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Impreza Host
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Impreza Host
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -355,7 +355,7 @@ Static npm sites: choose build_strategy=node_npm_static with a Git/context sourc
355
355
 
356
356
  ## Status
357
357
 
358
- **Package version: 0.53.0.** The tool catalog covers app deployment plus account +
358
+ **Package version: 0.54.0.** The tool catalog covers app deployment plus account +
359
359
  crypto balance, catalog + ordering, domains/DNS + registration, invoices, VPS
360
360
  lifecycle with snapshots and backups, dedicated / bare-metal servers, plan
361
361
  upgrades, and Titan / Google Workspace mailboxes — with a setup wizard that
@@ -542,7 +542,7 @@ and `impreza_api_search` finds anything not named here.
542
542
  | **Catalog & ordering** | |
543
543
  | `impreza_list_products` | `GET /v1/products` — products + pricing; the VPS is listed at its smallest size (`configurable: true`) |
544
544
  | `impreza_vps_offer` | `GET /v1/products/vps` — VPS locations, operating systems, CPU / memory / disk ranges and prices; with a whole configuration, `GET /v1/products/vps/quote` — its exact price |
545
- | `impreza_order_vps` | `POST /v1/orders` — a VPS by location, OS and size (or a fixed plan by `product_id`); paid from balance; bare OS by default (`app: "none"`); explicitly request `app: "@agent"` for deployment, or use `impreza_install_agent` later with customer confirmation; 202 + poll `impreza_list_services` |
545
+ | `impreza_order_vps` | `POST /v1/orders` — a VPS by location, OS and size (or a fixed plan by `product_id`); paid from balance; born with the Impreza agent by default (`app: "@agent"`, for the configurable VPS and fixed VPS plans alike, the same app `impreza_vps_offer` quotes, and echoed as `app_requested` in the result); a non-text `app` is refused before any request; pass `app: "none"` for a bare OS (the customer portal keeps a bare OS as its default) and add the agent later with `impreza_install_agent` with customer confirmation; 202 is not a ready agent: poll `impreza_list_services`, and the agent appears in `impreza_list_servers` once it has installed and connected |
546
546
  | **Domains & DNS** | |
547
547
  | `impreza_domain_check` | `GET /v1/domains/check` |
548
548
  | `impreza_domain_details` | `GET /v1/domains/{domain}` |
@@ -17,26 +17,26 @@ export const credentialDirectory = () => join(homedir(), '.impreza');
17
17
  export const credentialPath = () => join(credentialDirectory(), 'credentials.json');
18
18
  // POSIX mode bits do not restrict access on Windows: use a protected owner-only DACL.
19
19
  function windowsPrivacy(file, directory, set) {
20
- const script = `
21
- $ErrorActionPreference='Stop'
22
- $sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
23
- $p=$env:IMPREZA_PRIVATE_PATH
24
- if ($env:IMPREZA_SET_PRIVATE -eq '1') {
25
- if ($env:IMPREZA_PRIVATE_DIR -eq '1') {
26
- $acl=New-Object Security.AccessControl.DirectorySecurity
27
- $rule=New-Object Security.AccessControl.FileSystemAccessRule($sid,'FullControl','ContainerInherit,ObjectInherit','None','Allow')
28
- } else {
29
- $acl=New-Object Security.AccessControl.FileSecurity
30
- $rule=New-Object Security.AccessControl.FileSystemAccessRule($sid,'FullControl','Allow')
31
- }
32
- $acl.SetOwner($sid); $acl.SetAccessRuleProtection($true,$false); $acl.AddAccessRule($rule)
33
- if ($env:IMPREZA_PRIVATE_DIR -eq '1') { [IO.Directory]::SetAccessControl($p,$acl) } else { [IO.File]::SetAccessControl($p,$acl) }
34
- }
35
- if ($env:IMPREZA_PRIVATE_DIR -eq '1') { $acl=[IO.Directory]::GetAccessControl($p) } else { $acl=[IO.File]::GetAccessControl($p) }
36
- if (-not $acl.AreAccessRulesProtected -or $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { exit 1 }
37
- $rules=$acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])
38
- if ($rules.Count -eq 0) { exit 1 }
39
- foreach ($r in $rules) { if ($r.IsInherited -or $r.IdentityReference.Value -ne $sid.Value -or $r.AccessControlType -ne 'Allow') { exit 1 } }
20
+ const script = `
21
+ $ErrorActionPreference='Stop'
22
+ $sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
23
+ $p=$env:IMPREZA_PRIVATE_PATH
24
+ if ($env:IMPREZA_SET_PRIVATE -eq '1') {
25
+ if ($env:IMPREZA_PRIVATE_DIR -eq '1') {
26
+ $acl=New-Object Security.AccessControl.DirectorySecurity
27
+ $rule=New-Object Security.AccessControl.FileSystemAccessRule($sid,'FullControl','ContainerInherit,ObjectInherit','None','Allow')
28
+ } else {
29
+ $acl=New-Object Security.AccessControl.FileSecurity
30
+ $rule=New-Object Security.AccessControl.FileSystemAccessRule($sid,'FullControl','Allow')
31
+ }
32
+ $acl.SetOwner($sid); $acl.SetAccessRuleProtection($true,$false); $acl.AddAccessRule($rule)
33
+ if ($env:IMPREZA_PRIVATE_DIR -eq '1') { [IO.Directory]::SetAccessControl($p,$acl) } else { [IO.File]::SetAccessControl($p,$acl) }
34
+ }
35
+ if ($env:IMPREZA_PRIVATE_DIR -eq '1') { $acl=[IO.Directory]::GetAccessControl($p) } else { $acl=[IO.File]::GetAccessControl($p) }
36
+ if (-not $acl.AreAccessRulesProtected -or $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { exit 1 }
37
+ $rules=$acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])
38
+ if ($rules.Count -eq 0) { exit 1 }
39
+ foreach ($r in $rules) { if ($r.IsInherited -or $r.IdentityReference.Value -ne $sid.Value -or $r.AccessControlType -ne 'Allow') { exit 1 } }
40
40
  `;
41
41
  const executable = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
42
42
  const result = spawnSync(executable, ['-NoProfile', '-NonInteractive', '-Command', script], {
package/dist/server.js CHANGED
@@ -3212,7 +3212,7 @@ const TOOLS = [
3212
3212
  cpu_cores: { type: 'number', description: 'Optional: vCPU count, inside the range this tool lists.' },
3213
3213
  memory_gb: { type: 'number', description: 'Optional: memory in GB, inside the range this tool lists.' },
3214
3214
  disk_gb: { type: 'number', description: 'Optional: SSD disk in GB, inside the range this tool lists.' },
3215
- app: { type: 'string', description: "Optional: what it is born with — 'none' (default, bare OS); '@agent' is opt-in, '@agent-mcp', 'none', a '@panel-*' choice or a catalog app name. The customer can request the agent later via impreza_install_agent with confirmation." },
3215
+ app: { type: 'string', description: "Optional: what it is born with. Omitted: '@agent' (the Impreza agent), the same default impreza_order_vps uses, so the quote and the order match. 'none' for a bare OS, '@agent-mcp', a '@panel-*' choice or a catalog app name." },
3216
3216
  },
3217
3217
  additionalProperties: false,
3218
3218
  },
@@ -3220,7 +3220,7 @@ const TOOLS = [
3220
3220
  },
3221
3221
  {
3222
3222
  name: 'impreza_order_vps',
3223
- description: "Order a VPS and pay from the account credit balance — the balance must already cover the price (top up first with impreza_topup). For the VPS, choose location, os, cpu_cores, memory_gb and disk_gb from impreza_vps_offer and leave product_id out; the price is exactly what impreza_vps_offer quotes for that configuration. For a fixed plan such as Tor Hosting, pass its product_id from impreza_list_products instead (and config_options only if that plan has some). The VPS defaults to app=none (bare OS, no agent), matching the cart. Choose app=@agent explicitly, or request the agent later via impreza_install_agent with customer confirmation. Bare VPS services appear under impreza_list_services; impreza_list_servers only lists installed agents. Requires the \"manage\" scope. Returns order_id, invoice_id, amount, the configuration and status. If confirm_token is absent from this connection's input schema, use a connection that exposes it or a supported customer-portal flow. A quote or confirmation request is not completion. COSTS MONEY — takes two calls: the first is a QUOTE that charges nothing and returns the price plus a confirm_token, the second repeats the same arguments plus confirm_token and buys. Purchases also count against a daily spend ceiling on the credential.",
3223
+ description: "Order a VPS and pay from the account credit balance — the balance must already cover the price (top up first with impreza_topup). For the VPS, choose location, os, cpu_cores, memory_gb and disk_gb from impreza_vps_offer and leave product_id out; the price is exactly what impreza_vps_offer quotes for that configuration. For a fixed plan such as Tor Hosting, pass its product_id from impreza_list_products instead (and config_options only if that plan has some). Ordered here, a VPS (the configurable VPS or a fixed VPS plan) defaults to app=@agent: it is born with the Impreza agent so it can receive app deployments, and the result shows it as app_requested. Pass app=none for a bare OS (the customer portal and cart keep a bare OS as their default); a bare VPS can get the agent later through impreza_install_agent with customer confirmation. Installing the agent does not connect an MCP client or create a credential. An accepted order is not a ready agent: the service appears under impreza_list_services first, and the agent appears in impreza_list_servers only once it has installed and connected. Requires the \"manage\" scope. Returns order_id, invoice_id, amount, the configuration and status. If confirm_token is absent from this connection's input schema, use a connection that exposes it or a supported customer-portal flow. A quote or confirmation request is not completion. COSTS MONEY — takes two calls: the first is a QUOTE that charges nothing and returns the price plus a confirm_token, the second repeats the same arguments plus confirm_token and buys. Purchases also count against a daily spend ceiling on the credential.",
3224
3224
  inputSchema: {
3225
3225
  type: 'object',
3226
3226
  properties: {
@@ -3230,7 +3230,7 @@ const TOOLS = [
3230
3230
  cpu_cores: { type: 'number', description: 'VPS vCPU count, inside the range impreza_vps_offer lists.' },
3231
3231
  memory_gb: { type: 'number', description: 'VPS memory in GB, inside the range impreza_vps_offer lists.' },
3232
3232
  disk_gb: { type: 'number', description: 'VPS SSD disk in GB, inside the range impreza_vps_offer lists.' },
3233
- app: { type: 'string', description: "Optional initial software: 'none' (default, bare OS), opt-in @agent, @agent-mcp (agent plus MCP), a @panel-* control panel, or a catalog app name from impreza_list_apps. The customer may later request the agent through impreza_install_agent with confirmation." },
3233
+ app: { type: 'string', description: "Optional initial software, as text. Omitted, null or blank: '@agent' (the Impreza agent), for the configurable VPS and for fixed VPS plans alike. 'none' for a bare OS, '@agent-mcp' (agent plus MCP), a @panel-* control panel, or a catalog app name from impreza_list_apps. Plans that are not a VPS ignore it." },
3234
3234
  hostname: { type: 'string', description: 'Optional server hostname (a DNS name such as "web-1"). Two identical VPS orders within a day are refused as a repeat unless their hostnames differ.' },
3235
3235
  product_id: { type: 'number', description: 'Only for a fixed plan (e.g. Tor Hosting) from impreza_list_products. Leave it out for the VPS.' },
3236
3236
  config_options: { type: 'object', description: "Fixed plans only: optionId-to-value map from that plan's product details. Every option must belong to the selected plan." },
@@ -5511,14 +5511,19 @@ async function callTool(req) {
5511
5511
  // The server checks every value against the product; this only picks
5512
5512
  // which of the two reads the arguments ask for.
5513
5513
  const pick = {};
5514
- for (const k of ['billing_cycle', 'location', 'os', 'cpu_cores', 'memory_gb', 'disk_gb', 'app']) {
5514
+ for (const k of ['billing_cycle', 'location', 'os', 'cpu_cores', 'memory_gb', 'disk_gb']) {
5515
5515
  const v = args[k];
5516
5516
  if ((typeof v === 'string' && v !== '') || (typeof v === 'number' && Number.isFinite(v)))
5517
5517
  pick[k] = String(v);
5518
5518
  }
5519
5519
  const whole = ['billing_cycle', 'location', 'os', 'cpu_cores', 'memory_gb', 'disk_gb'].every((k) => k in pick);
5520
- if (whole)
5521
- return toResult(await impreza.get('/v1/products/vps/quote', pick));
5520
+ if (whole) {
5521
+ // Priced with the same app the order will carry (orderApp: omitted → @agent).
5522
+ const app = orderApp(args.app);
5523
+ if (app === undefined)
5524
+ return toError(ORDER_APP_TYPE);
5525
+ return toResult(await impreza.get('/v1/products/vps/quote', { ...pick, app }));
5526
+ }
5522
5527
  return toResult(await impreza.get('/v1/products/vps'));
5523
5528
  }
5524
5529
  case 'impreza_order_vps': {
@@ -5533,14 +5538,20 @@ async function callTool(req) {
5533
5538
  }
5534
5539
  body.product_id = productId;
5535
5540
  }
5536
- for (const k of ['location', 'os', 'cpu_cores', 'memory_gb', 'disk_gb', 'app', 'hostname', 'domain']) {
5541
+ for (const k of ['location', 'os', 'cpu_cores', 'memory_gb', 'disk_gb', 'hostname', 'domain']) {
5537
5542
  const v = args[k];
5538
5543
  if ((typeof v === 'string' && v !== '') || (typeof v === 'number' && Number.isFinite(v)))
5539
5544
  body[k] = v;
5540
5545
  }
5546
+ const app = orderApp(args.app);
5547
+ if (app === undefined)
5548
+ return toError(ORDER_APP_TYPE);
5549
+ body.app = app;
5541
5550
  if (args.config_options && typeof args.config_options === 'object')
5542
5551
  body.config_options = args.config_options;
5543
- return toResult(await impreza.post('/v1/orders', body));
5552
+ // The app this call asked for, shown with the result: the default is never applied silently.
5553
+ const placed = await impreza.post('/v1/orders', body);
5554
+ return toResult(placed && typeof placed === 'object' && !Array.isArray(placed) ? { ...placed, app_requested: app } : placed);
5544
5555
  }
5545
5556
  // ── Domains + DNS ────────────────────────────────────────────────
5546
5557
  case 'impreza_domain_check': {
@@ -6264,6 +6275,23 @@ async function apiCatalog(impreza) {
6264
6275
  catalogCache = ops;
6265
6276
  return ops;
6266
6277
  }
6278
+ /**
6279
+ * The initial app of a VPS bought through this MCP. Omitted (or blank) means the Impreza agent: a VPS ordered from
6280
+ * an AI assistant should be able to receive deployments. Applied before the quote and the order, so both carry the
6281
+ * same choice. An explicit 'none' (bare OS) or any other value passes through unchanged and the API validates it;
6282
+ * the API applies it to the configurable VPS and to fixed VPS plans and ignores it for plans that are not a VPS. The
6283
+ * REST API, the cart and the customer portal keep their own default (a bare OS). Anything that is not text (a number,
6284
+ * a boolean, an object, an array) is refused before any request (undefined).
6285
+ */
6286
+ const ORDER_APP_DEFAULT = '@agent';
6287
+ const ORDER_APP_TYPE = "app must be text: '@agent', 'none', '@agent-mcp', a @panel-* choice or a catalog app name.";
6288
+ function orderApp(value) {
6289
+ if (value === undefined || value === null || (typeof value === 'string' && value.trim() === ''))
6290
+ return ORDER_APP_DEFAULT;
6291
+ if (typeof value === 'string')
6292
+ return value;
6293
+ return undefined; // a number, boolean, object or array is not an app name: refused before any request
6294
+ }
6267
6295
  function svcId(args) {
6268
6296
  const raw = args.service_id;
6269
6297
  const s = typeof raw === 'number' ? String(raw) : String(raw ?? '').trim();