@mgsoftwarebv/mg-dashboard-mcp 7.4.0 → 7.4.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 +448 -468
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -674,26 +674,47 @@ var TRIGGER_TOOLS = [
|
|
|
674
674
|
},
|
|
675
675
|
required: ["action", "project"]
|
|
676
676
|
}
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: "trigger-env",
|
|
680
|
+
description: 'Manage a Trigger.dev project\'s environment variables via the official Management API (the webapp encrypts/decrypts \u2014 no direct SecretStore/DB writes, upgrade-safe). SUPERADMIN ONLY: this reads and writes production secrets across every project. Pick the operation with `action`:\n- "list": all variable names + a secret flag for the env (values are NOT returned \u2014 use "get").\n- "get": one variable. Secret values are masked unless includeSecret=true. Required: key.\n- "set": create or update a variable (upsert) + read-back verify. Required: key, value. Optional: isSecret (only sent when provided, so existing vars keep their flag).\n- "delete": remove a variable. Required: key.\nOn the self-hosted instance only the "prod" environment exists (env defaults to "prod").',
|
|
681
|
+
inputSchema: {
|
|
682
|
+
type: "object",
|
|
683
|
+
properties: {
|
|
684
|
+
action: { type: "string", enum: ["list", "get", "set", "delete"], description: "Which env-var operation to perform." },
|
|
685
|
+
project: { type: "string", description: 'Project slug from trigger-list (e.g. "domeinflits-vsJ2")' },
|
|
686
|
+
key: { type: "string", description: "Environment variable name. Required for get/set/delete." },
|
|
687
|
+
value: { type: "string", description: 'Value to store. Required for action="set".' },
|
|
688
|
+
env: { type: "string", description: 'Environment slug. Default "prod" (only prod exists on self-hosted).' },
|
|
689
|
+
isSecret: { type: "boolean", description: `action="set" only: mark the variable as secret. Omit to leave an existing var's flag unchanged.` },
|
|
690
|
+
includeSecret: { type: "boolean", description: 'action="get" only: reveal a secret value instead of masking it (default false).' }
|
|
691
|
+
},
|
|
692
|
+
required: ["action", "project"]
|
|
693
|
+
}
|
|
677
694
|
}
|
|
678
695
|
];
|
|
679
696
|
var TRIGGER_TOOL_NAMES = new Set(TRIGGER_TOOLS.map((t) => t.name));
|
|
680
697
|
var TRIGGER_TOOL_MODULE_MAP = {
|
|
681
698
|
"trigger-list": "ci_cd",
|
|
682
699
|
"trigger-runs": "ci_cd",
|
|
683
|
-
"trigger-run": "ci_cd"
|
|
700
|
+
"trigger-run": "ci_cd",
|
|
701
|
+
// trigger-env manages production secrets → gated behind the admin `settings`
|
|
702
|
+
// module AND an extra superadmin-only check in index.ts (defense in depth).
|
|
703
|
+
"trigger-env": "settings"
|
|
684
704
|
};
|
|
685
705
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
686
|
-
const sql26 = `SELECT re.\\"apiKey\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
|
|
706
|
+
const sql26 = `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`;
|
|
687
707
|
const cmd = [
|
|
688
708
|
`PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
|
|
689
|
-
`
|
|
690
|
-
'echo "$PORT|$
|
|
709
|
+
`ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql26}" 2>/dev/null | tr -d '[:space:]')`,
|
|
710
|
+
'echo "$PORT|$ROW"'
|
|
691
711
|
].join(" && ");
|
|
692
712
|
const result = await sshExec2(conn, cmd, proxy);
|
|
693
713
|
const output = result.stdout.trim();
|
|
694
714
|
const sepIdx = output.indexOf("|");
|
|
695
715
|
const port = sepIdx > 0 ? output.substring(0, sepIdx) : "";
|
|
696
|
-
const
|
|
716
|
+
const combined = sepIdx > 0 ? output.substring(sepIdx + 1) : "";
|
|
717
|
+
const [apiKey2, projectRef] = combined.split("~~");
|
|
697
718
|
if (!port) {
|
|
698
719
|
throw new Error(
|
|
699
720
|
`Could not find webapp port for ${WA_CONTAINER}. Is the container running?`
|
|
@@ -704,7 +725,7 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
704
725
|
`Could not get API key for project "${projectSlug}". Check if the project slug is correct (use trigger-list).`
|
|
705
726
|
);
|
|
706
727
|
}
|
|
707
|
-
return { port, apiKey: apiKey2 };
|
|
728
|
+
return { port, apiKey: apiKey2, projectRef: projectRef || "" };
|
|
708
729
|
}
|
|
709
730
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
710
731
|
const sql26 = `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`;
|
|
@@ -940,6 +961,108 @@ ${rawJson.substring(0, 500)}` }] };
|
|
|
940
961
|
return await waitForCompletion(conn, proxy, sshExec2, instance, result.id, waitSeconds);
|
|
941
962
|
}
|
|
942
963
|
// -----------------------------------------------------------------
|
|
964
|
+
case "trigger-env": {
|
|
965
|
+
const action = String(args2.action);
|
|
966
|
+
if (!["list", "get", "set", "delete"].includes(action)) {
|
|
967
|
+
return { content: [{ type: "text", text: "Error: action must be one of: list, get, set, delete" }] };
|
|
968
|
+
}
|
|
969
|
+
const project = String(args2.project);
|
|
970
|
+
const env = String(args2.env || "prod");
|
|
971
|
+
const instance = await discoverInstance(project, conn, proxy, sshExec2);
|
|
972
|
+
if (!instance.projectRef) {
|
|
973
|
+
return { content: [{ type: "text", text: `Error: could not resolve project ref for "${project}" (use trigger-list).` }] };
|
|
974
|
+
}
|
|
975
|
+
const apiBase = `/api/v1/projects/${instance.projectRef}/envvars/${encodeURIComponent(env)}`;
|
|
976
|
+
if (action === "list") {
|
|
977
|
+
const raw2 = await triggerApi(conn, proxy, sshExec2, instance, "GET", apiBase);
|
|
978
|
+
let vars;
|
|
979
|
+
try {
|
|
980
|
+
vars = JSON.parse(raw2);
|
|
981
|
+
} catch {
|
|
982
|
+
return { content: [{ type: "text", text: `Invalid API response:
|
|
983
|
+
${raw2.substring(0, 500)}` }] };
|
|
984
|
+
}
|
|
985
|
+
if (!Array.isArray(vars)) {
|
|
986
|
+
return { content: [{ type: "text", text: `Unexpected API response:
|
|
987
|
+
${raw2.substring(0, 500)}` }] };
|
|
988
|
+
}
|
|
989
|
+
if (vars.length === 0) {
|
|
990
|
+
return { content: [{ type: "text", text: `No environment variables for ${project}/${env}.` }] };
|
|
991
|
+
}
|
|
992
|
+
const lines = vars.map((v) => `${v.isSecret ? "[secret]" : " "} ${v.name}`).sort();
|
|
993
|
+
const text9 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
|
|
994
|
+
` + "-".repeat(50) + "\n" + lines.join("\n");
|
|
995
|
+
return { content: [{ type: "text", text: text9 }] };
|
|
996
|
+
}
|
|
997
|
+
if (action === "get") {
|
|
998
|
+
const key2 = String(args2.key ?? "");
|
|
999
|
+
if (!key2) return { content: [{ type: "text", text: 'Error: action="get" requires key' }] };
|
|
1000
|
+
const raw2 = await triggerApi(conn, proxy, sshExec2, instance, "GET", `${apiBase}/${encodeURIComponent(key2)}`);
|
|
1001
|
+
let v;
|
|
1002
|
+
try {
|
|
1003
|
+
v = JSON.parse(raw2);
|
|
1004
|
+
} catch {
|
|
1005
|
+
return { content: [{ type: "text", text: `Invalid API response:
|
|
1006
|
+
${raw2.substring(0, 500)}` }] };
|
|
1007
|
+
}
|
|
1008
|
+
if (v.error) return { content: [{ type: "text", text: `Error: ${v.error}` }] };
|
|
1009
|
+
const includeSecret = args2.includeSecret === true;
|
|
1010
|
+
const shown = v.isSecret && !includeSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (secret \u2014 pass includeSecret:true to reveal)" : v.value ?? "";
|
|
1011
|
+
const label = v.isSecret ? " [secret]" : "";
|
|
1012
|
+
return { content: [{ type: "text", text: `${project}/${env} ${key2}${label}
|
|
1013
|
+
${shown}` }] };
|
|
1014
|
+
}
|
|
1015
|
+
if (action === "set") {
|
|
1016
|
+
const key2 = String(args2.key ?? "");
|
|
1017
|
+
if (!key2) return { content: [{ type: "text", text: 'Error: action="set" requires key' }] };
|
|
1018
|
+
if (args2.value === void 0) return { content: [{ type: "text", text: 'Error: action="set" requires value' }] };
|
|
1019
|
+
const value = String(args2.value);
|
|
1020
|
+
const bodyObj = { name: key2, value };
|
|
1021
|
+
if (typeof args2.isSecret === "boolean") bodyObj.isSecret = args2.isSecret;
|
|
1022
|
+
const raw2 = await triggerApi(conn, proxy, sshExec2, instance, "POST", apiBase, JSON.stringify(bodyObj));
|
|
1023
|
+
let resp2;
|
|
1024
|
+
try {
|
|
1025
|
+
resp2 = JSON.parse(raw2);
|
|
1026
|
+
} catch {
|
|
1027
|
+
return { content: [{ type: "text", text: `Unexpected API response:
|
|
1028
|
+
${raw2.substring(0, 500)}` }] };
|
|
1029
|
+
}
|
|
1030
|
+
if (!resp2.success) {
|
|
1031
|
+
return { content: [{ type: "text", text: `Error: ${resp2.error || raw2.substring(0, 500)}` }] };
|
|
1032
|
+
}
|
|
1033
|
+
const verifyRaw = await triggerApi(conn, proxy, sshExec2, instance, "GET", `${apiBase}/${encodeURIComponent(key2)}`);
|
|
1034
|
+
let vv;
|
|
1035
|
+
try {
|
|
1036
|
+
vv = JSON.parse(verifyRaw);
|
|
1037
|
+
} catch {
|
|
1038
|
+
vv = {};
|
|
1039
|
+
}
|
|
1040
|
+
const verified = vv.value === value;
|
|
1041
|
+
const flag = typeof args2.isSecret === "boolean" ? ` isSecret=${args2.isSecret}` : "";
|
|
1042
|
+
return {
|
|
1043
|
+
content: [{
|
|
1044
|
+
type: "text",
|
|
1045
|
+
text: `Set ${project}/${env} ${key2}${flag}
|
|
1046
|
+
Verify read-back: ${verified ? "OK" : "MISMATCH \u2014 value did not persist as expected"}`
|
|
1047
|
+
}]
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
const key = String(args2.key ?? "");
|
|
1051
|
+
if (!key) return { content: [{ type: "text", text: 'Error: action="delete" requires key' }] };
|
|
1052
|
+
const raw = await triggerApi(conn, proxy, sshExec2, instance, "DELETE", `${apiBase}/${encodeURIComponent(key)}`);
|
|
1053
|
+
let resp;
|
|
1054
|
+
try {
|
|
1055
|
+
resp = JSON.parse(raw);
|
|
1056
|
+
} catch {
|
|
1057
|
+
return { content: [{ type: "text", text: `Unexpected API response:
|
|
1058
|
+
${raw.substring(0, 500)}` }] };
|
|
1059
|
+
}
|
|
1060
|
+
if (!resp.success) {
|
|
1061
|
+
return { content: [{ type: "text", text: `Error: ${resp.error || raw.substring(0, 500)}` }] };
|
|
1062
|
+
}
|
|
1063
|
+
return { content: [{ type: "text", text: `Deleted ${project}/${env} ${key}` }] };
|
|
1064
|
+
}
|
|
1065
|
+
// -----------------------------------------------------------------
|
|
943
1066
|
default:
|
|
944
1067
|
return { content: [{ type: "text", text: `Unknown trigger tool: ${name}` }] };
|
|
945
1068
|
}
|
|
@@ -6970,6 +7093,75 @@ var TOOL_MODULE_MAP = {
|
|
|
6970
7093
|
"cursor-remote-run": "cursor_remote",
|
|
6971
7094
|
...TRIGGER_TOOL_MODULE_MAP
|
|
6972
7095
|
};
|
|
7096
|
+
var SUPERADMIN_ONLY_TOOLS = /* @__PURE__ */ new Set(["trigger-env"]);
|
|
7097
|
+
function resolveContentDispatch(tool, a) {
|
|
7098
|
+
const action = typeof a.action === "string" ? a.action : "";
|
|
7099
|
+
const { action: _drop, ...params } = a;
|
|
7100
|
+
const registry = (backendAction) => ({
|
|
7101
|
+
route: "content-registry",
|
|
7102
|
+
body: { action: backendAction, params }
|
|
7103
|
+
});
|
|
7104
|
+
const managed = (backendAction) => ({
|
|
7105
|
+
route: "managed-site",
|
|
7106
|
+
body: { action: backendAction, params }
|
|
7107
|
+
});
|
|
7108
|
+
switch (tool) {
|
|
7109
|
+
// Singletons keep their original names (no `action` discriminator).
|
|
7110
|
+
case "save_content_quality_run":
|
|
7111
|
+
return registry("save_content_quality_run");
|
|
7112
|
+
case "link_content_pack_to_ticket":
|
|
7113
|
+
return registry("link_content_pack_to_ticket");
|
|
7114
|
+
case "content-source":
|
|
7115
|
+
if (action === "save") return registry("save_content_source");
|
|
7116
|
+
if (action === "search") return registry("search_content_sources");
|
|
7117
|
+
if (action === "get") {
|
|
7118
|
+
if (typeof a.id === "string" && a.id) return registry("get_content_source_by_id");
|
|
7119
|
+
if (typeof a.url === "string" && a.url) return registry("get_content_source_by_url");
|
|
7120
|
+
return { error: "content-source action=get needs `id` or `url`" };
|
|
7121
|
+
}
|
|
7122
|
+
return { error: "content-source: action must be save | get | search" };
|
|
7123
|
+
case "research":
|
|
7124
|
+
if (action === "topic")
|
|
7125
|
+
return {
|
|
7126
|
+
route: "research-topic",
|
|
7127
|
+
body: {
|
|
7128
|
+
topic: typeof a.topic === "string" ? a.topic : void 0,
|
|
7129
|
+
urls: Array.isArray(a.urls) ? a.urls : void 0,
|
|
7130
|
+
discover: a.discover && typeof a.discover === "object" ? a.discover : void 0,
|
|
7131
|
+
includeRawText: typeof a.includeRawText === "boolean" ? a.includeRawText : void 0,
|
|
7132
|
+
maxRawTextChars: typeof a.maxRawTextChars === "number" ? a.maxRawTextChars : void 0
|
|
7133
|
+
}
|
|
7134
|
+
};
|
|
7135
|
+
if (action === "save") return registry("save_research_pack");
|
|
7136
|
+
if (action === "get") return registry("get_research_pack");
|
|
7137
|
+
return { error: "research: action must be topic | save | get" };
|
|
7138
|
+
case "content-corpus":
|
|
7139
|
+
if (action === "save") return registry("save_content_corpus_item");
|
|
7140
|
+
if (action === "list") return registry("list_project_content_corpus");
|
|
7141
|
+
if (action === "context") return registry("get_content_context_for_project");
|
|
7142
|
+
return { error: "content-corpus: action must be save | list | context" };
|
|
7143
|
+
case "content-snapshot":
|
|
7144
|
+
if (action === "list") return registry("list_content_snapshots");
|
|
7145
|
+
if (action === "prune") return registry("prune_content_snapshots");
|
|
7146
|
+
return { error: "content-snapshot: action must be list | prune" };
|
|
7147
|
+
case "site-candidate":
|
|
7148
|
+
if (action === "add") return registry("add_site_candidates");
|
|
7149
|
+
if (action === "list") return registry("list_site_candidates");
|
|
7150
|
+
if (action === "set-status") return registry("set_candidate_status");
|
|
7151
|
+
return { error: "site-candidate: action must be add | list | set-status" };
|
|
7152
|
+
case "managed-site":
|
|
7153
|
+
if (action === "register") return managed("register_managed_site");
|
|
7154
|
+
if (action === "update") return managed("update_managed_site");
|
|
7155
|
+
if (action === "list") return managed("list_managed_sites");
|
|
7156
|
+
return { error: "managed-site: action must be register | update | list" };
|
|
7157
|
+
case "content-sync":
|
|
7158
|
+
if (action === "trigger") return managed("trigger_content_sync");
|
|
7159
|
+
if (action === "status") return managed("get_content_sync_status");
|
|
7160
|
+
return { error: "content-sync: action must be trigger | status" };
|
|
7161
|
+
default:
|
|
7162
|
+
return { error: `unknown content tool ${tool}` };
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
6973
7165
|
function normalizeAllowedTools(value) {
|
|
6974
7166
|
if (!Array.isArray(value)) return null;
|
|
6975
7167
|
const tools = value.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean);
|
|
@@ -8596,30 +8788,19 @@ var NO_FOOTER_TOOLS = /* @__PURE__ */ new Set();
|
|
|
8596
8788
|
var RAW_JSON_TOOLS = /* @__PURE__ */ new Set([
|
|
8597
8789
|
"get_mg_dashboard_commits",
|
|
8598
8790
|
"extract_article",
|
|
8599
|
-
|
|
8600
|
-
//
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
"
|
|
8604
|
-
"
|
|
8605
|
-
"
|
|
8606
|
-
"
|
|
8607
|
-
"
|
|
8608
|
-
"list_project_content_corpus",
|
|
8609
|
-
"get_content_context_for_project",
|
|
8791
|
+
// Content family (2026-DASMG-040/041/042) — compact JSON consumed verbatim.
|
|
8792
|
+
// Consolidated action-based tools (the Cursor surface); each proxies to the
|
|
8793
|
+
// backend content-registry / managed-site / research-topic dispatchers, which
|
|
8794
|
+
// still speak the original fine-grained action names.
|
|
8795
|
+
"research",
|
|
8796
|
+
"content-source",
|
|
8797
|
+
"content-corpus",
|
|
8798
|
+
"content-snapshot",
|
|
8799
|
+
"site-candidate",
|
|
8610
8800
|
"save_content_quality_run",
|
|
8611
|
-
"list_content_snapshots",
|
|
8612
8801
|
"link_content_pack_to_ticket",
|
|
8613
|
-
"
|
|
8614
|
-
"
|
|
8615
|
-
"list_site_candidates",
|
|
8616
|
-
"set_candidate_status",
|
|
8617
|
-
// Managed site content sync (2026-DASMG-042) — compact JSON consumed verbatim.
|
|
8618
|
-
"register_managed_site",
|
|
8619
|
-
"update_managed_site",
|
|
8620
|
-
"list_managed_sites",
|
|
8621
|
-
"trigger_content_sync",
|
|
8622
|
-
"get_content_sync_status"
|
|
8802
|
+
"managed-site",
|
|
8803
|
+
"content-sync"
|
|
8623
8804
|
]);
|
|
8624
8805
|
var TOOL_CACHE_TTL_MS = {
|
|
8625
8806
|
"list-servers": 6e4,
|
|
@@ -10770,7 +10951,7 @@ var TOOLS = [
|
|
|
10770
10951
|
},
|
|
10771
10952
|
{
|
|
10772
10953
|
name: "extract_article",
|
|
10773
|
-
description: "Extract readable article/content data from ONE PUBLIC URL, with a browser-render fallback for pages that block a normal fetch (403) or only render with JavaScript (ticket 2026-DASMG-039). Returns title, author, publishedAt, source, summary, facts[], optional rawText (capped), canonicalUrl, the `extractionMethod` used (http | browser | jsonld | rss | amp | metadata_only) and an explicit `limitations` array (e.g. blocked_without_browser, paywall_detected, partial_content, rawtext_truncated). PUBLIC URLs only \u2014 SSRF-guarded (private/loopback/metadata addresses rejected), no paywall bypass, no credentials; the source/canonical URL is always preserved. Use it to turn a news/article link into safe factual source material for agents; it never auto-publishes or rewrites. For several sources + cross-checking, use
|
|
10954
|
+
description: "Extract readable article/content data from ONE PUBLIC URL, with a browser-render fallback for pages that block a normal fetch (403) or only render with JavaScript (ticket 2026-DASMG-039). Returns title, author, publishedAt, source, summary, facts[], optional rawText (capped), canonicalUrl, the `extractionMethod` used (http | browser | jsonld | rss | amp | metadata_only) and an explicit `limitations` array (e.g. blocked_without_browser, paywall_detected, partial_content, rawtext_truncated). PUBLIC URLs only \u2014 SSRF-guarded (private/loopback/metadata addresses rejected), no paywall bypass, no credentials; the source/canonical URL is always preserved. Use it to turn a news/article link into safe factual source material for agents; it never auto-publishes or rewrites. For several sources + cross-checking, use `research` with action=topic.",
|
|
10774
10955
|
inputSchema: {
|
|
10775
10956
|
type: "object",
|
|
10776
10957
|
properties: {
|
|
@@ -10790,53 +10971,69 @@ var TOOLS = [
|
|
|
10790
10971
|
required: ["url"]
|
|
10791
10972
|
}
|
|
10792
10973
|
},
|
|
10974
|
+
// ----- Research + evidence packs (2026-DASMG-040/041), consolidated -----
|
|
10793
10975
|
{
|
|
10794
|
-
name: "
|
|
10795
|
-
description: "
|
|
10976
|
+
name: "research",
|
|
10977
|
+
description: 'Research + evidence-pack toolkit (2026-DASMG-040/041). Pick the operation with `action`:\n- "topic": turn SEVERAL public sources on one topic into a single cross-checked, sourced \'evidence pack\'. Composes extract_article per URL: collect, extract, cross-check, then hand a downstream agent a safe base to write ORIGINAL content from. Modes: curated (`urls` + optional `topic`), discovery (`topic` + `discover.enabled` \u2014 finds source URLs for you), hybrid (`urls` + `discover`). Returns `sources`, `discovery` report, `verifiedFacts`, `conflicts`, `uniqueClaims`, `openQuestions`, `verbatimWarnings`, `limitations`. Structured EVIDENCE ONLY \u2014 never ready-to-publish prose; every claim traces to a source id. PUBLIC URLs only (SSRF-guarded).\n- "save": persist an evidence pack. Pass an already-built `pack`, or `urls`/`topic` (+ optional `discover`) to research then save. Each source is deduped into content_sources and linked so packs are reusable without re-crawling. Returns pack id + compact counts + linked source ids.\n- "get": fetch a stored pack by `id`. Compact by default; set `includeSources` / `includeClaims` for the full arrays.',
|
|
10796
10978
|
inputSchema: {
|
|
10797
10979
|
type: "object",
|
|
10798
10980
|
properties: {
|
|
10981
|
+
action: {
|
|
10982
|
+
type: "string",
|
|
10983
|
+
enum: ["topic", "save", "get"],
|
|
10984
|
+
description: "topic = build a pack; save = persist a pack; get = fetch a stored pack."
|
|
10985
|
+
},
|
|
10986
|
+
id: {
|
|
10987
|
+
type: "string",
|
|
10988
|
+
description: "action=get: content_evidence_packs.id (uuid)."
|
|
10989
|
+
},
|
|
10799
10990
|
topic: {
|
|
10800
10991
|
type: "string",
|
|
10801
|
-
description: "
|
|
10992
|
+
description: "action=topic/save: the topic the pack is about (also the search query in discovery mode)."
|
|
10802
10993
|
},
|
|
10803
10994
|
urls: {
|
|
10804
10995
|
type: "array",
|
|
10805
10996
|
items: { type: "string" },
|
|
10806
|
-
description: "
|
|
10997
|
+
description: "action=topic/save: curated public http(s) URLs to extract and cross-check (max 20)."
|
|
10998
|
+
},
|
|
10999
|
+
pack: {
|
|
11000
|
+
type: "object",
|
|
11001
|
+
description: "action=save: an already-built pack payload (skips re-researching)."
|
|
10807
11002
|
},
|
|
10808
11003
|
discover: {
|
|
10809
11004
|
type: "object",
|
|
10810
|
-
description: "
|
|
11005
|
+
description: "action=topic/save: discovery options. enabled=true auto-finds source URLs for `topic` via an env-gated web-search backend + keyless sitemap crawl over allowDomains. Curated `urls` are kept and topped up to maxSources; degrades gracefully when no backend is configured.",
|
|
10811
11006
|
properties: {
|
|
10812
11007
|
enabled: { type: "boolean", description: "Turn discovery on (default off)." },
|
|
10813
11008
|
maxSources: { type: "number", description: "Cap on total sources, curated + discovered (1-12, default 8)." },
|
|
10814
|
-
recencyDays: { type: "number", description: "Prefer/keep sources no older than this (
|
|
11009
|
+
recencyDays: { type: "number", description: "Prefer/keep sources no older than this (sitemap lastmod)." },
|
|
10815
11010
|
locale: { type: "string", description: "Locale hint, e.g. nl-NL." },
|
|
10816
|
-
allowDomains: { type: "array", items: { type: "string" }, description: "
|
|
10817
|
-
blockDomains: { type: "array", items: { type: "string" }, description: "Domains to exclude from discovery." }
|
|
11011
|
+
allowDomains: { type: "array", items: { type: "string" }, description: "Prefer/guarantee domains; targets for sitemap top-up." },
|
|
11012
|
+
blockDomains: { type: "array", items: { type: "string" }, description: "Domains to exclude from discovery." },
|
|
11013
|
+
sourcePolicy: {
|
|
11014
|
+
type: "string",
|
|
11015
|
+
enum: ["official_only"],
|
|
11016
|
+
description: "Restrict discovered sources to official whitelist (grid operators, ACM, government)."
|
|
11017
|
+
}
|
|
10818
11018
|
}
|
|
10819
11019
|
},
|
|
10820
11020
|
includeRawText: {
|
|
10821
11021
|
type: "boolean",
|
|
10822
|
-
description: "
|
|
11022
|
+
description: "action=topic: reserved; claim mining always reads body text up to maxRawTextChars."
|
|
10823
11023
|
},
|
|
10824
11024
|
maxRawTextChars: {
|
|
10825
11025
|
type: "number",
|
|
10826
|
-
description: "
|
|
10827
|
-
}
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
properties: {
|
|
10838
|
-
url: { type: "string", description: "Source URL (required)." },
|
|
10839
|
-
canonicalUrl: { type: "string", description: "Canonical URL used for dedupe." },
|
|
11026
|
+
description: "action=topic: per-source body cap for claim mining (default 4000, max 20000)."
|
|
11027
|
+
},
|
|
11028
|
+
includeSources: { type: "boolean", description: "action=get: return the source list." },
|
|
11029
|
+
includeClaims: {
|
|
11030
|
+
type: "boolean",
|
|
11031
|
+
description: "action=get: return verified facts / conflicts / unique claims."
|
|
11032
|
+
},
|
|
11033
|
+
locale: { type: "string", description: "action=save: locale of the pack." },
|
|
11034
|
+
projectKey: { type: "string", description: "action=save: project identifier." },
|
|
11035
|
+
siteKey: { type: "string", description: "action=save: site identifier." },
|
|
11036
|
+
ticketNumber: { type: "string", description: "action=save: related ticket number." },
|
|
10840
11037
|
sourceType: {
|
|
10841
11038
|
type: "string",
|
|
10842
11039
|
enum: [
|
|
@@ -10849,112 +11046,31 @@ var TOOLS = [
|
|
|
10849
11046
|
"supplier",
|
|
10850
11047
|
"unknown"
|
|
10851
11048
|
],
|
|
10852
|
-
description: "
|
|
11049
|
+
description: "action=save: provenance label for linked sources."
|
|
10853
11050
|
},
|
|
10854
|
-
|
|
10855
|
-
author: { type: "string", description: "Author." },
|
|
10856
|
-
publishedAt: { type: "string", description: "Publish date (any parseable format)." },
|
|
10857
|
-
summary: { type: "string", description: "Short factual summary." },
|
|
10858
|
-
facts: { type: "array", items: { type: "string" }, description: "Key facts." },
|
|
10859
|
-
limitations: { type: "array", items: { type: "string" } },
|
|
10860
|
-
fullText: { type: "string", description: "Extracted body text." },
|
|
10861
|
-
language: { type: "string", description: "Language code." },
|
|
10862
|
-
projectKey: { type: "string", description: "Project/site identifier." },
|
|
10863
|
-
siteKey: { type: "string", description: "Site identifier." },
|
|
10864
|
-
ticketNumber: { type: "string", description: "Related ticket number." },
|
|
10865
|
-
metadata: { type: "object" },
|
|
10866
|
-
reExtract: {
|
|
10867
|
-
type: "boolean",
|
|
10868
|
-
description: "Force a re-fetch even when fields are supplied."
|
|
10869
|
-
}
|
|
10870
|
-
},
|
|
10871
|
-
required: ["url"]
|
|
10872
|
-
}
|
|
10873
|
-
},
|
|
10874
|
-
{
|
|
10875
|
-
name: "get_content_source_by_url",
|
|
10876
|
-
description: "Look up a stored source by URL or canonical URL (2026-DASMG-041). Compact by default (id + summary + counts); set `includeFullText` for the body. Use to answer 'was this link already scanned?' before re-extracting.",
|
|
10877
|
-
inputSchema: {
|
|
10878
|
-
type: "object",
|
|
10879
|
-
properties: {
|
|
10880
|
-
url: { type: "string", description: "URL or canonical URL." },
|
|
10881
|
-
includeFullText: { type: "boolean", description: "Return full_text." }
|
|
10882
|
-
},
|
|
10883
|
-
required: ["url"]
|
|
10884
|
-
}
|
|
10885
|
-
},
|
|
10886
|
-
{
|
|
10887
|
-
name: "get_content_source_by_id",
|
|
10888
|
-
description: "Fetch a stored source by id (2026-DASMG-041). Compact by default; set `includeFullText` for the body.",
|
|
10889
|
-
inputSchema: {
|
|
10890
|
-
type: "object",
|
|
10891
|
-
properties: {
|
|
10892
|
-
id: { type: "string", description: "content_sources.id (uuid)." },
|
|
10893
|
-
includeFullText: { type: "boolean", description: "Return full_text." }
|
|
11051
|
+
metadata: { type: "object", description: "action=save: arbitrary metadata." }
|
|
10894
11052
|
},
|
|
10895
|
-
required: ["
|
|
11053
|
+
required: ["action"]
|
|
10896
11054
|
}
|
|
10897
11055
|
},
|
|
11056
|
+
// ----- Content sources registry (2026-DASMG-041), consolidated -----
|
|
10898
11057
|
{
|
|
10899
|
-
name: "
|
|
10900
|
-
description:
|
|
11058
|
+
name: "content-source",
|
|
11059
|
+
description: 'Stored external/evidence sources registry (2026-DASMG-041). Pick the operation with `action`:\n- "save": persist (dedupe + upsert) ONE fetched source \u2014 the durable companion to extract_article. Pass a `url` (re-extracts internally) and/or already-extracted fields (title/summary/facts/fullText). Dedupes on canonical URL then text hash, snapshots the previous version when text changed. Returns the stored id + compact summary + created/updated/snapshotted. Required: url.\n- "get": look up ONE stored source by `id` OR by `url`/canonical url (\'was this link already scanned?\'). Compact by default; set `includeFullText` for the body. Required: id or url.\n- "search": full-text + filter search over stored sources (query, domain, sourceType, projectKey, ticketNumber, dateFrom/dateTo). Compact, paginated. Use to reuse evidence already in the registry.',
|
|
10901
11060
|
inputSchema: {
|
|
10902
11061
|
type: "object",
|
|
10903
11062
|
properties: {
|
|
10904
|
-
|
|
10905
|
-
domain: { type: "string", description: "Exact domain filter." },
|
|
10906
|
-
sourceType: {
|
|
11063
|
+
action: {
|
|
10907
11064
|
type: "string",
|
|
10908
|
-
enum: [
|
|
10909
|
-
|
|
10910
|
-
"client_site",
|
|
10911
|
-
"external_source",
|
|
10912
|
-
"competitor",
|
|
10913
|
-
"news",
|
|
10914
|
-
"government",
|
|
10915
|
-
"supplier",
|
|
10916
|
-
"unknown"
|
|
10917
|
-
]
|
|
11065
|
+
enum: ["save", "get", "search"],
|
|
11066
|
+
description: "save = upsert one source; get = fetch by id/url; search = filtered search."
|
|
10918
11067
|
},
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
limit: { type: "integer", description: "Max rows (default 20, max 100)." },
|
|
10924
|
-
offset: { type: "integer", description: "Pagination offset." },
|
|
10925
|
-
includeFullText: { type: "boolean", description: "Return full_text per row." }
|
|
10926
|
-
}
|
|
10927
|
-
}
|
|
10928
|
-
},
|
|
10929
|
-
{
|
|
10930
|
-
name: "save_research_pack",
|
|
10931
|
-
description: "Persist a research_topic evidence pack (2026-DASMG-041): cross-checked verified facts, conflicts, unique claims, open questions. Pass an already-built `pack`, or `urls`/`topic` to research then save. Add `discover` (with a topic) to auto-find sources before saving \u2014 the same discovery research_topic does (2026-DASMG-040). Each source is deduped into content_sources and linked via the join table, so packs can be reused without re-crawling. Returns the pack id + compact counts + linked source ids.",
|
|
10932
|
-
inputSchema: {
|
|
10933
|
-
type: "object",
|
|
10934
|
-
properties: {
|
|
10935
|
-
pack: { type: "object", description: "A research_topic result payload." },
|
|
10936
|
-
urls: {
|
|
10937
|
-
type: "array",
|
|
10938
|
-
items: { type: "string" },
|
|
10939
|
-
description: "URLs to research if no pack is given."
|
|
10940
|
-
},
|
|
10941
|
-
topic: { type: "string", description: "Topic to research if no pack is given." },
|
|
10942
|
-
discover: {
|
|
10943
|
-
type: "object",
|
|
10944
|
-
description: "Discovery options (same shape as research_topic). With a topic, enabled=true auto-finds source URLs before saving. Ignored when `pack` is supplied.",
|
|
10945
|
-
properties: {
|
|
10946
|
-
enabled: { type: "boolean" },
|
|
10947
|
-
maxSources: { type: "number" },
|
|
10948
|
-
recencyDays: { type: "number" },
|
|
10949
|
-
locale: { type: "string" },
|
|
10950
|
-
allowDomains: { type: "array", items: { type: "string" } },
|
|
10951
|
-
blockDomains: { type: "array", items: { type: "string" } }
|
|
10952
|
-
}
|
|
11068
|
+
id: { type: "string", description: "action=get: content_sources.id (uuid)." },
|
|
11069
|
+
url: {
|
|
11070
|
+
type: "string",
|
|
11071
|
+
description: "action=save (required) / action=get: source URL or canonical URL."
|
|
10953
11072
|
},
|
|
10954
|
-
|
|
10955
|
-
projectKey: { type: "string", description: "Project identifier." },
|
|
10956
|
-
siteKey: { type: "string", description: "Site identifier." },
|
|
10957
|
-
ticketNumber: { type: "string", description: "Related ticket number." },
|
|
11073
|
+
canonicalUrl: { type: "string", description: "action=save: canonical URL used for dedupe." },
|
|
10958
11074
|
sourceType: {
|
|
10959
11075
|
type: "string",
|
|
10960
11076
|
enum: [
|
|
@@ -10966,38 +11082,55 @@ var TOOLS = [
|
|
|
10966
11082
|
"government",
|
|
10967
11083
|
"supplier",
|
|
10968
11084
|
"unknown"
|
|
10969
|
-
]
|
|
11085
|
+
],
|
|
11086
|
+
description: "Provenance label. own_site/client_site = managed content; the rest are evidence only. (save/search filter)"
|
|
10970
11087
|
},
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
11088
|
+
title: { type: "string", description: "action=save: title." },
|
|
11089
|
+
author: { type: "string", description: "action=save: author." },
|
|
11090
|
+
publishedAt: { type: "string", description: "action=save: publish date (any parseable format)." },
|
|
11091
|
+
summary: { type: "string", description: "action=save: short factual summary." },
|
|
11092
|
+
facts: { type: "array", items: { type: "string" }, description: "action=save: key facts." },
|
|
11093
|
+
limitations: { type: "array", items: { type: "string" } },
|
|
11094
|
+
fullText: { type: "string", description: "action=save: extracted body text." },
|
|
11095
|
+
language: { type: "string", description: "action=save: language code." },
|
|
11096
|
+
projectKey: { type: "string", description: "Project/site identifier (save/search)." },
|
|
11097
|
+
siteKey: { type: "string", description: "action=save: site identifier." },
|
|
11098
|
+
ticketNumber: { type: "string", description: "Related ticket number (save/search filter)." },
|
|
11099
|
+
metadata: { type: "object" },
|
|
11100
|
+
reExtract: {
|
|
11101
|
+
type: "boolean",
|
|
11102
|
+
description: "action=save: force a re-fetch even when fields are supplied."
|
|
11103
|
+
},
|
|
11104
|
+
query: { type: "string", description: "action=search: free-text search (title/summary/body)." },
|
|
11105
|
+
domain: { type: "string", description: "action=search: exact domain filter." },
|
|
11106
|
+
dateFrom: { type: "string", description: "action=search: last-seen lower bound (ISO/date)." },
|
|
11107
|
+
dateTo: { type: "string", description: "action=search: last-seen upper bound (ISO/date)." },
|
|
11108
|
+
limit: { type: "integer", description: "action=search: max rows (default 20, max 100)." },
|
|
11109
|
+
offset: { type: "integer", description: "action=search: pagination offset." },
|
|
11110
|
+
includeFullText: {
|
|
10984
11111
|
type: "boolean",
|
|
10985
|
-
description: "
|
|
11112
|
+
description: "action=get/search: return full_text (per row for search)."
|
|
10986
11113
|
}
|
|
10987
11114
|
},
|
|
10988
|
-
required: ["
|
|
11115
|
+
required: ["action"]
|
|
10989
11116
|
}
|
|
10990
11117
|
},
|
|
11118
|
+
// ----- Own/managed content corpus (2026-DASMG-041), consolidated -----
|
|
10991
11119
|
{
|
|
10992
|
-
name: "
|
|
10993
|
-
description:
|
|
11120
|
+
name: "content-corpus",
|
|
11121
|
+
description: 'Your/your client\'s OWN managed pages (the corpus), 2026-DASMG-041. Pick the operation with `action`:\n- "save": upsert ONE own/managed page keyed by (projectKey, canonical url) \u2014 the current state of content you own. Pass a `url` (re-extracts) and/or title/h1/headings/fullText/internalLinks. Snapshots the previous version when text changed. For your pages, not external evidence. Required: projectKey, url.\n- "list": list corpus items for a project, filter by domain/sourceType/query. Compact, paginated. Required: projectKey.\n- "context": aggregate brief for a content agent \u2014 recent corpus items, recent evidence packs and related sources for a project (optionally focused by topic/url), plus totals. One call to brief an agent on what already exists. Required: projectKey.',
|
|
10994
11122
|
inputSchema: {
|
|
10995
11123
|
type: "object",
|
|
10996
11124
|
properties: {
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11125
|
+
action: {
|
|
11126
|
+
type: "string",
|
|
11127
|
+
enum: ["save", "list", "context"],
|
|
11128
|
+
description: "save = upsert one own page; list = list corpus items; context = aggregate agent brief."
|
|
11129
|
+
},
|
|
11130
|
+
projectKey: { type: "string", description: "Project identifier (required for all actions)." },
|
|
11131
|
+
url: { type: "string", description: "action=save (required) / action=context: page URL focus." },
|
|
11132
|
+
siteKey: { type: "string", description: "action=save: site identifier." },
|
|
11133
|
+
canonicalUrl: { type: "string", description: "action=save: canonical URL." },
|
|
11001
11134
|
sourceType: {
|
|
11002
11135
|
type: "string",
|
|
11003
11136
|
enum: [
|
|
@@ -11010,76 +11143,42 @@ var TOOLS = [
|
|
|
11010
11143
|
"supplier",
|
|
11011
11144
|
"unknown"
|
|
11012
11145
|
],
|
|
11013
|
-
description: "
|
|
11146
|
+
description: "action=save: defaults to own_site. action=list: filter."
|
|
11014
11147
|
},
|
|
11015
|
-
pagePath: { type: "string", description: "
|
|
11016
|
-
title: { type: "string", description: "
|
|
11017
|
-
h1: { type: "string", description: "
|
|
11148
|
+
pagePath: { type: "string", description: "action=save: path within the site." },
|
|
11149
|
+
title: { type: "string", description: "action=save: title." },
|
|
11150
|
+
h1: { type: "string", description: "action=save: main heading." },
|
|
11018
11151
|
headings: {
|
|
11019
11152
|
type: "array",
|
|
11020
11153
|
items: { type: "object" },
|
|
11021
|
-
description: "
|
|
11154
|
+
description: "action=save: headings ({level,text})."
|
|
11022
11155
|
},
|
|
11023
|
-
wordCount: { type: "integer", description: "
|
|
11024
|
-
fullText: { type: "string", description: "
|
|
11156
|
+
wordCount: { type: "integer", description: "action=save: word count." },
|
|
11157
|
+
fullText: { type: "string", description: "action=save: full page text." },
|
|
11025
11158
|
internalLinks: {
|
|
11026
11159
|
type: "array",
|
|
11027
11160
|
items: { type: "object" },
|
|
11028
|
-
description: "
|
|
11161
|
+
description: "action=save: internal links ({url,text})."
|
|
11029
11162
|
},
|
|
11030
11163
|
externalLinks: { type: "array", items: { type: "object" } },
|
|
11031
|
-
language: { type: "string", description: "
|
|
11032
|
-
ticketNumber: { type: "string", description: "
|
|
11164
|
+
language: { type: "string", description: "action=save: language code." },
|
|
11165
|
+
ticketNumber: { type: "string", description: "action=save: related ticket number." },
|
|
11033
11166
|
metadata: { type: "object" },
|
|
11034
11167
|
reExtract: {
|
|
11035
11168
|
type: "boolean",
|
|
11036
|
-
description: "
|
|
11037
|
-
}
|
|
11038
|
-
},
|
|
11039
|
-
required: ["projectKey", "url"]
|
|
11040
|
-
}
|
|
11041
|
-
},
|
|
11042
|
-
{
|
|
11043
|
-
name: "list_project_content_corpus",
|
|
11044
|
-
description: "List corpus items for a project (2026-DASMG-041), filter by domain/sourceType/query. Compact, paginated. Use to see content coverage and reuse own pages.",
|
|
11045
|
-
inputSchema: {
|
|
11046
|
-
type: "object",
|
|
11047
|
-
properties: {
|
|
11048
|
-
projectKey: { type: "string", description: "Project identifier (required)." },
|
|
11049
|
-
domain: { type: "string", description: "Domain filter." },
|
|
11050
|
-
sourceType: {
|
|
11051
|
-
type: "string",
|
|
11052
|
-
enum: [
|
|
11053
|
-
"own_site",
|
|
11054
|
-
"client_site",
|
|
11055
|
-
"external_source",
|
|
11056
|
-
"competitor",
|
|
11057
|
-
"news",
|
|
11058
|
-
"government",
|
|
11059
|
-
"supplier",
|
|
11060
|
-
"unknown"
|
|
11061
|
-
]
|
|
11169
|
+
description: "action=save: force a re-fetch even when fields are supplied."
|
|
11062
11170
|
},
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
name: "get_content_context_for_project",
|
|
11073
|
-
description: "Aggregate context for a content agent (2026-DASMG-041): recent corpus items, recent evidence packs and related sources for a project (optionally focused by topic or url), plus totals. One call to brief an agent on what already exists before it writes.",
|
|
11074
|
-
inputSchema: {
|
|
11075
|
-
type: "object",
|
|
11076
|
-
properties: {
|
|
11077
|
-
projectKey: { type: "string", description: "Project identifier (required)." },
|
|
11078
|
-
topic: { type: "string", description: "Optional topic focus." },
|
|
11079
|
-
url: { type: "string", description: "Optional url focus." },
|
|
11080
|
-
limit: { type: "integer", description: "Max items per section (default 10)." }
|
|
11171
|
+
domain: { type: "string", description: "action=list: domain filter." },
|
|
11172
|
+
query: { type: "string", description: "action=list: free-text search." },
|
|
11173
|
+
topic: { type: "string", description: "action=context: optional topic focus." },
|
|
11174
|
+
limit: {
|
|
11175
|
+
type: "integer",
|
|
11176
|
+
description: "action=list: max rows (default 20). action=context: max items per section (default 10)."
|
|
11177
|
+
},
|
|
11178
|
+
offset: { type: "integer", description: "action=list: pagination offset." },
|
|
11179
|
+
includeFullText: { type: "boolean", description: "action=list: return full_text per row." }
|
|
11081
11180
|
},
|
|
11082
|
-
required: ["projectKey"]
|
|
11181
|
+
required: ["action", "projectKey"]
|
|
11083
11182
|
}
|
|
11084
11183
|
},
|
|
11085
11184
|
{
|
|
@@ -11110,19 +11209,31 @@ var TOOLS = [
|
|
|
11110
11209
|
required: ["tool"]
|
|
11111
11210
|
}
|
|
11112
11211
|
},
|
|
11212
|
+
// ----- Content snapshots / version history (2026-DASMG-041), consolidated -----
|
|
11113
11213
|
{
|
|
11114
|
-
name: "
|
|
11115
|
-
description:
|
|
11214
|
+
name: "content-snapshot",
|
|
11215
|
+
description: 'Historical version snapshots of corpus items / sources (2026-DASMG-041). Pick the operation with `action`:\n- "list": list snapshots for a corpus item or source by id, url or canonical url, newest first. Compact; set `includeFullText` for the captured body. Use to see how content changed over time.\n- "prune": retention cleanup \u2014 keep the newest N snapshots per item and drop the rest older than the retention window (defaults keepPerItem 20, olderThanDays 365). Returns how many were pruned.',
|
|
11116
11216
|
inputSchema: {
|
|
11117
11217
|
type: "object",
|
|
11118
11218
|
properties: {
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11219
|
+
action: {
|
|
11220
|
+
type: "string",
|
|
11221
|
+
enum: ["list", "prune"],
|
|
11222
|
+
description: "list = show snapshots; prune = retention cleanup."
|
|
11223
|
+
},
|
|
11224
|
+
corpusItemId: { type: "string", description: "action=list: content_corpus_items.id." },
|
|
11225
|
+
contentSourceId: { type: "string", description: "action=list: content_sources.id." },
|
|
11226
|
+
url: { type: "string", description: "action=list: URL to match." },
|
|
11227
|
+
canonicalUrl: { type: "string", description: "action=list: canonical URL to match." },
|
|
11228
|
+
limit: { type: "integer", description: "action=list: max rows (default 20)." },
|
|
11229
|
+
includeFullText: { type: "boolean", description: "action=list: return captured full_text." },
|
|
11230
|
+
keepPerItem: { type: "integer", description: "action=prune: snapshots to keep per item (default 20)." },
|
|
11231
|
+
olderThanDays: {
|
|
11232
|
+
type: "integer",
|
|
11233
|
+
description: "action=prune: only prune snapshots older than this many days (default 365)."
|
|
11234
|
+
}
|
|
11235
|
+
},
|
|
11236
|
+
required: ["action"]
|
|
11126
11237
|
}
|
|
11127
11238
|
},
|
|
11128
11239
|
{
|
|
@@ -11137,31 +11248,41 @@ var TOOLS = [
|
|
|
11137
11248
|
required: ["packId", "ticketNumber"]
|
|
11138
11249
|
}
|
|
11139
11250
|
},
|
|
11251
|
+
// ----- Content/site candidates (2026-DASMG-041), consolidated -----
|
|
11140
11252
|
{
|
|
11141
|
-
name: "
|
|
11142
|
-
description:
|
|
11143
|
-
inputSchema: {
|
|
11144
|
-
type: "object",
|
|
11145
|
-
properties: {
|
|
11146
|
-
keepPerItem: { type: "integer", description: "Snapshots to keep per item (default 20)." },
|
|
11147
|
-
olderThanDays: {
|
|
11148
|
-
type: "integer",
|
|
11149
|
-
description: "Only prune snapshots older than this many days (default 365)."
|
|
11150
|
-
}
|
|
11151
|
-
}
|
|
11152
|
-
}
|
|
11153
|
-
},
|
|
11154
|
-
{
|
|
11155
|
-
name: "add_site_candidates",
|
|
11156
|
-
description: "Record which site(s) a stored source or research pack could be interesting for, for reuse later (2026-DASMG-041). Attach one or more candidates to a contentSourceId and/or packId; each candidate targets a managedSiteId (preferred) or a projectKey/domain. Re-adding the same (subject, target) updates instead of duplicating.",
|
|
11253
|
+
name: "site-candidate",
|
|
11254
|
+
description: 'Which site(s) a stored source/pack could be used on (2026-DASMG-041). Pick the operation with `action`:\n- "add": record candidates \u2014 attach one or more to a contentSourceId and/or packId; each candidate targets a managedSiteId (preferred) or projectKey/domain. Re-adding the same (subject, target) updates instead of duplicating. Required: candidates.\n- "list": list candidates, filter by managedSiteId / projectKey / domain, contentSourceId, packId, status. Use status=candidate to find unused content suggested for a site. Compact, paginated; includes source title / pack topic.\n- "set-status": update a candidate\'s status. status=used marks it published on the site (stamps usedAt + usedCorpusItemId / usedUrl); rejected = not a fit. Required: id, status.',
|
|
11157
11255
|
inputSchema: {
|
|
11158
11256
|
type: "object",
|
|
11159
11257
|
properties: {
|
|
11160
|
-
|
|
11161
|
-
|
|
11258
|
+
action: {
|
|
11259
|
+
type: "string",
|
|
11260
|
+
enum: ["add", "list", "set-status"],
|
|
11261
|
+
description: "add = record candidates; list = list candidates; set-status = update one candidate."
|
|
11262
|
+
},
|
|
11263
|
+
id: { type: "string", description: "action=set-status: content_site_candidates.id (uuid)." },
|
|
11264
|
+
status: {
|
|
11265
|
+
type: "string",
|
|
11266
|
+
enum: ["candidate", "used", "rejected"],
|
|
11267
|
+
description: "action=set-status: new status (required). action=list: filter \u2014 candidate = suggested, used = published, rejected = not a fit."
|
|
11268
|
+
},
|
|
11269
|
+
usedCorpusItemId: {
|
|
11270
|
+
type: "string",
|
|
11271
|
+
description: "action=set-status: content_corpus_items.id of the page produced (when used)."
|
|
11272
|
+
},
|
|
11273
|
+
usedUrl: { type: "string", description: "action=set-status: URL of the page produced (when used)." },
|
|
11274
|
+
reason: { type: "string", description: "action=set-status: optional note for the status change." },
|
|
11275
|
+
contentSourceId: {
|
|
11276
|
+
type: "string",
|
|
11277
|
+
description: "action=add: the source to suggest. action=list: filter."
|
|
11278
|
+
},
|
|
11279
|
+
packId: {
|
|
11280
|
+
type: "string",
|
|
11281
|
+
description: "action=add: content_evidence_packs.id to suggest. action=list: filter."
|
|
11282
|
+
},
|
|
11162
11283
|
candidates: {
|
|
11163
11284
|
type: "array",
|
|
11164
|
-
description: "
|
|
11285
|
+
description: "action=add: target sites. Each needs a managedSiteId, projectKey or domain.",
|
|
11165
11286
|
items: {
|
|
11166
11287
|
type: "object",
|
|
11167
11288
|
properties: {
|
|
@@ -11174,66 +11295,44 @@ var TOOLS = [
|
|
|
11174
11295
|
}
|
|
11175
11296
|
}
|
|
11176
11297
|
},
|
|
11177
|
-
|
|
11298
|
+
managedSiteId: { type: "string", description: "action=list: managed_sites.id filter." },
|
|
11299
|
+
projectKey: { type: "string", description: "action=list: project filter." },
|
|
11300
|
+
domain: { type: "string", description: "action=list: domain filter." },
|
|
11301
|
+
limit: { type: "integer", description: "action=list: max rows (default 20, max 100)." },
|
|
11302
|
+
offset: { type: "integer", description: "action=list: pagination offset." },
|
|
11303
|
+
ticketNumber: { type: "string", description: "action=add: related ticket number." },
|
|
11178
11304
|
metadata: { type: "object" }
|
|
11179
11305
|
},
|
|
11180
|
-
required: ["
|
|
11306
|
+
required: ["action"]
|
|
11181
11307
|
}
|
|
11182
11308
|
},
|
|
11309
|
+
// ----- Managed sites (2026-DASMG-042), consolidated -----
|
|
11183
11310
|
{
|
|
11184
|
-
name: "
|
|
11185
|
-
description:
|
|
11311
|
+
name: "managed-site",
|
|
11312
|
+
description: 'Sites registered for content sync (2026-DASMG-042). Pick the operation with `action`:\n- "register": register (or upsert by projectKey+domain) a site: domain, optional repo/branch + sitemapUrl, syncMode and a git pathUrlMap. Optional + additive \u2014 external/unmanaged sites still work via extract_article without this. Enables git-push/cron/manual ingestion that re-indexes only CHANGED pages. Required: projectKey, domain.\n- "update": update an existing site by id \u2014 only provided fields change (enable/disable, switch syncMode, set sitemapUrl/pathUrlMap, \u2026). Required: id.\n- "list": list managed sites, filter by projectKey/domain/repo/enabledOnly. Compact, paginated.\nTrigger/poll ingestion runs with the `content-sync` tool.',
|
|
11186
11313
|
inputSchema: {
|
|
11187
11314
|
type: "object",
|
|
11188
11315
|
properties: {
|
|
11189
|
-
|
|
11190
|
-
projectKey: { type: "string", description: "Project filter." },
|
|
11191
|
-
domain: { type: "string", description: "Domain filter." },
|
|
11192
|
-
contentSourceId: { type: "string", description: "content_sources.id filter." },
|
|
11193
|
-
packId: { type: "string", description: "content_evidence_packs.id filter." },
|
|
11194
|
-
status: {
|
|
11316
|
+
action: {
|
|
11195
11317
|
type: "string",
|
|
11196
|
-
enum: ["
|
|
11197
|
-
description: "
|
|
11318
|
+
enum: ["register", "update", "list"],
|
|
11319
|
+
description: "register = upsert a site; update = patch a site by id; list = list sites."
|
|
11198
11320
|
},
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
}
|
|
11202
|
-
}
|
|
11203
|
-
},
|
|
11204
|
-
{
|
|
11205
|
-
name: "set_candidate_status",
|
|
11206
|
-
description: "Update a candidate's status (2026-DASMG-041). Set status=used to mark the content as used on the site (stamps usedAt and records usedCorpusItemId / usedUrl), or rejected when it is not a fit.",
|
|
11207
|
-
inputSchema: {
|
|
11208
|
-
type: "object",
|
|
11209
|
-
properties: {
|
|
11210
|
-
id: { type: "string", description: "content_site_candidates.id (uuid)." },
|
|
11211
|
-
status: {
|
|
11321
|
+
id: { type: "string", description: "action=update: managed_sites.id (uuid, required)." },
|
|
11322
|
+
projectKey: {
|
|
11212
11323
|
type: "string",
|
|
11213
|
-
|
|
11214
|
-
description: "New status for the candidate."
|
|
11324
|
+
description: "action=register (required): project identifier. action=list: filter."
|
|
11215
11325
|
},
|
|
11216
|
-
|
|
11326
|
+
domain: {
|
|
11217
11327
|
type: "string",
|
|
11218
|
-
description: "
|
|
11328
|
+
description: "action=register (required): site domain, e.g. example.nl. action=list: filter."
|
|
11219
11329
|
},
|
|
11220
|
-
|
|
11221
|
-
reason: { type: "string", description: "Optional note for the status change." }
|
|
11222
|
-
},
|
|
11223
|
-
required: ["id", "status"]
|
|
11224
|
-
}
|
|
11225
|
-
},
|
|
11226
|
-
{
|
|
11227
|
-
name: "register_managed_site",
|
|
11228
|
-
description: "Register (or upsert by projectKey+domain) a site for content sync (2026-DASMG-042): domain, optional repo/branch + sitemapUrl, syncMode and a git pathUrlMap. Optional + additive \u2014 external/unmanaged sites still work via extract_article without this. Enables git-push/cron/manual ingestion that re-indexes only CHANGED pages into the corpus (hash-skips unchanged ones).",
|
|
11229
|
-
inputSchema: {
|
|
11230
|
-
type: "object",
|
|
11231
|
-
properties: {
|
|
11232
|
-
projectKey: { type: "string", description: "Project identifier (required)." },
|
|
11233
|
-
domain: { type: "string", description: "Site domain, e.g. example.nl (required)." },
|
|
11234
|
-
siteKey: { type: "string", description: "Optional site identifier within the project." },
|
|
11330
|
+
siteKey: { type: "string", description: "Site identifier within the project." },
|
|
11235
11331
|
name: { type: "string", description: "Human-friendly site name." },
|
|
11236
|
-
repo: {
|
|
11332
|
+
repo: {
|
|
11333
|
+
type: "string",
|
|
11334
|
+
description: "GitHub repo owner/repo that publishes the site (for git pushes). action=list: filter."
|
|
11335
|
+
},
|
|
11237
11336
|
branch: { type: "string", description: "Branch whose pushes trigger a sync; omit for any branch." },
|
|
11238
11337
|
sitemapUrl: { type: "string", description: "Sitemap URL for sitemap/hybrid detection." },
|
|
11239
11338
|
sourceType: {
|
|
@@ -11250,11 +11349,12 @@ var TOOLS = [
|
|
|
11250
11349
|
],
|
|
11251
11350
|
description: "Provenance label for produced corpus items (default own_site)."
|
|
11252
11351
|
},
|
|
11253
|
-
enabled: { type: "boolean", description: "
|
|
11352
|
+
enabled: { type: "boolean", description: "register/update: whether syncs run (default true)." },
|
|
11353
|
+
enabledOnly: { type: "boolean", description: "action=list: only enabled sites." },
|
|
11254
11354
|
syncMode: {
|
|
11255
11355
|
type: "string",
|
|
11256
11356
|
enum: ["sitemap", "git", "hybrid", "manual"],
|
|
11257
|
-
description: "Change detection: sitemap (lastmod/hash), git (changed paths via pathUrlMap), hybrid (both), manual (only
|
|
11357
|
+
description: "Change detection: sitemap (lastmod/hash), git (changed paths via pathUrlMap), hybrid (both), manual (only via content-sync trigger)."
|
|
11258
11358
|
},
|
|
11259
11359
|
pathUrlMap: {
|
|
11260
11360
|
type: "array",
|
|
@@ -11266,106 +11366,49 @@ var TOOLS = [
|
|
|
11266
11366
|
type: "boolean",
|
|
11267
11367
|
description: "Flag changed pages for an MG SEO CLI follow-up (soft handoff)."
|
|
11268
11368
|
},
|
|
11369
|
+
limit: { type: "integer", description: "action=list: max rows (default 50, max 200)." },
|
|
11370
|
+
offset: { type: "integer", description: "action=list: pagination offset." },
|
|
11269
11371
|
metadata: { type: "object" }
|
|
11270
11372
|
},
|
|
11271
|
-
required: ["
|
|
11373
|
+
required: ["action"]
|
|
11272
11374
|
}
|
|
11273
11375
|
},
|
|
11376
|
+
// ----- Content ingestion runs (2026-DASMG-042), consolidated -----
|
|
11274
11377
|
{
|
|
11275
|
-
name: "
|
|
11276
|
-
description:
|
|
11378
|
+
name: "content-sync",
|
|
11379
|
+
description: 'Content ingestion runs for a managed site (2026-DASMG-042). Pick the operation with `action`:\n- "trigger": start a run by siteId, or projectKey+domain. Detects changed pages (sitemap/git/hash), re-indexes only changes into the corpus and snapshots the previous version. Returns the runId; poll with action=status.\n- "status": read run status by runId (that run), or by siteId / projectKey+domain (recent runs). Returns changed/skipped/failed counts, the changed URLs, limitations and the site summary.',
|
|
11277
11380
|
inputSchema: {
|
|
11278
11381
|
type: "object",
|
|
11279
11382
|
properties: {
|
|
11280
|
-
|
|
11281
|
-
name: { type: "string", description: "Human-friendly site name." },
|
|
11282
|
-
domain: { type: "string", description: "Site domain." },
|
|
11283
|
-
siteKey: { type: "string", description: "Site identifier within the project." },
|
|
11284
|
-
repo: { type: "string", description: "GitHub repo owner/repo." },
|
|
11285
|
-
branch: { type: "string", description: "Branch whose pushes trigger a sync." },
|
|
11286
|
-
sitemapUrl: { type: "string", description: "Sitemap URL." },
|
|
11287
|
-
sourceType: {
|
|
11288
|
-
type: "string",
|
|
11289
|
-
enum: [
|
|
11290
|
-
"own_site",
|
|
11291
|
-
"client_site",
|
|
11292
|
-
"external_source",
|
|
11293
|
-
"competitor",
|
|
11294
|
-
"news",
|
|
11295
|
-
"government",
|
|
11296
|
-
"supplier",
|
|
11297
|
-
"unknown"
|
|
11298
|
-
]
|
|
11299
|
-
},
|
|
11300
|
-
enabled: { type: "boolean", description: "Enable/disable syncs." },
|
|
11301
|
-
syncMode: {
|
|
11383
|
+
action: {
|
|
11302
11384
|
type: "string",
|
|
11303
|
-
enum: ["
|
|
11385
|
+
enum: ["trigger", "status"],
|
|
11386
|
+
description: "trigger = start an ingestion run; status = read run status."
|
|
11304
11387
|
},
|
|
11305
|
-
pathUrlMap: { type: "array", items: { type: "object" } },
|
|
11306
|
-
maxUrlsPerRun: { type: "integer", description: "Per-run URL cap." },
|
|
11307
|
-
exportToSeoCli: { type: "boolean" },
|
|
11308
|
-
metadata: { type: "object" }
|
|
11309
|
-
},
|
|
11310
|
-
required: ["id"]
|
|
11311
|
-
}
|
|
11312
|
-
},
|
|
11313
|
-
{
|
|
11314
|
-
name: "list_managed_sites",
|
|
11315
|
-
description: "List managed sites (2026-DASMG-042), filter by projectKey/domain/repo/enabledOnly. Compact, paginated.",
|
|
11316
|
-
inputSchema: {
|
|
11317
|
-
type: "object",
|
|
11318
|
-
properties: {
|
|
11319
|
-
projectKey: { type: "string", description: "Project filter." },
|
|
11320
|
-
domain: { type: "string", description: "Domain filter." },
|
|
11321
|
-
repo: { type: "string", description: "Repo filter (owner/repo)." },
|
|
11322
|
-
enabledOnly: { type: "boolean", description: "Only enabled sites." },
|
|
11323
|
-
limit: { type: "integer", description: "Max rows (default 50, max 200)." },
|
|
11324
|
-
offset: { type: "integer", description: "Pagination offset." }
|
|
11325
|
-
}
|
|
11326
|
-
}
|
|
11327
|
-
},
|
|
11328
|
-
{
|
|
11329
|
-
name: "trigger_content_sync",
|
|
11330
|
-
description: "Start a content ingestion run for a managed site (2026-DASMG-042) by siteId, or projectKey+domain. Detects changed pages (sitemap/git/hash), re-indexes only changes into the corpus and snapshots the previous version. Returns the runId; poll get_content_sync_status for results.",
|
|
11331
|
-
inputSchema: {
|
|
11332
|
-
type: "object",
|
|
11333
|
-
properties: {
|
|
11334
11388
|
siteId: { type: "string", description: "managed_sites.id (uuid)." },
|
|
11335
11389
|
projectKey: { type: "string", description: "Project identifier (with domain)." },
|
|
11336
11390
|
domain: { type: "string", description: "Site domain (with projectKey)." },
|
|
11391
|
+
runId: { type: "string", description: "action=status: content_ingestion_runs.id (uuid)." },
|
|
11337
11392
|
triggerType: {
|
|
11338
11393
|
type: "string",
|
|
11339
11394
|
enum: ["manual", "git_push", "deploy", "cron", "sitemap"],
|
|
11340
|
-
description: "
|
|
11395
|
+
description: "action=trigger: label recorded on the run (default manual)."
|
|
11341
11396
|
},
|
|
11342
|
-
sourceCommit: { type: "string", description: "
|
|
11343
|
-
sourceRef: { type: "string", description: "
|
|
11397
|
+
sourceCommit: { type: "string", description: "action=trigger: source git commit/deploy id, when known." },
|
|
11398
|
+
sourceRef: { type: "string", description: "action=trigger: source git ref, when known." },
|
|
11344
11399
|
urls: {
|
|
11345
11400
|
type: "array",
|
|
11346
11401
|
items: { type: "string" },
|
|
11347
|
-
description: "
|
|
11402
|
+
description: "action=trigger: restrict the run to these URLs (skips detection)."
|
|
11348
11403
|
},
|
|
11349
11404
|
changedPaths: {
|
|
11350
11405
|
type: "array",
|
|
11351
11406
|
items: { type: "string" },
|
|
11352
|
-
description: "
|
|
11353
|
-
}
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
{
|
|
11358
|
-
name: "get_content_sync_status",
|
|
11359
|
-
description: "Read ingestion run status (2026-DASMG-042): by runId (that run), or by siteId / projectKey+domain (recent runs). Returns changed/skipped/failed counts, the changed URLs, limitations and the site summary.",
|
|
11360
|
-
inputSchema: {
|
|
11361
|
-
type: "object",
|
|
11362
|
-
properties: {
|
|
11363
|
-
runId: { type: "string", description: "content_ingestion_runs.id (uuid)." },
|
|
11364
|
-
siteId: { type: "string", description: "managed_sites.id (uuid)." },
|
|
11365
|
-
projectKey: { type: "string", description: "Project identifier (with domain)." },
|
|
11366
|
-
domain: { type: "string", description: "Site domain (with projectKey)." },
|
|
11367
|
-
limit: { type: "integer", description: "Max recent runs to return (default 10)." }
|
|
11368
|
-
}
|
|
11407
|
+
description: "action=trigger: changed repo paths to map to URLs via pathUrlMap."
|
|
11408
|
+
},
|
|
11409
|
+
limit: { type: "integer", description: "action=status: max recent runs to return (default 10)." }
|
|
11410
|
+
},
|
|
11411
|
+
required: ["action"]
|
|
11369
11412
|
}
|
|
11370
11413
|
},
|
|
11371
11414
|
{
|
|
@@ -11472,8 +11515,10 @@ var MCP_VERSION = "7.3.0";
|
|
|
11472
11515
|
async function handleListTools() {
|
|
11473
11516
|
if (!authContext) return { tools: TOOLS };
|
|
11474
11517
|
const allowedTools = authContext.allowedTools;
|
|
11518
|
+
const isSuperadmin = authContext.roleName === "superadmin";
|
|
11475
11519
|
const accessible = TOOLS.filter((tool) => {
|
|
11476
11520
|
if (allowedTools && !allowedTools.includes(tool.name)) return false;
|
|
11521
|
+
if (SUPERADMIN_ONLY_TOOLS.has(tool.name) && !isSuperadmin) return false;
|
|
11477
11522
|
const requiredModule = TOOL_MODULE_MAP[tool.name];
|
|
11478
11523
|
if (!requiredModule) return true;
|
|
11479
11524
|
return authContext.permissions.modules[requiredModule] === true;
|
|
@@ -11496,6 +11541,16 @@ async function handleCallTool(request) {
|
|
|
11496
11541
|
]
|
|
11497
11542
|
};
|
|
11498
11543
|
}
|
|
11544
|
+
if (SUPERADMIN_ONLY_TOOLS.has(name) && authContext.roleName !== "superadmin") {
|
|
11545
|
+
return {
|
|
11546
|
+
content: [
|
|
11547
|
+
{
|
|
11548
|
+
type: "text",
|
|
11549
|
+
text: `Access denied: "${name}" is restricted to superadmins (it manages production secrets).`
|
|
11550
|
+
}
|
|
11551
|
+
]
|
|
11552
|
+
};
|
|
11553
|
+
}
|
|
11499
11554
|
const requiredModule = TOOL_MODULE_MAP[name];
|
|
11500
11555
|
if (requiredModule && authContext.permissions.modules[requiredModule] !== true) {
|
|
11501
11556
|
return {
|
|
@@ -11631,106 +11686,31 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
11631
11686
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
11632
11687
|
};
|
|
11633
11688
|
}
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
headers: {
|
|
11647
|
-
"content-type": "application/json",
|
|
11648
|
-
authorization: `Bearer ${apiKey}`
|
|
11649
|
-
},
|
|
11650
|
-
body: JSON.stringify({
|
|
11651
|
-
topic,
|
|
11652
|
-
urls,
|
|
11653
|
-
discover: a.discover && typeof a.discover === "object" ? a.discover : void 0,
|
|
11654
|
-
includeRawText: typeof a.includeRawText === "boolean" ? a.includeRawText : void 0,
|
|
11655
|
-
maxRawTextChars: typeof a.maxRawTextChars === "number" ? a.maxRawTextChars : void 0
|
|
11656
|
-
})
|
|
11657
|
-
});
|
|
11658
|
-
if (!res.ok) {
|
|
11659
|
-
const detail = await res.text().catch(() => "");
|
|
11660
|
-
return {
|
|
11661
|
-
content: [
|
|
11662
|
-
{
|
|
11663
|
-
type: "text",
|
|
11664
|
-
text: `Error: research_topic failed (${res.status}). ${detail.slice(0, 300)}`
|
|
11665
|
-
}
|
|
11666
|
-
]
|
|
11667
|
-
};
|
|
11668
|
-
}
|
|
11669
|
-
const data = await res.json();
|
|
11670
|
-
return {
|
|
11671
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
11672
|
-
};
|
|
11673
|
-
}
|
|
11674
|
-
// ----- Content registry / corpus persistence (2026-DASMG-041) -----
|
|
11675
|
-
// All content-registry tools proxy to one dispatcher route; the tool name
|
|
11676
|
-
// is the dispatcher `action` and the args are forwarded verbatim as
|
|
11677
|
-
// `params` (the route validates them with the matching Zod schema).
|
|
11678
|
-
case "save_content_source":
|
|
11679
|
-
case "get_content_source_by_url":
|
|
11680
|
-
case "get_content_source_by_id":
|
|
11681
|
-
case "search_content_sources":
|
|
11682
|
-
case "save_research_pack":
|
|
11683
|
-
case "get_research_pack":
|
|
11684
|
-
case "save_content_corpus_item":
|
|
11685
|
-
case "list_project_content_corpus":
|
|
11686
|
-
case "get_content_context_for_project":
|
|
11689
|
+
// ----- Content family (2026-DASMG-040/041/042), consolidated -----
|
|
11690
|
+
// Action-based tools + the two singletons, mapped to the backend
|
|
11691
|
+
// content-registry / managed-site / research-topic dispatchers by
|
|
11692
|
+
// resolveContentDispatch (the backend still speaks the granular action
|
|
11693
|
+
// names, so no server change is needed).
|
|
11694
|
+
case "research":
|
|
11695
|
+
case "content-source":
|
|
11696
|
+
case "content-corpus":
|
|
11697
|
+
case "content-snapshot":
|
|
11698
|
+
case "site-candidate":
|
|
11699
|
+
case "managed-site":
|
|
11700
|
+
case "content-sync":
|
|
11687
11701
|
case "save_content_quality_run":
|
|
11688
|
-
case "
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
case "list_site_candidates":
|
|
11693
|
-
case "set_candidate_status": {
|
|
11694
|
-
const res = await fetch(`${dashboardBaseUrl}/api/tools/content-registry`, {
|
|
11695
|
-
method: "POST",
|
|
11696
|
-
headers: {
|
|
11697
|
-
"content-type": "application/json",
|
|
11698
|
-
authorization: `Bearer ${apiKey}`
|
|
11699
|
-
},
|
|
11700
|
-
body: JSON.stringify({ action: name, params: a })
|
|
11701
|
-
});
|
|
11702
|
-
if (!res.ok) {
|
|
11703
|
-
const detail = await res.text().catch(() => "");
|
|
11704
|
-
return {
|
|
11705
|
-
content: [
|
|
11706
|
-
{
|
|
11707
|
-
type: "text",
|
|
11708
|
-
text: `Error: ${name} failed (${res.status}). ${detail.slice(0, 300)}`
|
|
11709
|
-
}
|
|
11710
|
-
]
|
|
11711
|
-
};
|
|
11702
|
+
case "link_content_pack_to_ticket": {
|
|
11703
|
+
const dispatch = resolveContentDispatch(name, a);
|
|
11704
|
+
if ("error" in dispatch) {
|
|
11705
|
+
return { content: [{ type: "text", text: `Error: ${dispatch.error}` }] };
|
|
11712
11706
|
}
|
|
11713
|
-
const
|
|
11714
|
-
return {
|
|
11715
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
11716
|
-
};
|
|
11717
|
-
}
|
|
11718
|
-
// ----- Managed site content sync (2026-DASMG-042) -----
|
|
11719
|
-
// The 5 PUBLIC managed-site tools proxy to one dispatcher route; the tool
|
|
11720
|
-
// name is the dispatcher `action`, args forwarded verbatim as `params`
|
|
11721
|
-
// (the route validates them and only allows the public actions).
|
|
11722
|
-
case "register_managed_site":
|
|
11723
|
-
case "update_managed_site":
|
|
11724
|
-
case "list_managed_sites":
|
|
11725
|
-
case "trigger_content_sync":
|
|
11726
|
-
case "get_content_sync_status": {
|
|
11727
|
-
const res = await fetch(`${dashboardBaseUrl}/api/tools/managed-site`, {
|
|
11707
|
+
const res = await fetch(`${dashboardBaseUrl}/api/tools/${dispatch.route}`, {
|
|
11728
11708
|
method: "POST",
|
|
11729
11709
|
headers: {
|
|
11730
11710
|
"content-type": "application/json",
|
|
11731
11711
|
authorization: `Bearer ${apiKey}`
|
|
11732
11712
|
},
|
|
11733
|
-
body: JSON.stringify(
|
|
11713
|
+
body: JSON.stringify(dispatch.body)
|
|
11734
11714
|
});
|
|
11735
11715
|
if (!res.ok) {
|
|
11736
11716
|
const detail = await res.text().catch(() => "");
|