helipod-mcp 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 +22 -10
- package/dist/http.js +178 -7
- package/dist/index.js +178 -7
- package/dist/stdio.js +178 -7
- package/package.json +2 -2
- package/skills/helipod/SKILL.md +4 -3
- package/skills/helipod-databases/SKILL.md +37 -0
package/README.md
CHANGED
|
@@ -2,14 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Use [Helipod](https://helipod.io) from AI coding tools: deploy apps and databases, run VPS servers, buy domains and edit DNS, and send email, by asking in plain language.
|
|
4
4
|
|
|
5
|
-
- **MCP server** (`helipod-mcp`):
|
|
5
|
+
- **MCP server** (`helipod-mcp`): 89 tools over the Container, Database, VPS, Domain and Email APIs. Works in Claude Code, Codex, Cursor, VS Code, Windsurf, Claude Desktop and any MCP client.
|
|
6
6
|
- **Skills** (`skills/`): step-by-step playbooks so the agent deploys, prices and confirms the way Helipod expects (price first, confirm before deleting, read build logs on failure).
|
|
7
7
|
|
|
8
8
|
Step-by-step guides for each tool (Claude Code, Codex, Cursor, VS Code, Gemini CLI, opencode, Windsurf, Claude Desktop, Cline and others): **https://docs.helipod.io/docs/helipod/ai-agents**
|
|
9
9
|
|
|
10
|
+
## Quick start: sign in (no key)
|
|
11
|
+
|
|
12
|
+
Add the hosted server and sign in to Helipod in the browser; you choose which products (Container, VPS, Domain API) and which project the tool may use, and can disconnect it any time in **Settings → API keys → Connected apps**.
|
|
13
|
+
|
|
14
|
+
| Tool | Add | Sign in |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Claude Code | `/plugin marketplace add helipodapp/mcp` then `/plugin install helipod@mcp` (or `claude mcp add --transport http helipod https://api.helipod.io/mcp`) | `/mcp` → helipod → Authenticate |
|
|
17
|
+
| Codex | `[mcp_servers.helipod]` `url = "https://api.helipod.io/mcp"` in `~/.codex/config.toml` | `codex mcp login helipod` |
|
|
18
|
+
| Cursor | `{"mcpServers":{"helipod":{"url":"https://api.helipod.io/mcp"}}}` in `~/.cursor/mcp.json` | Settings → MCP → Needs login |
|
|
19
|
+
| opencode | `{"mcp":{"helipod":{"type":"remote","url":"https://api.helipod.io/mcp"}}}` | `opencode mcp auth helipod` |
|
|
20
|
+
| Gemini CLI | `{"mcpServers":{"helipod":{"httpUrl":"https://api.helipod.io/mcp"}}}` | `/mcp auth helipod` |
|
|
21
|
+
| VS Code | `{"servers":{"helipod":{"type":"http","url":"https://api.helipod.io/mcp"}}}` in `.vscode/mcp.json` | prompted when the server starts |
|
|
22
|
+
| Claude.ai, ChatGPT | Add a custom connector with the URL `https://api.helipod.io/mcp` | prompted when connecting |
|
|
23
|
+
|
|
24
|
+
Prefer a key, a local server, or a tool not listed? Read on.
|
|
25
|
+
|
|
10
26
|
## 1. Get a key
|
|
11
27
|
|
|
12
|
-
In the [dashboard](https://helipod.io/project) open a project, then **Settings → API keys**. Choose the products (Container API, VPS API, Domain API) and the reach (one project, or the whole account; the Domain API needs a whole-account key). The key starts with `hel_`.
|
|
28
|
+
In the [dashboard](https://helipod.io/project) open a project, then **Settings → API keys**. Choose the products (Container API, Database API, VPS API, Domain API) and the reach (one project, or the whole account; the Domain API needs a whole-account key). The key starts with `hel_`.
|
|
13
29
|
|
|
14
30
|
Optional: an **email API key** from the project's **Email** section, for the email tools.
|
|
15
31
|
|
|
@@ -24,14 +40,9 @@ Start with a key limited to one project while you try things out.
|
|
|
24
40
|
|
|
25
41
|
The server is the npm package `helipod-mcp` (Node.js 18 or newer). The configs below run it with `npx -y helipod-mcp` (or the installed `helipod-mcp` command) and pass `HELIPOD_API_KEY` from your environment.
|
|
26
42
|
|
|
27
|
-
### Claude Code
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
/plugin marketplace add helipodapp/mcp
|
|
31
|
-
/plugin install helipod@mcp
|
|
32
|
-
```
|
|
43
|
+
### Claude Code
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
The plugin (above) uses the hosted server with sign-in. Local server with a key instead:
|
|
35
46
|
|
|
36
47
|
```sh
|
|
37
48
|
claude mcp add helipod --env HELIPOD_API_KEY=$HELIPOD_API_KEY -- npx -y helipod-mcp
|
|
@@ -169,7 +180,7 @@ Pick your agents when asked (or pass `-a codex cursor gemini-cli opencode -y`).
|
|
|
169
180
|
## 3. Ask
|
|
170
181
|
|
|
171
182
|
- "Deploy this repo to my Helipod project and give me the URL."
|
|
172
|
-
- "
|
|
183
|
+
- "Create a Postgres database for this project and connect it to the api service."
|
|
173
184
|
- "Why did the last deploy fail?"
|
|
174
185
|
- "Is tokosaya.id available, and how much is it?"
|
|
175
186
|
- "Point www.tokosaya.id at my web service."
|
|
@@ -181,6 +192,7 @@ Pick your agents when asked (or pass `-a codex cursor gemini-cli opencode -y`).
|
|
|
181
192
|
| Area | Tools |
|
|
182
193
|
|---|---|
|
|
183
194
|
| Containers | projects, services (create from image, Git or Dockerfile), deploy, deployments and build logs, restart, variables, domains, scale, volumes, logs, metrics, templates, multi-service stacks |
|
|
195
|
+
| Databases | managed Postgres: create, connection string, branches per environment, compute settings, plan upgrades, usage, tables, read-only queries and confirmed writes |
|
|
184
196
|
| VPS | plans, create, power, reinstall, password, login details, terminate, renew, upgrade, auto-renew, firewall, snapshots |
|
|
185
197
|
| Domains | search, prices, register, transfer, renew, DNS records, nameservers, WHOIS privacy |
|
|
186
198
|
| Email | status, send, sent log, sending domains, templates, contacts |
|
package/dist/http.js
CHANGED
|
@@ -36410,14 +36410,184 @@ var emailTools = [
|
|
|
36410
36410
|
})
|
|
36411
36411
|
];
|
|
36412
36412
|
|
|
36413
|
+
// src/tools/databases.ts
|
|
36414
|
+
var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
|
|
36415
|
+
var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
|
|
36416
|
+
var dryRun2 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
|
|
36417
|
+
var databaseTools = [
|
|
36418
|
+
defineTool({
|
|
36419
|
+
name: "list_database_plans",
|
|
36420
|
+
title: "List database plans",
|
|
36421
|
+
kind: "read",
|
|
36422
|
+
description: "Managed Postgres plans: price (IDR), included compute hours and storage, maximum CU. Free is the default; paid plans are prepaid credits that never expire.",
|
|
36423
|
+
input: {},
|
|
36424
|
+
run: (_a3, api2) => api2.api("GET", "/databases/plans")
|
|
36425
|
+
}),
|
|
36426
|
+
defineTool({
|
|
36427
|
+
name: "list_databases",
|
|
36428
|
+
title: "List databases",
|
|
36429
|
+
kind: "read",
|
|
36430
|
+
description: "Managed Postgres databases in the projects this key can reach: status, plan and remaining credits, region, branches per environment.",
|
|
36431
|
+
input: {},
|
|
36432
|
+
run: (_a3, api2) => api2.api("GET", "/databases")
|
|
36433
|
+
}),
|
|
36434
|
+
defineTool({
|
|
36435
|
+
name: "get_database",
|
|
36436
|
+
title: "Get database",
|
|
36437
|
+
kind: "read",
|
|
36438
|
+
description: "One database with its plan, compute settings and branches.",
|
|
36439
|
+
input: { databaseId },
|
|
36440
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}`)
|
|
36441
|
+
}),
|
|
36442
|
+
defineTool({
|
|
36443
|
+
name: "create_database",
|
|
36444
|
+
title: "Create database",
|
|
36445
|
+
kind: "write",
|
|
36446
|
+
idempotent: true,
|
|
36447
|
+
description: "Create the managed Postgres database of a project (free plan; one database per project, one branch per environment). Returns the existing one if the project already has it.",
|
|
36448
|
+
input: { projectId: exports_external.string().uuid(), idempotencyKey: exports_external.string().max(128).optional() },
|
|
36449
|
+
run: (a, api2) => api2.api("POST", "/databases", { body: { projectId: a.projectId }, idempotencyKey: a.idempotencyKey })
|
|
36450
|
+
}),
|
|
36451
|
+
defineTool({
|
|
36452
|
+
name: "delete_database",
|
|
36453
|
+
title: "Delete database",
|
|
36454
|
+
kind: "destructive",
|
|
36455
|
+
description: "Delete a database with ALL its data and branches. Irreversible. Only after the user confirms; confirm must be the database name.",
|
|
36456
|
+
input: { databaseId, confirm: exports_external.string().describe("The database name, exactly") },
|
|
36457
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}`, { body: { confirm: a.confirm } })
|
|
36458
|
+
}),
|
|
36459
|
+
defineTool({
|
|
36460
|
+
name: "get_database_connection",
|
|
36461
|
+
title: "Get database connection",
|
|
36462
|
+
kind: "read",
|
|
36463
|
+
description: "Connection string (postgresql://…, pooled, SSL) and its parts for an environment's branch. Contains the password: store it as a secret variable (e.g. DATABASE_URL via set_variables secrets), never print it unless asked.",
|
|
36464
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
36465
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/connection`, { query: { environmentId: a.environmentId } })
|
|
36466
|
+
}),
|
|
36467
|
+
defineTool({
|
|
36468
|
+
name: "list_database_branches",
|
|
36469
|
+
title: "List database branches",
|
|
36470
|
+
kind: "read",
|
|
36471
|
+
description: "Branches of a database, one per environment (production is the primary).",
|
|
36472
|
+
input: { databaseId },
|
|
36473
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/branches`)
|
|
36474
|
+
}),
|
|
36475
|
+
defineTool({
|
|
36476
|
+
name: "create_database_branch",
|
|
36477
|
+
title: "Create database branch",
|
|
36478
|
+
kind: "write",
|
|
36479
|
+
idempotent: true,
|
|
36480
|
+
description: "Give an environment (e.g. staging) its own branch: a copy of the source environment's data at this moment (the primary when omitted).",
|
|
36481
|
+
input: { databaseId, environmentId: exports_external.string().uuid(), sourceEnvironmentId: exports_external.string().uuid().optional() },
|
|
36482
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/branches`, { body: { environmentId: a.environmentId, sourceEnvironmentId: a.sourceEnvironmentId } })
|
|
36483
|
+
}),
|
|
36484
|
+
defineTool({
|
|
36485
|
+
name: "delete_database_branch",
|
|
36486
|
+
title: "Delete database branch",
|
|
36487
|
+
kind: "destructive",
|
|
36488
|
+
description: "Delete an environment's branch and its data. The primary (production) branch cannot be deleted. Confirm with the user.",
|
|
36489
|
+
input: { databaseId, environmentId: exports_external.string().uuid() },
|
|
36490
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}/branches/${a.environmentId}`)
|
|
36491
|
+
}),
|
|
36492
|
+
defineTool({
|
|
36493
|
+
name: "get_database_settings",
|
|
36494
|
+
title: "Get database compute settings",
|
|
36495
|
+
kind: "read",
|
|
36496
|
+
description: "Compute settings (paid plans): minCu (floor), maxCu (autoscaling ceiling), suspendTimeoutSeconds (idle time before scaling to zero), the plan's maximum CU.",
|
|
36497
|
+
input: { databaseId },
|
|
36498
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/settings`)
|
|
36499
|
+
}),
|
|
36500
|
+
defineTool({
|
|
36501
|
+
name: "update_database_settings",
|
|
36502
|
+
title: "Update database compute settings",
|
|
36503
|
+
kind: "write",
|
|
36504
|
+
description: "Change compute for every branch (paid plans). A minCu above zero keeps compute running and uses credits while idle: run with dryRun first and show idleComputeHoursPerMonth.",
|
|
36505
|
+
input: {
|
|
36506
|
+
databaseId,
|
|
36507
|
+
minCu: exports_external.number().min(0.25).max(10).optional(),
|
|
36508
|
+
maxCu: exports_external.number().min(0.25).max(10).optional(),
|
|
36509
|
+
suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
|
|
36510
|
+
dryRun: dryRun2
|
|
36511
|
+
},
|
|
36512
|
+
run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
|
|
36513
|
+
body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
|
|
36514
|
+
query: { dryRun: a.dryRun ? "true" : undefined }
|
|
36515
|
+
})
|
|
36516
|
+
}),
|
|
36517
|
+
defineTool({
|
|
36518
|
+
name: "upgrade_database",
|
|
36519
|
+
title: "Upgrade database plan / top up",
|
|
36520
|
+
kind: "write",
|
|
36521
|
+
description: "Buy a paid plan or top up credits. COSTS MONEY from the wallet: call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
|
|
36522
|
+
input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun2, idempotencyKey: exports_external.string().max(128).optional() },
|
|
36523
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
36524
|
+
}),
|
|
36525
|
+
defineTool({
|
|
36526
|
+
name: "suspend_database",
|
|
36527
|
+
title: "Suspend database",
|
|
36528
|
+
kind: "destructive",
|
|
36529
|
+
description: "Stop the database's compute (connections fail until resumed). Confirm with the user.",
|
|
36530
|
+
input: { databaseId },
|
|
36531
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/suspend`)
|
|
36532
|
+
}),
|
|
36533
|
+
defineTool({
|
|
36534
|
+
name: "resume_database",
|
|
36535
|
+
title: "Resume database",
|
|
36536
|
+
kind: "write",
|
|
36537
|
+
description: "Start a suspended database again.",
|
|
36538
|
+
input: { databaseId },
|
|
36539
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/resume`)
|
|
36540
|
+
}),
|
|
36541
|
+
defineTool({
|
|
36542
|
+
name: "get_database_usage",
|
|
36543
|
+
title: "Database usage",
|
|
36544
|
+
kind: "read",
|
|
36545
|
+
description: "This period's compute (CU-hours), storage, history and network, plus the plan's remaining credits.",
|
|
36546
|
+
input: { databaseId },
|
|
36547
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/usage`)
|
|
36548
|
+
}),
|
|
36549
|
+
defineTool({
|
|
36550
|
+
name: "list_database_tables",
|
|
36551
|
+
title: "List database tables",
|
|
36552
|
+
kind: "read",
|
|
36553
|
+
description: "Tables and views in the public schema of an environment's branch.",
|
|
36554
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
36555
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables`, { query: { environmentId: a.environmentId } })
|
|
36556
|
+
}),
|
|
36557
|
+
defineTool({
|
|
36558
|
+
name: "describe_database_table",
|
|
36559
|
+
title: "Describe database table",
|
|
36560
|
+
kind: "read",
|
|
36561
|
+
description: "Columns of a table (name, type, nullable, default).",
|
|
36562
|
+
input: { databaseId, table: exports_external.string().min(1).max(63), environmentId: environmentId.optional() },
|
|
36563
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables/${encodeURIComponent(a.table)}/columns`, { query: { environmentId: a.environmentId } })
|
|
36564
|
+
}),
|
|
36565
|
+
defineTool({
|
|
36566
|
+
name: "query_database",
|
|
36567
|
+
title: "Query database (read-only)",
|
|
36568
|
+
kind: "read",
|
|
36569
|
+
description: "Run a read-only SQL statement (SELECT, EXPLAIN, SHOW…) in a read-only transaction; up to 200 rows. Use $1, $2… placeholders with params. Statements that change data are refused: use execute_database_sql.",
|
|
36570
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
36571
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId } })
|
|
36572
|
+
}),
|
|
36573
|
+
defineTool({
|
|
36574
|
+
name: "execute_database_sql",
|
|
36575
|
+
title: "Execute SQL (writes)",
|
|
36576
|
+
kind: "destructive",
|
|
36577
|
+
description: "Run a statement that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP…). Show the statement and the target environment to the user and get explicit confirmation first; prefer a staging branch for migrations.",
|
|
36578
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
36579
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId, allowWrite: true } })
|
|
36580
|
+
})
|
|
36581
|
+
];
|
|
36582
|
+
|
|
36413
36583
|
// src/tools/general.ts
|
|
36414
|
-
var API_PREFIXES = ["/containers/", "/vps", "/backup/", "/domains"];
|
|
36584
|
+
var API_PREFIXES = ["/containers/", "/databases", "/vps", "/backup/", "/domains"];
|
|
36415
36585
|
var generalTools = [
|
|
36416
36586
|
defineTool({
|
|
36417
36587
|
name: "helipod_request",
|
|
36418
36588
|
title: "Call any Helipod API endpoint",
|
|
36419
36589
|
kind: "destructive",
|
|
36420
|
-
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the
|
|
36590
|
+
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the Container, Database, VPS and Domain APIs (paths like /vps/{id}/backup), api=email for the Email API (paths like /broadcasts). Prefer the dedicated tools; ask the user before any call that costs money or deletes something.",
|
|
36421
36591
|
input: {
|
|
36422
36592
|
api: exports_external.enum(["core", "email"]),
|
|
36423
36593
|
method: exports_external.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
@@ -36439,16 +36609,17 @@ var generalTools = [
|
|
|
36439
36609
|
];
|
|
36440
36610
|
|
|
36441
36611
|
// src/server.ts
|
|
36442
|
-
var VERSION = "0.
|
|
36443
|
-
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
36612
|
+
var VERSION = "0.3.0";
|
|
36613
|
+
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed Postgres databases, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
36444
36614
|
- Start with list_projects to find projects and environment ids.
|
|
36445
|
-
- Anything that costs money (create/deploy/scale services, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
36446
|
-
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS): confirm with the user and name what will be lost.
|
|
36615
|
+
- Anything that costs money (create/deploy/scale services, upgrade a database plan, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
36616
|
+
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS, execute_database_sql): confirm with the user and name what will be lost.
|
|
36617
|
+
- Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
|
|
36447
36618
|
- Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
|
|
36448
36619
|
- Never print secrets (passwords, secret variable values) unless the user asks for them.
|
|
36449
36620
|
- Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys), 402 means plan limit or low balance.
|
|
36450
36621
|
Docs: https://docs.helipod.io`;
|
|
36451
|
-
var ALL_TOOLS = [...containerTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
36622
|
+
var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
36452
36623
|
function createHelipodServer(config2) {
|
|
36453
36624
|
const server = new McpServer({ name: "helipod", version: VERSION, title: "Helipod" }, { instructions: INSTRUCTIONS });
|
|
36454
36625
|
const api2 = new HelipodClient({ userAgent: `helipod-mcp/${VERSION}`, ...config2 });
|
package/dist/index.js
CHANGED
|
@@ -35555,14 +35555,184 @@ var emailTools = [
|
|
|
35555
35555
|
})
|
|
35556
35556
|
];
|
|
35557
35557
|
|
|
35558
|
+
// src/tools/databases.ts
|
|
35559
|
+
var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
|
|
35560
|
+
var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
|
|
35561
|
+
var dryRun2 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
|
|
35562
|
+
var databaseTools = [
|
|
35563
|
+
defineTool({
|
|
35564
|
+
name: "list_database_plans",
|
|
35565
|
+
title: "List database plans",
|
|
35566
|
+
kind: "read",
|
|
35567
|
+
description: "Managed Postgres plans: price (IDR), included compute hours and storage, maximum CU. Free is the default; paid plans are prepaid credits that never expire.",
|
|
35568
|
+
input: {},
|
|
35569
|
+
run: (_a3, api2) => api2.api("GET", "/databases/plans")
|
|
35570
|
+
}),
|
|
35571
|
+
defineTool({
|
|
35572
|
+
name: "list_databases",
|
|
35573
|
+
title: "List databases",
|
|
35574
|
+
kind: "read",
|
|
35575
|
+
description: "Managed Postgres databases in the projects this key can reach: status, plan and remaining credits, region, branches per environment.",
|
|
35576
|
+
input: {},
|
|
35577
|
+
run: (_a3, api2) => api2.api("GET", "/databases")
|
|
35578
|
+
}),
|
|
35579
|
+
defineTool({
|
|
35580
|
+
name: "get_database",
|
|
35581
|
+
title: "Get database",
|
|
35582
|
+
kind: "read",
|
|
35583
|
+
description: "One database with its plan, compute settings and branches.",
|
|
35584
|
+
input: { databaseId },
|
|
35585
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}`)
|
|
35586
|
+
}),
|
|
35587
|
+
defineTool({
|
|
35588
|
+
name: "create_database",
|
|
35589
|
+
title: "Create database",
|
|
35590
|
+
kind: "write",
|
|
35591
|
+
idempotent: true,
|
|
35592
|
+
description: "Create the managed Postgres database of a project (free plan; one database per project, one branch per environment). Returns the existing one if the project already has it.",
|
|
35593
|
+
input: { projectId: exports_external.string().uuid(), idempotencyKey: exports_external.string().max(128).optional() },
|
|
35594
|
+
run: (a, api2) => api2.api("POST", "/databases", { body: { projectId: a.projectId }, idempotencyKey: a.idempotencyKey })
|
|
35595
|
+
}),
|
|
35596
|
+
defineTool({
|
|
35597
|
+
name: "delete_database",
|
|
35598
|
+
title: "Delete database",
|
|
35599
|
+
kind: "destructive",
|
|
35600
|
+
description: "Delete a database with ALL its data and branches. Irreversible. Only after the user confirms; confirm must be the database name.",
|
|
35601
|
+
input: { databaseId, confirm: exports_external.string().describe("The database name, exactly") },
|
|
35602
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}`, { body: { confirm: a.confirm } })
|
|
35603
|
+
}),
|
|
35604
|
+
defineTool({
|
|
35605
|
+
name: "get_database_connection",
|
|
35606
|
+
title: "Get database connection",
|
|
35607
|
+
kind: "read",
|
|
35608
|
+
description: "Connection string (postgresql://…, pooled, SSL) and its parts for an environment's branch. Contains the password: store it as a secret variable (e.g. DATABASE_URL via set_variables secrets), never print it unless asked.",
|
|
35609
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
35610
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/connection`, { query: { environmentId: a.environmentId } })
|
|
35611
|
+
}),
|
|
35612
|
+
defineTool({
|
|
35613
|
+
name: "list_database_branches",
|
|
35614
|
+
title: "List database branches",
|
|
35615
|
+
kind: "read",
|
|
35616
|
+
description: "Branches of a database, one per environment (production is the primary).",
|
|
35617
|
+
input: { databaseId },
|
|
35618
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/branches`)
|
|
35619
|
+
}),
|
|
35620
|
+
defineTool({
|
|
35621
|
+
name: "create_database_branch",
|
|
35622
|
+
title: "Create database branch",
|
|
35623
|
+
kind: "write",
|
|
35624
|
+
idempotent: true,
|
|
35625
|
+
description: "Give an environment (e.g. staging) its own branch: a copy of the source environment's data at this moment (the primary when omitted).",
|
|
35626
|
+
input: { databaseId, environmentId: exports_external.string().uuid(), sourceEnvironmentId: exports_external.string().uuid().optional() },
|
|
35627
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/branches`, { body: { environmentId: a.environmentId, sourceEnvironmentId: a.sourceEnvironmentId } })
|
|
35628
|
+
}),
|
|
35629
|
+
defineTool({
|
|
35630
|
+
name: "delete_database_branch",
|
|
35631
|
+
title: "Delete database branch",
|
|
35632
|
+
kind: "destructive",
|
|
35633
|
+
description: "Delete an environment's branch and its data. The primary (production) branch cannot be deleted. Confirm with the user.",
|
|
35634
|
+
input: { databaseId, environmentId: exports_external.string().uuid() },
|
|
35635
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}/branches/${a.environmentId}`)
|
|
35636
|
+
}),
|
|
35637
|
+
defineTool({
|
|
35638
|
+
name: "get_database_settings",
|
|
35639
|
+
title: "Get database compute settings",
|
|
35640
|
+
kind: "read",
|
|
35641
|
+
description: "Compute settings (paid plans): minCu (floor), maxCu (autoscaling ceiling), suspendTimeoutSeconds (idle time before scaling to zero), the plan's maximum CU.",
|
|
35642
|
+
input: { databaseId },
|
|
35643
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/settings`)
|
|
35644
|
+
}),
|
|
35645
|
+
defineTool({
|
|
35646
|
+
name: "update_database_settings",
|
|
35647
|
+
title: "Update database compute settings",
|
|
35648
|
+
kind: "write",
|
|
35649
|
+
description: "Change compute for every branch (paid plans). A minCu above zero keeps compute running and uses credits while idle: run with dryRun first and show idleComputeHoursPerMonth.",
|
|
35650
|
+
input: {
|
|
35651
|
+
databaseId,
|
|
35652
|
+
minCu: exports_external.number().min(0.25).max(10).optional(),
|
|
35653
|
+
maxCu: exports_external.number().min(0.25).max(10).optional(),
|
|
35654
|
+
suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
|
|
35655
|
+
dryRun: dryRun2
|
|
35656
|
+
},
|
|
35657
|
+
run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
|
|
35658
|
+
body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
|
|
35659
|
+
query: { dryRun: a.dryRun ? "true" : undefined }
|
|
35660
|
+
})
|
|
35661
|
+
}),
|
|
35662
|
+
defineTool({
|
|
35663
|
+
name: "upgrade_database",
|
|
35664
|
+
title: "Upgrade database plan / top up",
|
|
35665
|
+
kind: "write",
|
|
35666
|
+
description: "Buy a paid plan or top up credits. COSTS MONEY from the wallet: call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
|
|
35667
|
+
input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun2, idempotencyKey: exports_external.string().max(128).optional() },
|
|
35668
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
35669
|
+
}),
|
|
35670
|
+
defineTool({
|
|
35671
|
+
name: "suspend_database",
|
|
35672
|
+
title: "Suspend database",
|
|
35673
|
+
kind: "destructive",
|
|
35674
|
+
description: "Stop the database's compute (connections fail until resumed). Confirm with the user.",
|
|
35675
|
+
input: { databaseId },
|
|
35676
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/suspend`)
|
|
35677
|
+
}),
|
|
35678
|
+
defineTool({
|
|
35679
|
+
name: "resume_database",
|
|
35680
|
+
title: "Resume database",
|
|
35681
|
+
kind: "write",
|
|
35682
|
+
description: "Start a suspended database again.",
|
|
35683
|
+
input: { databaseId },
|
|
35684
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/resume`)
|
|
35685
|
+
}),
|
|
35686
|
+
defineTool({
|
|
35687
|
+
name: "get_database_usage",
|
|
35688
|
+
title: "Database usage",
|
|
35689
|
+
kind: "read",
|
|
35690
|
+
description: "This period's compute (CU-hours), storage, history and network, plus the plan's remaining credits.",
|
|
35691
|
+
input: { databaseId },
|
|
35692
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/usage`)
|
|
35693
|
+
}),
|
|
35694
|
+
defineTool({
|
|
35695
|
+
name: "list_database_tables",
|
|
35696
|
+
title: "List database tables",
|
|
35697
|
+
kind: "read",
|
|
35698
|
+
description: "Tables and views in the public schema of an environment's branch.",
|
|
35699
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
35700
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables`, { query: { environmentId: a.environmentId } })
|
|
35701
|
+
}),
|
|
35702
|
+
defineTool({
|
|
35703
|
+
name: "describe_database_table",
|
|
35704
|
+
title: "Describe database table",
|
|
35705
|
+
kind: "read",
|
|
35706
|
+
description: "Columns of a table (name, type, nullable, default).",
|
|
35707
|
+
input: { databaseId, table: exports_external.string().min(1).max(63), environmentId: environmentId.optional() },
|
|
35708
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables/${encodeURIComponent(a.table)}/columns`, { query: { environmentId: a.environmentId } })
|
|
35709
|
+
}),
|
|
35710
|
+
defineTool({
|
|
35711
|
+
name: "query_database",
|
|
35712
|
+
title: "Query database (read-only)",
|
|
35713
|
+
kind: "read",
|
|
35714
|
+
description: "Run a read-only SQL statement (SELECT, EXPLAIN, SHOW…) in a read-only transaction; up to 200 rows. Use $1, $2… placeholders with params. Statements that change data are refused: use execute_database_sql.",
|
|
35715
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
35716
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId } })
|
|
35717
|
+
}),
|
|
35718
|
+
defineTool({
|
|
35719
|
+
name: "execute_database_sql",
|
|
35720
|
+
title: "Execute SQL (writes)",
|
|
35721
|
+
kind: "destructive",
|
|
35722
|
+
description: "Run a statement that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP…). Show the statement and the target environment to the user and get explicit confirmation first; prefer a staging branch for migrations.",
|
|
35723
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
35724
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId, allowWrite: true } })
|
|
35725
|
+
})
|
|
35726
|
+
];
|
|
35727
|
+
|
|
35558
35728
|
// src/tools/general.ts
|
|
35559
|
-
var API_PREFIXES = ["/containers/", "/vps", "/backup/", "/domains"];
|
|
35729
|
+
var API_PREFIXES = ["/containers/", "/databases", "/vps", "/backup/", "/domains"];
|
|
35560
35730
|
var generalTools = [
|
|
35561
35731
|
defineTool({
|
|
35562
35732
|
name: "helipod_request",
|
|
35563
35733
|
title: "Call any Helipod API endpoint",
|
|
35564
35734
|
kind: "destructive",
|
|
35565
|
-
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the
|
|
35735
|
+
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the Container, Database, VPS and Domain APIs (paths like /vps/{id}/backup), api=email for the Email API (paths like /broadcasts). Prefer the dedicated tools; ask the user before any call that costs money or deletes something.",
|
|
35566
35736
|
input: {
|
|
35567
35737
|
api: exports_external.enum(["core", "email"]),
|
|
35568
35738
|
method: exports_external.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
@@ -35584,16 +35754,17 @@ var generalTools = [
|
|
|
35584
35754
|
];
|
|
35585
35755
|
|
|
35586
35756
|
// src/server.ts
|
|
35587
|
-
var VERSION = "0.
|
|
35588
|
-
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
35757
|
+
var VERSION = "0.3.0";
|
|
35758
|
+
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed Postgres databases, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
35589
35759
|
- Start with list_projects to find projects and environment ids.
|
|
35590
|
-
- Anything that costs money (create/deploy/scale services, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
35591
|
-
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS): confirm with the user and name what will be lost.
|
|
35760
|
+
- Anything that costs money (create/deploy/scale services, upgrade a database plan, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
35761
|
+
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS, execute_database_sql): confirm with the user and name what will be lost.
|
|
35762
|
+
- Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
|
|
35592
35763
|
- Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
|
|
35593
35764
|
- Never print secrets (passwords, secret variable values) unless the user asks for them.
|
|
35594
35765
|
- Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys), 402 means plan limit or low balance.
|
|
35595
35766
|
Docs: https://docs.helipod.io`;
|
|
35596
|
-
var ALL_TOOLS = [...containerTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
35767
|
+
var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
35597
35768
|
function createHelipodServer(config2) {
|
|
35598
35769
|
const server = new McpServer({ name: "helipod", version: VERSION, title: "Helipod" }, { instructions: INSTRUCTIONS });
|
|
35599
35770
|
const api2 = new HelipodClient({ userAgent: `helipod-mcp/${VERSION}`, ...config2 });
|
package/dist/stdio.js
CHANGED
|
@@ -35662,14 +35662,184 @@ var emailTools = [
|
|
|
35662
35662
|
})
|
|
35663
35663
|
];
|
|
35664
35664
|
|
|
35665
|
+
// src/tools/databases.ts
|
|
35666
|
+
var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
|
|
35667
|
+
var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
|
|
35668
|
+
var dryRun2 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
|
|
35669
|
+
var databaseTools = [
|
|
35670
|
+
defineTool({
|
|
35671
|
+
name: "list_database_plans",
|
|
35672
|
+
title: "List database plans",
|
|
35673
|
+
kind: "read",
|
|
35674
|
+
description: "Managed Postgres plans: price (IDR), included compute hours and storage, maximum CU. Free is the default; paid plans are prepaid credits that never expire.",
|
|
35675
|
+
input: {},
|
|
35676
|
+
run: (_a3, api2) => api2.api("GET", "/databases/plans")
|
|
35677
|
+
}),
|
|
35678
|
+
defineTool({
|
|
35679
|
+
name: "list_databases",
|
|
35680
|
+
title: "List databases",
|
|
35681
|
+
kind: "read",
|
|
35682
|
+
description: "Managed Postgres databases in the projects this key can reach: status, plan and remaining credits, region, branches per environment.",
|
|
35683
|
+
input: {},
|
|
35684
|
+
run: (_a3, api2) => api2.api("GET", "/databases")
|
|
35685
|
+
}),
|
|
35686
|
+
defineTool({
|
|
35687
|
+
name: "get_database",
|
|
35688
|
+
title: "Get database",
|
|
35689
|
+
kind: "read",
|
|
35690
|
+
description: "One database with its plan, compute settings and branches.",
|
|
35691
|
+
input: { databaseId },
|
|
35692
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}`)
|
|
35693
|
+
}),
|
|
35694
|
+
defineTool({
|
|
35695
|
+
name: "create_database",
|
|
35696
|
+
title: "Create database",
|
|
35697
|
+
kind: "write",
|
|
35698
|
+
idempotent: true,
|
|
35699
|
+
description: "Create the managed Postgres database of a project (free plan; one database per project, one branch per environment). Returns the existing one if the project already has it.",
|
|
35700
|
+
input: { projectId: exports_external.string().uuid(), idempotencyKey: exports_external.string().max(128).optional() },
|
|
35701
|
+
run: (a, api2) => api2.api("POST", "/databases", { body: { projectId: a.projectId }, idempotencyKey: a.idempotencyKey })
|
|
35702
|
+
}),
|
|
35703
|
+
defineTool({
|
|
35704
|
+
name: "delete_database",
|
|
35705
|
+
title: "Delete database",
|
|
35706
|
+
kind: "destructive",
|
|
35707
|
+
description: "Delete a database with ALL its data and branches. Irreversible. Only after the user confirms; confirm must be the database name.",
|
|
35708
|
+
input: { databaseId, confirm: exports_external.string().describe("The database name, exactly") },
|
|
35709
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}`, { body: { confirm: a.confirm } })
|
|
35710
|
+
}),
|
|
35711
|
+
defineTool({
|
|
35712
|
+
name: "get_database_connection",
|
|
35713
|
+
title: "Get database connection",
|
|
35714
|
+
kind: "read",
|
|
35715
|
+
description: "Connection string (postgresql://…, pooled, SSL) and its parts for an environment's branch. Contains the password: store it as a secret variable (e.g. DATABASE_URL via set_variables secrets), never print it unless asked.",
|
|
35716
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
35717
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/connection`, { query: { environmentId: a.environmentId } })
|
|
35718
|
+
}),
|
|
35719
|
+
defineTool({
|
|
35720
|
+
name: "list_database_branches",
|
|
35721
|
+
title: "List database branches",
|
|
35722
|
+
kind: "read",
|
|
35723
|
+
description: "Branches of a database, one per environment (production is the primary).",
|
|
35724
|
+
input: { databaseId },
|
|
35725
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/branches`)
|
|
35726
|
+
}),
|
|
35727
|
+
defineTool({
|
|
35728
|
+
name: "create_database_branch",
|
|
35729
|
+
title: "Create database branch",
|
|
35730
|
+
kind: "write",
|
|
35731
|
+
idempotent: true,
|
|
35732
|
+
description: "Give an environment (e.g. staging) its own branch: a copy of the source environment's data at this moment (the primary when omitted).",
|
|
35733
|
+
input: { databaseId, environmentId: exports_external.string().uuid(), sourceEnvironmentId: exports_external.string().uuid().optional() },
|
|
35734
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/branches`, { body: { environmentId: a.environmentId, sourceEnvironmentId: a.sourceEnvironmentId } })
|
|
35735
|
+
}),
|
|
35736
|
+
defineTool({
|
|
35737
|
+
name: "delete_database_branch",
|
|
35738
|
+
title: "Delete database branch",
|
|
35739
|
+
kind: "destructive",
|
|
35740
|
+
description: "Delete an environment's branch and its data. The primary (production) branch cannot be deleted. Confirm with the user.",
|
|
35741
|
+
input: { databaseId, environmentId: exports_external.string().uuid() },
|
|
35742
|
+
run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}/branches/${a.environmentId}`)
|
|
35743
|
+
}),
|
|
35744
|
+
defineTool({
|
|
35745
|
+
name: "get_database_settings",
|
|
35746
|
+
title: "Get database compute settings",
|
|
35747
|
+
kind: "read",
|
|
35748
|
+
description: "Compute settings (paid plans): minCu (floor), maxCu (autoscaling ceiling), suspendTimeoutSeconds (idle time before scaling to zero), the plan's maximum CU.",
|
|
35749
|
+
input: { databaseId },
|
|
35750
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/settings`)
|
|
35751
|
+
}),
|
|
35752
|
+
defineTool({
|
|
35753
|
+
name: "update_database_settings",
|
|
35754
|
+
title: "Update database compute settings",
|
|
35755
|
+
kind: "write",
|
|
35756
|
+
description: "Change compute for every branch (paid plans). A minCu above zero keeps compute running and uses credits while idle: run with dryRun first and show idleComputeHoursPerMonth.",
|
|
35757
|
+
input: {
|
|
35758
|
+
databaseId,
|
|
35759
|
+
minCu: exports_external.number().min(0.25).max(10).optional(),
|
|
35760
|
+
maxCu: exports_external.number().min(0.25).max(10).optional(),
|
|
35761
|
+
suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
|
|
35762
|
+
dryRun: dryRun2
|
|
35763
|
+
},
|
|
35764
|
+
run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
|
|
35765
|
+
body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
|
|
35766
|
+
query: { dryRun: a.dryRun ? "true" : undefined }
|
|
35767
|
+
})
|
|
35768
|
+
}),
|
|
35769
|
+
defineTool({
|
|
35770
|
+
name: "upgrade_database",
|
|
35771
|
+
title: "Upgrade database plan / top up",
|
|
35772
|
+
kind: "write",
|
|
35773
|
+
description: "Buy a paid plan or top up credits. COSTS MONEY from the wallet: call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
|
|
35774
|
+
input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun2, idempotencyKey: exports_external.string().max(128).optional() },
|
|
35775
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
35776
|
+
}),
|
|
35777
|
+
defineTool({
|
|
35778
|
+
name: "suspend_database",
|
|
35779
|
+
title: "Suspend database",
|
|
35780
|
+
kind: "destructive",
|
|
35781
|
+
description: "Stop the database's compute (connections fail until resumed). Confirm with the user.",
|
|
35782
|
+
input: { databaseId },
|
|
35783
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/suspend`)
|
|
35784
|
+
}),
|
|
35785
|
+
defineTool({
|
|
35786
|
+
name: "resume_database",
|
|
35787
|
+
title: "Resume database",
|
|
35788
|
+
kind: "write",
|
|
35789
|
+
description: "Start a suspended database again.",
|
|
35790
|
+
input: { databaseId },
|
|
35791
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/resume`)
|
|
35792
|
+
}),
|
|
35793
|
+
defineTool({
|
|
35794
|
+
name: "get_database_usage",
|
|
35795
|
+
title: "Database usage",
|
|
35796
|
+
kind: "read",
|
|
35797
|
+
description: "This period's compute (CU-hours), storage, history and network, plus the plan's remaining credits.",
|
|
35798
|
+
input: { databaseId },
|
|
35799
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/usage`)
|
|
35800
|
+
}),
|
|
35801
|
+
defineTool({
|
|
35802
|
+
name: "list_database_tables",
|
|
35803
|
+
title: "List database tables",
|
|
35804
|
+
kind: "read",
|
|
35805
|
+
description: "Tables and views in the public schema of an environment's branch.",
|
|
35806
|
+
input: { databaseId, environmentId: environmentId.optional() },
|
|
35807
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables`, { query: { environmentId: a.environmentId } })
|
|
35808
|
+
}),
|
|
35809
|
+
defineTool({
|
|
35810
|
+
name: "describe_database_table",
|
|
35811
|
+
title: "Describe database table",
|
|
35812
|
+
kind: "read",
|
|
35813
|
+
description: "Columns of a table (name, type, nullable, default).",
|
|
35814
|
+
input: { databaseId, table: exports_external.string().min(1).max(63), environmentId: environmentId.optional() },
|
|
35815
|
+
run: (a, api2) => api2.api("GET", `/databases/${a.databaseId}/tables/${encodeURIComponent(a.table)}/columns`, { query: { environmentId: a.environmentId } })
|
|
35816
|
+
}),
|
|
35817
|
+
defineTool({
|
|
35818
|
+
name: "query_database",
|
|
35819
|
+
title: "Query database (read-only)",
|
|
35820
|
+
kind: "read",
|
|
35821
|
+
description: "Run a read-only SQL statement (SELECT, EXPLAIN, SHOW…) in a read-only transaction; up to 200 rows. Use $1, $2… placeholders with params. Statements that change data are refused: use execute_database_sql.",
|
|
35822
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
35823
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId } })
|
|
35824
|
+
}),
|
|
35825
|
+
defineTool({
|
|
35826
|
+
name: "execute_database_sql",
|
|
35827
|
+
title: "Execute SQL (writes)",
|
|
35828
|
+
kind: "destructive",
|
|
35829
|
+
description: "Run a statement that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP…). Show the statement and the target environment to the user and get explicit confirmation first; prefer a staging branch for migrations.",
|
|
35830
|
+
input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
|
|
35831
|
+
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId, allowWrite: true } })
|
|
35832
|
+
})
|
|
35833
|
+
];
|
|
35834
|
+
|
|
35665
35835
|
// src/tools/general.ts
|
|
35666
|
-
var API_PREFIXES = ["/containers/", "/vps", "/backup/", "/domains"];
|
|
35836
|
+
var API_PREFIXES = ["/containers/", "/databases", "/vps", "/backup/", "/domains"];
|
|
35667
35837
|
var generalTools = [
|
|
35668
35838
|
defineTool({
|
|
35669
35839
|
name: "helipod_request",
|
|
35670
35840
|
title: "Call any Helipod API endpoint",
|
|
35671
35841
|
kind: "destructive",
|
|
35672
|
-
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the
|
|
35842
|
+
description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the Container, Database, VPS and Domain APIs (paths like /vps/{id}/backup), api=email for the Email API (paths like /broadcasts). Prefer the dedicated tools; ask the user before any call that costs money or deletes something.",
|
|
35673
35843
|
input: {
|
|
35674
35844
|
api: exports_external.enum(["core", "email"]),
|
|
35675
35845
|
method: exports_external.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
@@ -35691,16 +35861,17 @@ var generalTools = [
|
|
|
35691
35861
|
];
|
|
35692
35862
|
|
|
35693
35863
|
// src/server.ts
|
|
35694
|
-
var VERSION = "0.
|
|
35695
|
-
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
35864
|
+
var VERSION = "0.3.0";
|
|
35865
|
+
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed Postgres databases, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
35696
35866
|
- Start with list_projects to find projects and environment ids.
|
|
35697
|
-
- Anything that costs money (create/deploy/scale services, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
35698
|
-
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS): confirm with the user and name what will be lost.
|
|
35867
|
+
- Anything that costs money (create/deploy/scale services, upgrade a database plan, create/renew/upgrade VPS, register/renew/transfer domains): run the dryRun or price lookup first, tell the user the cost in IDR and wait for an explicit yes.
|
|
35868
|
+
- Destructive tools (delete, terminate, reinstall, rollback, replace DNS, execute_database_sql): confirm with the user and name what will be lost.
|
|
35869
|
+
- Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
|
|
35699
35870
|
- Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
|
|
35700
35871
|
- Never print secrets (passwords, secret variable values) unless the user asks for them.
|
|
35701
35872
|
- Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys), 402 means plan limit or low balance.
|
|
35702
35873
|
Docs: https://docs.helipod.io`;
|
|
35703
|
-
var ALL_TOOLS = [...containerTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
35874
|
+
var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...generalTools];
|
|
35704
35875
|
function createHelipodServer(config2) {
|
|
35705
35876
|
const server = new McpServer({ name: "helipod", version: VERSION, title: "Helipod" }, { instructions: INSTRUCTIONS });
|
|
35706
35877
|
const api2 = new HelipodClient({ userAgent: `helipod-mcp/${VERSION}`, ...config2 });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helipod-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Helipod: containers, VPS, domains and email from Claude Code, Codex, Cursor and other AI tools.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for Helipod: containers, databases, VPS, domains and email from Claude Code, Codex, Cursor and other AI tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"helipod-mcp": "dist/stdio.js"
|
package/skills/helipod/SKILL.md
CHANGED
|
@@ -5,13 +5,13 @@ description: Use when the user mentions Helipod (helipod.io) or wants to work wi
|
|
|
5
5
|
|
|
6
6
|
# Helipod
|
|
7
7
|
|
|
8
|
-
Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, images or Dockerfiles), VPS, domain registration and DNS, email sending, and an AI gateway. Everything is billed from a prepaid wallet in Rupiah (IDR).
|
|
8
|
+
Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, images or Dockerfiles), managed Postgres databases, VPS, domain registration and DNS, email sending, and an AI gateway. Everything is billed from a prepaid wallet in Rupiah (IDR).
|
|
9
9
|
|
|
10
10
|
## Access
|
|
11
11
|
|
|
12
12
|
- **MCP tools** (preferred): if tools like `list_projects`, `create_service`, `list_vps_plans` are available, use them.
|
|
13
13
|
- **Without MCP**: call the HTTP API with `Authorization: Bearer $HELIPOD_API_KEY`.
|
|
14
|
-
-
|
|
14
|
+
- Container, Database, VPS, Domain APIs: `https://api.helipod.io/api` (`/containers/...`, `/databases/...`, `/vps/...`, `/domains/...`)
|
|
15
15
|
- Email API: `https://mail.helipod.io/api/email` with `$HELIPOD_EMAIL_API_KEY` (a separate email key)
|
|
16
16
|
- AI gateway: `https://ai.helipod.io/v1` with a `hel_ai_...` key (OpenAI and Anthropic compatible)
|
|
17
17
|
- Keys are created in the dashboard: Project → Settings → API keys. A key is limited to the products and project chosen there; `403 SCOPE_NOT_ALLOWED` means the key lacks that product, `404` on an id means it is outside the key's reach.
|
|
@@ -29,7 +29,8 @@ Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, imag
|
|
|
29
29
|
|
|
30
30
|
| Task | Skill |
|
|
31
31
|
|---|---|
|
|
32
|
-
| Deploy an app
|
|
32
|
+
| Deploy an app, env vars, domains, scaling, logs | `helipod-deploy` |
|
|
33
|
+
| Managed Postgres: create, connect, branches, SQL, plans | `helipod-databases` |
|
|
33
34
|
| Servers with root access | `helipod-vps` |
|
|
34
35
|
| Buy a domain, DNS, point a domain at an app | `helipod-domains` |
|
|
35
36
|
| Send email from an app | `helipod-email` |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: helipod-databases
|
|
3
|
+
description: Create and use a managed Postgres database on Helipod - activate it for a project, connect an app through a secret DATABASE_URL, give each environment its own branch, inspect tables, run migrations safely, scale and upgrade plans. Use when the user needs a Postgres database for a Helipod project or wants to query or change one.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Helipod managed Postgres
|
|
7
|
+
|
|
8
|
+
Follow the `helipod` skill rules (price first, confirm destructive actions, never print secrets). The key needs the **Database API** product.
|
|
9
|
+
|
|
10
|
+
## Create and connect
|
|
11
|
+
|
|
12
|
+
1. `list_projects` → the project. One database per project; each environment gets its own branch (production is the primary).
|
|
13
|
+
2. `create_database` with the projectId (free plan; returns the existing database if there is one).
|
|
14
|
+
3. `get_database_connection` (add `environmentId` for a non-production environment) → `uri`. Never paste it into code or chat.
|
|
15
|
+
4. Give it to the app as a secret variable: `set_variables` on the service with `secrets: { "DATABASE_URL": "<uri>" }`, then `deploy_service`. The URI is pooled and requires SSL (already in the string).
|
|
16
|
+
|
|
17
|
+
## Environments
|
|
18
|
+
|
|
19
|
+
- `create_database_branch` gives staging (or any environment) its own copy of production's data at that moment. Then `get_database_connection` with that environmentId.
|
|
20
|
+
- Run migrations on staging first, check, then production.
|
|
21
|
+
- `delete_database_branch` removes a non-primary branch and its data (confirm first).
|
|
22
|
+
|
|
23
|
+
## Inspect and change data
|
|
24
|
+
|
|
25
|
+
- `list_database_tables`, `describe_database_table`, `query_database` (read-only, up to 200 rows, `$1` placeholders with `params`).
|
|
26
|
+
- `execute_database_sql` for INSERT/UPDATE/DELETE/CREATE/ALTER/DROP: show the exact statement and the environment, wait for a clear yes. Prefer the app's own migration tool for schema changes when the project has one.
|
|
27
|
+
|
|
28
|
+
## Plans, credits, scaling
|
|
29
|
+
|
|
30
|
+
- Free plan: fixed small limits. Paid plans (`list_database_plans`): prepaid compute hours and storage that never expire; when credits run out the database is suspended until topped up.
|
|
31
|
+
- `upgrade_database` with `dryRun: true` first → show price and wallet balance after → only then without dryRun.
|
|
32
|
+
- `get_database_usage` shows what is used and what is left.
|
|
33
|
+
- Paid plans: `update_database_settings` (dryRun first). A `minCu` above 0 keeps compute warm but uses credits even when idle; `maxCu` caps autoscaling; `suspendTimeoutSeconds` is the idle time before scaling to zero.
|
|
34
|
+
|
|
35
|
+
## Errors
|
|
36
|
+
|
|
37
|
+
`PLAN_REQUIRED` (settings need a paid plan), `INSUFFICIENT_BALANCE` (top up the wallet), `ENVIRONMENT_BRANCH_MISSING` (create the branch first), `WRITE_NOT_ALLOWED` (use execute_database_sql), `SQL_ERROR` (the database's own message).
|