@mgsoftwarebv/mg-dashboard-mcp 7.4.20 → 7.4.21

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.
Files changed (2) hide show
  1. package/dist/index.js +276 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1316,6 +1316,257 @@ async function proxyJson(ctx, route, body) {
1316
1316
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1317
1317
  };
1318
1318
  }
1319
+
1320
+ // src/mailserver-tools.ts
1321
+ var MAILSERVER_TOOL_NAME = "mailserver";
1322
+ var MAILSERVER_ACTIONS = [
1323
+ "list-domains",
1324
+ "add-domain",
1325
+ "verify-domain",
1326
+ "list-mailboxes",
1327
+ "create-mailbox",
1328
+ "list-keys",
1329
+ "create-key",
1330
+ "send"
1331
+ ];
1332
+ var DEFAULT_MAILSERVER_URL = "https://mailserver.mgsoftware.nl";
1333
+ var MAILSERVER_TOOLS = [
1334
+ {
1335
+ name: MAILSERVER_TOOL_NAME,
1336
+ description: "Configure internal MG Mailserver (mailserver.mgsoftware.nl, send domain mgsoftware.email). Do not use the Postgres tables directly. Actions: list-domains, add-domain, verify-domain, list-mailboxes, create-mailbox, list-keys, create-key (plaintext secret once), send. Customer mailboxes stay on mijn.host \u2014 this is internal Cloudflare Email only.",
1337
+ inputSchema: {
1338
+ type: "object",
1339
+ properties: {
1340
+ action: {
1341
+ type: "string",
1342
+ enum: [...MAILSERVER_ACTIONS],
1343
+ description: "Which mailserver operation to run."
1344
+ },
1345
+ domain: {
1346
+ type: "string",
1347
+ description: "action=add-domain: hostname to register (e.g. mgsoftware.email)."
1348
+ },
1349
+ domainId: {
1350
+ type: "string",
1351
+ description: "UUID of a domains row (verify-domain, create-mailbox, create-key)."
1352
+ },
1353
+ email: {
1354
+ type: "string",
1355
+ description: "action=create-mailbox: full address on a registered domain."
1356
+ },
1357
+ password: {
1358
+ type: "string",
1359
+ description: "action=create-mailbox: webmail password (min 8 chars)."
1360
+ },
1361
+ displayName: {
1362
+ type: "string",
1363
+ description: "action=create-mailbox: optional display name."
1364
+ },
1365
+ keyName: {
1366
+ type: "string",
1367
+ description: "action=create-key: label for the frs_ key."
1368
+ },
1369
+ permissions: {
1370
+ type: "array",
1371
+ items: { type: "string" },
1372
+ description: 'action=create-key: default ["send"].'
1373
+ },
1374
+ from: {
1375
+ type: "string",
1376
+ description: "action=send: From (e.g. MG Mailserver <noreply@mgsoftware.email>)."
1377
+ },
1378
+ to: {
1379
+ type: "string",
1380
+ description: "action=send: recipient email (or comma-separated)."
1381
+ },
1382
+ subject: { type: "string", description: "action=send: subject." },
1383
+ text: { type: "string", description: "action=send: plain text body." },
1384
+ html: { type: "string", description: "action=send: HTML body." }
1385
+ },
1386
+ required: ["action"]
1387
+ }
1388
+ }
1389
+ ];
1390
+ function tokenFromDotenv(content) {
1391
+ for (const line of content.split(/\r?\n/)) {
1392
+ const trimmed = line.trim();
1393
+ if (!trimmed || trimmed.startsWith("#")) continue;
1394
+ const eq4 = trimmed.indexOf("=");
1395
+ if (eq4 < 1) continue;
1396
+ const key = trimmed.slice(0, eq4).trim();
1397
+ if (key !== "MAILSERVER_MCP_TOKEN") continue;
1398
+ let value = trimmed.slice(eq4 + 1).trim();
1399
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1400
+ value = value.slice(1, -1);
1401
+ }
1402
+ return value;
1403
+ }
1404
+ return "";
1405
+ }
1406
+ function asString(value) {
1407
+ return typeof value === "string" ? value.trim() : "";
1408
+ }
1409
+ function planMailserverCall(action, args2) {
1410
+ switch (action) {
1411
+ case "list-domains":
1412
+ return { method: "GET", path: "/api/domains" };
1413
+ case "add-domain": {
1414
+ const domain = asString(args2.domain);
1415
+ if (!domain) return { error: "add-domain needs `domain`" };
1416
+ return { method: "POST", path: "/api/domains", body: { domain } };
1417
+ }
1418
+ case "verify-domain": {
1419
+ const domainId = asString(args2.domainId);
1420
+ if (!domainId) return { error: "verify-domain needs `domainId`" };
1421
+ return { method: "POST", path: `/api/domains/${domainId}/verify` };
1422
+ }
1423
+ case "list-mailboxes":
1424
+ return { method: "GET", path: "/api/imap/accounts" };
1425
+ case "create-mailbox": {
1426
+ const email = asString(args2.email);
1427
+ const password = asString(args2.password);
1428
+ const domainId = asString(args2.domainId);
1429
+ if (!email) return { error: "create-mailbox needs `email`" };
1430
+ if (password.length < 8) return { error: "create-mailbox needs `password` (min 8)" };
1431
+ if (!domainId) return { error: "create-mailbox needs `domainId` (or let the handler resolve it)" };
1432
+ return {
1433
+ method: "POST",
1434
+ path: "/api/imap/accounts",
1435
+ body: {
1436
+ email,
1437
+ password,
1438
+ domain_id: domainId,
1439
+ display_name: asString(args2.displayName) || void 0
1440
+ }
1441
+ };
1442
+ }
1443
+ case "list-keys":
1444
+ return { method: "GET", path: "/api/api-keys" };
1445
+ case "create-key": {
1446
+ const domainId = asString(args2.domainId);
1447
+ const keyName = asString(args2.keyName);
1448
+ if (!domainId) return { error: "create-key needs `domainId`" };
1449
+ if (!keyName) return { error: "create-key needs `keyName`" };
1450
+ const permissions = Array.isArray(args2.permissions) ? args2.permissions.filter((p) => typeof p === "string") : ["send"];
1451
+ return {
1452
+ method: "POST",
1453
+ path: "/api/api-keys",
1454
+ body: { domainId, keyName, permissions }
1455
+ };
1456
+ }
1457
+ case "send": {
1458
+ const from = asString(args2.from);
1459
+ const toRaw = asString(args2.to);
1460
+ const subject = asString(args2.subject);
1461
+ if (!from || !toRaw || !subject) {
1462
+ return { error: "send needs `from`, `to`, and `subject`" };
1463
+ }
1464
+ const to = toRaw.includes(",") ? toRaw.split(",").map((s) => s.trim()).filter(Boolean) : toRaw;
1465
+ return {
1466
+ method: "POST",
1467
+ path: "/api/emails",
1468
+ body: {
1469
+ from,
1470
+ to,
1471
+ subject,
1472
+ text: asString(args2.text) || void 0,
1473
+ html: asString(args2.html) || void 0
1474
+ }
1475
+ };
1476
+ }
1477
+ default:
1478
+ return {
1479
+ error: `Unknown action "${action}". Use: ${MAILSERVER_ACTIONS.join(", ")}`
1480
+ };
1481
+ }
1482
+ }
1483
+ function domainListFromPayload(payload) {
1484
+ if (!payload || typeof payload !== "object") return [];
1485
+ const data = payload.data;
1486
+ const list = data?.domains;
1487
+ if (!Array.isArray(list)) return [];
1488
+ return list.filter(
1489
+ (d) => Boolean(d) && typeof d === "object" && typeof d.id === "string" && typeof d.domain === "string"
1490
+ );
1491
+ }
1492
+ async function mailserverFetch(deps, plan) {
1493
+ const fetchFn = deps.fetchFn ?? fetch;
1494
+ const url = `${deps.baseUrl.replace(/\/$/, "")}${plan.path}`;
1495
+ const res = await fetchFn(url, {
1496
+ method: plan.method,
1497
+ headers: {
1498
+ authorization: `Bearer ${deps.token}`,
1499
+ ...plan.body ? { "content-type": "application/json" } : {}
1500
+ },
1501
+ body: plan.body ? JSON.stringify(plan.body) : void 0
1502
+ });
1503
+ const text21 = await res.text();
1504
+ let json = text21;
1505
+ try {
1506
+ json = text21 ? JSON.parse(text21) : null;
1507
+ } catch {
1508
+ json = { error: text21.slice(0, 500) };
1509
+ }
1510
+ return { status: res.status, json };
1511
+ }
1512
+ async function handleMailserverTool(args2, deps) {
1513
+ const action = asString(args2.action);
1514
+ if (!deps.token) {
1515
+ return {
1516
+ content: [
1517
+ {
1518
+ type: "text",
1519
+ text: "MAILSERVER_MCP_TOKEN is not set (env or MG Mailserver env-store web/production)."
1520
+ }
1521
+ ]
1522
+ };
1523
+ }
1524
+ if (action === "create-mailbox" && !asString(args2.domainId)) {
1525
+ const email = asString(args2.email);
1526
+ const host = email.split("@")[1]?.toLowerCase();
1527
+ if (!host) {
1528
+ return {
1529
+ content: [{ type: "text", text: "create-mailbox needs a full `email` (user@domain)." }]
1530
+ };
1531
+ }
1532
+ const listed = await mailserverFetch(deps, { method: "GET", path: "/api/domains" });
1533
+ const match = domainListFromPayload(listed.json).find(
1534
+ (d) => d.domain.toLowerCase() === host
1535
+ );
1536
+ if (!match) {
1537
+ return {
1538
+ content: [
1539
+ {
1540
+ type: "text",
1541
+ text: JSON.stringify(
1542
+ {
1543
+ error: `No registered domain for ${host}. add-domain first.`,
1544
+ status: listed.status,
1545
+ domains: listed.json
1546
+ },
1547
+ null,
1548
+ 2
1549
+ )
1550
+ }
1551
+ ]
1552
+ };
1553
+ }
1554
+ args2 = { ...args2, domainId: match.id };
1555
+ }
1556
+ const plan = planMailserverCall(action, args2);
1557
+ if ("error" in plan) {
1558
+ return { content: [{ type: "text", text: plan.error }] };
1559
+ }
1560
+ const result = await mailserverFetch(deps, plan);
1561
+ return {
1562
+ content: [
1563
+ {
1564
+ type: "text",
1565
+ text: JSON.stringify({ status: result.status, body: result.json }, null, 2)
1566
+ }
1567
+ ]
1568
+ };
1569
+ }
1319
1570
  var ALGORITHM = "aes-256-gcm";
