@mgsoftwarebv/mg-dashboard-mcp 6.0.0 → 6.0.2

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 CHANGED
@@ -1121,6 +1121,7 @@ async function handleVercelTool(name, args2, deps) {
1121
1121
  sinceExplicit ? Promise.resolve(null) : getDeploymentCreatedMs(token, deploymentId)
1122
1122
  ]);
1123
1123
  const maxWindowMin = 7 * 24 * 60;
1124
+ const autoCapMin = 30;
1124
1125
  let sinceMs;
1125
1126
  let windowNote = "";
1126
1127
  if (sinceExplicit) {
@@ -1129,17 +1130,18 @@ async function handleVercelTool(name, args2, deps) {
1129
1130
  windowNote = `window: last ${capped} min (caller-specified)`;
1130
1131
  } else if (deploymentCreatedMs) {
1131
1132
  const bufferMs = 5 * 6e4;
1132
- sinceMs = deploymentCreatedMs - bufferMs;
1133
- const ageMin = Math.max(1, Math.round((Date.now() - sinceMs) / 6e4));
1134
- if (ageMin > maxWindowMin) {
1135
- sinceMs = Date.now() - maxWindowMin * 6e4;
1136
- windowNote = `window: capped to ${maxWindowMin} min (deployment is older than 7 days)`;
1137
- } else {
1133
+ const sinceDeploymentMs = deploymentCreatedMs - bufferMs;
1134
+ const ageMin = Math.max(1, Math.round((Date.now() - sinceDeploymentMs) / 6e4));
1135
+ if (ageMin <= autoCapMin) {
1136
+ sinceMs = sinceDeploymentMs;
1138
1137
  windowNote = `window: auto ${ageMin} min from deployment createdAt - 5 min buffer`;
1138
+ } else {
1139
+ sinceMs = Date.now() - autoCapMin * 6e4;
1140
+ windowNote = `window: capped to last ${autoCapMin} min (deployment is ${ageMin} min old \u2014 pass sinceMinutes to widen up to ${maxWindowMin})`;
1139
1141
  }
1140
1142
  } else {
1141
- sinceMs = Date.now() - 60 * 6e4;
1142
- windowNote = "window: last 60 min (deployment metadata unavailable, used fallback)";
1143
+ sinceMs = Date.now() - autoCapMin * 6e4;
1144
+ windowNote = `window: last ${autoCapMin} min (deployment metadata unavailable, used fallback)`;
1143
1145
  }
1144
1146
  const { logs, error } = await getRuntimeLogs(
1145
1147
  token,
@@ -1152,10 +1154,15 @@ async function handleVercelTool(name, args2, deps) {
1152
1154
  const hint = error.includes("404") || error.includes("400") ? '\n\nThis endpoint requires both project ID and deployment ID and may not be available on every Vercel plan. Use kind="webhooks" or the supabase MCP (vercel_deployment_log table) for archived runtime logs.' : "";
1153
1155
  return { content: [{ type: "text", text: `Error: ${error}${hint}` }] };
1154
1156
  }
1155
- const text = `${formatRuntimeLogs(logs)}
1157
+ const body = formatRuntimeLogs(logs);
1158
+ const hitDurationLimit = /Exceeded query duration limit/i.test(body);
1159
+ const footer = hitDurationLimit ? `
1160
+
1161
+ [${windowNote}]
1162
+ [hint] Vercel hit its 5-min query budget for this window. Try a smaller sinceMinutes (e.g. 5-10), lower limit, or use kind="webhooks" / the supabase MCP vercel_deployment_log table for archived logs.` : `
1156
1163
 
1157
1164
  [${windowNote}]`;
1158
- return { content: [{ type: "text", text }] };
1165
+ return { content: [{ type: "text", text: body + footer }] };
1159
1166
  }
1160
1167
  if (kind === "webhooks") {
1161
1168
  const limit = Math.min(Math.max(Number(args2.limit) || 25, 1), 200);
@@ -3386,7 +3393,10 @@ function formatDbQueryFooter(output, appliedLimit, maxRows, explainMode) {
3386
3393
  return "\n\n[explain] Plan returned, no rows executed.";
3387
3394
  }
3388
3395
  if (!appliedLimit) return "";
3389
- const rows = output.split("\n").filter((l) => l.trim() && !/^\s*[-+|]/.test(l)).length;
3396
+ const rows = parseRowCountFromOutput(output);
3397
+ if (rows == null) return `
3398
+
3399
+ [ok] auto-LIMIT ${maxRows} applied (row count not detected).`;
3390
3400
  if (rows > maxRows) {
3391
3401
  return `
3392
3402
 
@@ -3396,6 +3406,21 @@ function formatDbQueryFooter(output, appliedLimit, maxRows, explainMode) {
3396
3406
 
3397
3407
  [ok] returned ${rows} row(s), under auto-LIMIT ${maxRows}.`;
3398
3408
  }
3409
+ function parseRowCountFromOutput(output) {
3410
+ const lines = output.split("\n");
3411
+ for (let i = lines.length - 1; i >= 0; i--) {
3412
+ const l = lines[i]?.trim();
3413
+ if (!l) continue;
3414
+ const m1 = /^\(\s*(\d+)\s+rows?\s*\)$/i.exec(l);
3415
+ if (m1?.[1]) return Number(m1[1]);
3416
+ const m2 = /^(\d+)\s+rows?\s+in\s+set\b/i.exec(l);
3417
+ if (m2?.[1]) return Number(m2[1]);
3418
+ const m3 = /^\(\s*(\d+)\s+rows?\s+affected\s*\)$/i.exec(l);
3419
+ if (m3?.[1]) return Number(m3[1]);
3420
+ if (/[a-zA-Z0-9]/.test(l) && i < lines.length - 3) return null;
3421
+ }
3422
+ return null;
3423
+ }
3399
3424
  function assertSafeSql(query) {
3400
3425
  const trimmed = query.trim();
3401
3426
  for (const pattern of BLOCKED_SQL_PATTERNS) {
@@ -3497,6 +3522,28 @@ function formatDnsDiff(domain, before, after, change) {
3497
3522
  lines.push("", "Re-run without `dryRun: true` to apply.");
3498
3523
  return lines.join("\n");
3499
3524
  }
3525
+ function describeDnsCandidates(records, type, name, attemptedValue) {
3526
+ const sameTypeAndName = records.filter((r) => r.type === type && r.name === name);
3527
+ const fmt = (r) => ` - ${r.type.padEnd(6)} ${r.name.padEnd(30)} ttl=${String(r.ttl).padEnd(5)} ${r.value}`;
3528
+ if (sameTypeAndName.length > 0) {
3529
+ const lines = ["Current records with this type + name (use one of these values verbatim):"];
3530
+ for (const r of sameTypeAndName.slice(0, 10)) lines.push(fmt(r));
3531
+ if (sameTypeAndName.length > 10) lines.push(` ...and ${sameTypeAndName.length - 10} more`);
3532
+ lines.push(`(attempted value: ${attemptedValue})`);
3533
+ return lines.join("\n");
3534
+ }
3535
+ const sameName = records.filter((r) => r.name === name);
3536
+ if (sameName.length > 0) {
3537
+ const types = [...new Set(sameName.map((r) => r.type))].sort().join(", ");
3538
+ const lines = [
3539
+ `No ${type} records exist for "${name}", but other records do (types: ${types}). Sample:`
3540
+ ];
3541
+ for (const r of sameName.slice(0, 10)) lines.push(fmt(r));
3542
+ if (sameName.length > 10) lines.push(` ...and ${sameName.length - 10} more`);
3543
+ return lines.join("\n");
3544
+ }
3545
+ return `No records found at all for name "${name}". Run dns-list to inspect the zone.`;
3546
+ }
3500
3547
  async function mijnhostFetch(path, options = {}) {
3501
3548
  const key = requireMijnhostApiKey();
3502
3549
  const res = await fetch(`${MIJNHOST_BASE_URL}${path}`, {
@@ -3810,7 +3857,7 @@ var TOOLS = [
3810
3857
  // ----- Domains (mijn.host) -----
3811
3858
  {
3812
3859
  name: "domain-list",
3813
- description: "List all domains from the mijn.host account. Returns domain name, status, renewal date (= expiration), and tags. Requires MIJNHOST_API_KEY.\n\nPass `details: true` to also fetch DNS zone summary per domain in parallel: NS records, MX target, and presence of SPF/DMARC. Useful as a single-call overview instead of N follow-up dns-list calls. Skipped for inactive/expired domains.",
3860
+ description: "List all domains from the mijn.host account. Returns domain name, status, renewal date (= expiration), and tags. Requires MIJNHOST_API_KEY.\n\nPass `details: true` to also fetch DNS zone summary per domain in parallel: MX target(s) and presence of SPF/DMARC TXT records. Useful as a single-call overview instead of N follow-up dns-list calls. Skipped for inactive/expired domains.",
3814
3861
  inputSchema: {
3815
3862
  type: "object",
3816
3863
  properties: {
@@ -3855,7 +3902,7 @@ var TOOLS = [
3855
3902
  // ----- Vercel -----
3856
3903
  ...VERCEL_TOOLS
3857
3904
  ];
3858
- var MCP_VERSION = "6.0.0";
3905
+ var MCP_VERSION = "6.0.2";
3859
3906
  async function handleListTools() {
3860
3907
  if (!authContext) return { tools: TOOLS };
3861
3908
  const accessible = TOOLS.filter((tool) => {
@@ -5107,15 +5154,14 @@ ${lines2.join("\n")}` }] };
5107
5154
  `/domains/${encodeURIComponent(domain)}/dns`
5108
5155
  );
