vpsnet-mcp 2.3.14 → 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 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 standard config works across most MCP clients:
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 add to `roo_mcp_settings.json`:
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
- Install using the VS Code CLI:
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
- Or add to your VS Code MCP config manually using the standard config above.
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
- Follow the [Windsurf MCP documentation](https://docs.windsurf.com/windsurf/mcp). Use the standard config above.
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
 
@@ -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.filter((tool) => !legacyAggregateTools.has(tool.name)) };
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);
@@ -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 services.', inputSchema: {},
81
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, _meta: { 'openai/profile': true },
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,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.14",
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": {
@@ -38,6 +38,8 @@
38
38
  "build/scope-catalog.js",
39
39
  "build/remote-auth-error.js",
40
40
  "build/remote-tools.js",
41
+ "build/update-info.js",
42
+ "scripts/update-clients.json",
41
43
  "build/remote-orders.js"
42
44
  ],
43
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
+ }