helipod-mcp 0.3.1 → 0.4.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 +5 -5
- package/dist/http.js +28 -21
- package/dist/index.js +28 -21
- package/dist/stdio.js +29 -22
- package/package.json +1 -1
- package/skills/helipod/SKILL.md +1 -1
- package/skills/helipod-email/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -27,11 +27,11 @@ Prefer a key, a local server, or a tool not listed? Read on.
|
|
|
27
27
|
|
|
28
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_`.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
For the email tools, enable **Email API** on the key (or sign in with it ticked). An email key from the project's **Email** section also still works, as `HELIPOD_EMAIL_API_KEY`.
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
export HELIPOD_API_KEY=hel_...
|
|
34
|
-
export HELIPOD_EMAIL_API_KEY=... # optional
|
|
34
|
+
export HELIPOD_EMAIL_API_KEY=... # optional: only if you use a separate email key
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Start with a key limited to one project while you try things out.
|
|
@@ -150,11 +150,11 @@ Gemini CLI only starts MCP servers in folders you have trusted.
|
|
|
150
150
|
|
|
151
151
|
### Other clients
|
|
152
152
|
|
|
153
|
-
Run `npx -y helipod-mcp` as a stdio MCP server with `HELIPOD_API_KEY` (
|
|
153
|
+
Run `npx -y helipod-mcp` as a stdio MCP server with `HELIPOD_API_KEY` in its environment (enable the Email API on it for the email tools, or add `HELIPOD_EMAIL_API_KEY`).
|
|
154
154
|
|
|
155
155
|
## Hosted server (nothing to install)
|
|
156
156
|
|
|
157
|
-
Clients that connect by URL can use `https://api.helipod.io/mcp` (Streamable HTTP) with `Authorization: Bearer hel_...` (
|
|
157
|
+
Clients that connect by URL can use `https://api.helipod.io/mcp` (Streamable HTTP) with `Authorization: Bearer hel_...` (the same key or sign-in serves the email tools when it has the Email API enabled; `X-Helipod-Email-Key` still works). Same tools; the key is used per request and never stored.
|
|
158
158
|
|
|
159
159
|
```sh
|
|
160
160
|
claude mcp add --transport http helipod https://api.helipod.io/mcp --header "Authorization: Bearer $HELIPOD_API_KEY"
|
|
@@ -211,7 +211,7 @@ Each tool is marked read-only, write or destructive, so clients can ask before r
|
|
|
211
211
|
| Variable | Default |
|
|
212
212
|
|---|---|
|
|
213
213
|
| `HELIPOD_API_KEY` | (required for containers, VPS, domains) |
|
|
214
|
-
| `HELIPOD_EMAIL_API_KEY` | (
|
|
214
|
+
| `HELIPOD_EMAIL_API_KEY` | (optional; the email tools use `HELIPOD_API_KEY` when it has the Email API enabled) |
|
|
215
215
|
| `HELIPOD_API_URL` | `https://api.helipod.io/api` |
|
|
216
216
|
| `HELIPOD_EMAIL_API_URL` | `https://mail.helipod.io/api/email` |
|
|
217
217
|
|
package/dist/http.js
CHANGED
|
@@ -35614,10 +35614,12 @@ class HelipodClient {
|
|
|
35614
35614
|
return this.request(this.apiUrl, opts.auth === false ? undefined : this.cfg.apiKey, method, path, opts);
|
|
35615
35615
|
}
|
|
35616
35616
|
email(method, path, opts = {}) {
|
|
35617
|
-
|
|
35618
|
-
|
|
35617
|
+
const key = this.cfg.emailApiKey || this.cfg.apiKey;
|
|
35618
|
+
if (!key) {
|
|
35619
|
+
throw new HelipodError(401, "NO_API_KEY", "No key set. Use HELIPOD_API_KEY with the Email API enabled (or HELIPOD_EMAIL_API_KEY). Create keys in the Helipod dashboard: Project → Settings → API keys.");
|
|
35619
35620
|
}
|
|
35620
|
-
|
|
35621
|
+
const headers = !this.cfg.emailApiKey && opts.project ? { "x-helipod-project": opts.project } : undefined;
|
|
35622
|
+
return this.request(this.emailApiUrl, key, method, path, { ...opts, headers });
|
|
35621
35623
|
}
|
|
35622
35624
|
async request(base, key, method, path, opts) {
|
|
35623
35625
|
const raw = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -35631,6 +35633,9 @@ class HelipodClient {
|
|
|
35631
35633
|
const headers = { accept: "application/json", "user-agent": this.cfg.userAgent ?? "helipod-mcp" };
|
|
35632
35634
|
if (key)
|
|
35633
35635
|
headers.authorization = `Bearer ${key}`;
|
|
35636
|
+
for (const [k, v] of Object.entries(opts.headers ?? {}))
|
|
35637
|
+
if (/^[0-9a-f-]{36}$/i.test(v))
|
|
35638
|
+
headers[k] = v;
|
|
35634
35639
|
if (opts.body !== undefined)
|
|
35635
35640
|
headers["content-type"] = "application/json";
|
|
35636
35641
|
if (opts.idempotencyKey)
|
|
@@ -36326,14 +36331,15 @@ var domainTools = [
|
|
|
36326
36331
|
];
|
|
36327
36332
|
|
|
36328
36333
|
// src/tools/email.ts
|
|
36334
|
+
var projectId = exports_external.string().uuid().optional().describe("Project whose email to use. Only needed with a whole-account key that reaches several projects with email.");
|
|
36329
36335
|
var emailTools = [
|
|
36330
36336
|
defineTool({
|
|
36331
36337
|
name: "email_status",
|
|
36332
36338
|
title: "Email status",
|
|
36333
36339
|
kind: "read",
|
|
36334
36340
|
description: "Email service of the key's project: plan, usage today and this month, limits, counts.",
|
|
36335
|
-
input: {},
|
|
36336
|
-
run: (
|
|
36341
|
+
input: { projectId },
|
|
36342
|
+
run: (a, api2) => api2.email("GET", "/status", { project: a.projectId })
|
|
36337
36343
|
}),
|
|
36338
36344
|
defineTool({
|
|
36339
36345
|
name: "send_email",
|
|
@@ -36341,6 +36347,7 @@ var emailTools = [
|
|
|
36341
36347
|
kind: "write",
|
|
36342
36348
|
description: "Send one email from a verified domain of the project (counts against the plan's quota). Use templateId + templateData, or subject + html/text. Pass an idempotencyKey when retrying.",
|
|
36343
36349
|
input: {
|
|
36350
|
+
projectId,
|
|
36344
36351
|
from: exports_external.string().describe('"hello@example.com" or "Acme <hello@example.com>"'),
|
|
36345
36352
|
to: exports_external.array(exports_external.string()).min(1).max(50),
|
|
36346
36353
|
cc: exports_external.array(exports_external.string()).max(50).optional(),
|
|
@@ -36354,63 +36361,63 @@ var emailTools = [
|
|
|
36354
36361
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
36355
36362
|
idempotencyKey: exports_external.string().max(128).optional()
|
|
36356
36363
|
},
|
|
36357
|
-
run: (a, api2) => api2.email("POST", "/send", { body: a })
|
|
36364
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/send", { body: a, project })
|
|
36358
36365
|
}),
|
|
36359
36366
|
defineTool({
|
|
36360
36367
|
name: "list_emails",
|
|
36361
36368
|
title: "List sent emails",
|
|
36362
36369
|
kind: "read",
|
|
36363
36370
|
description: "Email log, newest first, with delivery status.",
|
|
36364
|
-
input: { status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
36365
|
-
run: (a, api2) => api2.email("GET", "/emails", { query: a })
|
|
36371
|
+
input: { projectId, status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
36372
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("GET", "/emails", { query: a, project })
|
|
36366
36373
|
}),
|
|
36367
36374
|
defineTool({
|
|
36368
36375
|
name: "get_email",
|
|
36369
36376
|
title: "Get email",
|
|
36370
36377
|
kind: "read",
|
|
36371
36378
|
description: "One sent email with its status and events.",
|
|
36372
|
-
input: { emailId: exports_external.string() },
|
|
36373
|
-
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}
|
|
36379
|
+
input: { projectId, emailId: exports_external.string() },
|
|
36380
|
+
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}`, { project: a.projectId })
|
|
36374
36381
|
}),
|
|
36375
36382
|
defineTool({
|
|
36376
36383
|
name: "list_email_domains",
|
|
36377
36384
|
title: "List sending domains",
|
|
36378
36385
|
kind: "read",
|
|
36379
36386
|
description: "Sending domains with verification status and the DNS records to add.",
|
|
36380
|
-
input: {},
|
|
36381
|
-
run: (
|
|
36387
|
+
input: { projectId },
|
|
36388
|
+
run: (a, api2) => api2.email("GET", "/domains", { project: a.projectId })
|
|
36382
36389
|
}),
|
|
36383
36390
|
defineTool({
|
|
36384
36391
|
name: "add_email_domain",
|
|
36385
36392
|
title: "Add sending domain",
|
|
36386
36393
|
kind: "write",
|
|
36387
36394
|
description: "Add a sending domain; the response lists the DNS records the user must create, then call verify_email_domain.",
|
|
36388
|
-
input: { domain: exports_external.string() },
|
|
36389
|
-
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain } })
|
|
36395
|
+
input: { projectId, domain: exports_external.string() },
|
|
36396
|
+
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain }, project: a.projectId })
|
|
36390
36397
|
}),
|
|
36391
36398
|
defineTool({
|
|
36392
36399
|
name: "verify_email_domain",
|
|
36393
36400
|
title: "Verify sending domain",
|
|
36394
36401
|
kind: "write",
|
|
36395
36402
|
description: "Check a sending domain's DNS records again.",
|
|
36396
|
-
input: { domainId: exports_external.string() },
|
|
36397
|
-
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify
|
|
36403
|
+
input: { projectId, domainId: exports_external.string() },
|
|
36404
|
+
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify`, { project: a.projectId })
|
|
36398
36405
|
}),
|
|
36399
36406
|
defineTool({
|
|
36400
36407
|
name: "list_email_templates",
|
|
36401
36408
|
title: "List email templates",
|
|
36402
36409
|
kind: "read",
|
|
36403
36410
|
description: "Email templates of the project.",
|
|
36404
|
-
input: {},
|
|
36405
|
-
run: (
|
|
36411
|
+
input: { projectId },
|
|
36412
|
+
run: (a, api2) => api2.email("GET", "/templates", { project: a.projectId })
|
|
36406
36413
|
}),
|
|
36407
36414
|
defineTool({
|
|
36408
36415
|
name: "add_contact",
|
|
36409
36416
|
title: "Add contact",
|
|
36410
36417
|
kind: "write",
|
|
36411
36418
|
description: "Add or update an audience contact.",
|
|
36412
|
-
input: { email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
36413
|
-
run: (a, api2) => api2.email("POST", "/contacts", { body: a })
|
|
36419
|
+
input: { projectId, email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
36420
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/contacts", { body: a, project })
|
|
36414
36421
|
})
|
|
36415
36422
|
];
|
|
36416
36423
|
|
|
@@ -36613,7 +36620,7 @@ var generalTools = [
|
|
|
36613
36620
|
];
|
|
36614
36621
|
|
|
36615
36622
|
// src/server.ts
|
|
36616
|
-
var VERSION = "0.
|
|
36623
|
+
var VERSION = "0.4.0";
|
|
36617
36624
|
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.
|
|
36618
36625
|
- Start with list_projects to find projects and environment ids.
|
|
36619
36626
|
- 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.
|
package/dist/index.js
CHANGED
|
@@ -34759,10 +34759,12 @@ class HelipodClient {
|
|
|
34759
34759
|
return this.request(this.apiUrl, opts.auth === false ? undefined : this.cfg.apiKey, method, path, opts);
|
|
34760
34760
|
}
|
|
34761
34761
|
email(method, path, opts = {}) {
|
|
34762
|
-
|
|
34763
|
-
|
|
34762
|
+
const key = this.cfg.emailApiKey || this.cfg.apiKey;
|
|
34763
|
+
if (!key) {
|
|
34764
|
+
throw new HelipodError(401, "NO_API_KEY", "No key set. Use HELIPOD_API_KEY with the Email API enabled (or HELIPOD_EMAIL_API_KEY). Create keys in the Helipod dashboard: Project → Settings → API keys.");
|
|
34764
34765
|
}
|
|
34765
|
-
|
|
34766
|
+
const headers = !this.cfg.emailApiKey && opts.project ? { "x-helipod-project": opts.project } : undefined;
|
|
34767
|
+
return this.request(this.emailApiUrl, key, method, path, { ...opts, headers });
|
|
34766
34768
|
}
|
|
34767
34769
|
async request(base, key, method, path, opts) {
|
|
34768
34770
|
const raw = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -34776,6 +34778,9 @@ class HelipodClient {
|
|
|
34776
34778
|
const headers = { accept: "application/json", "user-agent": this.cfg.userAgent ?? "helipod-mcp" };
|
|
34777
34779
|
if (key)
|
|
34778
34780
|
headers.authorization = `Bearer ${key}`;
|
|
34781
|
+
for (const [k, v] of Object.entries(opts.headers ?? {}))
|
|
34782
|
+
if (/^[0-9a-f-]{36}$/i.test(v))
|
|
34783
|
+
headers[k] = v;
|
|
34779
34784
|
if (opts.body !== undefined)
|
|
34780
34785
|
headers["content-type"] = "application/json";
|
|
34781
34786
|
if (opts.idempotencyKey)
|
|
@@ -35471,14 +35476,15 @@ var domainTools = [
|
|
|
35471
35476
|
];
|
|
35472
35477
|
|
|
35473
35478
|
// src/tools/email.ts
|
|
35479
|
+
var projectId = exports_external.string().uuid().optional().describe("Project whose email to use. Only needed with a whole-account key that reaches several projects with email.");
|
|
35474
35480
|
var emailTools = [
|
|
35475
35481
|
defineTool({
|
|
35476
35482
|
name: "email_status",
|
|
35477
35483
|
title: "Email status",
|
|
35478
35484
|
kind: "read",
|
|
35479
35485
|
description: "Email service of the key's project: plan, usage today and this month, limits, counts.",
|
|
35480
|
-
input: {},
|
|
35481
|
-
run: (
|
|
35486
|
+
input: { projectId },
|
|
35487
|
+
run: (a, api2) => api2.email("GET", "/status", { project: a.projectId })
|
|
35482
35488
|
}),
|
|
35483
35489
|
defineTool({
|
|
35484
35490
|
name: "send_email",
|
|
@@ -35486,6 +35492,7 @@ var emailTools = [
|
|
|
35486
35492
|
kind: "write",
|
|
35487
35493
|
description: "Send one email from a verified domain of the project (counts against the plan's quota). Use templateId + templateData, or subject + html/text. Pass an idempotencyKey when retrying.",
|
|
35488
35494
|
input: {
|
|
35495
|
+
projectId,
|
|
35489
35496
|
from: exports_external.string().describe('"hello@example.com" or "Acme <hello@example.com>"'),
|
|
35490
35497
|
to: exports_external.array(exports_external.string()).min(1).max(50),
|
|
35491
35498
|
cc: exports_external.array(exports_external.string()).max(50).optional(),
|
|
@@ -35499,63 +35506,63 @@ var emailTools = [
|
|
|
35499
35506
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
35500
35507
|
idempotencyKey: exports_external.string().max(128).optional()
|
|
35501
35508
|
},
|
|
35502
|
-
run: (a, api2) => api2.email("POST", "/send", { body: a })
|
|
35509
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/send", { body: a, project })
|
|
35503
35510
|
}),
|
|
35504
35511
|
defineTool({
|
|
35505
35512
|
name: "list_emails",
|
|
35506
35513
|
title: "List sent emails",
|
|
35507
35514
|
kind: "read",
|
|
35508
35515
|
description: "Email log, newest first, with delivery status.",
|
|
35509
|
-
input: { status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
35510
|
-
run: (a, api2) => api2.email("GET", "/emails", { query: a })
|
|
35516
|
+
input: { projectId, status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
35517
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("GET", "/emails", { query: a, project })
|
|
35511
35518
|
}),
|
|
35512
35519
|
defineTool({
|
|
35513
35520
|
name: "get_email",
|
|
35514
35521
|
title: "Get email",
|
|
35515
35522
|
kind: "read",
|
|
35516
35523
|
description: "One sent email with its status and events.",
|
|
35517
|
-
input: { emailId: exports_external.string() },
|
|
35518
|
-
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}
|
|
35524
|
+
input: { projectId, emailId: exports_external.string() },
|
|
35525
|
+
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}`, { project: a.projectId })
|
|
35519
35526
|
}),
|
|
35520
35527
|
defineTool({
|
|
35521
35528
|
name: "list_email_domains",
|
|
35522
35529
|
title: "List sending domains",
|
|
35523
35530
|
kind: "read",
|
|
35524
35531
|
description: "Sending domains with verification status and the DNS records to add.",
|
|
35525
|
-
input: {},
|
|
35526
|
-
run: (
|
|
35532
|
+
input: { projectId },
|
|
35533
|
+
run: (a, api2) => api2.email("GET", "/domains", { project: a.projectId })
|
|
35527
35534
|
}),
|
|
35528
35535
|
defineTool({
|
|
35529
35536
|
name: "add_email_domain",
|
|
35530
35537
|
title: "Add sending domain",
|
|
35531
35538
|
kind: "write",
|
|
35532
35539
|
description: "Add a sending domain; the response lists the DNS records the user must create, then call verify_email_domain.",
|
|
35533
|
-
input: { domain: exports_external.string() },
|
|
35534
|
-
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain } })
|
|
35540
|
+
input: { projectId, domain: exports_external.string() },
|
|
35541
|
+
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain }, project: a.projectId })
|
|
35535
35542
|
}),
|
|
35536
35543
|
defineTool({
|
|
35537
35544
|
name: "verify_email_domain",
|
|
35538
35545
|
title: "Verify sending domain",
|
|
35539
35546
|
kind: "write",
|
|
35540
35547
|
description: "Check a sending domain's DNS records again.",
|
|
35541
|
-
input: { domainId: exports_external.string() },
|
|
35542
|
-
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify
|
|
35548
|
+
input: { projectId, domainId: exports_external.string() },
|
|
35549
|
+
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify`, { project: a.projectId })
|
|
35543
35550
|
}),
|
|
35544
35551
|
defineTool({
|
|
35545
35552
|
name: "list_email_templates",
|
|
35546
35553
|
title: "List email templates",
|
|
35547
35554
|
kind: "read",
|
|
35548
35555
|
description: "Email templates of the project.",
|
|
35549
|
-
input: {},
|
|
35550
|
-
run: (
|
|
35556
|
+
input: { projectId },
|
|
35557
|
+
run: (a, api2) => api2.email("GET", "/templates", { project: a.projectId })
|
|
35551
35558
|
}),
|
|
35552
35559
|
defineTool({
|
|
35553
35560
|
name: "add_contact",
|
|
35554
35561
|
title: "Add contact",
|
|
35555
35562
|
kind: "write",
|
|
35556
35563
|
description: "Add or update an audience contact.",
|
|
35557
|
-
input: { email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
35558
|
-
run: (a, api2) => api2.email("POST", "/contacts", { body: a })
|
|
35564
|
+
input: { projectId, email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
35565
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/contacts", { body: a, project })
|
|
35559
35566
|
})
|
|
35560
35567
|
];
|
|
35561
35568
|
|
|
@@ -35758,7 +35765,7 @@ var generalTools = [
|
|
|
35758
35765
|
];
|
|
35759
35766
|
|
|
35760
35767
|
// src/server.ts
|
|
35761
|
-
var VERSION = "0.
|
|
35768
|
+
var VERSION = "0.4.0";
|
|
35762
35769
|
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.
|
|
35763
35770
|
- Start with list_projects to find projects and environment ids.
|
|
35764
35771
|
- 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.
|
package/dist/stdio.js
CHANGED
|
@@ -34866,10 +34866,12 @@ class HelipodClient {
|
|
|
34866
34866
|
return this.request(this.apiUrl, opts.auth === false ? undefined : this.cfg.apiKey, method, path, opts);
|
|
34867
34867
|
}
|
|
34868
34868
|
email(method, path, opts = {}) {
|
|
34869
|
-
|
|
34870
|
-
|
|
34869
|
+
const key = this.cfg.emailApiKey || this.cfg.apiKey;
|
|
34870
|
+
if (!key) {
|
|
34871
|
+
throw new HelipodError(401, "NO_API_KEY", "No key set. Use HELIPOD_API_KEY with the Email API enabled (or HELIPOD_EMAIL_API_KEY). Create keys in the Helipod dashboard: Project → Settings → API keys.");
|
|
34871
34872
|
}
|
|
34872
|
-
|
|
34873
|
+
const headers = !this.cfg.emailApiKey && opts.project ? { "x-helipod-project": opts.project } : undefined;
|
|
34874
|
+
return this.request(this.emailApiUrl, key, method, path, { ...opts, headers });
|
|
34873
34875
|
}
|
|
34874
34876
|
async request(base, key, method, path, opts) {
|
|
34875
34877
|
const raw = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -34883,6 +34885,9 @@ class HelipodClient {
|
|
|
34883
34885
|
const headers = { accept: "application/json", "user-agent": this.cfg.userAgent ?? "helipod-mcp" };
|
|
34884
34886
|
if (key)
|
|
34885
34887
|
headers.authorization = `Bearer ${key}`;
|
|
34888
|
+
for (const [k, v] of Object.entries(opts.headers ?? {}))
|
|
34889
|
+
if (/^[0-9a-f-]{36}$/i.test(v))
|
|
34890
|
+
headers[k] = v;
|
|
34886
34891
|
if (opts.body !== undefined)
|
|
34887
34892
|
headers["content-type"] = "application/json";
|
|
34888
34893
|
if (opts.idempotencyKey)
|
|
@@ -35578,14 +35583,15 @@ var domainTools = [
|
|
|
35578
35583
|
];
|
|
35579
35584
|
|
|
35580
35585
|
// src/tools/email.ts
|
|
35586
|
+
var projectId = exports_external.string().uuid().optional().describe("Project whose email to use. Only needed with a whole-account key that reaches several projects with email.");
|
|
35581
35587
|
var emailTools = [
|
|
35582
35588
|
defineTool({
|
|
35583
35589
|
name: "email_status",
|
|
35584
35590
|
title: "Email status",
|
|
35585
35591
|
kind: "read",
|
|
35586
35592
|
description: "Email service of the key's project: plan, usage today and this month, limits, counts.",
|
|
35587
|
-
input: {},
|
|
35588
|
-
run: (
|
|
35593
|
+
input: { projectId },
|
|
35594
|
+
run: (a, api2) => api2.email("GET", "/status", { project: a.projectId })
|
|
35589
35595
|
}),
|
|
35590
35596
|
defineTool({
|
|
35591
35597
|
name: "send_email",
|
|
@@ -35593,6 +35599,7 @@ var emailTools = [
|
|
|
35593
35599
|
kind: "write",
|
|
35594
35600
|
description: "Send one email from a verified domain of the project (counts against the plan's quota). Use templateId + templateData, or subject + html/text. Pass an idempotencyKey when retrying.",
|
|
35595
35601
|
input: {
|
|
35602
|
+
projectId,
|
|
35596
35603
|
from: exports_external.string().describe('"hello@example.com" or "Acme <hello@example.com>"'),
|
|
35597
35604
|
to: exports_external.array(exports_external.string()).min(1).max(50),
|
|
35598
35605
|
cc: exports_external.array(exports_external.string()).max(50).optional(),
|
|
@@ -35606,63 +35613,63 @@ var emailTools = [
|
|
|
35606
35613
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
35607
35614
|
idempotencyKey: exports_external.string().max(128).optional()
|
|
35608
35615
|
},
|
|
35609
|
-
run: (a, api2) => api2.email("POST", "/send", { body: a })
|
|
35616
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/send", { body: a, project })
|
|
35610
35617
|
}),
|
|
35611
35618
|
defineTool({
|
|
35612
35619
|
name: "list_emails",
|
|
35613
35620
|
title: "List sent emails",
|
|
35614
35621
|
kind: "read",
|
|
35615
35622
|
description: "Email log, newest first, with delivery status.",
|
|
35616
|
-
input: { status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
35617
|
-
run: (a, api2) => api2.email("GET", "/emails", { query: a })
|
|
35623
|
+
input: { projectId, status: exports_external.string().optional(), search: exports_external.string().optional(), limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().optional() },
|
|
35624
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("GET", "/emails", { query: a, project })
|
|
35618
35625
|
}),
|
|
35619
35626
|
defineTool({
|
|
35620
35627
|
name: "get_email",
|
|
35621
35628
|
title: "Get email",
|
|
35622
35629
|
kind: "read",
|
|
35623
35630
|
description: "One sent email with its status and events.",
|
|
35624
|
-
input: { emailId: exports_external.string() },
|
|
35625
|
-
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}
|
|
35631
|
+
input: { projectId, emailId: exports_external.string() },
|
|
35632
|
+
run: (a, api2) => api2.email("GET", `/emails/${a.emailId}`, { project: a.projectId })
|
|
35626
35633
|
}),
|
|
35627
35634
|
defineTool({
|
|
35628
35635
|
name: "list_email_domains",
|
|
35629
35636
|
title: "List sending domains",
|
|
35630
35637
|
kind: "read",
|
|
35631
35638
|
description: "Sending domains with verification status and the DNS records to add.",
|
|
35632
|
-
input: {},
|
|
35633
|
-
run: (
|
|
35639
|
+
input: { projectId },
|
|
35640
|
+
run: (a, api2) => api2.email("GET", "/domains", { project: a.projectId })
|
|
35634
35641
|
}),
|
|
35635
35642
|
defineTool({
|
|
35636
35643
|
name: "add_email_domain",
|
|
35637
35644
|
title: "Add sending domain",
|
|
35638
35645
|
kind: "write",
|
|
35639
35646
|
description: "Add a sending domain; the response lists the DNS records the user must create, then call verify_email_domain.",
|
|
35640
|
-
input: { domain: exports_external.string() },
|
|
35641
|
-
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain } })
|
|
35647
|
+
input: { projectId, domain: exports_external.string() },
|
|
35648
|
+
run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain }, project: a.projectId })
|
|
35642
35649
|
}),
|
|
35643
35650
|
defineTool({
|
|
35644
35651
|
name: "verify_email_domain",
|
|
35645
35652
|
title: "Verify sending domain",
|
|
35646
35653
|
kind: "write",
|
|
35647
35654
|
description: "Check a sending domain's DNS records again.",
|
|
35648
|
-
input: { domainId: exports_external.string() },
|
|
35649
|
-
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify
|
|
35655
|
+
input: { projectId, domainId: exports_external.string() },
|
|
35656
|
+
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify`, { project: a.projectId })
|
|
35650
35657
|
}),
|
|
35651
35658
|
defineTool({
|
|
35652
35659
|
name: "list_email_templates",
|
|
35653
35660
|
title: "List email templates",
|
|
35654
35661
|
kind: "read",
|
|
35655
35662
|
description: "Email templates of the project.",
|
|
35656
|
-
input: {},
|
|
35657
|
-
run: (
|
|
35663
|
+
input: { projectId },
|
|
35664
|
+
run: (a, api2) => api2.email("GET", "/templates", { project: a.projectId })
|
|
35658
35665
|
}),
|
|
35659
35666
|
defineTool({
|
|
35660
35667
|
name: "add_contact",
|
|
35661
35668
|
title: "Add contact",
|
|
35662
35669
|
kind: "write",
|
|
35663
35670
|
description: "Add or update an audience contact.",
|
|
35664
|
-
input: { email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
35665
|
-
run: (a, api2) => api2.email("POST", "/contacts", { body: a })
|
|
35671
|
+
input: { projectId, email: exports_external.string(), firstName: exports_external.string().optional(), lastName: exports_external.string().optional() },
|
|
35672
|
+
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/contacts", { body: a, project })
|
|
35666
35673
|
})
|
|
35667
35674
|
];
|
|
35668
35675
|
|
|
@@ -35865,7 +35872,7 @@ var generalTools = [
|
|
|
35865
35872
|
];
|
|
35866
35873
|
|
|
35867
35874
|
// src/server.ts
|
|
35868
|
-
var VERSION = "0.
|
|
35875
|
+
var VERSION = "0.4.0";
|
|
35869
35876
|
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.
|
|
35870
35877
|
- Start with list_projects to find projects and environment ids.
|
|
35871
35878
|
- 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.
|
|
@@ -35895,6 +35902,6 @@ var server = createHelipodServer({
|
|
|
35895
35902
|
emailApiUrl: process.env.HELIPOD_EMAIL_API_URL
|
|
35896
35903
|
});
|
|
35897
35904
|
if (!process.env.HELIPOD_API_KEY && !process.env.HELIPOD_EMAIL_API_KEY) {
|
|
35898
|
-
console.error("helipod-mcp: set HELIPOD_API_KEY (
|
|
35905
|
+
console.error("helipod-mcp: set HELIPOD_API_KEY (enable the Email API on it for the email tools). Create keys in the Helipod dashboard: Project → Settings → API keys.");
|
|
35899
35906
|
}
|
|
35900
35907
|
await server.connect(new StdioServerTransport);
|
package/package.json
CHANGED
package/skills/helipod/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, imag
|
|
|
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
|
-
- Email API: `https://mail.helipod.io/api/email` with `$
|
|
15
|
+
- Email API: `https://mail.helipod.io/api/email` with `$HELIPOD_API_KEY` when the key has the Email API enabled (a whole-account key adds `X-Helipod-Project: <projectId>`), or a separate email key `$HELIPOD_EMAIL_API_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.
|
|
18
18
|
- Every response is `{ "success": true, "data": ... }` or `{ "success": false, "error": "...", "code": "..." }`.
|
|
@@ -5,7 +5,7 @@ description: Send transactional email from an application through Helipod Email
|
|
|
5
5
|
|
|
6
6
|
# Helipod Email
|
|
7
7
|
|
|
8
|
-
Uses
|
|
8
|
+
Uses the Helipod API key `HELIPOD_API_KEY` with **Email API** enabled (Settings → API keys; a whole-account key adds `X-Helipod-Project: <projectId>`), or a separate email key from the Email section as `HELIPOD_EMAIL_API_KEY`. Base URL: `https://mail.helipod.io/api/email`.
|
|
9
9
|
|
|
10
10
|
## Set up
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ Never put the key in client-side code. Store it as a secret (on Helipod: `set_va
|
|
|
20
20
|
```js
|
|
21
21
|
await fetch("https://mail.helipod.io/api/email/send", {
|
|
22
22
|
method: "POST",
|
|
23
|
-
headers: { Authorization: `Bearer ${process.env.HELIPOD_EMAIL_API_KEY}`, "Content-Type": "application/json" },
|
|
23
|
+
headers: { Authorization: `Bearer ${process.env.HELIPOD_EMAIL_API_KEY ?? process.env.HELIPOD_API_KEY}`, "Content-Type": "application/json" },
|
|
24
24
|
body: JSON.stringify({
|
|
25
25
|
from: "Acme <hello@mail.example.com>",
|
|
26
26
|
to: ["user@example.com"],
|