@mgsoftwarebv/mg-dashboard-mcp 7.4.8 → 7.4.9

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 +223 -41
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import { sql } from 'drizzle-orm';
20
20
  import { once } from 'events';
21
21
  import { lookup } from 'dns/promises';
22
22
  import { connect } from 'tls';
23
- import { pgTable, timestamp, jsonb, uuid, boolean, text, pgEnum, integer, uniqueIndex, index, check, bigint, doublePrecision, foreignKey } from 'drizzle-orm/pg-core';
23
+ import { pgTable, timestamp, jsonb, uuid, boolean, text, pgEnum, integer, uniqueIndex, index, check, bigint, doublePrecision, foreignKey, date, real } from 'drizzle-orm/pg-core';
24
24
  import { HeadObjectCommand, ListObjectsV2Command, DeleteObjectsCommand, S3Client, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
25
25
 
26
26
  var __defProp = Object.defineProperty;
@@ -990,9 +990,9 @@ ${raw2.substring(0, 500)}` }] };
990
990
  return { content: [{ type: "text", text: `No environment variables for ${project}/${env}.` }] };
991
991
  }
992
992
  const lines = vars.map((v) => `${v.isSecret ? "[secret]" : " "} ${v.name}`).sort();
993
- const text13 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
993
+ const text14 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
994
994
  ` + "-".repeat(50) + "\n" + lines.join("\n");
995
- return { content: [{ type: "text", text: text13 }] };
995
+ return { content: [{ type: "text", text: text14 }] };
996
996
  }
997
997
  if (action === "get") {
998
998
  const key2 = String(args2.key ?? "");
@@ -1079,9 +1079,9 @@ async function fetchAndFormatRun(conn, proxy, sshExec2, instance, runId) {
1079
1079
  return { content: [{ type: "text", text: `Invalid API response:
1080
1080
  ${rawJson.substring(0, 500)}` }] };
1081
1081
  }
1082
- let text13 = formatRunDetail(run);
1083
- if (logs) text13 += "\n\n--- Logs ---\n" + logs;
1084
- return { content: [{ type: "text", text: text13 }] };
1082
+ let text14 = formatRunDetail(run);
1083
+ if (logs) text14 += "\n\n--- Logs ---\n" + logs;
1084
+ return { content: [{ type: "text", text: text14 }] };
1085
1085
  }
1086
1086
  async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSeconds) {
1087
1087
  const pollInterval = 3e3;
@@ -1103,10 +1103,10 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
1103
1103
  continue;
1104
1104
  }
1105
1105
  if (TERMINAL_STATUSES.has(run.status)) {
1106
- let text13 = formatRunDetail(run);
1106
+ let text14 = formatRunDetail(run);
1107
1107
  const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
1108
- if (logs) text13 += "\n\n--- Logs ---\n" + logs;
1109
- return { content: [{ type: "text", text: text13 }] };
1108
+ if (logs) text14 += "\n\n--- Logs ---\n" + logs;
1109
+ return { content: [{ type: "text", text: text14 }] };
1110
1110
  }
1111
1111
  }
1112
1112
  return {
@@ -3793,10 +3793,10 @@ var ZodObject = class _ZodObject extends ZodType {
3793
3793
  // }) as any;
3794
3794
  // return merged;
3795
3795
  // }
3796
- catchall(index10) {
3796
+ catchall(index11) {
3797
3797
  return new _ZodObject({
3798
3798
  ...this._def,
3799
- catchall: index10
3799
+ catchall: index11
3800
3800
  });
3801
3801
  }
3802
3802
  pick(mask) {
@@ -4114,9 +4114,9 @@ function mergeValues(a, b) {
4114
4114
  return { valid: false };
4115
4115
  }
4116
4116
  const newArray = [];
4117
- for (let index10 = 0; index10 < a.length; index10++) {
4118
- const itemA = a[index10];
4119
- const itemB = b[index10];
4117
+ for (let index11 = 0; index11 < a.length; index11++) {
4118
+ const itemA = a[index11];
4119
+ const itemB = b[index11];
4120
4120
  const sharedValue = mergeValues(itemA, itemB);
4121
4121
  if (!sharedValue.valid) {
4122
4122
  return { valid: false };
@@ -4322,10 +4322,10 @@ var ZodMap = class extends ZodType {
4322
4322
  }
4323
4323
  const keyType = this._def.keyType;
4324
4324
  const valueType = this._def.valueType;
4325
- const pairs = [...ctx.data.entries()].map(([key, value], index10) => {
4325
+ const pairs = [...ctx.data.entries()].map(([key, value], index11) => {
4326
4326
  return {
4327
- key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index10, "key"])),
4328
- value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index10, "value"]))
4327
+ key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index11, "key"])),
4328
+ value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index11, "value"]))
4329
4329
  };