1320
1571
  var IV_LENGTH = 16;
1321
1572
  var AUTH_TAG_LENGTH = 16;
@@ -9554,6 +9805,23 @@ function decrypt2(payload) {
9554
9805
  decrypted += decipher.final("utf8");
9555
9806
  return decrypted;
9556
9807
  }
9808
+ async function readMailserverMcpTokenFromEnvStore() {
9809
+ try {
9810
+ const { stageIds } = await resolveReleaseProfileStageIds("MG Mailserver");
9811
+ const data = await db.execute(sql`
9812
+ SELECT env_data_encrypted
9813
+ FROM env_config
9814
+ WHERE app_name = 'web'
9815
+ AND environment = 'production'
9816
+ AND release_profile_stage_id = ANY(${uuidArrayParam(stageIds)})
9817
+ `);
9818
+ const row = data[0];
9819
+ if (!row) return "";
9820
+ return tokenFromDotenv(decrypt2(row.env_data_encrypted));
9821
+ } catch {
9822
+ return "";
9823
+ }
9824
+ }
9557
9825
  function posixQuote(arg) {
9558
9826
  if (arg === "") return "''";
9559
9827
  if (/^[A-Za-z0-9._\/=:@%+\-]+$/.test(arg)) return arg;
@@ -13635,6 +13903,7 @@ var TOOLS = [
13635
13903
  required: ["action"]
13636
13904
  }
13637
13905
  },
