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 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
- Optional: an **email API key** from the project's **Email** section, for the email tools.
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` (and optionally `HELIPOD_EMAIL_API_KEY`) in its environment.
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_...` (and `X-Helipod-Email-Key` for the email tools). Same tools; the key is used per request and never stored.
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` | (required for email tools) |
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
- if (!this.cfg.emailApiKey) {
35618
- throw new HelipodError(401, "NO_EMAIL_API_KEY", "HELIPOD_EMAIL_API_KEY is not set. Create an email API key in the Helipod dashboard Email section.");
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
- return this.request(this.emailApiUrl, this.cfg.emailApiKey, method, path, opts);
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: (_a3, api2) => api2.email("GET", "/status")
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: (_a3, api2) => api2.email("GET", "/domains")
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: (_a3, api2) => api2.email("GET", "/templates")
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.3.1";
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
- if (!this.cfg.emailApiKey) {
34763
- throw new HelipodError(401, "NO_EMAIL_API_KEY", "HELIPOD_EMAIL_API_KEY is not set. Create an email API key in the Helipod dashboard Email section.");
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
- return this.request(this.emailApiUrl, this.cfg.emailApiKey, method, path, opts);
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: (_a3, api2) => api2.email("GET", "/status")
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: (_a3, api2) => api2.email("GET", "/domains")
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: (_a3, api2) => api2.email("GET", "/templates")
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.3.1";
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
- if (!this.cfg.emailApiKey) {
34870
- throw new HelipodError(401, "NO_EMAIL_API_KEY", "HELIPOD_EMAIL_API_KEY is not set. Create an email API key in the Helipod dashboard Email section.");
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
- return this.request(this.emailApiUrl, this.cfg.emailApiKey, method, path, opts);
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: (_a3, api2) => api2.email("GET", "/status")
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: (_a3, api2) => api2.email("GET", "/domains")
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: (_a3, api2) => api2.email("GET", "/templates")
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.3.1";
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 (and optionally HELIPOD_EMAIL_API_KEY). Create keys in the Helipod dashboard.");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helipod-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
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": {
@@ -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 `$HELIPOD_EMAIL_API_KEY` (a separate email key)
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 a separate **email API key** (dashboard → Email → API keys) as `HELIPOD_EMAIL_API_KEY`. Base URL: `https://mail.helipod.io/api/email`.
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"],