@mgsoftwarebv/mg-dashboard-mcp 7.4.5 → 7.4.6

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 +262 -51
  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, foreignKey } from 'drizzle-orm/pg-core';
23
+ import { pgTable, timestamp, jsonb, uuid, boolean, text, pgEnum, integer, uniqueIndex, index, check, bigint, doublePrecision, foreignKey } 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;
@@ -703,10 +703,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
703
703
  "trigger-env": "settings"
704
704
  };
705
705
  async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
706
- const sql27 = `SELECT re.\\"apiKey\\" || '~~' || p.\\"externalRef\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
706
+ const sql28 = `SELECT re.\\"apiKey\\" || '~~' || p.\\"externalRef\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
707
707
  const cmd = [
708
708
  `PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
709
- `ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql27}" 2>/dev/null | tr -d '[:space:]')`,
709
+ `ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null | tr -d '[:space:]')`,
710
710
  'echo "$PORT|$ROW"'
711
711
  ].join(" && ");
712
712
  const result = await sshExec2(conn, cmd, proxy);
@@ -728,8 +728,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
728
728
  return { port, apiKey: apiKey2, projectRef: projectRef || "" };
729
729
  }
730
730
  async function fetchRunLogs(runId, conn, proxy, sshExec2) {
731
- const sql27 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
732
- const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql27}" 2>/dev/null`;
731
+ const sql28 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
732
+ const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
733
733
  const result = await sshExec2(conn, cmd, proxy);
734
734
  const output = result.stdout.trim();
735
735
  if (!output) return "";
