vpsnet-mcp 2.3.13 → 2.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/build/http-server.js +2 -1
- package/build/remote-policy.js +10 -2
- package/build/remote-tools.js +17 -2
- package/build/scope-catalog.js +417 -0
- package/build/update-info.js +135 -0
- package/package.json +4 -1
- package/scripts/update-clients.json +204 -0
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ endpoint and task skills. The npm package supplies the local MCP server;
|
|
|
40
40
|
installable plugin bundles also carry the AI application's manifest and skills.
|
|
41
41
|
|
|
42
42
|
Build all client bundles with `python3 scripts/package-distribution.py`.
|
|
43
|
+
The same build creates four [self-contained skill imports](docs/STANDALONE_SKILLS.md)
|
|
44
|
+
for clients that import one skill folder or ZIP at a time.
|
|
43
45
|
The output includes checksums and identifies each format. Public directory
|
|
44
46
|
submission materials are in [the catalog kit](distribution/catalogs/README.md).
|
|
45
47
|
|
|
@@ -337,7 +339,10 @@ Choose your environment:
|
|
|
337
339
|
- [VS Code with GitHub Copilot](#vs-code-with-github-copilot) — Copilot agent mode
|
|
338
340
|
- [Cline](#cline) / [Cursor](#cursor) / [Windsurf](#windsurf) / [Roo Code](#roo-code) / [Codex](#codex) — Other clients
|
|
339
341
|
|
|
340
|
-
The
|
|
342
|
+
The following local stdio example uses the `mcpServers` configuration format
|
|
343
|
+
supported by several MCP clients. Use your client's native schema; VS Code's
|
|
344
|
+
`.vscode/mcp.json` uses `servers` instead. For customer OAuth and the hosted
|
|
345
|
+
connector, use [AI client setup](docs/CLIENTS.md).
|
|
341
346
|
|
|
342
347
|
```json
|
|
343
348
|
{
|
|
@@ -495,7 +500,8 @@ Go to **Cursor Settings** > **MCP** > **Add new MCP Server**. Use command type w
|
|
|
495
500
|
<details>
|
|
496
501
|
<summary>Roo Code</summary>
|
|
497
502
|
|
|
498
|
-
Open Roo Code MCP settings and
|
|
503
|
+
Open Roo Code MCP settings and choose **Edit Global MCP** (currently
|
|
504
|
+
`mcp_settings.json`), or **Edit Project MCP** for `.roo/mcp.json`:
|
|
499
505
|
|
|
500
506
|
```json
|
|
501
507
|
{
|
|
@@ -516,24 +522,74 @@ Open Roo Code MCP settings and add to `roo_mcp_settings.json`:
|
|
|
516
522
|
</details>
|
|
517
523
|
|
|
518
524
|
<details>
|
|
519
|
-
<summary>VS Code with GitHub Copilot</summary>
|
|
525
|
+
<summary id="vs-code-with-github-copilot">VS Code with GitHub Copilot</summary>
|
|
520
526
|
|
|
521
|
-
|
|
527
|
+
For the hosted customer OAuth connection, run **MCP: Add Server** and enter
|
|
528
|
+
`https://mcp.vpsnet.com/mcp`, or merge this native VS Code entry into
|
|
529
|
+
`.vscode/mcp.json`, preserving other servers:
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"servers": {
|
|
534
|
+
"vpsnet": {
|
|
535
|
+
"type": "http",
|
|
536
|
+
"url": "https://mcp.vpsnet.com/mcp"
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Complete the browser sign-in and consent flow when requested. Copilot's portable
|
|
543
|
+
workspace `.mcp.json` and user `~/.copilot/mcp-config.json` use a top-level
|
|
544
|
+
`mcpServers` object instead. These formats are distinct from the Claude Code
|
|
545
|
+
extension's configuration.
|
|
546
|
+
|
|
547
|
+
For the separate local stdio package with your own VPSnet API key, install using
|
|
548
|
+
the VS Code CLI:
|
|
522
549
|
|
|
523
550
|
```bash
|
|
524
551
|
code --add-mcp '{"name":"vpsnet","command":"npx","args":["-y","vpsnet-mcp"],"env":{"VPSNET_API_KEY":"your_api_key_here"}}'
|
|
525
552
|
```
|
|
526
553
|
|
|
527
|
-
|
|
554
|
+
For manual native VS Code configuration, place that local server's `command`,
|
|
555
|
+
`args` and private `env` under `servers.vpsnet`, rather than copying the generic
|
|
556
|
+
`mcpServers` wrapper. VS Code's **MCP: Reset Cached Tools** refreshes a stale
|
|
557
|
+
tool catalog. See [official MCP configuration](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
|
|
528
558
|
|
|
529
559
|
> This is for **GitHub Copilot** agent mode in VS Code. For the **Claude Code** extension, see the [Claude Code for VS Code Extension](#claude-code-for-vs-code-extension) section.
|
|
530
560
|
|
|
531
561
|
</details>
|
|
532
562
|
|
|
533
563
|
<details>
|
|
534
|
-
<summary>Windsurf</summary>
|
|
564
|
+
<summary id="windsurf">Windsurf / Devin Desktop</summary>
|
|
565
|
+
|
|
566
|
+
Current official Windsurf documentation redirects to Devin Desktop. Choose the
|
|
567
|
+
agent you are using: legacy Cascade and the current Devin Local agent have
|
|
568
|
+
different MCP setup paths.
|
|
569
|
+
|
|
570
|
+
For Devin Local, add and authenticate the hosted connector with the Devin CLI:
|
|
571
|
+
|
|
572
|
+
```sh
|
|
573
|
+
devin mcp add vpsnet https://mcp.vpsnet.com/mcp
|
|
574
|
+
devin mcp login vpsnet --scopes services:read
|
|
575
|
+
```
|
|
535
576
|
|
|
536
|
-
|
|
577
|
+
This requests a read baseline. Request the documented scopes required for other
|
|
578
|
+
tasks through a new authorization flow before approving them. If authorization
|
|
579
|
+
is lost, use the Desktop **Authenticate** action, or `devin mcp logout vpsnet`
|
|
580
|
+
followed by the login command. Current CLI versions use dedicated
|
|
581
|
+
`.devin/mcp_config.json`, `.devin/mcp_config.local.json` or user
|
|
582
|
+
`~/.config/devin/mcp_config.json` files.
|
|
583
|
+
|
|
584
|
+
For legacy Cascade, open the panel's **Actions → Open MCP config file** and merge
|
|
585
|
+
a server under `mcpServers` with `serverUrl: "https://mcp.vpsnet.com/mcp"`.
|
|
586
|
+
Complete its OAuth flow. Cascade has a 100-tool total limit, so inspect the
|
|
587
|
+
enabled tools. The local stdio example above is a separate API-key connection.
|
|
588
|
+
|
|
589
|
+
Follow the official [Devin Local MCP guide](https://docs.devin.ai/cli/extensibility/mcp/configuration)
|
|
590
|
+
or [legacy Cascade guide](https://docs.devin.ai/desktop/cascade/mcp) for your
|
|
591
|
+
surface. Client authentication and VPSnet compatibility still need verification
|
|
592
|
+
on the actual client version.
|
|
537
593
|
|
|
538
594
|
</details>
|
|
539
595
|
|
package/build/http-server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
3
3
|
import { createVpsnetServer } from "./index.js";
|
|
4
4
|
import { resolveApiBase } from "./api.js";
|
|
5
5
|
import { requireRequestAuthorization, withAuthorization } from "./request-context.js";
|
|
6
|
+
import { scopeDescriptions } from "./scope-catalog.js";
|
|
6
7
|
import { supportedScopes, orderScopes } from "./remote-policy.js";
|
|
7
8
|
function publicUrl(value) {
|
|
8
9
|
const url = new URL(value);
|
|
@@ -70,7 +71,7 @@ export function createRemoteServer(options) {
|
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
if (req.method === 'GET' && ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'].includes(req.url ?? '')) {
|
|
73
|
-
return json(res, 200, { resource: resource.href, authorization_servers: [issuer], scopes_supported: allowedScopes, bearer_methods_supported: ['header'], resource_name: 'VPSnet' });
|
|
74
|
+
return json(res, 200, { resource: resource.href, authorization_servers: [issuer], scopes_supported: allowedScopes, scope_descriptions: scopeDescriptions(allowedScopes), bearer_methods_supported: ['header'], resource_name: 'VPSnet' });
|
|
74
75
|
}
|
|
75
76
|
if (req.url !== '/mcp')
|
|
76
77
|
return json(res, 404, { error: 'not_found' });
|
package/build/remote-policy.js
CHANGED
|
@@ -8,6 +8,7 @@ export const remoteInstructions = [
|
|
|
8
8
|
"Firecracker VPS supports general-purpose and resource-intensive Linux workloads with substantial server resources and low virtualization overhead. MicroVM describes the lightweight design, not a small server limit. Consider it when KVM-specific features are unnecessary; match current plans to resource and availability requirements.",
|
|
9
9
|
"Cloud VPS (KVM) offers Linux and BSD support, automatic Windows Server provisioning, three-copy Ceph NVMe storage on enterprise drives with power-loss protection (PLP), HA restart on another host after host failure, and live migration to another node for planned host upgrades, with migration back when appropriate.",
|
|
10
10
|
"Use the connected VPSnet account and only the services and permissions it granted. Never request a model API key.",
|
|
11
|
+
"On first VPSnet use in a conversation, call get_update_info once when available. Read an installed skill's bundled plugin-version reference if accessible; otherwise omit pluginVersion rather than infer it from clientInfo.version or repository HEAD. Keep checked/dismissed state only in conversation context. If an actually observed plugin/local version is older, give one concise native update advisory and continue the task. For unknown/current versions or unavailable release facts, stay quiet unless the user asks about updates. Never force an update, change grants or treat a published runtime as proof of deployment.",
|
|
11
12
|
"Tool discovery describes supported capabilities, not granted authority. Compare the scopes and selected services from get_connected_account with each tool's required scope metadata. Explain missing permissions only when relevant to the user's task. Ask the customer to approve the required permission by re-authorizing this existing VPSnet connection through the AI app's OAuth flow; never work around a denial.",
|
|
12
13
|
"Inspect current capabilities and state before a change. Honor the user's existing authorization; obtain consent when a change expands the task, destroys unrelated data or adds unapproved exposure.",
|
|
13
14
|
"Keep operation IDs, reason codes and idempotency keys. Accepted means pending; read the final state before claiming success.",
|
|
@@ -118,7 +119,7 @@ const permissions = new Map([
|
|
|
118
119
|
["get_system_status", "authenticated"],
|
|
119
120
|
["get_faq", "authenticated"],
|
|
120
121
|
["get_pricing", "authenticated"],
|
|
121
|
-
["get_connected_account", "authenticated"],
|
|
122
|
+
["get_connected_account", "authenticated"], ["get_update_info", "authenticated"],
|
|
122
123
|
["open_vm_session", "vm:session"], ["run_vm_command", "vm:exec"], ["read_vm_file", "vm:files:read"],
|
|
123
124
|
["write_vm_file", "vm:files:write"], ["create_vm_file", "vm:files:write"], ["inspect_vm", "vm:diagnostics"], ["get_vm_operation", "vm:session"], ["cancel_vm_operation", "vm:session"],
|
|
124
125
|
]);
|
|
@@ -227,7 +228,14 @@ export function installRemotePolicy(server, scopes, profile = 'directory') {
|
|
|
227
228
|
return setRequestHandler(schema, handler);
|
|
228
229
|
return setRequestHandler(schema, async (...args) => {
|
|
229
230
|
const response = await handler(...args);
|
|
230
|
-
return { ...response, tools: response.tools
|
|
231
|
+
return { ...response, tools: response.tools
|
|
232
|
+
.filter((tool) => !legacyAggregateTools.has(tool.name))
|
|
233
|
+
// SDK registerTool preserves _meta but drops unknown top-level config
|
|
234
|
+
// fields. Add the canonical declaration to the emitted descriptor,
|
|
235
|
+
// using exactly the same scopes as its compatibility mirror.
|
|
236
|
+
.map((tool) => ({
|
|
237
|
+
...tool, securitySchemes: tool._meta?.securitySchemes,
|
|
238
|
+
})) };
|
|
231
239
|
});
|
|
232
240
|
});
|
|
233
241
|
const original = server.registerTool.bind(server);
|
package/build/remote-tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import { apiRequest, resolveApiBase } from './api.js';
|
|
4
|
+
import { getUpdateInfo, updateInfoOutput } from './update-info.js';
|
|
4
5
|
const resource = { orderNo: z.string().regex(/^[A-Za-z0-9-]{1,64}$/), sessionBinding: z.string().regex(/^[a-f0-9]{32}$/, 'sessionBinding must contain exactly 32 lowercase hexadecimal characters (0-9, a-f), without hyphens. For a new open_vm_session call, omit it so VPSnet creates it; otherwise reuse the returned sessionBinding.').describe('Exactly 32 lowercase hexadecimal characters (0-9, a-f), without hyphens. Copy sessionBinding from open_vm_session and reuse it for every operation and reconnect. Not a credential.') };
|
|
5
6
|
const session = { ...resource, sessionId: z.string().uuid() };
|
|
6
7
|
const idempotencyKey = z.string().regex(/^[A-Za-z0-9_-]{16,128}$/).describe('Stable key for this exact operation. Reuse after a lost response; never automatically replace.');
|
|
@@ -76,9 +77,23 @@ async function operationResult(orderNo, sessionBinding, operationId, cancel = fa
|
|
|
76
77
|
return runtime(await lease(orderNo, sessionBinding, true), cancel ? 'POST' : 'GET', `/mcp/operations/${operationId}${cancel ? '/cancel' : ''}`, cancel ? {} : undefined);
|
|
77
78
|
}
|
|
78
79
|
export function registerRemoteTools(server) {
|
|
80
|
+
server.registerTool('get_update_info', {
|
|
81
|
+
description: 'Read public VPSnet releases and reviewed client update guidance without changing software, credentials or permissions. On first VPSnet use in a conversation, check once when available. Pass installed versions only if actually observed or user-reported; never infer them from host clientInfo.version or the gateway runtime. Unknown installed versions stay unknown. If an observed version is older, give one concise advisory and continue; otherwise stay quiet unless the user asks about updates. Remember checks and dismissals in conversation context, not customer tracking. VPSnet maintains the hosted gateway.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
client: z.string().max(64).optional().describe('Known client identifier such as codex, claude-code or gemini-cli; omit when unknown.'),
|
|
84
|
+
pluginVersion: z.string().max(63).optional().describe('Observed or user-reported installed VPSnet plugin version, not the host application version. Omit if unknown.'),
|
|
85
|
+
localMcpVersion: z.string().max(63).optional().describe('Observed or user-reported local vpsnet-mcp npm version. Omit for remote-only connections or when unknown.'),
|
|
86
|
+
}, outputSchema: updateInfoOutput,
|
|
87
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
88
|
+
}, async (args) => { const data = await getUpdateInfo(args); return { ...result(data), structuredContent: data }; });
|
|
89
|
+
const spendingLimit = z.union([z.number().finite().nonnegative(), z.string().regex(/^\d+(?:\.\d+)?$/)]).nullable().optional();
|
|
79
90
|
server.registerTool('get_connected_account', {
|
|
80
|
-
description: 'Identify the connected VPSnet account and its granted permissions and
|
|
81
|
-
|
|
91
|
+
description: 'Identify the connected VPSnet account and its granted permissions, selected services and spending limits.', inputSchema: {},
|
|
92
|
+
outputSchema: z.object({
|
|
93
|
+
id: z.string().min(1), name: z.string(), scopes: z.array(z.string()), services: z.array(z.string()),
|
|
94
|
+
function_daily_limit_eur: spendingLimit, order_daily_limit_eur: spendingLimit, order_monthly_limit_eur: spendingLimit,
|
|
95
|
+
}).passthrough(),
|
|
96
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
82
97
|
}, async () => { const { data } = await apiRequest('GET', '/account/mcp/profile'); return { ...result(data), structuredContent: data }; });
|
|
83
98
|
server.registerTool('open_vm_session', {
|
|
84
99
|
description: 'Open an authorized Linux session on a selected KVM or Firecracker service. The VPSnet guest channel works without a VM IP, SSH password or guest internet connection; choose connectionMethod="guest" for network/SSH troubleshooting or "ssh" for the independent SSH path. Auto prefers the guest channel. Requires explicit VM permission. Omit sessionBinding for a new session; VPSnet generates it and returns it with sessionId. Reuse both returned values for commands, files and reconnects. The OS user is root. Read /etc/os-release through the first operation to verify guest connectivity before choosing OS-specific commands or making changes. An empty managed-app list does not mean the server has no software.',
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// Generated from backend app/Services/Mcp/scope-catalog.json.
|
|
2
|
+
// Update with scripts/sync-scope-catalog.py; OAuth policy is enforced separately.
|
|
3
|
+
export const scopeCatalog = {
|
|
4
|
+
"services:read": {
|
|
5
|
+
"label": {
|
|
6
|
+
"en": "View selected servers",
|
|
7
|
+
"lt": "Peržiūrėti pasirinktus serverius"
|
|
8
|
+
},
|
|
9
|
+
"description": {
|
|
10
|
+
"en": "View selected servers, status, graphs, history, backups and snapshots; browsing backup files may queue a saved browse operation.",
|
|
11
|
+
"lt": "Peržiūrėti pasirinktus serverius, būseną, grafikus, istoriją, atsargines ir momentines kopijas; kopijų failų naršymas gali sukurti išsaugomą užduotį."
|
|
12
|
+
},
|
|
13
|
+
"implies": [],
|
|
14
|
+
"billing": "none",
|
|
15
|
+
"legacy": false
|
|
16
|
+
},
|
|
17
|
+
"services:manage": {
|
|
18
|
+
"label": {
|
|
19
|
+
"en": "Manage selected servers",
|
|
20
|
+
"lt": "Valdyti pasirinktus serverius"
|
|
21
|
+
},
|
|
22
|
+
"description": {
|
|
23
|
+
"en": "Start, stop or restart selected servers and change their settings, including guest DNS and firewall reset. Includes viewing selected servers.",
|
|
24
|
+
"lt": "Paleisti, stabdyti ar perkrauti pasirinktus serverius ir keisti jų nustatymus, įskaitant DNS bei ugniasienės atkūrimą. Apima serverių peržiūrą."
|
|
25
|
+
},
|
|
26
|
+
"implies": [
|
|
27
|
+
"services:read"
|
|
28
|
+
],
|
|
29
|
+
"billing": "none",
|
|
30
|
+
"legacy": false
|
|
31
|
+
},
|
|
32
|
+
"applications:read": {
|
|
33
|
+
"label": {
|
|
34
|
+
"en": "View selected server applications",
|
|
35
|
+
"lt": "Peržiūrėti pasirinktų serverių programas"
|
|
36
|
+
},
|
|
37
|
+
"description": {
|
|
38
|
+
"en": "View applications, recipes, health and logs on selected servers; diagnostics may start saved inspection jobs.",
|
|
39
|
+
"lt": "Peržiūrėti pasirinktų serverių programas, receptus, būseną ir žurnalus; diagnostika gali paleisti išsaugomas patikros užduotis."
|
|
40
|
+
},
|
|
41
|
+
"implies": [],
|
|
42
|
+
"billing": "none",
|
|
43
|
+
"legacy": false
|
|
44
|
+
},
|
|
45
|
+
"applications:manage": {
|
|
46
|
+
"label": {
|
|
47
|
+
"en": "Manage selected server applications",
|
|
48
|
+
"lt": "Valdyti pasirinktų serverių programas"
|
|
49
|
+
},
|
|
50
|
+
"description": {
|
|
51
|
+
"en": "Install, configure, update, stop, remove or roll back applications and recipes on selected servers. May replace or delete application data. Includes application viewing.",
|
|
52
|
+
"lt": "Diegti, konfigūruoti, atnaujinti, stabdyti, šalinti ar atkurti pasirinktų serverių programas ir receptus. Gali pakeisti ar ištrinti programų duomenis. Apima programų peržiūrą."
|
|
53
|
+
},
|
|
54
|
+
"implies": [
|
|
55
|
+
"applications:read"
|
|
56
|
+
],
|
|
57
|
+
"billing": "none",
|
|
58
|
+
"legacy": false
|
|
59
|
+
},
|
|
60
|
+
"networking:read": {
|
|
61
|
+
"label": {
|
|
62
|
+
"en": "View account networks",
|
|
63
|
+
"lt": "Peržiūrėti paskyros tinklus"
|
|
64
|
+
},
|
|
65
|
+
"description": {
|
|
66
|
+
"en": "View existing networks, attachments, routes and network appliance status across your account.",
|
|
67
|
+
"lt": "Peržiūrėti esamus paskyros tinklus, jungtis, maršrutus ir tinklo įrenginių būseną."
|
|
68
|
+
},
|
|
69
|
+
"implies": [],
|
|
70
|
+
"billing": "none",
|
|
71
|
+
"legacy": false
|
|
72
|
+
},
|
|
73
|
+
"networking:manage": {
|
|
74
|
+
"label": {
|
|
75
|
+
"en": "Manage account networks",
|
|
76
|
+
"lt": "Valdyti paskyros tinklus"
|
|
77
|
+
},
|
|
78
|
+
"description": {
|
|
79
|
+
"en": "Create, change or remove existing-account networks, routes, attachments and appliances. May interrupt connectivity. Includes network viewing; excludes purchases and renewals.",
|
|
80
|
+
"lt": "Kurti, keisti ar šalinti paskyros tinklus, maršrutus, jungtis ir įrenginius. Gali sutrikdyti ryšį. Apima tinklų peržiūrą; pirkimai ir pratęsimai neįtraukti."
|
|
81
|
+
},
|
|
82
|
+
"implies": [
|
|
83
|
+
"networking:read"
|
|
84
|
+
],
|
|
85
|
+
"billing": "none",
|
|
86
|
+
"legacy": false
|
|
87
|
+
},
|
|
88
|
+
"functions:read": {
|
|
89
|
+
"label": {
|
|
90
|
+
"en": "View Functions and run results",
|
|
91
|
+
"lt": "Peržiūrėti Functions ir vykdymo rezultatus"
|
|
92
|
+
},
|
|
93
|
+
"description": {
|
|
94
|
+
"en": "View account Functions, their configuration, history and invocation results.",
|
|
95
|
+
"lt": "Peržiūrėti paskyros Functions, jų konfigūraciją, istoriją ir vykdymo rezultatus."
|
|
96
|
+
},
|
|
97
|
+
"implies": [],
|
|
98
|
+
"billing": "none",
|
|
99
|
+
"legacy": false
|
|
100
|
+
},
|
|
101
|
+
"functions:manage": {
|
|
102
|
+
"label": {
|
|
103
|
+
"en": "Manage Functions",
|
|
104
|
+
"lt": "Valdyti Functions"
|
|
105
|
+
},
|
|
106
|
+
"description": {
|
|
107
|
+
"en": "Create, update or delete account Functions and their code. Includes viewing Functions; running them requires separate paid execution permission.",
|
|
108
|
+
"lt": "Kurti, atnaujinti ar trinti paskyros Functions ir jų kodą. Apima Functions peržiūrą; vykdymui reikia atskiro mokamo vykdymo leidimo."
|
|
109
|
+
},
|
|
110
|
+
"implies": [
|
|
111
|
+
"functions:read"
|
|
112
|
+
],
|
|
113
|
+
"billing": "none",
|
|
114
|
+
"legacy": false
|
|
115
|
+
},
|
|
116
|
+
"functions:invoke": {
|
|
117
|
+
"label": {
|
|
118
|
+
"en": "Run Functions (usage charges)",
|
|
119
|
+
"lt": "Vykdyti Functions (mokama pagal naudojimą)"
|
|
120
|
+
},
|
|
121
|
+
"description": {
|
|
122
|
+
"en": "Run existing account Functions using your VPSnet balance. Usage charges apply; your AI subscription does not cover them. Includes viewing Functions and run results. The daily limit counts total account Functions spending, including pending runs.",
|
|
123
|
+
"lt": "Vykdyti paskyros Functions naudojant VPSnet balansą. Taikomi naudojimo mokesčiai; AI prenumerata jų nepadengia. Apima Functions ir rezultatų peržiūrą. Dienos riba apima visas paskyros Functions išlaidas, įskaitant laukiančius vykdymus."
|
|
124
|
+
},
|
|
125
|
+
"implies": [
|
|
126
|
+
"functions:read"
|
|
127
|
+
],
|
|
128
|
+
"billing": "usage_balance",
|
|
129
|
+
"legacy": false
|
|
130
|
+
},
|
|
131
|
+
"vm:diagnostics": {
|
|
132
|
+
"label": {
|
|
133
|
+
"en": "Inspect selected servers",
|
|
134
|
+
"lt": "Tikrinti pasirinktus serverius"
|
|
135
|
+
},
|
|
136
|
+
"description": {
|
|
137
|
+
"en": "Run bounded diagnostics on selected servers and save operation results. Does not grant arbitrary commands or file access.",
|
|
138
|
+
"lt": "Vykdyti ribotą pasirinktų serverių diagnostiką ir išsaugoti rezultatus. Nesuteikia savavališkų komandų ar failų prieigos."
|
|
139
|
+
},
|
|
140
|
+
"implies": [],
|
|
141
|
+
"billing": "none",
|
|
142
|
+
"legacy": false
|
|
143
|
+
},
|
|
144
|
+
"vm:files:read": {
|
|
145
|
+
"label": {
|
|
146
|
+
"en": "Read selected server files as root",
|
|
147
|
+
"lt": "Skaityti pasirinktų serverių failus root teisėmis"
|
|
148
|
+
},
|
|
149
|
+
"description": {
|
|
150
|
+
"en": "Read files on selected servers with root privileges through saved guest operations. Files may contain secrets.",
|
|
151
|
+
"lt": "Skaityti pasirinktų serverių failus root teisėmis per išsaugomas užduotis. Failuose gali būti paslapčių."
|
|
152
|
+
},
|
|
153
|
+
"implies": [],
|
|
154
|
+
"billing": "none",
|
|
155
|
+
"legacy": false
|
|
156
|
+
},
|
|
157
|
+
"vm:files:write": {
|
|
158
|
+
"label": {
|
|
159
|
+
"en": "Read and change selected server files as root",
|
|
160
|
+
"lt": "Skaityti ir keisti pasirinktų serverių failus root teisėmis"
|
|
161
|
+
},
|
|
162
|
+
"description": {
|
|
163
|
+
"en": "Create or overwrite files on selected servers with root privileges. Includes reading files; changes can replace data or affect server operation.",
|
|
164
|
+
"lt": "Kurti ar perrašyti pasirinktų serverių failus root teisėmis. Apima failų skaitymą; pakeitimai gali pakeisti duomenis ar paveikti serverio veikimą."
|
|
165
|
+
},
|
|
166
|
+
"implies": [
|
|
167
|
+
"vm:files:read"
|
|
168
|
+
],
|
|
169
|
+
"billing": "none",
|
|
170
|
+
"legacy": false
|
|
171
|
+
},
|
|
172
|
+
"vm:exec": {
|
|
173
|
+
"label": {
|
|
174
|
+
"en": "Run selected server commands as root",
|
|
175
|
+
"lt": "Vykdyti pasirinktų serverių komandas root teisėmis"
|
|
176
|
+
},
|
|
177
|
+
"description": {
|
|
178
|
+
"en": "Run arbitrary commands and install root SSH access on selected servers. Can read, change or delete data through commands. Includes bounded server diagnostics.",
|
|
179
|
+
"lt": "Vykdyti savavališkas komandas ir įdiegti root SSH prieigą pasirinktuose serveriuose. Komandos gali skaityti, keisti ar trinti duomenis. Apima ribotą serverių diagnostiką."
|
|
180
|
+
},
|
|
181
|
+
"implies": [
|
|
182
|
+
"vm:diagnostics"
|
|
183
|
+
],
|
|
184
|
+
"billing": "none",
|
|
185
|
+
"legacy": false
|
|
186
|
+
},
|
|
187
|
+
"vm:files": {
|
|
188
|
+
"label": {
|
|
189
|
+
"en": "Read and change selected server files as root (legacy)",
|
|
190
|
+
"lt": "Skaityti ir keisti pasirinktų serverių failus root teisėmis (senasis leidimas)"
|
|
191
|
+
},
|
|
192
|
+
"description": {
|
|
193
|
+
"en": "Legacy combined file permission: includes vm:files:read and vm:files:write on selected servers. Can read secrets and overwrite data. Not advertised for new connections.",
|
|
194
|
+
"lt": "Senasis jungtinis failų leidimas: apima vm:files:read ir vm:files:write pasirinktuose serveriuose. Gali skaityti paslaptis ir perrašyti duomenis. Naujiems prisijungimams neskelbiamas."
|
|
195
|
+
},
|
|
196
|
+
"implies": [
|
|
197
|
+
"vm:files:read",
|
|
198
|
+
"vm:files:write"
|
|
199
|
+
],
|
|
200
|
+
"billing": "none",
|
|
201
|
+
"legacy": true
|
|
202
|
+
},
|
|
203
|
+
"services:reinstall": {
|
|
204
|
+
"label": {
|
|
205
|
+
"en": "Reinstall selected server operating systems",
|
|
206
|
+
"lt": "Perdiegti pasirinktų serverių operacines sistemas"
|
|
207
|
+
},
|
|
208
|
+
"description": {
|
|
209
|
+
"en": "Reinstall operating systems on selected servers. This can erase existing server data.",
|
|
210
|
+
"lt": "Perdiegti pasirinktų serverių operacines sistemas. Tai gali ištrinti esamus serverio duomenis."
|
|
211
|
+
},
|
|
212
|
+
"implies": [],
|
|
213
|
+
"billing": "none",
|
|
214
|
+
"legacy": false
|
|
215
|
+
},
|
|
216
|
+
"services:restore": {
|
|
217
|
+
"label": {
|
|
218
|
+
"en": "Restore or delete selected server snapshots",
|
|
219
|
+
"lt": "Atkurti arba trinti pasirinktų serverių momentines kopijas"
|
|
220
|
+
},
|
|
221
|
+
"description": {
|
|
222
|
+
"en": "Restore selected servers from snapshots or delete snapshots. Restoring replaces current data; deleting removes a recovery point.",
|
|
223
|
+
"lt": "Atkurti pasirinktus serverius iš momentinių kopijų arba trinti kopijas. Atkūrimas pakeičia esamus duomenis; ištrynus kopiją prarandamas atkūrimo taškas."
|
|
224
|
+
},
|
|
225
|
+
"implies": [],
|
|
226
|
+
"billing": "none",
|
|
227
|
+
"legacy": false
|
|
228
|
+
},
|
|
229
|
+
"services:rescue": {
|
|
230
|
+
"label": {
|
|
231
|
+
"en": "Start or exit selected server rescue mode",
|
|
232
|
+
"lt": "Įjungti ar išjungti pasirinktų serverių atkūrimo režimą"
|
|
233
|
+
},
|
|
234
|
+
"description": {
|
|
235
|
+
"en": "Start or exit rescue mode on selected servers. May restart a server or interrupt its workload. Includes viewing selected servers.",
|
|
236
|
+
"lt": "Įjungti ar išjungti pasirinktų serverių atkūrimo režimą. Gali perkrauti serverį ar nutraukti jo darbą. Apima serverių peržiūrą."
|
|
237
|
+
},
|
|
238
|
+
"implies": [
|
|
239
|
+
"services:read"
|
|
240
|
+
],
|
|
241
|
+
"billing": "none",
|
|
242
|
+
"legacy": false
|
|
243
|
+
},
|
|
244
|
+
"dns:read": {
|
|
245
|
+
"label": {
|
|
246
|
+
"en": "View account DNS",
|
|
247
|
+
"lt": "Peržiūrėti paskyros DNS"
|
|
248
|
+
},
|
|
249
|
+
"description": {
|
|
250
|
+
"en": "View account DNS zones, records, diagnostics, DNSSEC state and zone history.",
|
|
251
|
+
"lt": "Peržiūrėti paskyros DNS zonas, įrašus, diagnostiką, DNSSEC būseną ir zonų istoriją."
|
|
252
|
+
},
|
|
253
|
+
"implies": [],
|
|
254
|
+
"billing": "none",
|
|
255
|
+
"legacy": false
|
|
256
|
+
},
|
|
257
|
+
"dns:write": {
|
|
258
|
+
"label": {
|
|
259
|
+
"en": "Manage account DNS",
|
|
260
|
+
"lt": "Valdyti paskyros DNS"
|
|
261
|
+
},
|
|
262
|
+
"description": {
|
|
263
|
+
"en": "Create, change, import or delete account DNS zones and records. Can redirect traffic or interrupt domains. Includes DNS viewing.",
|
|
264
|
+
"lt": "Kurti, keisti, importuoti ar trinti paskyros DNS zonas ir įrašus. Gali nukreipti srautą ar sutrikdyti domenų veikimą. Apima DNS peržiūrą."
|
|
265
|
+
},
|
|
266
|
+
"implies": [
|
|
267
|
+
"dns:read"
|
|
268
|
+
],
|
|
269
|
+
"billing": "none",
|
|
270
|
+
"legacy": false
|
|
271
|
+
},
|
|
272
|
+
"dnssec:manage": {
|
|
273
|
+
"label": {
|
|
274
|
+
"en": "Manage account DNSSEC",
|
|
275
|
+
"lt": "Valdyti paskyros DNSSEC"
|
|
276
|
+
},
|
|
277
|
+
"description": {
|
|
278
|
+
"en": "Change DNSSEC signing for account DNS zones. Incorrect changes can interrupt DNS resolution. Includes DNS viewing.",
|
|
279
|
+
"lt": "Keisti paskyros DNS zonų DNSSEC pasirašymą. Klaidingi pakeitimai gali sutrikdyti DNS veikimą. Apima DNS peržiūrą."
|
|
280
|
+
},
|
|
281
|
+
"implies": [
|
|
282
|
+
"dns:read"
|
|
283
|
+
],
|
|
284
|
+
"billing": "none",
|
|
285
|
+
"legacy": false
|
|
286
|
+
},
|
|
287
|
+
"domains:read": {
|
|
288
|
+
"label": {
|
|
289
|
+
"en": "View account domains",
|
|
290
|
+
"lt": "Peržiūrėti paskyros domenus"
|
|
291
|
+
},
|
|
292
|
+
"description": {
|
|
293
|
+
"en": "View account domains, nameservers, glue records, parent DS records and contacts.",
|
|
294
|
+
"lt": "Peržiūrėti paskyros domenus, vardų serverius, glue ir tėvinės zonos DS įrašus bei kontaktus."
|
|
295
|
+
},
|
|
296
|
+
"implies": [],
|
|
297
|
+
"billing": "none",
|
|
298
|
+
"legacy": false
|
|
299
|
+
},
|
|
300
|
+
"domains:manage": {
|
|
301
|
+
"label": {
|
|
302
|
+
"en": "Manage account domains",
|
|
303
|
+
"lt": "Valdyti paskyros domenus"
|
|
304
|
+
},
|
|
305
|
+
"description": {
|
|
306
|
+
"en": "Change account domain nameservers, glue records, parent DS records and contacts. Can interrupt domain resolution. Includes domain viewing; registration requires separate purchase permission.",
|
|
307
|
+
"lt": "Keisti paskyros domenų vardų serverius, glue ir tėvinės zonos DS įrašus bei kontaktus. Gali sutrikdyti domenų veikimą. Apima peržiūrą; registravimui reikia atskiro pirkimo leidimo."
|
|
308
|
+
},
|
|
309
|
+
"implies": [
|
|
310
|
+
"domains:read"
|
|
311
|
+
],
|
|
312
|
+
"billing": "none",
|
|
313
|
+
"legacy": false
|
|
314
|
+
},
|
|
315
|
+
"certificates:read": {
|
|
316
|
+
"label": {
|
|
317
|
+
"en": "View account TLS certificates",
|
|
318
|
+
"lt": "Peržiūrėti paskyros TLS sertifikatus"
|
|
319
|
+
},
|
|
320
|
+
"description": {
|
|
321
|
+
"en": "View account TLS certificates, validation and actions, and download available certificate material.",
|
|
322
|
+
"lt": "Peržiūrėti paskyros TLS sertifikatus, patvirtinimą, veiksmus ir atsisiųsti prieinamą sertifikatų medžiagą."
|
|
323
|
+
},
|
|
324
|
+
"implies": [],
|
|
325
|
+
"billing": "none",
|
|
326
|
+
"legacy": false
|
|
327
|
+
},
|
|
328
|
+
"certificates:manage": {
|
|
329
|
+
"label": {
|
|
330
|
+
"en": "Manage account TLS certificates",
|
|
331
|
+
"lt": "Valdyti paskyros TLS sertifikatus"
|
|
332
|
+
},
|
|
333
|
+
"description": {
|
|
334
|
+
"en": "Create or renew free certificates and manage existing certificates and automatic SSL subscriptions, including cancellation or revocation. Includes certificate viewing; excludes purchases.",
|
|
335
|
+
"lt": "Kurti ar pratęsti nemokamus sertifikatus ir valdyti esamus sertifikatus bei automatinio SSL prenumeratas, įskaitant atšaukimą. Apima sertifikatų peržiūrą; pirkimai neįtraukti."
|
|
336
|
+
},
|
|
337
|
+
"implies": [
|
|
338
|
+
"certificates:read"
|
|
339
|
+
],
|
|
340
|
+
"billing": "none",
|
|
341
|
+
"legacy": false
|
|
342
|
+
},
|
|
343
|
+
"ssh_keys:read": {
|
|
344
|
+
"label": {
|
|
345
|
+
"en": "View account public SSH keys",
|
|
346
|
+
"lt": "Peržiūrėti paskyros viešuosius SSH raktus"
|
|
347
|
+
},
|
|
348
|
+
"description": {
|
|
349
|
+
"en": "View public SSH keys saved in your account. Does not expose private keys.",
|
|
350
|
+
"lt": "Peržiūrėti paskyroje išsaugotus viešuosius SSH raktus. Privatūs raktai neatskleidžiami."
|
|
351
|
+
},
|
|
352
|
+
"implies": [],
|
|
353
|
+
"billing": "none",
|
|
354
|
+
"legacy": false
|
|
355
|
+
},
|
|
356
|
+
"ssh_keys:manage": {
|
|
357
|
+
"label": {
|
|
358
|
+
"en": "Manage account public SSH keys",
|
|
359
|
+
"lt": "Valdyti paskyros viešuosius SSH raktus"
|
|
360
|
+
},
|
|
361
|
+
"description": {
|
|
362
|
+
"en": "Add or delete account public SSH keys. Includes key viewing; installing a key on a server requires vm:exec permission.",
|
|
363
|
+
"lt": "Pridėti ar trinti paskyros viešuosius SSH raktus. Apima raktų peržiūrą; diegimui serveryje reikia vm:exec leidimo."
|
|
364
|
+
},
|
|
365
|
+
"implies": [
|
|
366
|
+
"ssh_keys:read"
|
|
367
|
+
],
|
|
368
|
+
"billing": "none",
|
|
369
|
+
"legacy": false
|
|
370
|
+
},
|
|
371
|
+
"orders:read": {
|
|
372
|
+
"label": {
|
|
373
|
+
"en": "View server plans and server or domain purchase results",
|
|
374
|
+
"lt": "Peržiūrėti serverių planus ir serverių ar domenų pirkimo rezultatus"
|
|
375
|
+
},
|
|
376
|
+
"description": {
|
|
377
|
+
"en": "View server plans, order options and existing server or domain purchase results. Does not submit a purchase. Available only through the separate orders profile.",
|
|
378
|
+
"lt": "Peržiūrėti serverių planus, užsakymo parinktis ir esamų serverių ar domenų pirkimų rezultatus. Pirkimo neatlieka. Prieinama tik atskirame užsakymų profilyje."
|
|
379
|
+
},
|
|
380
|
+
"implies": [],
|
|
381
|
+
"billing": "none",
|
|
382
|
+
"legacy": false
|
|
383
|
+
},
|
|
384
|
+
"orders:servers": {
|
|
385
|
+
"label": {
|
|
386
|
+
"en": "Purchase VPS servers from your balance",
|
|
387
|
+
"lt": "Pirkti VPS serverius iš balanso"
|
|
388
|
+
},
|
|
389
|
+
"description": {
|
|
390
|
+
"en": "Quote and purchase VPS servers using your VPSnet balance after approval of the final gross EUR amount and period, within this connection’s daily and monthly limits. Includes viewing order plans and purchase results. Available only through the separate orders profile.",
|
|
391
|
+
"lt": "Gauti VPS serverių pasiūlymus ir pirkti iš VPSnet balanso, patvirtinus galutinę EUR sumą su mokesčiais bei laikotarpį, neviršijant jungties dienos ir mėnesio ribų. Apima planų ir pirkimų rezultatų peržiūrą. Prieinama tik atskirame užsakymų profilyje."
|
|
392
|
+
},
|
|
393
|
+
"implies": [
|
|
394
|
+
"orders:read"
|
|
395
|
+
],
|
|
396
|
+
"billing": "purchase_balance",
|
|
397
|
+
"legacy": false
|
|
398
|
+
},
|
|
399
|
+
"orders:domains": {
|
|
400
|
+
"label": {
|
|
401
|
+
"en": "Register domains from your balance",
|
|
402
|
+
"lt": "Registruoti domenus iš balanso"
|
|
403
|
+
},
|
|
404
|
+
"description": {
|
|
405
|
+
"en": "Quote and register domains using your VPSnet balance after approval of the final gross EUR amount and period, within this connection’s daily and monthly limits. Registration may be non-refundable. Includes viewing order plans and purchase results. Available only through the separate orders profile.",
|
|
406
|
+
"lt": "Gauti domenų pasiūlymus ir registruoti iš VPSnet balanso, patvirtinus galutinę EUR sumą su mokesčiais bei laikotarpį, neviršijant jungties dienos ir mėnesio ribų. Registravimo mokestis gali būti negrąžinamas. Apima planų ir pirkimų rezultatų peržiūrą. Prieinama tik atskirame užsakymų profilyje."
|
|
407
|
+
},
|
|
408
|
+
"implies": [
|
|
409
|
+
"orders:read"
|
|
410
|
+
],
|
|
411
|
+
"billing": "purchase_balance",
|
|
412
|
+
"legacy": false
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
export function scopeDescriptions(scopes) {
|
|
416
|
+
return Object.fromEntries(scopes.map(scope => [scope, scopeCatalog[scope].description.en]));
|
|
417
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const RELEASE_FEED_URL = 'https://raw.githubusercontent.com/bacarrdy/vpsnet-mcp/main/distribution/releases/published.json';
|
|
4
|
+
export const MAX_FEED_BYTES = 65536;
|
|
5
|
+
const stable = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
6
|
+
const entry = z.object({
|
|
7
|
+
version: z.string().max(63).regex(stable), source_commit: z.string().regex(/^[0-9a-f]{40}$/),
|
|
8
|
+
verified_at: z.string().max(40).datetime({ offset: true }), url: z.string().max(256),
|
|
9
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/), ci_run: z.number().int().positive().safe(),
|
|
10
|
+
ci_conclusion: z.literal('success'),
|
|
11
|
+
}).strict();
|
|
12
|
+
const feedSchema = z.object({ schema_version: z.literal(1), plugin: entry, mcp: entry }).strict();
|
|
13
|
+
const instructions = JSON.parse(readFileSync(new URL('../scripts/update-clients.json', import.meta.url), 'utf8'));
|
|
14
|
+
const runtimeVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
15
|
+
export function validatePublishedFeed(value) {
|
|
16
|
+
const feed = feedSchema.parse(value);
|
|
17
|
+
if (feed.plugin.url !== `https://github.com/bacarrdy/vpsnet-mcp/releases/download/plugin-${feed.plugin.version}/vpsnet-plugin-${feed.plugin.version}.zip`
|
|
18
|
+
|| feed.mcp.url !== `https://registry.npmjs.org/vpsnet-mcp/-/vpsnet-mcp-${feed.mcp.version}.tgz`) {
|
|
19
|
+
throw new Error('Unexpected release artifact URL');
|
|
20
|
+
}
|
|
21
|
+
return feed;
|
|
22
|
+
}
|
|
23
|
+
/** Reject duplicate keys (including escaped aliases) before normal JSON parsing. */
|
|
24
|
+
export function parsePublishedFeed(text) {
|
|
25
|
+
const token = /\s*("(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"|true|false|null|-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?|[{}\[\],:])/gy;
|
|
26
|
+
const stack = [];
|
|
27
|
+
let position = 0;
|
|
28
|
+
while (position < text.length && text.slice(position).trim()) {
|
|
29
|
+
token.lastIndex = position;
|
|
30
|
+
const match = token.exec(text);
|
|
31
|
+
if (!match)
|
|
32
|
+
throw new Error('Invalid release JSON');
|
|
33
|
+
const value = match[1], frame = stack.at(-1);
|
|
34
|
+
if (value === '{' || value === '[')
|
|
35
|
+
stack.push({ object: value === '{', key: value === '{', keys: new Set() });
|
|
36
|
+
else if (value === '}' || value === ']')
|
|
37
|
+
stack.pop();
|
|
38
|
+
else if (value === ',' && frame?.object)
|
|
39
|
+
frame.key = true;
|
|
40
|
+
else if (value.startsWith('"') && frame?.object && frame.key) {
|
|
41
|
+
const name = JSON.parse(value);
|
|
42
|
+
if (frame.keys.has(name))
|
|
43
|
+
throw new Error('Duplicate release key');
|
|
44
|
+
frame.keys.add(name);
|
|
45
|
+
frame.key = false;
|
|
46
|
+
}
|
|
47
|
+
position = token.lastIndex;
|
|
48
|
+
}
|
|
49
|
+
return validatePublishedFeed(JSON.parse(text));
|
|
50
|
+
}
|
|
51
|
+
export async function fetchPublishedFeed(fetcher = fetch) {
|
|
52
|
+
const response = await fetcher(RELEASE_FEED_URL, {
|
|
53
|
+
redirect: 'error', signal: AbortSignal.timeout(5000), headers: { Accept: 'application/json' },
|
|
54
|
+
});
|
|
55
|
+
if (response.status !== 200 || !response.body)
|
|
56
|
+
throw new Error('Release feed unavailable');
|
|
57
|
+
const reader = response.body.getReader();
|
|
58
|
+
const chunks = [];
|
|
59
|
+
let size = 0;
|
|
60
|
+
try {
|
|
61
|
+
while (true) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done)
|
|
64
|
+
break;
|
|
65
|
+
size += value.byteLength;
|
|
66
|
+
if (size > MAX_FEED_BYTES)
|
|
67
|
+
throw new Error('Release feed too large');
|
|
68
|
+
chunks.push(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await reader.cancel().catch(() => { });
|
|
73
|
+
}
|
|
74
|
+
return parsePublishedFeed(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
|
|
75
|
+
}
|
|
76
|
+
let cache;
|
|
77
|
+
async function cachedFeed() {
|
|
78
|
+
if (!cache || cache.expires <= Date.now()) {
|
|
79
|
+
// Public facts only: never cache account data, tokens or installed versions.
|
|
80
|
+
cache = { expires: Date.now() + 300_000, value: fetchPublishedFeed() };
|
|
81
|
+
}
|
|
82
|
+
return cache.value;
|
|
83
|
+
}
|
|
84
|
+
const observedSchema = z.object({ version: z.string().nullable(), source: z.enum(['caller_reported', 'runtime', 'unknown']) });
|
|
85
|
+
export const updateInfoOutput = {
|
|
86
|
+
schema_version: z.literal(1), checked_at: z.string(), client: z.string(),
|
|
87
|
+
published: z.object({ plugin: entry, mcp: entry }).nullable(),
|
|
88
|
+
observed: z.object({ plugin: observedSchema, local_mcp: observedSchema, server: observedSchema }),
|
|
89
|
+
update_available: z.object({ plugin: z.boolean().nullable(), local_mcp: z.boolean().nullable(), server: z.boolean().nullable() }),
|
|
90
|
+
compatibility: z.object({ status: z.literal('unknown'), reason: z.string() }),
|
|
91
|
+
distribution_status: z.object({ source_release_published: z.boolean().nullable(), npm_package_published: z.boolean().nullable(),
|
|
92
|
+
remote_deployment_status: z.literal('unknown'), official_registry: z.object({ status: z.literal('unknown'), version: z.null(), checked_at: z.null() }),
|
|
93
|
+
downstream_catalogs: z.record(z.unknown()),
|
|
94
|
+
}),
|
|
95
|
+
instructions: z.object({ reviewed_at: z.string(), source_urls: z.array(z.string()), steps: z.array(z.string()) }).nullable(),
|
|
96
|
+
server_update: z.string(), advisory: z.literal(true), reason: z.string().nullable(),
|
|
97
|
+
};
|
|
98
|
+
function observed(value, runtime = false) {
|
|
99
|
+
return value && value.length < 64 && stable.test(value)
|
|
100
|
+
? { version: value, source: runtime ? 'runtime' : 'caller_reported' }
|
|
101
|
+
: { version: null, source: 'unknown' };
|
|
102
|
+
}
|
|
103
|
+
function older(current, latest) {
|
|
104
|
+
if (current === null)
|
|
105
|
+
return null;
|
|
106
|
+
const a = current.split('.').map(BigInt), b = latest.split('.').map(BigInt);
|
|
107
|
+
for (let i = 0; i < 3; i++)
|
|
108
|
+
if (a[i] !== b[i])
|
|
109
|
+
return a[i] < b[i];
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
export async function getUpdateInfo(args, lookup = cachedFeed) {
|
|
113
|
+
let published = null;
|
|
114
|
+
try {
|
|
115
|
+
published = validatePublishedFeed(await lookup());
|
|
116
|
+
}
|
|
117
|
+
catch { /* Do not echo untrusted errors. */ }
|
|
118
|
+
const client = args.client ?? 'generic';
|
|
119
|
+
const observations = { plugin: observed(args.pluginVersion), local_mcp: observed(args.localMcpVersion), server: observed(runtimeVersion, true) };
|
|
120
|
+
return z.object(updateInfoOutput).parse({
|
|
121
|
+
schema_version: 1, checked_at: new Date().toISOString(), client,
|
|
122
|
+
published: published ? { plugin: published.plugin, mcp: published.mcp } : null,
|
|
123
|
+
observed: observations,
|
|
124
|
+
update_available: { plugin: published ? older(observations.plugin.version, published.plugin.version) : null,
|
|
125
|
+
local_mcp: published ? older(observations.local_mcp.version, published.mcp.version) : null,
|
|
126
|
+
server: published ? older(observations.server.version, published.mcp.version) : null },
|
|
127
|
+
compatibility: { status: 'unknown', reason: 'No version-specific client acceptance evidence is supplied by this check.' },
|
|
128
|
+
distribution_status: { source_release_published: published ? true : null, npm_package_published: published ? true : null,
|
|
129
|
+
remote_deployment_status: 'unknown', official_registry: { status: 'unknown', version: null, checked_at: null }, downstream_catalogs: {} },
|
|
130
|
+
instructions: Object.hasOwn(instructions, client) ? instructions[client] : null,
|
|
131
|
+
server_update: 'The hosted gateway is maintained by VPSnet. Updating a local npm package does not update it.',
|
|
132
|
+
advisory: true,
|
|
133
|
+
reason: published ? null : 'Published release information is unavailable or invalid; installed software is unchanged.',
|
|
134
|
+
});
|
|
135
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vpsnet-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for managing VPSnet.com services, applications, DNS, domains, billing, and server lifecycle operations",
|
|
6
6
|
"bin": {
|
|
@@ -35,8 +35,11 @@
|
|
|
35
35
|
"build/http-server.js",
|
|
36
36
|
"build/request-context.js",
|
|
37
37
|
"build/remote-policy.js",
|
|
38
|
+
"build/scope-catalog.js",
|
|
38
39
|
"build/remote-auth-error.js",
|
|
39
40
|
"build/remote-tools.js",
|
|
41
|
+
"build/update-info.js",
|
|
42
|
+
"scripts/update-clients.json",
|
|
40
43
|
"build/remote-orders.js"
|
|
41
44
|
],
|
|
42
45
|
"repository": "github:bacarrdy/vpsnet-mcp",
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claude-code": {
|
|
3
|
+
"reviewed_at": "2026-10-14",
|
|
4
|
+
"source_urls": [
|
|
5
|
+
"https://code.claude.com/docs/en/plugins/cli-reference",
|
|
6
|
+
"https://code.claude.com/docs/en/discover-plugins"
|
|
7
|
+
],
|
|
8
|
+
"steps": [
|
|
9
|
+
"Use claude plugin list --json to check the installed VPSnet plugin and its source.",
|
|
10
|
+
"For the VPSnet marketplace installation, run claude plugin update vpsnet@vpsnet in your own terminal.",
|
|
11
|
+
"Use /reload-plugins in the running session or start a new session. For another install source, use its native update control."
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"claude": {
|
|
15
|
+
"reviewed_at": "2026-10-14",
|
|
16
|
+
"source_urls": [
|
|
17
|
+
"https://claude.com/docs/plugins/overview"
|
|
18
|
+
],
|
|
19
|
+
"steps": [
|
|
20
|
+
"Open Customize > Plugins. For an added marketplace, use Check for updates where available.",
|
|
21
|
+
"Uploaded or organization-managed plugins follow their owner's upload, sync and review process. A GitHub release alone does not prove the account's plugin updated."
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"cowork": {
|
|
25
|
+
"reviewed_at": "2026-10-14",
|
|
26
|
+
"source_urls": [
|
|
27
|
+
"https://claude.com/docs/plugins/overview",
|
|
28
|
+
"https://support.claude.com/en/articles/13837433-manage-plugins-for-your-organization"
|
|
29
|
+
],
|
|
30
|
+
"steps": [
|
|
31
|
+
"Manage the account plugin in Customize > Plugins using the source's native update controls.",
|
|
32
|
+
"For an organization marketplace, ask its owner to upload or re-sync and complete any required review. The served version can remain pinned or await approval."
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"grok": {
|
|
36
|
+
"reviewed_at": "2026-10-14",
|
|
37
|
+
"source_urls": [
|
|
38
|
+
"https://docs.x.ai/grok/connectors"
|
|
39
|
+
],
|
|
40
|
+
"steps": [
|
|
41
|
+
"Manage the custom remote connection at grok.com/connectors.",
|
|
42
|
+
"The official connector guide does not establish plugin ZIP updates or a tool-cache refresh command. A remote gateway deployment does not establish whether this conversation refreshed its tools."
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"codex": {
|
|
46
|
+
"reviewed_at": "2026-10-14",
|
|
47
|
+
"source_urls": [
|
|
48
|
+
"https://learn.chatgpt.com/docs/cli/reference",
|
|
49
|
+
"https://learn.chatgpt.com/docs/plugins"
|
|
50
|
+
],
|
|
51
|
+
"steps": [
|
|
52
|
+
"Use codex plugin list --json to inspect installed and available plugin versions and the configured marketplace name.",
|
|
53
|
+
"For that configured marketplace, use codex plugin marketplace upgrade <marketplace-name> --json, then start a new session. Do not assume the active thread reloads plugins."
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"chatgpt": {
|
|
57
|
+
"reviewed_at": "2026-10-14",
|
|
58
|
+
"source_urls": [
|
|
59
|
+
"https://developers.openai.com/plugins/deploy/connect-chatgpt",
|
|
60
|
+
"https://developers.openai.com/plugins/deploy/submission"
|
|
61
|
+
],
|
|
62
|
+
"steps": [
|
|
63
|
+
"For a custom MCP connection, open its plugin settings, use Refresh and start a new conversation.",
|
|
64
|
+
"Published package updates and server rescans are publisher actions, with their own review and publication state. A local ZIP or GitHub release does not prove a directory update."
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"gemini-cli": {
|
|
68
|
+
"reviewed_at": "2026-10-14",
|
|
69
|
+
"source_urls": [
|
|
70
|
+
"https://geminicli.com/docs/extensions/reference/",
|
|
71
|
+
"https://geminicli.com/docs/reference/commands/"
|
|
72
|
+
],
|
|
73
|
+
"steps": [
|
|
74
|
+
"For a named installed extension, run gemini extensions update vpsnet in your terminal and restart the CLI.",
|
|
75
|
+
"/mcp reload re-discovers tools and /skills reload refreshes skills on host versions that support them; these do not install a new extension."
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"gemini-browser": {
|
|
79
|
+
"reviewed_at": "2026-10-14",
|
|
80
|
+
"source_urls": [
|
|
81
|
+
"https://support.google.com/gemini/answer/17209137"
|
|
82
|
+
],
|
|
83
|
+
"steps": [
|
|
84
|
+
"Manage the custom Connected App in Gemini settings. This remote connection is not a Gemini CLI extension.",
|
|
85
|
+
"The reviewed guide does not establish a package-upgrade or tool-cache refresh command."
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"cursor": {
|
|
89
|
+
"reviewed_at": "2026-10-14",
|
|
90
|
+
"source_urls": [
|
|
91
|
+
"https://cursor.com/docs/reference/plugins",
|
|
92
|
+
"https://cursor.com/docs/plugins"
|
|
93
|
+
],
|
|
94
|
+
"steps": [
|
|
95
|
+
"For a local plugin copy, replace it with the complete reviewed new package and reload the window or restart.",
|
|
96
|
+
"Public marketplace updates require provider review; team marketplace sync and local updates are separate. No universal public-store update command is established here."
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"kimi-code": {
|
|
100
|
+
"reviewed_at": "2026-10-14",
|
|
101
|
+
"source_urls": [
|
|
102
|
+
"https://www.kimi.com/code/docs/en/kimi-code-cli/customization/plugins.html"
|
|
103
|
+
],
|
|
104
|
+
"steps": [
|
|
105
|
+
"For a copied local installation, reinstall the reviewed complete source with /plugins install ./vpsnet, then /reload or start a new session.",
|
|
106
|
+
"Use the plugin manager update action where available. Do not assume custom VPSnet plugins auto-update."
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"kimi-work": {
|
|
110
|
+
"reviewed_at": "2026-10-14",
|
|
111
|
+
"source_urls": [
|
|
112
|
+
"https://www.kimi.com/en/help/plugins-and-skills/create",
|
|
113
|
+
"https://www.kimi.com/en/help/plugins-and-skills/publish"
|
|
114
|
+
],
|
|
115
|
+
"steps": [
|
|
116
|
+
"Use Plugin Builder in Kimi Work to convert the reviewed source update and inspect the resulting package.",
|
|
117
|
+
"Personal installation and public marketplace review are separate; no automatic source-update contract was established."
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"kiro": {
|
|
121
|
+
"reviewed_at": "2026-10-14",
|
|
122
|
+
"source_urls": [
|
|
123
|
+
"https://kiro.dev/docs/powers/installation/",
|
|
124
|
+
"https://kiro.dev/docs/mcp/configuration/"
|
|
125
|
+
],
|
|
126
|
+
"steps": [
|
|
127
|
+
"For a repository-backed Power, use its Check for updates and Install updates controls.",
|
|
128
|
+
"Standalone MCP configuration is separate. Power OAuth scope compatibility needs client verification; installing a package does not grant additional scopes."
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"vscode": {
|
|
132
|
+
"reviewed_at": "2026-10-14",
|
|
133
|
+
"source_urls": [
|
|
134
|
+
"https://code.visualstudio.com/docs/agent-customization/agent-plugins",
|
|
135
|
+
"https://code.visualstudio.com/docs/agents/reference/mcp-configuration"
|
|
136
|
+
],
|
|
137
|
+
"steps": [
|
|
138
|
+
"MCP: Reset Cached Tools refreshes discovery, not installed plugin files.",
|
|
139
|
+
"Use Extensions: Check for Extension Updates for the extension inventory. AgentHost /plugin update applies to its own plugin inventory; inspect which host and install source you use first."
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"devin": {
|
|
143
|
+
"reviewed_at": "2026-10-14",
|
|
144
|
+
"source_urls": [
|
|
145
|
+
"https://docs.devin.ai/cli/extensibility/plugins/overview",
|
|
146
|
+
"https://docs.devin.ai/cli/extensibility/mcp/configuration"
|
|
147
|
+
],
|
|
148
|
+
"steps": [
|
|
149
|
+
"For a Devin CLI plugin installation, use devin plugins update vpsnet. A linked local source reloads on a new session.",
|
|
150
|
+
"Legacy Cascade and Devin Local are different hosts; do not assume a Devin CLI action updates a legacy Cascade configuration."
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
"qwen": {
|
|
154
|
+
"reviewed_at": "2026-10-14",
|
|
155
|
+
"source_urls": [
|
|
156
|
+
"https://qwenlm.github.io/qwen-code-docs/en/users/extension/introduction/"
|
|
157
|
+
],
|
|
158
|
+
"steps": [
|
|
159
|
+
"Use qwen extensions update <installed-name> for a native installed extension, then restart after terminal changes.",
|
|
160
|
+
"Pinned exact npm versions remain pinned. Marketplace source refresh is separate from updating installed extensions."
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"cline": {
|
|
164
|
+
"reviewed_at": "2026-10-14",
|
|
165
|
+
"source_urls": [
|
|
166
|
+
"https://docs.cline.bot/customization/plugins",
|
|
167
|
+
"https://docs.cline.bot/mcp/mcp-overview"
|
|
168
|
+
],
|
|
169
|
+
"steps": [
|
|
170
|
+
"Inspect the actual Cline host and installed source; plugin support differs between CLI/SDK and IDE extensions.",
|
|
171
|
+
"No custom-plugin automatic upgrade or remote tool-refresh guarantee was established. Use the host-native manager and current official instructions."
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"mistral-work": {
|
|
175
|
+
"reviewed_at": "2026-10-14",
|
|
176
|
+
"source_urls": [
|
|
177
|
+
"https://docs.mistral.ai/vibe/work/connectors/mcp-connectors"
|
|
178
|
+
],
|
|
179
|
+
"steps": [
|
|
180
|
+
"After tools change, open the existing Vibe Work connector and use Functions > Refresh tools. This host does not support dynamic tool discovery.",
|
|
181
|
+
"Do not substitute Vibe Code CLI: direct OAuth MCP is not supported there by the reviewed documentation."
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"mistral-code": {
|
|
185
|
+
"reviewed_at": "2026-10-14",
|
|
186
|
+
"source_urls": [
|
|
187
|
+
"https://docs.mistral.ai/vibe/code/cli/mcp-servers"
|
|
188
|
+
],
|
|
189
|
+
"steps": [
|
|
190
|
+
"The reviewed Vibe Code CLI documentation explicitly excludes OAuth MCP servers. A plugin update does not fix that limitation.",
|
|
191
|
+
"Use a supported OAuth client; never substitute customer passwords or manually supplied tokens."
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
"perplexity": {
|
|
195
|
+
"reviewed_at": "2026-10-14",
|
|
196
|
+
"source_urls": [
|
|
197
|
+
"https://www.perplexity.ai/help-center/en/articles/13915507-adding-custom-remote-connectors"
|
|
198
|
+
],
|
|
199
|
+
"steps": [
|
|
200
|
+
"Manage the existing custom remote connector in account settings. Native skill ZIP replacement is a separate operation.",
|
|
201
|
+
"The reviewed connector guide does not establish automatic tool-list refresh, custom plugin upgrades or release notifications."
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
}
|