13906
+ ...MAILSERVER_TOOLS,
13638
13907
  // ----- Trigger.dev -----
13639
13908
  ...TRIGGER_TOOLS
13640
13909
  ];
@@ -17395,6 +17664,13 @@ Install: GET https://dashboard.mgsoftware.nl/api/downloads/versions.txt then \u2
17395
17664
  ]
17396
17665
  };
17397
17666
  }
17667
+ case MAILSERVER_TOOL_NAME: {
17668
+ const token = process.env.MAILSERVER_MCP_TOKEN?.trim() || await readMailserverMcpTokenFromEnvStore();
17669
+ return handleMailserverTool(a, {
17670
+ baseUrl: process.env.MAILSERVER_URL?.trim() || DEFAULT_MAILSERVER_URL,
17671
+ token
17672
+ });
17673
+ }
17398
17674
  default:
17399
17675
  if (TRIGGER_TOOL_NAMES.has(name)) {
17400
17676
  return handleTriggerTool(name, a, { sshExec, getServerConnection });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgsoftwarebv/mg-dashboard-mcp",
3
- "version": "7.4.20",
3
+ "version": "7.4.21",
4
4
  "description": "MCP Server for MG Dashboard - SSH, SFTP, Docker, domains, DNS, and environment config tools for Cursor",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",