5109
5156
  const recs = r.data.records || [];
5110
- const ns = recs.filter((x) => x.type === "NS").map((x) => x.value).sort();
5111
5157
  const mx = recs.filter((x) => x.type === "MX").map((x) => x.value).sort();
5112
5158
  const hasSpf = recs.some((x) => x.type === "TXT" && x.value.toLowerCase().includes("v=spf1"));
5113
5159
  const hasDmarc = recs.some(
5114
5160
  (x) => x.type === "TXT" && (x.name.toLowerCase().startsWith("_dmarc") || x.value.toLowerCase().includes("v=dmarc1"))
5115
5161
  );
5116
- return { ns, mx, hasSpf, hasDmarc };
5162
+ return { mx, hasSpf, hasDmarc };
5117
5163
  } catch (err) {
5118
- return { ns: [], mx: [], hasSpf: false, hasDmarc: false, error: err instanceof Error ? err.message : String(err) };
5164
+ return { mx: [], hasSpf: false, hasDmarc: false, error: err instanceof Error ? err.message : String(err) };
5119
5165
  }
5120
5166
  }
5121
5167
  const queue = [...activeDomains];
@@ -5134,12 +5180,9 @@ ${lines2.join("\n")}` }] };
5134
5180
  if (!s) return head;
5135
5181
  if (s.error) return `${head}
