@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.
- package/dist/index.js +276 -0
- 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