4330
4330
  });
4331
4331
  if (ctx.common.async) {
@@ -6538,6 +6538,97 @@ pgTable(
6538
6538
  }).onDelete("cascade")
6539
6539
  ]
6540
6540
  );
6541
+ pgTable(
6542
+ "wiki_page",
6543
+ {
6544
+ id: uuid("id").primaryKey().defaultRandom(),
6545
+ slug: text("slug").notNull().unique(),
6546
+ title: text("title").notNull().default(""),
6547
+ summary: text("summary"),
6548
+ content: text("content").notNull().default(""),
6549
+ status: text("status").notNull().default("draft"),
6550
+ pageUpdatedOn: date("page_updated_on"),
6551
+ reviewedBy: text("reviewed_by"),
6552
+ reviewedOn: date("reviewed_on"),
6553
+ lastVerifiedOn: date("last_verified_on"),
6554
+ verifyCount: integer("verify_count").notNull().default(0),
6555
+ verifyNote: text("verify_note"),
6556
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6557
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6558
+ },
6559
+ (table) => [
6560
+ index("idx_wiki_page_status").on(table.status),
6561
+ index("idx_wiki_page_last_verified").on(table.lastVerifiedOn)
6562
+ ]
6563
+ );
6564
+ pgTable(
6565
+ "wiki_page_flag",
6566
+ {
6567
+ id: uuid("id").primaryKey().defaultRandom(),
6568
+ pageSlug: text("page_slug").notNull(),
6569
+ kind: text("kind").notNull(),
6570
+ detail: text("detail").notNull().default(""),
6571
+ createdBy: text("created_by"),
6572
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6573
+ resolvedAt: timestamp("resolved_at", { withTimezone: true }),
6574
+ resolvedBy: text("resolved_by")
6575
+ },
6576
+ (table) => [index("idx_wiki_page_flag_created").on(table.createdAt)]
6577
+ );
6578
+ pgTable(
6579
+ "wiki_page_evidence",
6580
+ {
6581
+ id: uuid("id").primaryKey().defaultRandom(),
6582
+ pageSlug: text("page_slug").notNull(),
6583
+ evidenceType: text("evidence_type").notNull(),
6584
+ evidenceRef: text("evidence_ref").notNull(),
6585
+ matchScore: real("match_score"),
6586
+ matchReason: text("match_reason"),
6587
+ snippet: text("snippet"),
6588
+ status: text("status").notNull().default("pending"),
6589
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6590
+ resolvedAt: timestamp("resolved_at", { withTimezone: true })
6591
+ },
6592
+ (table) => [
6593
+ uniqueIndex("wiki_page_evidence_page_slug_evidence_type_evidence_ref_key").on(
6594
+ table.pageSlug,
6595
+ table.evidenceType,
6596
+ table.evidenceRef
6597
+ ),
6598
+ index("idx_wiki_page_evidence_created").on(table.createdAt)
6599
+ ]
6600
+ );
6601
+ pgTable(
6602
+ "wiki_page_read",
6603
+ {
6604
+ id: uuid("id").primaryKey().defaultRandom(),
6605
+ slug: text("slug").notNull(),
6606
+ caller: text("caller"),
6607
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6608
+ },
6609
+ (table) => [
6610
+ index("idx_wiki_page_read_slug").on(table.slug, table.createdAt),
6611
+ index("idx_wiki_page_read_created").on(table.createdAt)
6612
+ ]
6613
+ );
6614
+ pgTable(
6615
+ "wiki_verify_event",
6616
+ {
6617
+ id: uuid("id").primaryKey().defaultRandom(),
6618
+ pageSlug: text("page_slug").notNull(),
6619
+ outcome: text("outcome").notNull(),
6620
+ runMode: text("run_mode"),
6621
+ note: text("note"),
6622
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6623
+ },
6624
+ (table) => [
6625
+ index("idx_wiki_verify_event_created").on(table.createdAt),
6626
+ index("idx_wiki_verify_event_outcome_created").on(
6627
+ table.outcome,
6628
+ table.createdAt
6629
+ )
6630
+ ]
6631
+ );
6541
6632
  var GITHUB_API = "https://api.github.com";