5136
5182
  dns: error: ${s.error}`;
5137
- const ns = s.ns.length ? s.ns.join(", ") : "(none)";
5138
5183
  const mx = s.mx.length ? s.mx.join(", ") : "(none)";
5139
- const mail = `mx=${mx} spf=${s.hasSpf ? "yes" : "NO"} dmarc=${s.hasDmarc ? "yes" : "NO"}`;
5140
5184
  return `${head}
5141
- ns: ${ns}
5142
- ${mail}`;
5185
+ mx=${mx} spf=${s.hasSpf ? "yes" : "NO"} dmarc=${s.hasDmarc ? "yes" : "NO"}`;
5143
5186
  });
5144
5187
  return {
5145
5188
  content: [{
@@ -5206,7 +5249,10 @@ ${lines.join("\n")}` }] };
5206
5249
  (r) => r.type === type && r.name === dnsName && r.value === oldValue
5207
5250
  );
5208
5251
  if (idx === -1) {
5209
- throw new Error(`No matching DNS record found: ${type} ${dnsName} = ${oldValue}`);
5252
+ throw new Error(
5253
+ `No matching DNS record found: ${type} ${dnsName} = ${oldValue}
5254
+ ` + describeDnsCandidates(current.data.records, type, dnsName, oldValue)
5255
+ );
5210
5256
  }
5211
5257
  const updated = [...current.data.records];
5212
5258
  const before = updated[idx];
@@ -5230,7 +5276,10 @@ ${lines.join("\n")}` }] };
5230
5276
  (r) => !(r.type === type && r.name === dnsName && r.value === value)
5231
5277
  );
5232
5278
  if (removed.length === 0) {
5233
- throw new Error(`No matching DNS record found: ${type} ${dnsName} = ${value}`);
5279
+ throw new Error(
5280
+ `No matching DNS record found: ${type} ${dnsName} = ${value}
5281
+ ` + describeDnsCandidates(current.data.records, type, dnsName, value)
5282
+ );
5234
5283
  }
5235
5284
  nextRecords = remaining;
5236
5285
  diffArgs = { verb: "delete", removed };