kiwivm-mcp 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fujuntao
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 ADDED
@@ -0,0 +1,78 @@
1
+ # kiwivm-mcp
2
+
3
+ MCP server for managing KiwiVM (64clouds/BuyVM) VPS instances via Claude Code.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Environment Variables
8
+
9
+ ```bash
10
+ export KIWIVM_VEID=12345678
11
+ export KIWIVM_API_KEY=your_api_key_here
12
+ ```
13
+
14
+ Get your VEID and API key from the KiwiVM Control Panel → REST API page.
15
+
16
+ ### 2. Install
17
+
18
+ ```bash
19
+ npx kiwivm-mcp
20
+ ```
21
+
22
+ Requires **Node.js >= 24** (native TypeScript support).
23
+
24
+ ### 3. Claude Code Configuration
25
+
26
+ Add to your Claude Code settings:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "kiwivm": {
32
+ "command": "npx",
33
+ "args": ["kiwivm-mcp"],
34
+ "env": {
35
+ "KIWIVM_VEID": "12345678",
36
+ "KIWIVM_API_KEY": "your_key"
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Or with global install:
44
+
45
+ ```bash
46
+ npm install -g kiwivm-mcp
47
+ ```
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "kiwivm": {
53
+ "command": "kiwivm-mcp",
54
+ "env": {
55
+ "KIWIVM_VEID": "12345678",
56
+ "KIWIVM_API_KEY": "your_key"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## Available Tools
64
+
65
+ | Tool | Description |
66
+ |------|-------------|
67
+ | `kiwivm_power` | Start, stop, restart, or force kill the VPS |
68
+ | `kiwivm_service_info` | Get plan details, IPs, OS, and bandwidth. Set `include_live=true` for live CPU, RAM, disk, uptime (may take 15s) |
69
+ | `kiwivm_snapshot` | Create, list, delete, restore, toggle sticky, export/import snapshots |
70
+ | `kiwivm_backup` | List automatic backups, copy backup to restorable snapshot |
71
+ | `kiwivm_system` | Set hostname, PTR/rDNS, reset root password, manage SSH keys, list/reinstall OS templates |
72
+ | `kiwivm_network` | Add/delete IPv6 /64 subnets, assign/delete/list private IP addresses |
73
+ | `kiwivm_monitoring` | View raw usage stats, audit log, API rate limit status |
74
+ | `kiwivm_admin` | View suspensions/policy violations, unsuspend, resolve violations |
75
+
76
+ ## License
77
+
78
+ MIT
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "kiwivm-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for managing KiwiVM VPS instances",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "kiwivm-mcp": "src/index.ts"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/FuJuntao/kiwivm-mcp"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "src/**/*.ts",
19
+ "LICENSE",
20
+ "README.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=24.0.0"
24
+ },
25
+ "scripts": {
26
+ "lint": "biome lint src",
27
+ "format": "biome format --write src",
28
+ "check": "biome check src",
29
+ "test": "vitest run",
30
+ "typecheck": "tsc --noEmit",
31
+ "prepublishOnly": "npm run typecheck"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.12.3"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "^2.4.12",
38
+ "@tsconfig/node-lts": "^24.0.0",
39
+ "@tsconfig/node-ts": "^23.6.4",
40
+ "@tsconfig/strictest": "^2.0.8",
41
+ "@types/node": "^24.0.0",
42
+ "typescript": "^6.0.2",
43
+ "vitest": "^4.1.4"
44
+ }
45
+ }
@@ -0,0 +1,68 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { KiwiVMClient } from "./client.js";
3
+ import { KiwiVMError } from "./types.js";
4
+
5
+ global.fetch = vi.fn();
6
+
7
+ const client = new KiwiVMClient({
8
+ veid: "12345",
9
+ apiKey: "test-key",
10
+ });
11
+
12
+ describe("KiwiVMClient", () => {
13
+ afterEach(() => vi.resetAllMocks());
14
+
15
+ it("sends POST with form-encoded body and returns success", async () => {
16
+ vi.mocked(fetch).mockResolvedValueOnce({
17
+ ok: true,
18
+ json: () => Promise.resolve({ error: 0 }),
19
+ } as Response);
20
+
21
+ const result = await client.call("restart");
22
+ expect(result.error).toBe(0);
23
+
24
+ const call = vi.mocked(fetch).mock.calls[0];
25
+ expect(call?.[0]).toBe("https://api.64clouds.com/v1/restart");
26
+ expect((call?.[1] as RequestInit).method).toBe("POST");
27
+ expect((call?.[1] as RequestInit).headers).toEqual({
28
+ "Content-Type": "application/x-www-form-urlencoded",
29
+ });
30
+
31
+ const body = (call?.[1] as RequestInit).body as URLSearchParams;
32
+ expect(body.get("veid")).toBe("12345");
33
+ expect(body.get("api_key")).toBe("test-key");
34
+ });
35
+
36
+ it("passes additional params to request body", async () => {
37
+ vi.mocked(fetch).mockResolvedValueOnce({
38
+ ok: true,
39
+ json: () => Promise.resolve({ error: 0 }),
40
+ } as Response);
41
+
42
+ await client.call("snapshot/create", { description: "test" });
43
+
44
+ const body = (vi.mocked(fetch).mock.calls[0]?.[1] as RequestInit)
45
+ .body as URLSearchParams;
46
+ expect(body.get("description")).toBe("test");
47
+ });
48
+
49
+ it("throws KiwiVMError on API error", async () => {
50
+ vi.mocked(fetch).mockResolvedValue({
51
+ ok: true,
52
+ json: () => Promise.resolve({ error: 1, message: "Invalid VEID" }),
53
+ } as Response);
54
+
55
+ await expect(client.call("restart")).rejects.toThrow(KiwiVMError);
56
+ await expect(client.call("restart")).rejects.toThrow("Invalid VEID");
57
+ });
58
+
59
+ it("throws on HTTP error", async () => {
60
+ vi.mocked(fetch).mockResolvedValueOnce({
61
+ ok: false,
62
+ status: 500,
63
+ statusText: "Internal Server Error",
64
+ } as Response);
65
+
66
+ await expect(client.call("restart")).rejects.toThrow("HTTP 500");
67
+ });
68
+ });
package/src/client.ts ADDED
@@ -0,0 +1,55 @@
1
+ import type { KiwiVMResponse } from "./types.js";
2
+ import { KiwiVMError } from "./types.js";
3
+
4
+ const API_BASE = "https://api.64clouds.com/v1";
5
+
6
+ export interface ClientOptions {
7
+ veid: string;
8
+ apiKey: string;
9
+ }
10
+
11
+ export class KiwiVMClient {
12
+ private veid: string;
13
+ private apiKey: string;
14
+
15
+ constructor(options: ClientOptions) {
16
+ this.veid = options.veid;
17
+ this.apiKey = options.apiKey;
18
+ }
19
+
20
+ async call<T extends KiwiVMResponse = KiwiVMResponse>(
21
+ endpoint: string,
22
+ params: Record<string, string | number | boolean | undefined> = {},
23
+ ): Promise<T> {
24
+ const body = new URLSearchParams();
25
+ body.set("veid", this.veid);
26
+ body.set("api_key", this.apiKey);
27
+ for (const [key, value] of Object.entries(params)) {
28
+ if (value !== undefined) {
29
+ body.set(key, String(value));
30
+ }
31
+ }
32
+
33
+ const response = await fetch(`${API_BASE}/${endpoint}`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
36
+ body,
37
+ });
38
+
39
+ if (!response.ok) {
40
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
41
+ }
42
+
43
+ const data = (await response.json()) as T & KiwiVMResponse;
44
+
45
+ if (data.error !== 0) {
46
+ throw new KiwiVMError(
47
+ data.message ?? `API error: ${data.error}`,
48
+ data.error,
49
+ data,
50
+ );
51
+ }
52
+
53
+ return data as T;
54
+ }
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { KiwiVMClient } from "./client.js";
6
+ import { createAllTools } from "./tools/index.js";
7
+
8
+ function main() {
9
+ const veid = process.env["KIWIVM_VEID"];
10
+ const apiKey = process.env["KIWIVM_API_KEY"];
11
+
12
+ if (!veid || !apiKey) {
13
+ console.error(
14
+ "Error: KIWIVM_VEID and KIWIVM_API_KEY environment variables are required",
15
+ );
16
+ process.exit(1);
17
+ }
18
+
19
+ const client = new KiwiVMClient({ veid, apiKey });
20
+ const server = new McpServer({ name: "kiwivm-mcp", version: "0.1.0" });
21
+
22
+ createAllTools(server, client);
23
+
24
+ server.connect(new StdioServerTransport()).catch(console.error);
25
+ }
26
+
27
+ main();
@@ -0,0 +1,43 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import { callApi } from "./utils.js";
5
+
6
+ export function createAdminTools(
7
+ server: McpServer,
8
+ client: KiwiVMClient,
9
+ ): void {
10
+ server.registerTool(
11
+ "kiwivm_admin",
12
+ {
13
+ description:
14
+ "Administration: get suspension details, get policy violations, unsuspend VPS, " +
15
+ "resolve policy violations",
16
+ inputSchema: {
17
+ action: z.enum([
18
+ "getSuspensions",
19
+ "getPolicyViolations",
20
+ "unsuspend",
21
+ "resolvePolicyViolation",
22
+ ]),
23
+ record_id: z.string().optional(),
24
+ },
25
+ },
26
+ async ({ action, record_id }) => {
27
+ switch (action) {
28
+ case "getSuspensions":
29
+ return callApi(() => client.call("getSuspensionDetails"));
30
+ case "getPolicyViolations":
31
+ return callApi(() => client.call("getPolicyViolations"));
32
+ case "unsuspend":
33
+ return callApi(() =>
34
+ client.call("unsuspend", { recordId: record_id }),
35
+ );
36
+ case "resolvePolicyViolation":
37
+ return callApi(() =>
38
+ client.call("resolvePolicyViolation", { recordId: record_id }),
39
+ );
40
+ }
41
+ },
42
+ );
43
+ }
@@ -0,0 +1,36 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import type { Backup, KiwiVMResponse } from "../types.js";
5
+ import { callApi } from "./utils.js";
6
+
7
+ interface BackupListResponse extends KiwiVMResponse {
8
+ backups: Backup[];
9
+ }
10
+
11
+ export function createBackupTools(
12
+ server: McpServer,
13
+ client: KiwiVMClient,
14
+ ): void {
15
+ server.registerTool(
16
+ "kiwivm_backup",
17
+ {
18
+ description:
19
+ "Manage automatic backups: list available backups, or copy a backup to a restorable snapshot",
20
+ inputSchema: {
21
+ action: z.enum(["list", "copyToSnapshot"]),
22
+ backup_token: z.string().optional(),
23
+ },
24
+ },
25
+ async ({ action, backup_token }) => {
26
+ switch (action) {
27
+ case "list":
28
+ return callApi(() => client.call<BackupListResponse>("backup/list"));
29
+ case "copyToSnapshot":
30
+ return callApi(() =>
31
+ client.call("backup/copyToSnapshot", { backupToken: backup_token }),
32
+ );
33
+ }
34
+ },
35
+ );
36
+ }
@@ -0,0 +1,21 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { KiwiVMClient } from "../client.js";
3
+ import { createAdminTools } from "./admin.js";
4
+ import { createBackupTools } from "./backups.js";
5
+ import { createInfoTools } from "./info.js";
6
+ import { createMonitoringTools } from "./monitoring.js";
7
+ import { createNetworkTools } from "./network.js";
8
+ import { createPowerTools } from "./power.js";
9
+ import { createSnapshotTools } from "./snapshots.js";
10
+ import { createSystemTools } from "./system.js";
11
+
12
+ export function createAllTools(server: McpServer, client: KiwiVMClient): void {
13
+ createPowerTools(server, client);
14
+ createInfoTools(server, client);
15
+ createSnapshotTools(server, client);
16
+ createBackupTools(server, client);
17
+ createSystemTools(server, client);
18
+ createNetworkTools(server, client);
19
+ createMonitoringTools(server, client);
20
+ createAdminTools(server, client);
21
+ }
@@ -0,0 +1,28 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import type { LiveServiceInfo, ServiceInfo } from "../types.js";
5
+ import { callApi } from "./utils.js";
6
+
7
+ export function createInfoTools(server: McpServer, client: KiwiVMClient): void {
8
+ server.registerTool(
9
+ "kiwivm_service_info",
10
+ {
11
+ description:
12
+ "Get VPS service information including plan details, IP addresses, OS, and bandwidth usage. " +
13
+ "Set include_live=true for real-time status (CPU, RAM, disk, uptime, screenshot). " +
14
+ "Note: include_live may take up to 15 seconds to complete.",
15
+ inputSchema: {
16
+ include_live: z.boolean().optional(),
17
+ },
18
+ },
19
+ async ({ include_live }) => {
20
+ if (include_live) {
21
+ return callApi(() =>
22
+ client.call<LiveServiceInfo>("getLiveServiceInfo"),
23
+ );
24
+ }
25
+ return callApi(() => client.call<ServiceInfo>("getServiceInfo"));
26
+ },
27
+ );
28
+ }
@@ -0,0 +1,30 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import { callApi } from "./utils.js";
5
+
6
+ export function createMonitoringTools(
7
+ server: McpServer,
8
+ client: KiwiVMClient,
9
+ ): void {
10
+ server.registerTool(
11
+ "kiwivm_monitoring",
12
+ {
13
+ description:
14
+ "Monitoring and statistics: view raw usage stats, audit log, and API rate limit status",
15
+ inputSchema: {
16
+ action: z.enum(["usageStats", "auditLog", "rateLimit"]),
17
+ },
18
+ },
19
+ async ({ action }) => {
20
+ switch (action) {
21
+ case "usageStats":
22
+ return callApi(() => client.call("getRawUsageStats"));
23
+ case "auditLog":
24
+ return callApi(() => client.call("getAuditLog"));
25
+ case "rateLimit":
26
+ return callApi(() => client.call("getRateLimitStatus"));
27
+ }
28
+ },
29
+ );
30
+ }
@@ -0,0 +1,42 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import { callApi } from "./utils.js";
5
+
6
+ export function createNetworkTools(
7
+ server: McpServer,
8
+ client: KiwiVMClient,
9
+ ): void {
10
+ server.registerTool(
11
+ "kiwivm_network",
12
+ {
13
+ description:
14
+ "Network management: add/delete IPv6 /64 subnets, assign/delete private IP addresses, " +
15
+ "list available private IPs",
16
+ inputSchema: {
17
+ action: z.enum([
18
+ "addIPv6",
19
+ "deleteIPv6",
20
+ "listPrivateIps",
21
+ "assignPrivateIp",
22
+ "deletePrivateIp",
23
+ ]),
24
+ ip: z.string().optional(),
25
+ },
26
+ },
27
+ async ({ action, ip }) => {
28
+ switch (action) {
29
+ case "addIPv6":
30
+ return callApi(() => client.call("ipv6/add"));
31
+ case "deleteIPv6":
32
+ return callApi(() => client.call("ipv6/delete", { ip }));
33
+ case "listPrivateIps":
34
+ return callApi(() => client.call("privateIp/getAvailableIps"));
35
+ case "assignPrivateIp":
36
+ return callApi(() => client.call("privateIp/assign", { ip }));
37
+ case "deletePrivateIp":
38
+ return callApi(() => client.call("privateIp/delete", { ip }));
39
+ }
40
+ },
41
+ );
42
+ }
@@ -0,0 +1,21 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import { callApi } from "./utils.js";
5
+
6
+ export function createPowerTools(
7
+ server: McpServer,
8
+ client: KiwiVMClient,
9
+ ): void {
10
+ server.registerTool(
11
+ "kiwivm_power",
12
+ {
13
+ description:
14
+ "Control VPS power state (start, stop, restart, or force kill)",
15
+ inputSchema: {
16
+ action: z.enum(["start", "stop", "restart", "kill"]),
17
+ },
18
+ },
19
+ async ({ action }) => callApi(() => client.call(action)),
20
+ );
21
+ }
@@ -0,0 +1,76 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import type { KiwiVMResponse, Snapshot } from "../types.js";
5
+ import { callApi } from "./utils.js";
6
+
7
+ export function createSnapshotTools(
8
+ server: McpServer,
9
+ client: KiwiVMClient,
10
+ ): void {
11
+ server.registerTool(
12
+ "kiwivm_snapshot",
13
+ {
14
+ description:
15
+ "Manage VPS snapshots: create, list, delete, restore, toggle sticky, export, import",
16
+ inputSchema: {
17
+ action: z.enum([
18
+ "create",
19
+ "list",
20
+ "delete",
21
+ "restore",
22
+ "toggleSticky",
23
+ "export",
24
+ "import",
25
+ ]),
26
+ description: z.string().optional(),
27
+ snapshot: z.string().optional(),
28
+ sticky: z.number().int().min(0).max(1).optional(),
29
+ source_veid: z.string().optional(),
30
+ source_token: z.string().optional(),
31
+ },
32
+ },
33
+ async (args) => {
34
+ switch (args.action) {
35
+ case "create":
36
+ return callApi(() =>
37
+ client.call("snapshot/create", {
38
+ description: args.description,
39
+ }),
40
+ );
41
+ case "list":
42
+ return callApi(() =>
43
+ client.call<KiwiVMResponse & { snapshots: Snapshot[] }>(
44
+ "snapshot/list",
45
+ ),
46
+ );
47
+ case "delete":
48
+ return callApi(() =>
49
+ client.call("snapshot/delete", { snapshot: args.snapshot }),
50
+ );
51
+ case "restore":
52
+ return callApi(() =>
53
+ client.call("snapshot/restore", { snapshot: args.snapshot }),
54
+ );
55
+ case "toggleSticky":
56
+ return callApi(() =>
57
+ client.call("snapshot/toggleSticky", {
58
+ snapshot: args.snapshot,
59
+ sticky: args.sticky,
60
+ }),
61
+ );
62
+ case "export":
63
+ return callApi(() =>
64
+ client.call("snapshot/export", { snapshot: args.snapshot }),
65
+ );
66
+ case "import":
67
+ return callApi(() =>
68
+ client.call("snapshot/import", {
69
+ sourceVeid: args.source_veid,
70
+ sourceToken: args.source_token,
71
+ }),
72
+ );
73
+ }
74
+ },
75
+ );
76
+ }
@@ -0,0 +1,58 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { KiwiVMClient } from "../client.js";
4
+ import { callApi } from "./utils.js";
5
+
6
+ export function createSystemTools(
7
+ server: McpServer,
8
+ client: KiwiVMClient,
9
+ ): void {
10
+ server.registerTool(
11
+ "kiwivm_system",
12
+ {
13
+ description:
14
+ "System management: set hostname, set PTR/rDNS, reset root password, manage SSH keys, " +
15
+ "get available OS templates, reinstall OS",
16
+ inputSchema: {
17
+ action: z.enum([
18
+ "setHostname",
19
+ "setPTR",
20
+ "resetRootPassword",
21
+ "getSSHKeys",
22
+ "updateSSHKeys",
23
+ "getAvailableOS",
24
+ "reinstallOS",
25
+ ]),
26
+ new_hostname: z.string().optional(),
27
+ ip: z.string().optional(),
28
+ ptr: z.string().optional(),
29
+ ssh_keys: z.string().optional(),
30
+ os: z.string().optional(),
31
+ },
32
+ },
33
+ async (args) => {
34
+ switch (args.action) {
35
+ case "setHostname":
36
+ return callApi(() =>
37
+ client.call("setHostname", { newHostname: args.new_hostname }),
38
+ );
39
+ case "setPTR":
40
+ return callApi(() =>
41
+ client.call("setPTR", { ip: args.ip, ptr: args.ptr }),
42
+ );
43
+ case "resetRootPassword":
44
+ return callApi(() => client.call("resetRootPassword"));
45
+ case "getSSHKeys":
46
+ return callApi(() => client.call("getSshKeys"));
47
+ case "updateSSHKeys":
48
+ return callApi(() =>
49
+ client.call("updateSshKeys", { sshKeys: args.ssh_keys }),
50
+ );
51
+ case "getAvailableOS":
52
+ return callApi(() => client.call("getAvailableOS"));
53
+ case "reinstallOS":
54
+ return callApi(() => client.call("reinstallOS", { os: args.os }));
55
+ }
56
+ },
57
+ );
58
+ }
@@ -0,0 +1,23 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { KiwiVMError } from "../types.js";
3
+
4
+ /**
5
+ * Wraps an API call, formatting the result or error as a CallToolResult.
6
+ */
7
+ export async function callApi<T>(
8
+ fn: () => Promise<T>,
9
+ ): Promise<CallToolResult> {
10
+ try {
11
+ const result = await fn();
12
+ return {
13
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
14
+ };
15
+ } catch (error) {
16
+ const message =
17
+ error instanceof KiwiVMError ? error.message : String(error);
18
+ return {
19
+ content: [{ type: "text", text: `Error: ${message}` }],
20
+ isError: true,
21
+ };
22
+ }
23
+ }
package/src/types.ts ADDED
@@ -0,0 +1,94 @@
1
+ export interface KiwiVMResponse {
2
+ error: number;
3
+ message?: string;
4
+ }
5
+
6
+ export class KiwiVMError extends Error {
7
+ public readonly errorCode: number;
8
+ public readonly rawResponse: KiwiVMResponse;
9
+
10
+ constructor(message: string, errorCode: number, rawResponse: KiwiVMResponse) {
11
+ super(message);
12
+ this.name = "KiwiVMError";
13
+ this.errorCode = errorCode;
14
+ this.rawResponse = rawResponse;
15
+ }
16
+ }
17
+
18
+ export interface ServiceInfo extends KiwiVMResponse {
19
+ hostname?: string;
20
+ node_alias?: string;
21
+ node_location?: string;
22
+ location_ipv6_ready?: number;
23
+ plan?: string;
24
+ plan_monthly_data?: number;
25
+ plan_disk?: number;
26
+ plan_ram?: number;
27
+ plan_swap?: number;
28
+ os?: string;
29
+ email?: string;
30
+ data_counter?: number;
31
+ data_next_reset?: number;
32
+ ip_addresses?: string[];
33
+ private_ip_addresses?: string[];
34
+ ip_nullroutes?: Record<
35
+ string,
36
+ {
37
+ nullroute_timestamp: number;
38
+ nullroute_duration_s: number;
39
+ log: string;
40
+ }
41
+ >;
42
+ iso1?: string;
43
+ iso2?: string;
44
+ available_isos?: string[];
45
+ plan_max_ipv6s?: number;
46
+ rdns_api_available?: number;
47
+ plan_private_network_available?: number;
48
+ location_private_network_available?: number;
49
+ ptr?: Record<string, string>;
50
+ suspended?: number;
51
+ policy_violation?: number;
52
+ suspension_count?: number;
53
+ total_abuse_points?: number;
54
+ max_abuse_points?: number;
55
+ }
56
+
57
+ export interface LiveServiceInfo extends ServiceInfo {
58
+ vm_type?: "ovz" | "kvm";
59
+ vz_status?: Record<string, unknown>;
60
+ vz_quota?: Record<string, unknown>;
61
+ ve_status?: "Starting" | "Running" | "Stopped";
62
+ ve_mac1?: string;
63
+ ve_used_disk_space_b?: number;
64
+ ve_disk_quota_gb?: number;
65
+ is_cpu_throttled?: number;
66
+ is_disk_throttled?: number;
67
+ ssh_port?: number;
68
+ live_hostname?: string;
69
+ load_average?: string;
70
+ mem_available_kb?: number;
71
+ swap_total_kb?: number;
72
+ swap_available_kb?: number;
73
+ screendump_png_base64?: string;
74
+ }
75
+
76
+ export interface Snapshot {
77
+ fileName: string;
78
+ os: string;
79
+ description: string;
80
+ size: number;
81
+ md5: string;
82
+ sticky: number;
83
+ purgesIn: number;
84
+ downloadLink: string;
85
+ downloadLinkSSL: string;
86
+ }
87
+
88
+ export interface Backup {
89
+ backupToken: string;
90
+ size: number;
91
+ os: string;
92
+ md5: string;
93
+ timestamp: number;
94
+ }