6542
6633
  var ReleaseProfileQuickCreateError = class extends Error {
6543
6634
  constructor(message, code = "INTERNAL_SERVER_ERROR") {
@@ -7941,7 +8032,7 @@ function getEncryptionKey() {
7941
8032
  throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
7942
8033
  return buf;
7943
8034
  }
7944
- function encrypt(text13) {
8035
+ function encrypt(text14) {
7945
8036
  const key = getEncryptionKey();
7946
8037
  const iv = randomBytes(ENC_IV_LENGTH);
7947
8038
  const cipher = createCipheriv(
@@ -7949,7 +8040,7 @@ function encrypt(text13) {
7949
8040
  new Uint8Array(key),
7950
8041
  new Uint8Array(iv)
7951
8042
  );
7952
- let encrypted = cipher.update(text13, "utf8", "hex");
8043
+ let encrypted = cipher.update(text14, "utf8", "hex");
7953
8044
  encrypted += cipher.final("hex");
7954
8045
  const authTag = cipher.getAuthTag();
7955
8046
  return Buffer.concat([
@@ -8669,10 +8760,10 @@ async function r2GetObjectRange(bucket, key, range) {
8669
8760
  const body = result.Body;
8670
8761
  if (!body?.transformToString)
8671
8762
  throw new Error("R2 returned no readable body");
8672
- const text13 = await body.transformToString();
8763
+ const text14 = await body.transformToString();
8673
8764
  const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
8674
8765
  return `${header}
8675
- ${text13}`;
8766
+ ${text14}`;
8676
8767
  } catch (e) {
8677
8768
  throw r2WrapError(bucket, key, e);
8678
8769
  }
@@ -9035,15 +9126,15 @@ async function sftpRead(opts, filePath, proxy, options) {
9035
9126
  clearTimeout(timer);
9036
9127
  cleanup?.();
9037
9128
  cleanup = void 0;
9038
- const text13 = Buffer.concat(
9129
+ const text14 = Buffer.concat(
9039
9130
  chunks.map((ch) => new Uint8Array(ch))
9040
9131
  ).toString("utf-8");
9041
9132
  if (!isWholeFileRequest) {
9042
9133
  const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
9043
9134
  resolve(`${header}
9044
- ${text13}`);
9135
+ ${text14}`);
9045
9136
  } else {
9046
- resolve(text13);
9137
+ resolve(text14);
9047
9138
  }
9048
9139
  });
9049
9140
  rs.on("error", (e) => {
@@ -9190,11 +9281,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
9190
9281
  if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
9191
9282
  return e.names;
9192
9283
  }
9193
- function truncateForLLM(text13, maxBytes) {
9194
- const totalBytes = Buffer.byteLength(text13, "utf8");
9284
+ function truncateForLLM(text14, maxBytes) {
9285
+ const totalBytes = Buffer.byteLength(text14, "utf8");
9195
9286
  if (totalBytes <= maxBytes)
9196
- return { text: text13, truncated: false, totalBytes, shownBytes: totalBytes };
9197
- const buf = Buffer.from(text13, "utf8");
9287
+ return { text: text14, truncated: false, totalBytes, shownBytes: totalBytes };
9288
+ const buf = Buffer.from(text14, "utf8");
9198
9289
  let cut = maxBytes;
9199
9290
  while (cut > 0 && (buf[cut] & 192) === 128) cut--;
9200
9291
  const head = buf.subarray(0, cut).toString("utf8");
@@ -9225,10 +9316,10 @@ function postprocessResult(result, meta) {
9225
9316
  if (!result.content?.length) return result;
9226
9317
  if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
9227
9318
  const block = result.content[0];
9228
- let text13 = String(block.text ?? "");
9229
- const trunc = truncateForLLM(text13, RESPONSE_MAX_BYTES);
9319
+ let text14 = String(block.text ?? "");
9320
+ const trunc = truncateForLLM(text14, RESPONSE_MAX_BYTES);
9230
9321
  if (trunc.truncated) {
9231
- text13 = trunc.text + "\n\n... " + buildTruncationHint(
9322
+ text14 = trunc.text + "\n\n... " + buildTruncationHint(
9232
9323
  meta.toolName,
9233
9324
  meta.args,
9234
9325
  trunc.totalBytes,
@@ -9242,11 +9333,11 @@ function postprocessResult(result, meta) {
9242
9333
  const parts = [`took ${tookStr}`, sizeStr];
9243
9334
  if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
9244
9335
  if (meta.cached) parts.push("cached");
9245
- text13 = `${text13}
9336
+ text14 = `${text14}
9246
9337
 
9247
9338
  [${parts.join(", ")}]`;
9248
9339
  }
9249
- return { ...result, content: [{ ...block, text: text13 }] };
9340
+ return { ...result, content: [{ ...block, text: text14 }] };
9250
9341
  }
9251
9342
  function buildPipelineScript(commands, shell, marker, stopOnError) {
9252
9343
  if (shell === "powershell") {
@@ -11753,6 +11844,32 @@ var TOOLS = [
11753
11844
  }
11754
11845
  }
11755
11846
  },
11847
+ {
11848
+ name: "submit-team-memory",
11849
+ description: "Manually push a curated problem\u2192solution note into the team's searchable memory (agent_memory). Use ONLY when the user explicitly asks to push/share context to team memory (e.g. no agent-host daemon). Do NOT auto-submit. Do NOT dump raw chat. English; no secrets/PII. Pass repo for the current project. Distinct from wiki intake \u2014 this is searchable via search-team-memory.",
11850
+ inputSchema: {
11851
+ type: "object",
11852
+ properties: {
11853
+ title: {
11854
+ type: "string",
11855
+ description: "Short English title for the note (max 300 chars)."
11856
+ },
11857
+ content: {
11858
+ type: "string",
11859
+ description: "Problem / root cause / solution / context markdown (min ~40 chars, max ~24k stored)."
11860
+ },
11861
+ repo: {
11862
+ type: "string",
11863
+ description: "Git repository, e.g. 'github.com/MGSoftwareBV/fenicks' or 'github.com/MGSoftwareBV/mg-boiler'."
11864
+ },
11865
+ externalRef: {
11866
+ type: "string",
11867
+ description: "Optional client dedupe key; re-submitting the same ref upserts the row."
11868
+ }
11869
+ },
11870
+ required: ["title", "content", "repo"]
11871
+ }
11872
+ },
11756
11873
  {
11757
11874
  name: "search-wiki",
11758
11875
  description: "Search the team's curated knowledge wiki (the distilled layer ABOVE team memory): compiled pages about how systems actually work, customer setups/quirks, and known recurring issues with their standard fixes. Check this BEFORE search-team-memory \u2014 it is smaller, curated, and deduplicated, so a hit here is usually the complete answer. Every page lists its `sources` (team-memory ids, ticket numbers, commits): follow those with get-team-memory for full raw detail instead of broad searches. Fall back to search-team-memory when nothing relevant comes back or the page's `updated` date looks stale.",
@@ -12127,7 +12244,7 @@ async function executeToolCall(name, a, _serverId) {
12127
12244
  ]
12128
12245
  };
12129
12246
  }
12130
- const lines = data.hits.map((hit, index10) => {
12247
+ const lines = data.hits.map((hit, index11) => {
12131
12248
  const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
12132
12249
  const repo = hit.repo ?? "unknown repo";
12133
12250
  const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
@@ -12137,7 +12254,7 @@ async function executeToolCall(name, a, _serverId) {
12137
12254
  ...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
12138
12255
  ].filter(Boolean);
12139
12256
  const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
12140
- return `${index10 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
12257
+ return `${index11 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
12141
12258
  id: ${hit.id}
12142
12259
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
12143
12260
  });
@@ -12239,6 +12356,71 @@ ${facets.join(", ")}` : ""),
12239
12356
  ]
12240
12357
  };
12241
12358
  }
12359
+ // ----- Team memory: manual curated submit (no daemon) -----
12360
+ case "submit-team-memory": {
12361
+ const title = typeof a.title === "string" ? a.title.trim() : "";
12362
+ const content = typeof a.content === "string" ? a.content.trim() : "";
12363
+ const repo = typeof a.repo === "string" ? a.repo.trim() : "";
12364
+ const externalRef = typeof a.externalRef === "string" ? a.externalRef.trim() : void 0;
12365
+ if (!title || !content || !repo) {
12366
+ return {
12367
+ content: [
12368
+ {
12369
+ type: "text",
12370
+ text: "Error: title, content, and repo are required."
12371
+ }
12372
+ ]
12373
+ };
12374
+ }
12375
+ if (content.length < 40) {
12376
+ return {
12377
+ content: [
12378
+ {
12379
+ type: "text",
12380
+ text: "Error: content must be at least 40 characters (curated problem\u2192solution, not a one-liner)."
12381
+ }
12382
+ ]
12383
+ };
12384
+ }
12385
+ const res = await fetch(`${dashboardBaseUrl}/api/team-memory/submit`, {
12386
+ method: "POST",
12387
+ headers: {
12388
+ "content-type": "application/json",
12389
+ authorization: `Bearer ${apiKey}`
12390
+ },
12391
+ body: JSON.stringify({
12392
+ title,
12393
+ content,
12394
+ gitRepository: repo,
12395
+ ...externalRef ? { externalRef } : {}
12396
+ })
12397
+ });
12398
+ if (!res.ok) {
12399
+ const detail = await res.text().catch(() => "");
12400
+ return {
12401
+ content: [
12402
+ {
12403
+ type: "text",
12404
+ text: `Error: team-memory submit failed (${res.status}). ${detail.slice(0, 400)}`
12405
+ }
12406
+ ]
12407
+ };
12408
+ }
12409
+ const data = await res.json();
12410
+ return {
12411
+ content: [
12412
+ {
12413
+ type: "text",
12414
+ text: `Submitted to team memory${data.upserted ? "" : " (unchanged \u2014 identical content)"}.
12415
+ id: ${data.id}
12416
+ localConversationId: ${data.localConversationId}
12417
+ repo: ${repo}
12418
+ summarized: ${data.summarized}, embedded: ${data.embedded}
12419
+ Searchable via search-team-memory once indexing completes (usually immediate).`
12420
+ }
12421
+ ]
12422
+ };
12423
+ }
12242
12424
  // ----- Knowledge wiki: search -----
12243
12425
  case "search-wiki": {
12244
12426
  const query = typeof a.query === "string" ? a.query.trim() : "";
@@ -12279,12 +12461,12 @@ ${facets.join(", ")}` : ""),
12279
12461
  ]
12280
12462
  };
12281
12463
  }
12282
- const lines = data.hits.map((hit, index10) => {
12464
+ const lines = data.hits.map((hit, index11) => {
12283
12465
  const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
12284
12466
  const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
12285
12467
  const sources = hit.sources.length ? `
12286
12468
  sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
12287
- return `${index10 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
12469
+ return `${index11 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
12288
12470
  slug: ${hit.slug}${sources}
12289
12471
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
12290
12472
  });
@@ -13491,8 +13673,8 @@ ${sample.join("\n")}${files.length > 5 ? `
13491
13673
  };
13492
13674
  const filtered = sortRows(applyFilter(only.rows));
13493
13675
  if (format === "json") {
13494
- const text14 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
13495
- return { content: [{ type: "text", text: text14 }] };
13676
+ const text15 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
13677
+ return { content: [{ type: "text", text: text15 }] };
13496
13678
  }
13497
13679
  if (groupByProject) {
13498
13680
  const groups = /* @__PURE__ */ new Map();
@@ -13519,8 +13701,8 @@ ${sample.join("\n")}${files.length > 5 ? `
13519
13701
  }
13520
13702
  const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
13521
13703
  const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
13522
- const text13 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
13523
- return { content: [{ type: "text", text: text13 }] };
13704
+ const text14 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
13705
+ return { content: [{ type: "text", text: text14 }] };
13524
13706
  }
13525
13707
  if (format === "json") {
13526
13708
  const lines = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgsoftwarebv/mg-dashboard-mcp",
3
- "version": "7.4.8",
3
+ "version": "7.4.9",
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",