@@ -811,8 +811,8 @@ async function handleTriggerTool(name, args2, deps) {
811
811
  switch (name) {
812
812
  // -----------------------------------------------------------------
813
813
  case "trigger-list": {
814
- const sql27 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
815
- const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql27}" 2>/dev/null`;
814
+ const sql28 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
815
+ const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
816
816
  const result = await sshExec2(conn, cmd, proxy);
817
817
  const output = result.stdout.trim();
818
818
  if (!output) {
@@ -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 text11 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
993
+ const text12 = `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: text11 }] };
995
+ return { content: [{ type: "text", text: text12 }] };
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 text11 = formatRunDetail(run);
1083
- if (logs) text11 += "\n\n--- Logs ---\n" + logs;
1084
- return { content: [{ type: "text", text: text11 }] };
1082
+ let text12 = formatRunDetail(run);
1083
+ if (logs) text12 += "\n\n--- Logs ---\n" + logs;
1084
+ return { content: [{ type: "text", text: text12 }] };
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 text11 = formatRunDetail(run);
1106
+ let text12 = formatRunDetail(run);
1107
1107
  const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
1108
- if (logs) text11 += "\n\n--- Logs ---\n" + logs;
1109
- return { content: [{ type: "text", text: text11 }] };
1108
+ if (logs) text12 += "\n\n--- Logs ---\n" + logs;
1109
+ return { content: [{ type: "text", text: text12 }] };
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(index8) {
3796
+ catchall(index9) {
3797
3797
  return new _ZodObject({
3798
3798
  ...this._def,
3799
- catchall: index8
3799
+ catchall: index9
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 index8 = 0; index8 < a.length; index8++) {
4118
- const itemA = a[index8];
4119
- const itemB = b[index8];
4117
+ for (let index9 = 0; index9 < a.length; index9++) {
4118
+ const itemA = a[index9];
4119
+ const itemB = b[index9];
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], index8) => {
4325
+ const pairs = [...ctx.data.entries()].map(([key, value], index9) => {
4326
4326
  return {
4327
- key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index8, "key"])),
4328
- value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index8, "value"]))
4327
+ key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index9, "key"])),
4328
+ value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index9, "value"]))
4329
4329
  };
4330
4330
  });
4331
4331
  if (ctx.common.async) {
@@ -6123,6 +6123,128 @@ pgTable(
6123
6123
  index("dns_migration_mailbox_migration_idx").on(table.migrationId)
6124
6124
  ]
6125
6125
  );
6126
+ var directoryLinkType = pgEnum("directory_link_type", [
6127
+ "dofollow",
6128
+ "nofollow",
6129
+ "conditional"
6130
+ ]);
6131
+ var directoryPricing = pgEnum("directory_pricing", [
6132
+ "free",
6133
+ "freemium",
6134
+ "paid"
6135
+ ]);
6136
+ var directoryRegion = pgEnum("directory_region", ["nl", "intl"]);
6137
+ var directoryAutomationLevel = pgEnum("directory_automation_level", [
6138
+ "auto",
6139
+ "semi",
6140
+ "manual"
6141
+ ]);
6142
+ var directorySubmissionStatus = pgEnum(
6143
+ "directory_submission_status",
6144
+ [
6145
+ "pending",
6146
+ "queued",
6147
+ "submitted",
6148
+ "verification_needed",
6149
+ "awaiting_review",
6150
+ "live",
6151
+ "rejected",
6152
+ "failed",
6153
+ "manual"
6154
+ ]
6155
+ );
6156
+ var linkDirectory = pgTable(
6157
+ "link_directory",
6158
+ {
6159
+ id: uuid("id").primaryKey().defaultRandom(),
6160
+ name: text("name").notNull(),
6161
+ url: text("url").notNull(),
6162
+ submitUrl: text("submit_url").notNull(),
6163
+ dr: integer("dr").notNull().default(0),
6164
+ linkType: directoryLinkType("link_type").notNull().default("nofollow"),
6165
+ pricing: directoryPricing("pricing").notNull().default("free"),
6166
+ category: text("category").notNull().default("General Directory"),
6167
+ region: directoryRegion("region").notNull().default("intl"),
6168
+ automationLevel: directoryAutomationLevel("automation_level").notNull().default("auto"),
6169
+ enabled: boolean("enabled").notNull().default(false),
6170
+ formMapping: jsonb("form_mapping").$type(),
6171
+ lastCheckedAt: timestamp("last_checked_at", { withTimezone: true }),
6172
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6173
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6174
+ },
6175
+ (table) => [
6176
+ uniqueIndex("link_directory_submit_url_uidx").on(table.submitUrl),
6177
+ index("link_directory_enabled_dr_idx").on(table.enabled, table.dr),
6178
+ index("link_directory_region_idx").on(table.region)
6179
+ ]
6180
+ );
6181
+ var linkBuildingClient = pgTable(
6182
+ "link_building_client",
6183
+ {
6184
+ id: uuid("id").primaryKey().defaultRandom(),
6185
+ name: text("name").notNull(),
6186
+ websiteUrl: text("website_url").notNull(),
6187
+ mailboxAddress: text("mailbox_address"),
6188
+ refrontMailAccountId: text("refront_mail_account_id"),
6189
+ profile: jsonb("profile").$type().notNull().default({}),
6190
+ weeklyPace: integer("weekly_pace").notNull().default(12),
6191
+ autoSubmit: boolean("auto_submit").notNull().default(false),
6192
+ startingDr: doublePrecision("starting_dr"),
6193
+ currentDr: doublePrecision("current_dr"),
6194
+ isActive: boolean("is_active").notNull().default(true),
6195
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6196
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6197
+ },
6198
+ (table) => [
6199
+ uniqueIndex("link_building_client_website_uidx").on(table.websiteUrl),
6200
+ index("link_building_client_active_idx").on(table.isActive)
6201
+ ]
6202
+ );
6203
+ pgTable(
6204
+ "directory_submission",
6205
+ {
6206
+ id: uuid("id").primaryKey().defaultRandom(),
6207
+ clientId: uuid("client_id").notNull().references(() => linkBuildingClient.id, { onDelete: "cascade" }),
6208
+ directoryId: uuid("directory_id").notNull().references(() => linkDirectory.id, { onDelete: "cascade" }),
6209
+ status: directorySubmissionStatus("status").notNull().default("pending"),
6210
+ listingUrl: text("listing_url"),
6211
+ dofollowConfirmed: boolean("dofollow_confirmed"),
6212
+ screenshotPath: text("screenshot_path"),
6213
+ log: jsonb("log").$type().notNull().default({}),
6214
+ queuedAt: timestamp("queued_at", { withTimezone: true }),
6215
+ submittedAt: timestamp("submitted_at", { withTimezone: true }),
6216
+ verifiedAt: timestamp("verified_at", { withTimezone: true }),
6217
+ lastError: text("last_error"),
6218
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6219
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6220
+ },
6221
+ (table) => [
6222
+ uniqueIndex("directory_submission_client_directory_uidx").on(
6223
+ table.clientId,
6224
+ table.directoryId
6225
+ ),
6226
+ index("directory_submission_status_idx").on(table.status),
6227
+ index("directory_submission_client_status_idx").on(
6228
+ table.clientId,
6229
+ table.status
6230
+ )
6231
+ ]
6232
+ );
6233
+ pgTable(
6234
+ "client_dr_snapshot",
6235
+ {
6236
+ id: uuid("id").primaryKey().defaultRandom(),
6237
+ clientId: uuid("client_id").notNull().references(() => linkBuildingClient.id, { onDelete: "cascade" }),
6238
+ dr: doublePrecision("dr").notNull(),
6239
+ measuredAt: timestamp("measured_at", { withTimezone: true }).notNull().defaultNow()
6240
+ },
6241
+ (table) => [
6242
+ index("client_dr_snapshot_client_measured_idx").on(
6243
+ table.clientId,
6244
+ table.measuredAt
6245
+ )
6246
+ ]
6247
+ );
6126
6248
  var githubToken = pgTable(
6127
6249
  "github_token",
6128
6250
  {
@@ -7746,7 +7868,7 @@ function getEncryptionKey() {
7746
7868
  throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
7747
7869
  return buf;
7748
7870
  }
7749
- function encrypt(text11) {
7871
+ function encrypt(text12) {
7750
7872
  const key = getEncryptionKey();
7751
7873
  const iv = randomBytes(ENC_IV_LENGTH);
7752
7874
  const cipher = createCipheriv(
@@ -7754,7 +7876,7 @@ function encrypt(text11) {
7754
7876
  new Uint8Array(key),
7755
7877
  new Uint8Array(iv)
7756
7878
  );
7757
- let encrypted = cipher.update(text11, "utf8", "hex");
7879
+ let encrypted = cipher.update(text12, "utf8", "hex");
7758
7880
  encrypted += cipher.final("hex");
7759
7881
  const authTag = cipher.getAuthTag();
7760
7882
  return Buffer.concat([
@@ -8474,10 +8596,10 @@ async function r2GetObjectRange(bucket, key, range) {
8474
8596
  const body = result.Body;
8475
8597
  if (!body?.transformToString)
8476
8598
  throw new Error("R2 returned no readable body");
8477
- const text11 = await body.transformToString();
8599
+ const text12 = await body.transformToString();
8478
8600
  const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
8479
8601
  return `${header}
8480
- ${text11}`;
8602
+ ${text12}`;
8481
8603
  } catch (e) {
8482
8604
  throw r2WrapError(bucket, key, e);
8483
8605
  }
@@ -8840,15 +8962,15 @@ async function sftpRead(opts, filePath, proxy, options) {
8840
8962
  clearTimeout(timer);
8841
8963
  cleanup?.();
8842
8964
  cleanup = void 0;
8843
- const text11 = Buffer.concat(
8965
+ const text12 = Buffer.concat(
8844
8966
  chunks.map((ch) => new Uint8Array(ch))
8845
8967
  ).toString("utf-8");
8846
8968
  if (!isWholeFileRequest) {
8847
8969
  const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
8848
8970
  resolve(`${header}
8849
- ${text11}`);
8971
+ ${text12}`);
8850
8972
  } else {
8851
- resolve(text11);
8973
+ resolve(text12);
8852
8974
  }
8853
8975
  });
8854
8976
  rs.on("error", (e) => {
@@ -8995,11 +9117,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
8995
9117
  if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
8996
9118
  return e.names;
8997
9119
  }
8998
- function truncateForLLM(text11, maxBytes) {
8999
- const totalBytes = Buffer.byteLength(text11, "utf8");
9120
+ function truncateForLLM(text12, maxBytes) {
9121
+ const totalBytes = Buffer.byteLength(text12, "utf8");
9000
9122
  if (totalBytes <= maxBytes)
9001
- return { text: text11, truncated: false, totalBytes, shownBytes: totalBytes };
9002
- const buf = Buffer.from(text11, "utf8");
9123
+ return { text: text12, truncated: false, totalBytes, shownBytes: totalBytes };
9124
+ const buf = Buffer.from(text12, "utf8");
9003
9125
  let cut = maxBytes;
9004
9126
  while (cut > 0 && (buf[cut] & 192) === 128) cut--;
9005
9127
  const head = buf.subarray(0, cut).toString("utf8");
@@ -9030,10 +9152,10 @@ function postprocessResult(result, meta) {
9030
9152
  if (!result.content?.length) return result;
9031
9153
  if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
9032
9154
  const block = result.content[0];
9033
- let text11 = String(block.text ?? "");
9034
- const trunc = truncateForLLM(text11, RESPONSE_MAX_BYTES);
9155
+ let text12 = String(block.text ?? "");
9156
+ const trunc = truncateForLLM(text12, RESPONSE_MAX_BYTES);
9035
9157
  if (trunc.truncated) {
9036
- text11 = trunc.text + "\n\n... " + buildTruncationHint(
9158
+ text12 = trunc.text + "\n\n... " + buildTruncationHint(
9037
9159
  meta.toolName,
9038
9160
  meta.args,
9039
9161
  trunc.totalBytes,
@@ -9047,11 +9169,11 @@ function postprocessResult(result, meta) {
9047
9169
  const parts = [`took ${tookStr}`, sizeStr];
9048
9170
  if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
9049
9171
  if (meta.cached) parts.push("cached");
9050
- text11 = `${text11}
9172
+ text12 = `${text12}
9051
9173
 
9052
9174
  [${parts.join(", ")}]`;
9053
9175
  }
9054
- return { ...result, content: [{ ...block, text: text11 }] };
9176
+ return { ...result, content: [{ ...block, text: text12 }] };
9055
9177
  }
9056
9178
  function buildPipelineScript(commands, shell, marker, stopOnError) {
9057
9179
  if (shell === "powershell") {
@@ -9837,11 +9959,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
9837
9959
  applied_by TEXT
9838
9960
  );
9839
9961
  `.trim();
9840
- function normaliseMigrationSql(sql27) {
9841
- return sql27.replace(/\r\n/g, "\n").trim() + "\n";
9962
+ function normaliseMigrationSql(sql28) {
9963
+ return sql28.replace(/\r\n/g, "\n").trim() + "\n";
9842
9964
  }
9843
- function migrationSha256(sql27) {
9844
- return createHash("sha256").update(sql27.replace(/\r\n/g, "\n"), "utf8").digest("hex");
9965
+ function migrationSha256(sql28) {
9966
+ return createHash("sha256").update(sql28.replace(/\r\n/g, "\n"), "utf8").digest("hex");
9845
9967
  }
9846
9968
  function dollarQuoteTag(value) {
9847
9969
  let tag = "_mcp";
@@ -11590,6 +11712,35 @@ var TOOLS = [
11590
11712
  required: ["slug"]
11591
11713
  }
11592
11714
  },
11715
+ {
11716
+ name: "flag-wiki-page",
11717
+ description: "Report that a knowledge-wiki page was WRONG, outdated or incomplete when you actually used it during real work. This is the wiki's feedback loop: open flags jump the daily reviewer queue and show in the backoffice review panel, so the page gets fixed instead of misleading the next agent. Call it right after discovering the mismatch, with a concrete `detail` of what was wrong and what turned out to be true.",
11718
+ inputSchema: {
11719
+ type: "object",
11720
+ properties: {
11721
+ slug: {
11722
+ type: "string",
11723
+ description: "Slug of the page that misled you, e.g. 'wiki/known-issues/pm2-stale-env-after-reload'."
11724
+ },
11725
+ kind: {
11726
+ type: "string",
11727
+ enum: [
11728
+ "feedback",
11729
+ "stale",
11730
+ "duplicate",
11731
+ "unverified",
11732
+ "obsolete-candidate"
11733
+ ],
11734
+ description: "feedback (default) = wrong/incomplete in practice; stale = outdated vs current reality; duplicate = covers the same thing as another page; obsolete-candidate = the described system/practice appears no longer in use."
11735
+ },
11736
+ detail: {
11737
+ type: "string",
11738
+ description: "What was wrong and what is actually true (be concrete: the claim, the observed reality, ticket/commit refs if any). Min 5 chars."
11739
+ }
11740
+ },
11741
+ required: ["slug", "detail"]
11742
+ }
11743
+ },
11593
11744
  // ----- Cursor Remote (Agent Control) -----
11594
11745
  {
11595
11746
  name: "cursor-remote-list",
@@ -11899,7 +12050,7 @@ async function executeToolCall(name, a, _serverId) {
11899
12050
  ]
11900
12051
  };
11901
12052
  }
11902
- const lines = data.hits.map((hit, index8) => {
12053
+ const lines = data.hits.map((hit, index9) => {
11903
12054
  const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
11904
12055
  const repo = hit.repo ?? "unknown repo";
11905
12056
  const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
@@ -11909,7 +12060,7 @@ async function executeToolCall(name, a, _serverId) {
11909
12060
  ...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
11910
12061
  ].filter(Boolean);
11911
12062
  const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
11912
- return `${index8 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
12063
+ return `${index9 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
11913
12064
  id: ${hit.id}
11914
12065
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
11915
12066
  });
@@ -12050,12 +12201,12 @@ ${facets.join(", ")}` : ""),
12050
12201
  ]
12051
12202
  };
12052
12203
  }
12053
- const lines = data.hits.map((hit, index8) => {
12204
+ const lines = data.hits.map((hit, index9) => {
12054
12205
  const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
12055
12206
  const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
12056
12207
  const sources = hit.sources.length ? `
12057
12208
  sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
12058
- return `${index8 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
12209
+ return `${index9 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
12059
12210
  slug: ${hit.slug}${sources}
12060
12211
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
12061
12212
  });
@@ -12119,6 +12270,66 @@ Sources: ${page.sources.map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
12119
12270
  ]
12120
12271
  };
12121
12272
  }
12273
+ // ----- Knowledge wiki: feedback flag -----
12274
+ case "flag-wiki-page": {
12275
+ const slug = typeof a.slug === "string" ? a.slug.trim() : "";
12276
+ const detail = typeof a.detail === "string" ? a.detail.trim() : "";
12277
+ if (!slug || detail.length < 5) {
12278
+ return {
12279
+ content: [
12280
+ {
12281
+ type: "text",
12282
+ text: "Error: slug and a concrete detail (min 5 chars) are required"
12283
+ }
12284
+ ]
12285
+ };
12286
+ }
12287
+ const kind = typeof a.kind === "string" && [
12288
+ "feedback",
12289
+ "stale",
12290
+ "duplicate",
12291
+ "unverified",
12292
+ "obsolete-candidate"
12293
+ ].includes(a.kind) ? a.kind : "feedback";
12294
+ const res = await fetch(`${dashboardBaseUrl}/api/wiki/flag`, {
12295
+ method: "POST",
12296
+ headers: {
12297
+ "content-type": "application/json",
12298
+ authorization: `Bearer ${apiKey}`
12299
+ },
12300
+ body: JSON.stringify({ slug, kind, detail })
12301
+ });
12302
+ if (!res.ok) {
12303
+ const errDetail = await res.text().catch(() => "");
12304
+ return {
12305
+ content: [
12306
+ {
12307
+ type: "text",
12308
+ text: `Error: wiki flag failed (${res.status}). ${errDetail.slice(0, 300)}`
12309
+ }
12310
+ ]
12311
+ };
12312
+ }
12313
+ const data = await res.json();
12314
+ if (!data.found || !data.flag) {
12315
+ return {
12316
+ content: [
12317
+ {
12318
+ type: "text",
12319
+ text: `No wiki page found for slug "${slug}" \u2014 nothing flagged. Check the slug via search-wiki.`
12320
+ }
12321
+ ]
12322
+ };
12323
+ }
12324
+ return {
12325
+ content: [
12326
+ {
12327
+ type: "text",
12328
+ text: `Flagged ${data.flag.slug} (${kind}). The daily wiki reviewer will pick it up next run; it also shows in the backoffice review queue.`
12329
+ }
12330
+ ]
12331
+ };
12332
+ }
12122
12333
  // ----- Cursor Remote (Agent Control) -----
12123
12334
  case "cursor-remote-list": {
12124
12335
  const onlineOnly = a.onlineOnly === true;
@@ -13202,8 +13413,8 @@ ${sample.join("\n")}${files.length > 5 ? `
13202
13413
  };
13203
13414
  const filtered = sortRows(applyFilter(only.rows));
13204
13415
  if (format === "json") {
13205
- const text12 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
13206
- return { content: [{ type: "text", text: text12 }] };
13416
+ const text13 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
13417
+ return { content: [{ type: "text", text: text13 }] };
13207
13418
  }
13208
13419
  if (groupByProject) {
13209
13420
  const groups = /* @__PURE__ */ new Map();
@@ -13230,8 +13441,8 @@ ${sample.join("\n")}${files.length > 5 ? `
13230
13441
  }
13231
13442
  const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
13232
13443
  const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
13233
- const text11 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
13234
- return { content: [{ type: "text", text: text11 }] };
13444
+ const text12 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
13445
+ return { content: [{ type: "text", text: text12 }] };
13235
13446
  }
13236
13447
  if (format === "json") {
13237
13448
  const lines = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgsoftwarebv/mg-dashboard-mcp",
3
- "version": "7.4.5",
3
+ "version": "7.4.6",
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",