@selfhost.dev/mcp-server 0.1.1 → 0.3.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/README.md +80 -2
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/auth.js +0 -11
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/billing.d.ts +2 -0
- package/dist/tools/billing.js +248 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/database-users.d.ts +2 -0
- package/dist/tools/database-users.js +181 -0
- package/dist/tools/database-users.js.map +1 -0
- package/dist/tools/hetzner.d.ts +2 -0
- package/dist/tools/hetzner.js +263 -0
- package/dist/tools/hetzner.js.map +1 -0
- package/dist/tools/instances.js +58 -85
- package/dist/tools/instances.js.map +1 -1
- package/dist/tools/pgbouncer.d.ts +2 -0
- package/dist/tools/pgbouncer.js +155 -0
- package/dist/tools/pgbouncer.js.map +1 -0
- package/dist/tools/pitr.d.ts +2 -0
- package/dist/tools/pitr.js +196 -0
- package/dist/tools/pitr.js.map +1 -0
- package/dist/tools/scaling.d.ts +2 -0
- package/dist/tools/scaling.js +290 -0
- package/dist/tools/scaling.js.map +1 -0
- package/dist/types/tiers.js +28 -1
- package/dist/types/tiers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiRequest } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Database users — DBMS-level credentials (distinct from the platform login).
|
|
5
|
+
*
|
|
6
|
+
* Endpoints:
|
|
7
|
+
* - GET /aws/v1/instances/:id/database_users
|
|
8
|
+
* - POST /aws/v1/instances/:id/database_users
|
|
9
|
+
* - PATCH /aws/v1/instances/:id/database_users/:user_id
|
|
10
|
+
* - DELETE /aws/v1/instances/:id/database_users/:user_id
|
|
11
|
+
* - POST /aws/v1/instances/:id/database_users/:user_id/rotate_password
|
|
12
|
+
*
|
|
13
|
+
* Use these to provision separate credentials for apps / people instead of
|
|
14
|
+
* sharing the master admin password.
|
|
15
|
+
*/
|
|
16
|
+
const ROLES = ["read_only", "read_write", "admin"];
|
|
17
|
+
export function registerDatabaseUserTools(server) {
|
|
18
|
+
server.tool("list_database_users", "List DBMS-level users on an instance. Returns each user's id, username, role, status (active | pending | deleting), target database, connection limit, and password expiry metadata.", {
|
|
19
|
+
instance_id: z.string().min(1),
|
|
20
|
+
}, async ({ instance_id }) => {
|
|
21
|
+
const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users`, {
|
|
22
|
+
toolName: "list_database_users",
|
|
23
|
+
skipOrgInjection: true,
|
|
24
|
+
});
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `Failed to list database users (${result.statusCode}): ${result.message}`,
|
|
30
|
+
}],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
34
|
+
});
|
|
35
|
+
server.tool("create_database_user", "Create a DBMS-level user on an instance. Username 1-31 chars, letters/digits/underscores only. Password 8-128 printable chars. Role determines DB privileges: read_only (SELECT), read_write (SELECT/INSERT/UPDATE/DELETE), admin (broader DDL). Target database scopes the grant to one DB; leave empty for cluster-level access.", {
|
|
36
|
+
instance_id: z.string().min(1),
|
|
37
|
+
username: z.string().min(1).max(31).regex(/^[a-zA-Z0-9_]+$/, "letters, digits, underscores only"),
|
|
38
|
+
password: z.string().min(8).max(128),
|
|
39
|
+
role: z.enum(ROLES),
|
|
40
|
+
target_database: z.string().optional().describe("Specific DB to grant on; omit for cluster-wide"),
|
|
41
|
+
connection_limit: z.number().int().min(1).max(1000).optional()
|
|
42
|
+
.describe("Max concurrent connections for this user (default unlimited)"),
|
|
43
|
+
password_expiry_days: z.number().int().min(1).max(365).optional()
|
|
44
|
+
.describe("Days until the password expires; omit for no expiry"),
|
|
45
|
+
}, async ({ instance_id, username, password, role, target_database, connection_limit, password_expiry_days }) => {
|
|
46
|
+
const body = {
|
|
47
|
+
username,
|
|
48
|
+
password,
|
|
49
|
+
role,
|
|
50
|
+
};
|
|
51
|
+
if (target_database !== undefined)
|
|
52
|
+
body.target_database = target_database;
|
|
53
|
+
if (connection_limit !== undefined)
|
|
54
|
+
body.connection_limit = connection_limit;
|
|
55
|
+
if (password_expiry_days !== undefined)
|
|
56
|
+
body.password_expiry_days = password_expiry_days;
|
|
57
|
+
const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
body,
|
|
60
|
+
toolName: "create_database_user",
|
|
61
|
+
skipOrgInjection: true,
|
|
62
|
+
});
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `Failed to create database user (${result.statusCode}): ${result.message}`,
|
|
68
|
+
}],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{ type: "text", text: `Database user \`${username}\` create requested. Status will transition from \`pending\` to \`active\` once applied — poll \`list_database_users\`.` },
|
|
74
|
+
{ type: "text", text: JSON.stringify(result.data, null, 2) },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
server.tool("update_database_user", "Update a database user — change role, connection limit, or password expiry. Username and target database are immutable (delete + recreate to change them).", {
|
|
79
|
+
instance_id: z.string().min(1),
|
|
80
|
+
user_id: z.string().min(1).describe("Database user PID (from list_database_users)"),
|
|
81
|
+
role: z.enum(ROLES).optional(),
|
|
82
|
+
connection_limit: z.number().int().min(1).max(1000).optional(),
|
|
83
|
+
password_expiry_days: z.number().int().min(0).max(365).optional()
|
|
84
|
+
.describe("0 removes expiry; otherwise sets a new countdown from now"),
|
|
85
|
+
}, async ({ instance_id, user_id, role, connection_limit, password_expiry_days }) => {
|
|
86
|
+
const body = {};
|
|
87
|
+
if (role !== undefined)
|
|
88
|
+
body.role = role;
|
|
89
|
+
if (connection_limit !== undefined)
|
|
90
|
+
body.connection_limit = connection_limit;
|
|
91
|
+
if (password_expiry_days !== undefined)
|
|
92
|
+
body.password_expiry_days = password_expiry_days;
|
|
93
|
+
if (Object.keys(body).length === 0) {
|
|
94
|
+
return {
|
|
95
|
+
content: [{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: "No changes to apply — pass at least one of role / connection_limit / password_expiry_days.",
|
|
98
|
+
}],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}`, {
|
|
102
|
+
method: "PATCH",
|
|
103
|
+
body,
|
|
104
|
+
toolName: "update_database_user",
|
|
105
|
+
skipOrgInjection: true,
|
|
106
|
+
});
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: `Failed to update database user (${result.statusCode}): ${result.message}`,
|
|
112
|
+
}],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
116
|
+
});
|
|
117
|
+
server.tool("delete_database_user", "Soft-delete a database user. Existing connections opened by this user are terminated. The record is preserved for audit — contact support to recover.", {
|
|
118
|
+
instance_id: z.string().min(1),
|
|
119
|
+
user_id: z.string().min(1),
|
|
120
|
+
username: z.string().optional().describe("Username for the confirmation message (cosmetic)"),
|
|
121
|
+
confirm: z.boolean().describe("Must be true to proceed. Existing connections will be dropped."),
|
|
122
|
+
}, async ({ instance_id, user_id, username, confirm }) => {
|
|
123
|
+
if (!confirm) {
|
|
124
|
+
const label = username ? `\`${username}\` (${user_id})` : user_id;
|
|
125
|
+
return {
|
|
126
|
+
content: [{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `⚠️ About to DELETE database user ${label} on instance ${instance_id}. Apps/scripts using this credential will lose access immediately.\n\nCall again with confirm=true to proceed.`,
|
|
129
|
+
}],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}`, {
|
|
133
|
+
method: "DELETE",
|
|
134
|
+
toolName: "delete_database_user",
|
|
135
|
+
skipOrgInjection: true,
|
|
136
|
+
});
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: `Failed to delete database user (${result.statusCode}): ${result.message}`,
|
|
142
|
+
}],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { content: [{ type: "text", text: "Database user delete requested — status will move to `deleting` and then disappear from the list." }] };
|
|
146
|
+
});
|
|
147
|
+
server.tool("rotate_database_user_password", "Rotate the password for a database user. The new password is generated server-side and returned ONCE in the response. SECURITY: save it immediately — there is no way to retrieve it later. Existing connections using the old password are terminated.", {
|
|
148
|
+
instance_id: z.string().min(1),
|
|
149
|
+
user_id: z.string().min(1),
|
|
150
|
+
confirm: z.boolean().describe("Must be true. Old password is invalidated immediately; apps and scripts must pick up the new value."),
|
|
151
|
+
}, async ({ instance_id, user_id, confirm }) => {
|
|
152
|
+
if (!confirm) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `⚠️ About to ROTATE the password for database user ${user_id} on instance ${instance_id}.\n\nThe new password is shown ONCE in the response — save it before doing anything else.\nApps and scripts using the old password will lose connections immediately.\n\nCall again with confirm=true to proceed.`,
|
|
157
|
+
}],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}/rotate_password`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
toolName: "rotate_database_user_password",
|
|
163
|
+
skipOrgInjection: true,
|
|
164
|
+
});
|
|
165
|
+
if (!result.success) {
|
|
166
|
+
return {
|
|
167
|
+
content: [{
|
|
168
|
+
type: "text",
|
|
169
|
+
text: `Failed to rotate password (${result.statusCode}): ${result.message}`,
|
|
170
|
+
}],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{ type: "text", text: "🔑 New password generated. Copy it now — it will not be shown again." },
|
|
176
|
+
{ type: "text", text: JSON.stringify(result.data, null, 2) },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=database-users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-users.js","sourceRoot":"","sources":["../../src/tools/database-users.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiRequest } from "../client.js";
|
|
3
|
+
import { session } from "../session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Hetzner Cloud — Postgres-only provisioning.
|
|
6
|
+
*
|
|
7
|
+
* Backend endpoints (added in selfhost-dev/selfhost PR #983):
|
|
8
|
+
* - POST /hetzner/v1/instances — provision
|
|
9
|
+
* - GET /hetzner/v1/instances/:pid — read
|
|
10
|
+
* - GET /hetzner/v1/instances/:pid/status — provisioning progress
|
|
11
|
+
* - DELETE /hetzner/v1/instances/:pid — destroy
|
|
12
|
+
*
|
|
13
|
+
* Limitations vs AWS today:
|
|
14
|
+
* - Postgres only (no MySQL, no MongoDB)
|
|
15
|
+
* - No list endpoint — callers must keep track of `pid` after create
|
|
16
|
+
* - No start / stop / reboot / update / fork
|
|
17
|
+
* - No scaling, no PITR, no PgBouncer, no database-user management
|
|
18
|
+
*
|
|
19
|
+
* Locations + server types are hardcoded here to mirror the Rails CLI
|
|
20
|
+
* registry. When the backend ships discovery endpoints we'll switch these
|
|
21
|
+
* tools over.
|
|
22
|
+
*/
|
|
23
|
+
function resolveOrgId(organization_id) {
|
|
24
|
+
return organization_id || session.getActiveOrgId();
|
|
25
|
+
}
|
|
26
|
+
const noOrgMessage = "No organization selected. Call `select_organization` first or pass an organization_id.";
|
|
27
|
+
// --- Static reference data ---------------------------------------------------
|
|
28
|
+
const CLOUD_PROVIDERS = [
|
|
29
|
+
{
|
|
30
|
+
id: "aws",
|
|
31
|
+
name: "Amazon Web Services (AWS)",
|
|
32
|
+
capabilities: "Full lifecycle: create / list / start / stop / reboot / update / fork / delete; multi-AZ; replicas; auto-scaling; scheduled scaling; PITR; PgBouncer; database users; alerts; backup policies.",
|
|
33
|
+
create_tool: "create_instance",
|
|
34
|
+
list_tool: "list_instances",
|
|
35
|
+
notes: "Production-ready. Use for any workload that needs HA, replicas, or any of the lifecycle ops above. Available in 20+ AWS regions.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "hetzner",
|
|
39
|
+
name: "Hetzner Cloud",
|
|
40
|
+
capabilities: "Provisioning only: create / read / status / delete. No start / stop / reboot / update / fork; no scaling; no PITR; no PgBouncer; no database users; no alerts.",
|
|
41
|
+
create_tool: "create_hetzner_instance",
|
|
42
|
+
list_tool: null,
|
|
43
|
+
notes: "Cheaper than AWS for Postgres workloads but BE feature surface is currently limited. Use only when the user explicitly asks for Hetzner. Postgres only.",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const HETZNER_LOCATIONS = [
|
|
47
|
+
{ code: "fsn1", name: "Falkenstein", country: "Germany", continent: "Europe" },
|
|
48
|
+
{ code: "nbg1", name: "Nuremberg", country: "Germany", continent: "Europe" },
|
|
49
|
+
{ code: "hel1", name: "Helsinki", country: "Finland", continent: "Europe" },
|
|
50
|
+
{ code: "ash", name: "Ashburn, VA", country: "United States", continent: "North America" },
|
|
51
|
+
{ code: "sin", name: "Singapore", country: "Singapore", continent: "Asia" },
|
|
52
|
+
];
|
|
53
|
+
const HETZNER_SERVER_TYPES = [
|
|
54
|
+
{ code: "cpx11", vcpu: 2, memory_gb: 2, disk_gb: 40, cpu_family: "AMD" },
|
|
55
|
+
{ code: "cpx21", vcpu: 3, memory_gb: 4, disk_gb: 80, cpu_family: "AMD" },
|
|
56
|
+
{ code: "cpx22", vcpu: 4, memory_gb: 4, disk_gb: 80, cpu_family: "AMD" },
|
|
57
|
+
{ code: "cpx31", vcpu: 4, memory_gb: 8, disk_gb: 160, cpu_family: "AMD" },
|
|
58
|
+
{ code: "cpx42", vcpu: 8, memory_gb: 16, disk_gb: 240, cpu_family: "AMD" },
|
|
59
|
+
{ code: "cpx51", vcpu: 16, memory_gb: 32, disk_gb: 360, cpu_family: "AMD" },
|
|
60
|
+
];
|
|
61
|
+
// --- Schemas -----------------------------------------------------------------
|
|
62
|
+
const cidrRangeSchema = z.object({
|
|
63
|
+
cidr: z.string().min(1).describe("CIDR range, e.g. 10.0.0.0/24"),
|
|
64
|
+
name: z.string().optional().describe("Friendly label"),
|
|
65
|
+
port: z.number().int().optional().describe("Port allowed by this rule (default: 5432)"),
|
|
66
|
+
description: z.string().optional(),
|
|
67
|
+
});
|
|
68
|
+
const LOCATION_CODES = HETZNER_LOCATIONS.map((l) => l.code);
|
|
69
|
+
const SERVER_TYPE_CODES = HETZNER_SERVER_TYPES.map((s) => s.code);
|
|
70
|
+
// --- Registration ------------------------------------------------------------
|
|
71
|
+
export function registerHetznerTools(server) {
|
|
72
|
+
// ----- Provider discovery -------------------------------------------------
|
|
73
|
+
server.tool("list_cloud_providers", "List the cloud providers SelfHost can provision Postgres on. Call this whenever the user expresses an intent to create a database — present the options and ask which one they want before choosing the create tool. The response includes capabilities and the specific create tool for each provider.", {}, async () => {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{ type: "text", text: JSON.stringify(CLOUD_PROVIDERS, null, 2) },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
server.tool("list_hetzner_locations", "List the Hetzner Cloud locations (datacenters) available for provisioning. Returns an array of `{code, name, country, continent}`. The `code` is what `create_hetzner_instance` expects for its `region` param. Ask the user which one they want before creating.", {}, async () => {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{ type: "text", text: JSON.stringify(HETZNER_LOCATIONS, null, 2) },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
server.tool("list_hetzner_server_types", "List the Hetzner Cloud server types available for Postgres. Returns an array of `{code, vcpu, memory_gb, disk_gb, cpu_family}`. The `code` is what `create_hetzner_instance` expects for its `server_type` param. Note: `disk_gb` is the server's local disk; the database storage is a separately-attached Hetzner Volume sized via the `storage_size` param at create time.", {}, async () => {
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{ type: "text", text: JSON.stringify(HETZNER_SERVER_TYPES, null, 2) },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
// ----- Provisioning -------------------------------------------------------
|
|
95
|
+
server.tool("create_hetzner_instance", `Provision a new Postgres database on Hetzner Cloud.
|
|
96
|
+
|
|
97
|
+
RECOMMENDED FLOW:
|
|
98
|
+
1. Call \`list_cloud_providers\` and ask the user which provider they want.
|
|
99
|
+
2. If Hetzner: call \`list_hetzner_locations\` and let the user pick a region.
|
|
100
|
+
3. Call \`list_hetzner_server_types\` and let the user pick a size.
|
|
101
|
+
4. Confirm storage_size (separate Hetzner Volume, in GB).
|
|
102
|
+
5. Then call this tool.
|
|
103
|
+
|
|
104
|
+
IMPORTANT:
|
|
105
|
+
- The password returned in the response is shown ONCE. Save it immediately — there's no way to retrieve it.
|
|
106
|
+
- Hetzner integration is currently provision/delete only. After creation, use \`get_hetzner_instance_status\` to poll provisioning progress, and \`get_hetzner_instance\` to read details. To remove it, use \`delete_hetzner_instance\`.
|
|
107
|
+
- Provisioning takes ~5-10 minutes (network + firewall + SSH key + server + volume + load balancer + Ansible/autobase playbook + Cloudflare DNS).`, {
|
|
108
|
+
organization_id: z.string().optional()
|
|
109
|
+
.describe("Organization PID (defaults to active org)"),
|
|
110
|
+
name: z.string().min(1).max(63)
|
|
111
|
+
.describe("Instance name (used for DNS host)"),
|
|
112
|
+
region: z.enum(LOCATION_CODES)
|
|
113
|
+
.describe("Hetzner location code — call list_hetzner_locations to see options"),
|
|
114
|
+
server_type: z.enum(SERVER_TYPE_CODES)
|
|
115
|
+
.describe("Hetzner server type — call list_hetzner_server_types to see options"),
|
|
116
|
+
storage_size: z.number().int().min(10).max(10240)
|
|
117
|
+
.describe("Database storage size in GB (Hetzner Volume, separate from server local disk)"),
|
|
118
|
+
db_version: z.string().optional()
|
|
119
|
+
.describe("Postgres version (default: 18)"),
|
|
120
|
+
high_availability: z.boolean().optional()
|
|
121
|
+
.describe("Provision a 3-node cluster with a load balancer (extra ~€5/mo). Default: false (single-node, public IP only)"),
|
|
122
|
+
public_access: z.boolean().optional()
|
|
123
|
+
.describe("Expose port 5432 to the internet (default: true). Combine with allowed_cidr_ranges to restrict."),
|
|
124
|
+
backup_enabled: z.boolean().optional()
|
|
125
|
+
.describe("Enable automated backups (default: true)"),
|
|
126
|
+
username: z.string().regex(/^[a-z_][a-z0-9_]{0,30}$/i).optional()
|
|
127
|
+
.describe("Database username (default: postgres)"),
|
|
128
|
+
delete_protection: z.boolean().optional()
|
|
129
|
+
.describe("Prevent accidental deletion (recommended for production)"),
|
|
130
|
+
allowed_cidr_ranges: z.array(cidrRangeSchema).max(24).optional()
|
|
131
|
+
.describe("IP whitelist on port 5432 (max 24 ranges). Merged with the internal 10.0.0.0/16 rule."),
|
|
132
|
+
}, async (params) => {
|
|
133
|
+
const orgId = resolveOrgId(params.organization_id);
|
|
134
|
+
if (!orgId) {
|
|
135
|
+
return { content: [{ type: "text", text: noOrgMessage }] };
|
|
136
|
+
}
|
|
137
|
+
const body = {
|
|
138
|
+
organization_id: orgId,
|
|
139
|
+
name: params.name,
|
|
140
|
+
region: params.region,
|
|
141
|
+
server_type: params.server_type,
|
|
142
|
+
storage_size: params.storage_size,
|
|
143
|
+
};
|
|
144
|
+
if (params.db_version !== undefined)
|
|
145
|
+
body.db_version = params.db_version;
|
|
146
|
+
if (params.high_availability !== undefined)
|
|
147
|
+
body.high_availability = params.high_availability;
|
|
148
|
+
if (params.public_access !== undefined)
|
|
149
|
+
body.public_access = params.public_access;
|
|
150
|
+
if (params.backup_enabled !== undefined)
|
|
151
|
+
body.backup_enabled = params.backup_enabled;
|
|
152
|
+
if (params.username !== undefined)
|
|
153
|
+
body.username = params.username;
|
|
154
|
+
if (params.delete_protection !== undefined)
|
|
155
|
+
body.delete_protection = params.delete_protection;
|
|
156
|
+
if (params.allowed_cidr_ranges !== undefined)
|
|
157
|
+
body.allowed_cidr_ranges = params.allowed_cidr_ranges;
|
|
158
|
+
const result = await apiRequest("/hetzner/v1/instances", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body,
|
|
161
|
+
toolName: "create_hetzner_instance",
|
|
162
|
+
skipOrgInjection: true,
|
|
163
|
+
});
|
|
164
|
+
if (!result.success) {
|
|
165
|
+
return {
|
|
166
|
+
content: [{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: `Failed to create Hetzner instance (${result.statusCode}): ${result.message}`,
|
|
169
|
+
}],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const data = result.data;
|
|
173
|
+
const lines = [
|
|
174
|
+
"Hetzner instance provisioning started!",
|
|
175
|
+
"",
|
|
176
|
+
`PID: ${data.pid}`,
|
|
177
|
+
];
|
|
178
|
+
if (data.host)
|
|
179
|
+
lines.push(`Host: ${data.host}`);
|
|
180
|
+
if (data.username)
|
|
181
|
+
lines.push(`Username: ${data.username}`);
|
|
182
|
+
if (data.password) {
|
|
183
|
+
lines.push(`Password: ${data.password}`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push("⚠️ SAVE THIS PASSWORD NOW — it cannot be retrieved again.");
|
|
186
|
+
}
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push(`Poll provisioning progress with \`get_hetzner_instance_status\` (pid: ${data.pid}). Provisioning typically takes 5–10 minutes.`);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{ type: "text", text: lines.join("\n") },
|
|
192
|
+
{ type: "text", text: JSON.stringify(data, null, 2) },
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
server.tool("get_hetzner_instance", "Get details for a single Hetzner instance by PID. Returns the full record including name, region, server_type, storage_size, high_availability, public_access, host (DNS name), and creation timestamps.", {
|
|
197
|
+
pid: z.string().min(1).describe("Hetzner instance PID (returned by create_hetzner_instance)"),
|
|
198
|
+
}, async ({ pid }) => {
|
|
199
|
+
const result = await apiRequest(`/hetzner/v1/instances/${pid}`, {
|
|
200
|
+
toolName: "get_hetzner_instance",
|
|
201
|
+
skipOrgInjection: true,
|
|
202
|
+
});
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
return {
|
|
205
|
+
content: [{
|
|
206
|
+
type: "text",
|
|
207
|
+
text: `Failed to get Hetzner instance (${result.statusCode}): ${result.message}`,
|
|
208
|
+
}],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
212
|
+
});
|
|
213
|
+
server.tool("get_hetzner_instance_status", "Poll the provisioning progress of a Hetzner instance. Returns `{pid, status, progress: {step_name: 'completed' | 'pending', ...}}`. Use this after `create_hetzner_instance` until `status` is terminal (e.g. `running` or `failed`). Steps include network, firewall, ssh_key, server, volume, load_balancer (HA only), ansible, finalize.", {
|
|
214
|
+
pid: z.string().min(1),
|
|
215
|
+
}, async ({ pid }) => {
|
|
216
|
+
const result = await apiRequest(`/hetzner/v1/instances/${pid}/status`, {
|
|
217
|
+
toolName: "get_hetzner_instance_status",
|
|
218
|
+
skipOrgInjection: true,
|
|
219
|
+
});
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
return {
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: `Failed to get Hetzner instance status (${result.statusCode}): ${result.message}`,
|
|
225
|
+
}],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
229
|
+
});
|
|
230
|
+
server.tool("delete_hetzner_instance", "Delete a Hetzner instance and all associated resources (server, volume, firewall, network, load balancer if HA, DNS record). IRREVERSIBLE — there's no `start` / `stop` on Hetzner; deletion is the only way to remove an instance.", {
|
|
231
|
+
pid: z.string().min(1),
|
|
232
|
+
confirm: z.boolean().describe("Must be true to proceed. ALL DATA on this instance will be permanently lost."),
|
|
233
|
+
}, async ({ pid, confirm }) => {
|
|
234
|
+
if (!confirm) {
|
|
235
|
+
return {
|
|
236
|
+
content: [{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: `⚠️ About to DELETE Hetzner instance ${pid}.\n\nThis action is IRREVERSIBLE. Server, volume, firewall, network, load balancer, and DNS records will all be removed. All data will be lost.\n\nCall again with confirm=true to proceed.`,
|
|
239
|
+
}],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const result = await apiRequest(`/hetzner/v1/instances/${pid}`, {
|
|
243
|
+
method: "DELETE",
|
|
244
|
+
toolName: "delete_hetzner_instance",
|
|
245
|
+
skipOrgInjection: true,
|
|
246
|
+
});
|
|
247
|
+
if (!result.success) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{
|
|
250
|
+
type: "text",
|
|
251
|
+
text: `Failed to delete Hetzner instance (${result.statusCode}): ${result.message}`,
|
|
252
|
+
}],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
content: [
|
|
257
|
+
{ type: "text", text: `Hetzner instance ${pid} delete requested. Resources are being torn down.` },
|
|
258
|
+
{ type: "text", text: JSON.stringify(result.data, null, 2) },
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=hetzner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hetzner.js","sourceRoot":"","sources":["../../src/tools/hetzner.ts"],"names":[],"mappings":"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"}
|