chain-insights 0.2.16
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/LICENSE +21 -0
- package/README.md +165 -0
- package/bin/cli.js +10 -0
- package/bin/install.cjs +252 -0
- package/bin/mcp-proxy.cjs +10 -0
- package/dist/active-BSrxLKwn.mjs +50 -0
- package/dist/active-BSrxLKwn.mjs.map +1 -0
- package/dist/active-Dv7Tu-O4.cjs +68 -0
- package/dist/app-BjjuQM0B.mjs +155 -0
- package/dist/app-BjjuQM0B.mjs.map +1 -0
- package/dist/app-Dq1TdB6p.cjs +161 -0
- package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
- package/dist/assets/bg-pattern.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/call-args-DQA2QcRA.cjs +27 -0
- package/dist/call-args-Lk_wOJxd.mjs +29 -0
- package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
- package/dist/capabilities-CB97WMA5.cjs +83 -0
- package/dist/capabilities-DliMBim-.mjs +84 -0
- package/dist/capabilities-DliMBim-.mjs.map +1 -0
- package/dist/cases-By7INiOa.mjs +6 -0
- package/dist/cases-CDcNU91B.cjs +9 -0
- package/dist/chunk-CZWwpsFl.cjs +43 -0
- package/dist/cli.cjs +752 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +753 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-D4Bq0rp9.mjs +111 -0
- package/dist/client-D4Bq0rp9.mjs.map +1 -0
- package/dist/client-D4fZgIaO.cjs +132 -0
- package/dist/config-Bmdl5hdk.cjs +67 -0
- package/dist/config-BwrBYmiC.mjs +44 -0
- package/dist/config-BwrBYmiC.mjs.map +1 -0
- package/dist/data-extractor-BNGj7ECT.cjs +347 -0
- package/dist/data-extractor-DFzsa5CS.mjs +336 -0
- package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
- package/dist/dossier-BsroDgD3.mjs +76 -0
- package/dist/dossier-BsroDgD3.mjs.map +1 -0
- package/dist/dossier-DtxREpPm.cjs +76 -0
- package/dist/evidence-BGcdKxuV.cjs +200 -0
- package/dist/evidence-BhvFW-y_.mjs +195 -0
- package/dist/evidence-BhvFW-y_.mjs.map +1 -0
- package/dist/format-Ce1RObVl.mjs +22 -0
- package/dist/format-Ce1RObVl.mjs.map +1 -0
- package/dist/format-DOrPvXEr.cjs +20 -0
- package/dist/frontmatter-D8wWCeOa.mjs +26 -0
- package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
- package/dist/frontmatter-DgAuai7E.cjs +35 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
- package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
- package/dist/graph-reports-C4TBjCkM.mjs +63 -0
- package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
- package/dist/graph-reports-DU05YCei.cjs +64 -0
- package/dist/html-generator-CAv81IWH.cjs +85 -0
- package/dist/html-generator-V6Bp0uRb.mjs +68 -0
- package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +187 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/init-BjuFt54X.cjs +232 -0
- package/dist/init-CaOsHTIo.mjs +232 -0
- package/dist/init-CaOsHTIo.mjs.map +1 -0
- package/dist/mcp-proxy.cjs +1257 -0
- package/dist/mcp-proxy.d.cts +12 -0
- package/dist/mcp-proxy.d.cts.map +1 -0
- package/dist/mcp-proxy.d.mts +12 -0
- package/dist/mcp-proxy.d.mts.map +1 -0
- package/dist/mcp-proxy.mjs +1255 -0
- package/dist/mcp-proxy.mjs.map +1 -0
- package/dist/output-root-CFYms3ad.cjs +43 -0
- package/dist/output-root-CmWM7aV2.mjs +33 -0
- package/dist/output-root-CmWM7aV2.mjs.map +1 -0
- package/dist/parser-BUIWW1OH.cjs +182 -0
- package/dist/parser-DO0_SssG.mjs +182 -0
- package/dist/parser-DO0_SssG.mjs.map +1 -0
- package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
- package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
- package/dist/public-tools-XSpkz2ky.cjs +2556 -0
- package/dist/resolver-C2ZS7oC8.mjs +201 -0
- package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
- package/dist/resolver-zYbu4wDV.cjs +203 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/runner-1Eq55OYb.cjs +148 -0
- package/dist/runner-BhUHbiHG.mjs +149 -0
- package/dist/runner-BhUHbiHG.mjs.map +1 -0
- package/dist/schema-4XpzDFQM.cjs +55 -0
- package/dist/schema-8d0rVIdZ.mjs +37 -0
- package/dist/schema-8d0rVIdZ.mjs.map +1 -0
- package/dist/schema-cache-9CksD7tX.mjs +34 -0
- package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
- package/dist/schema-cache-CgWRCN2N.cjs +36 -0
- package/dist/selector-CkFcTXzz.cjs +10 -0
- package/dist/selector-xjm6NTHI.mjs +12 -0
- package/dist/selector-xjm6NTHI.mjs.map +1 -0
- package/dist/server-BkM5xrXb.mjs +45 -0
- package/dist/server-BkM5xrXb.mjs.map +1 -0
- package/dist/server-DXowbpfi.cjs +54 -0
- package/dist/session-BpNylyuJ.cjs +115 -0
- package/dist/session-CcTgYxsj.mjs +115 -0
- package/dist/session-CcTgYxsj.mjs.map +1 -0
- package/dist/setup-DOpKPrlx.cjs +81 -0
- package/dist/setup-DyrWHuwQ.mjs +80 -0
- package/dist/setup-DyrWHuwQ.mjs.map +1 -0
- package/dist/store-BiUhQOIf.cjs +230 -0
- package/dist/store-BoWE-Gtl.mjs +225 -0
- package/dist/store-BoWE-Gtl.mjs.map +1 -0
- package/dist/templates/graph.html +1406 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
- package/dist/tool-visibility-CwgY205r.cjs +36 -0
- package/dist/tools-Cp2jAAAb.mjs +100 -0
- package/dist/tools-Cp2jAAAb.mjs.map +1 -0
- package/dist/tools-f_vJUZAF.cjs +139 -0
- package/dist/topup-server-BZuQifvh.cjs +940 -0
- package/dist/topup-server-DUjyFftI.mjs +919 -0
- package/dist/topup-server-DUjyFftI.mjs.map +1 -0
- package/dist/version-1gP19Lhi.mjs +8 -0
- package/dist/version-1gP19Lhi.mjs.map +1 -0
- package/dist/version-BNGtdpmH.cjs +18 -0
- package/dist/viz-BlCJe6Tk.mjs +35 -0
- package/dist/viz-BlCJe6Tk.mjs.map +1 -0
- package/dist/viz-ClezVXrJ.cjs +44 -0
- package/dist/wallet-BMelXBYP.mjs +104 -0
- package/dist/wallet-BMelXBYP.mjs.map +1 -0
- package/dist/wallet-RnvvSpV2.cjs +146 -0
- package/docs/architecture.md +145 -0
- package/docs/contributing.md +68 -0
- package/docs/debugging.md +68 -0
- package/docs/development.md +44 -0
- package/docs/graph-tools.md +251 -0
- package/docs/images/graph-mcp-iframe.png +0 -0
- package/docs/images/graph-visualization.png +0 -0
- package/docs/images/topup-page.png +0 -0
- package/docs/investigation-workspaces.md +151 -0
- package/docs/mcp-proxy.md +180 -0
- package/package.json +59 -0
- package/skills/chain-insights-developer-experience/SKILL.md +101 -0
- package/skills/chain-insights-investigation/SKILL.md +285 -0
- package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
- package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
- package/skills/chain-insights-trace-funds/SKILL.md +249 -0
- package/skills/ci-case/SKILL.md +43 -0
- package/skills/ci-status/SKILL.md +45 -0
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
- package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { lstat, readFile, readdir, realpath } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
//#region src/server/app.ts
|
|
6
|
+
const WORKSPACE_TREE_ROOTS = [
|
|
7
|
+
"cases",
|
|
8
|
+
"reports",
|
|
9
|
+
".chain-insights/schema"
|
|
10
|
+
];
|
|
11
|
+
const WORKSPACE_TREE_MAX_DEPTH = 4;
|
|
12
|
+
function withinRoot(root, target) {
|
|
13
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
14
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
15
|
+
}
|
|
16
|
+
async function realPathWithinRoot(root, target) {
|
|
17
|
+
try {
|
|
18
|
+
const [realRoot, realTarget] = await Promise.all([realpath(root), realpath(target)]);
|
|
19
|
+
return withinRoot(realRoot, realTarget);
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function toWorkspaceRelative(root, target) {
|
|
25
|
+
return path.relative(root, target).split(path.sep).join("/");
|
|
26
|
+
}
|
|
27
|
+
async function listWorkspaceEntries(workspaceRoot, roots = WORKSPACE_TREE_ROOTS, maxDepth = WORKSPACE_TREE_MAX_DEPTH) {
|
|
28
|
+
const entries = [];
|
|
29
|
+
const root = path.resolve(workspaceRoot);
|
|
30
|
+
async function visit(target, depth) {
|
|
31
|
+
const resolved = path.resolve(target);
|
|
32
|
+
if (!withinRoot(root, resolved)) return;
|
|
33
|
+
let info;
|
|
34
|
+
try {
|
|
35
|
+
info = await lstat(resolved);
|
|
36
|
+
} catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const type = info.isSymbolicLink() ? "symlink" : info.isDirectory() ? "directory" : info.isFile() ? "file" : null;
|
|
40
|
+
if (!type) return;
|
|
41
|
+
const entry = {
|
|
42
|
+
path: toWorkspaceRelative(root, resolved),
|
|
43
|
+
type
|
|
44
|
+
};
|
|
45
|
+
if (type === "file") entry.size = info.size;
|
|
46
|
+
entries.push(entry);
|
|
47
|
+
if (type !== "directory" || depth >= maxDepth) return;
|
|
48
|
+
if (!await realPathWithinRoot(root, resolved)) return;
|
|
49
|
+
let children;
|
|
50
|
+
try {
|
|
51
|
+
children = await readdir(resolved);
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const child of children.sort()) await visit(path.join(resolved, child), depth + 1);
|
|
56
|
+
}
|
|
57
|
+
for (const rootName of roots) {
|
|
58
|
+
const target = path.resolve(root, rootName);
|
|
59
|
+
if (withinRoot(root, target)) await visit(target, 0);
|
|
60
|
+
}
|
|
61
|
+
return entries;
|
|
62
|
+
}
|
|
63
|
+
async function findVizHtml(vizId) {
|
|
64
|
+
const home = os.homedir();
|
|
65
|
+
const filename = `${vizId}.html`;
|
|
66
|
+
const centralPath = path.join(home, ".chain-insights", "viz", filename);
|
|
67
|
+
try {
|
|
68
|
+
return await readFile(centralPath, "utf-8");
|
|
69
|
+
} catch {}
|
|
70
|
+
const underscoreIdx = vizId.lastIndexOf("_");
|
|
71
|
+
if (underscoreIdx > 0) {
|
|
72
|
+
const possibleCaseId = vizId.substring(0, underscoreIdx);
|
|
73
|
+
const casePath = path.join(home, ".chain-insights", "cases", possibleCaseId, "viz", filename);
|
|
74
|
+
try {
|
|
75
|
+
return await readFile(casePath, "utf-8");
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
const casesDir = path.join(home, ".chain-insights", "cases");
|
|
79
|
+
try {
|
|
80
|
+
const cases = await readdir(casesDir);
|
|
81
|
+
for (const caseId of cases) {
|
|
82
|
+
const casePath = path.join(casesDir, caseId, "viz", filename);
|
|
83
|
+
try {
|
|
84
|
+
return await readFile(casePath, "utf-8");
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function isSafeGraphReportFilename(filename) {
|
|
91
|
+
return filename.endsWith(".graph.json") && /^[A-Za-z0-9._-]+$/.test(filename) && !filename.includes("..") && !filename.includes("/") && !filename.includes("\\");
|
|
92
|
+
}
|
|
93
|
+
function createApp() {
|
|
94
|
+
const app = new Hono();
|
|
95
|
+
app.get("/health", (c) => c.json({
|
|
96
|
+
ok: true,
|
|
97
|
+
ts: Date.now()
|
|
98
|
+
}));
|
|
99
|
+
app.get("/status", async (c) => {
|
|
100
|
+
const { loadConfig } = await import("./config-BwrBYmiC.mjs").then((n) => n.t);
|
|
101
|
+
const config = await loadConfig();
|
|
102
|
+
return c.json({
|
|
103
|
+
dataDir: config.dataDir,
|
|
104
|
+
graphMcpMode: config.graphMcpMode,
|
|
105
|
+
server: "running"
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
app.get("/viz/:id", async (c) => {
|
|
109
|
+
const id = c.req.param("id");
|
|
110
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id)) return c.json({ error: "Invalid visualization ID" }, 400);
|
|
111
|
+
const html = await findVizHtml(id);
|
|
112
|
+
if (!html) return c.json({ error: "Visualization not found" }, 404);
|
|
113
|
+
return c.html(html);
|
|
114
|
+
});
|
|
115
|
+
app.get("/graph-reports/:filename", async (c) => {
|
|
116
|
+
const filename = c.req.param("filename");
|
|
117
|
+
if (!isSafeGraphReportFilename(filename)) return c.json({ error: "Invalid graph report filename" }, 400);
|
|
118
|
+
const { workspaceOutputPaths } = await import("./output-root-CmWM7aV2.mjs").then((n) => n.t);
|
|
119
|
+
const paths = workspaceOutputPaths();
|
|
120
|
+
const graphPath = path.resolve(paths.reportGraphsRoot, filename);
|
|
121
|
+
if (!withinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Invalid graph report filename" }, 400);
|
|
122
|
+
if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Graph report not found" }, 404);
|
|
123
|
+
try {
|
|
124
|
+
const graph = await readFile(graphPath, "utf-8");
|
|
125
|
+
return c.body(graph, 200, {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"Access-Control-Allow-Origin": "*"
|
|
128
|
+
});
|
|
129
|
+
} catch {
|
|
130
|
+
return c.json({ error: "Graph report not found" }, 404);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
app.get("/graph-reports/*", (c) => {
|
|
134
|
+
return c.json({ error: "Invalid graph report filename" }, 400);
|
|
135
|
+
});
|
|
136
|
+
app.get("/workspace/tree", async (c) => {
|
|
137
|
+
const { workspaceOutputPaths } = await import("./output-root-CmWM7aV2.mjs").then((n) => n.t);
|
|
138
|
+
const paths = workspaceOutputPaths();
|
|
139
|
+
const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS);
|
|
140
|
+
return c.json({
|
|
141
|
+
schema: "chain-insights.workspace-tree.v1",
|
|
142
|
+
root: paths.root,
|
|
143
|
+
entries
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
app.onError((err, c) => {
|
|
147
|
+
console.error(err);
|
|
148
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
149
|
+
});
|
|
150
|
+
return app;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { createApp as t };
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=app-BjjuQM0B.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-BjjuQM0B.mjs","names":[],"sources":["../src/server/app.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport { lstat, readFile, readdir, realpath } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\n\nconst WORKSPACE_TREE_ROOTS = ['cases', 'reports', '.chain-insights/schema']\nconst WORKSPACE_TREE_MAX_DEPTH = 4\n\ninterface WorkspaceTreeEntry {\n path: string\n type: 'file' | 'directory' | 'symlink'\n size?: number\n}\n\nfunction withinRoot(root: string, target: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(target))\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))\n}\n\nasync function realPathWithinRoot(root: string, target: string): Promise<boolean> {\n try {\n const [realRoot, realTarget] = await Promise.all([realpath(root), realpath(target)])\n return withinRoot(realRoot, realTarget)\n } catch {\n return false\n }\n}\n\nfunction toWorkspaceRelative(root: string, target: string): string {\n return path.relative(root, target).split(path.sep).join('/')\n}\n\nasync function listWorkspaceEntries(\n workspaceRoot: string,\n roots = WORKSPACE_TREE_ROOTS,\n maxDepth = WORKSPACE_TREE_MAX_DEPTH\n): Promise<WorkspaceTreeEntry[]> {\n const entries: WorkspaceTreeEntry[] = []\n const root = path.resolve(workspaceRoot)\n\n async function visit(target: string, depth: number): Promise<void> {\n const resolved = path.resolve(target)\n if (!withinRoot(root, resolved)) return\n\n let info: Awaited<ReturnType<typeof lstat>>\n try {\n info = await lstat(resolved)\n } catch {\n return\n }\n\n const type = info.isSymbolicLink() ? 'symlink' : info.isDirectory() ? 'directory' : info.isFile() ? 'file' : null\n if (!type) return\n\n const entry: WorkspaceTreeEntry = {\n path: toWorkspaceRelative(root, resolved),\n type,\n }\n if (type === 'file') entry.size = info.size\n entries.push(entry)\n\n if (type !== 'directory' || depth >= maxDepth) return\n if (!await realPathWithinRoot(root, resolved)) return\n\n let children: string[]\n try {\n children = await readdir(resolved)\n } catch {\n return\n }\n\n for (const child of children.sort()) {\n await visit(path.join(resolved, child), depth + 1)\n }\n }\n\n for (const rootName of roots) {\n const target = path.resolve(root, rootName)\n if (withinRoot(root, target)) await visit(target, 0)\n }\n\n return entries\n}\n\nasync function findVizHtml(vizId: string): Promise<string | null> {\n const home = os.homedir()\n const filename = `${vizId}.html`\n\n // 1. Check central standalone directory first (fast, single path)\n const centralPath = path.join(home, '.chain-insights', 'viz', filename)\n try {\n return await readFile(centralPath, 'utf-8')\n } catch { /* not found here, continue */ }\n\n // 2. Check per-case directory using vizId prefix (case-based vizs use <caseId>_<timestamp>)\n // The vizId for case-based vizs is formatted as <case-id>_<timestamp>,\n // so extract the case-id prefix to check its directory first.\n const underscoreIdx = vizId.lastIndexOf('_')\n if (underscoreIdx > 0) {\n const possibleCaseId = vizId.substring(0, underscoreIdx)\n const casePath = path.join(home, '.chain-insights', 'cases', possibleCaseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not found here, continue */ }\n }\n\n // 3. Fallback: scan all case directories (CONTEXT.md: ~/.chain-insights/cases/<case-id>/viz/)\n const casesDir = path.join(home, '.chain-insights', 'cases')\n try {\n const cases = await readdir(casesDir)\n for (const caseId of cases) {\n const casePath = path.join(casesDir, caseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not in this case dir */ }\n }\n } catch { /* cases dir doesn't exist */ }\n\n return null\n}\n\nfunction isSafeGraphReportFilename(filename: string): boolean {\n return (\n filename.endsWith('.graph.json') &&\n /^[A-Za-z0-9._-]+$/.test(filename) &&\n !filename.includes('..') &&\n !filename.includes('/') &&\n !filename.includes('\\\\')\n )\n}\n\nexport function createApp(): Hono {\n const app = new Hono()\n\n app.get('/health', (c) => c.json({ ok: true, ts: Date.now() }))\n\n app.get('/status', async (c) => {\n const { loadConfig } = await import('../config/index.js')\n const config = await loadConfig()\n return c.json({\n dataDir: config.dataDir,\n graphMcpMode: config.graphMcpMode,\n server: 'running',\n })\n })\n\n app.get('/viz/:id', async (c) => {\n const id = c.req.param('id')\n if (!/^[a-zA-Z0-9_-]+$/.test(id)) {\n return c.json({ error: 'Invalid visualization ID' }, 400)\n }\n const html = await findVizHtml(id)\n if (!html) {\n return c.json({ error: 'Visualization not found' }, 404)\n }\n return c.html(html)\n })\n\n app.get('/graph-reports/:filename', async (c) => {\n const filename = c.req.param('filename')\n if (!isSafeGraphReportFilename(filename)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const graphPath = path.resolve(paths.reportGraphsRoot, filename)\n if (!withinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n\n try {\n const graph = await readFile(graphPath, 'utf-8')\n return c.body(graph, 200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n } catch {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n })\n\n app.get('/graph-reports/*', (c) => {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n })\n\n app.get('/workspace/tree', async (c) => {\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS)\n return c.json({\n schema: 'chain-insights.workspace-tree.v1',\n root: paths.root,\n entries,\n })\n })\n\n app.onError((err, c) => {\n console.error(err)\n return c.json({ error: 'Internal server error' }, 500)\n })\n\n return app\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
node_os = require_chunk.__toESM(node_os, 1);
|
|
7
|
+
let hono = require("hono");
|
|
8
|
+
//#region src/server/app.ts
|
|
9
|
+
const WORKSPACE_TREE_ROOTS = [
|
|
10
|
+
"cases",
|
|
11
|
+
"reports",
|
|
12
|
+
".chain-insights/schema"
|
|
13
|
+
];
|
|
14
|
+
const WORKSPACE_TREE_MAX_DEPTH = 4;
|
|
15
|
+
function withinRoot(root, target) {
|
|
16
|
+
const relative = node_path.default.relative(node_path.default.resolve(root), node_path.default.resolve(target));
|
|
17
|
+
return relative === "" || !relative.startsWith("..") && !node_path.default.isAbsolute(relative);
|
|
18
|
+
}
|
|
19
|
+
async function realPathWithinRoot(root, target) {
|
|
20
|
+
try {
|
|
21
|
+
const [realRoot, realTarget] = await Promise.all([(0, node_fs_promises.realpath)(root), (0, node_fs_promises.realpath)(target)]);
|
|
22
|
+
return withinRoot(realRoot, realTarget);
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function toWorkspaceRelative(root, target) {
|
|
28
|
+
return node_path.default.relative(root, target).split(node_path.default.sep).join("/");
|
|
29
|
+
}
|
|
30
|
+
async function listWorkspaceEntries(workspaceRoot, roots = WORKSPACE_TREE_ROOTS, maxDepth = WORKSPACE_TREE_MAX_DEPTH) {
|
|
31
|
+
const entries = [];
|
|
32
|
+
const root = node_path.default.resolve(workspaceRoot);
|
|
33
|
+
async function visit(target, depth) {
|
|
34
|
+
const resolved = node_path.default.resolve(target);
|
|
35
|
+
if (!withinRoot(root, resolved)) return;
|
|
36
|
+
let info;
|
|
37
|
+
try {
|
|
38
|
+
info = await (0, node_fs_promises.lstat)(resolved);
|
|
39
|
+
} catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const type = info.isSymbolicLink() ? "symlink" : info.isDirectory() ? "directory" : info.isFile() ? "file" : null;
|
|
43
|
+
if (!type) return;
|
|
44
|
+
const entry = {
|
|
45
|
+
path: toWorkspaceRelative(root, resolved),
|
|
46
|
+
type
|
|
47
|
+
};
|
|
48
|
+
if (type === "file") entry.size = info.size;
|
|
49
|
+
entries.push(entry);
|
|
50
|
+
if (type !== "directory" || depth >= maxDepth) return;
|
|
51
|
+
if (!await realPathWithinRoot(root, resolved)) return;
|
|
52
|
+
let children;
|
|
53
|
+
try {
|
|
54
|
+
children = await (0, node_fs_promises.readdir)(resolved);
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const child of children.sort()) await visit(node_path.default.join(resolved, child), depth + 1);
|
|
59
|
+
}
|
|
60
|
+
for (const rootName of roots) {
|
|
61
|
+
const target = node_path.default.resolve(root, rootName);
|
|
62
|
+
if (withinRoot(root, target)) await visit(target, 0);
|
|
63
|
+
}
|
|
64
|
+
return entries;
|
|
65
|
+
}
|
|
66
|
+
async function findVizHtml(vizId) {
|
|
67
|
+
const home = node_os.default.homedir();
|
|
68
|
+
const filename = `${vizId}.html`;
|
|
69
|
+
const centralPath = node_path.default.join(home, ".chain-insights", "viz", filename);
|
|
70
|
+
try {
|
|
71
|
+
return await (0, node_fs_promises.readFile)(centralPath, "utf-8");
|
|
72
|
+
} catch {}
|
|
73
|
+
const underscoreIdx = vizId.lastIndexOf("_");
|
|
74
|
+
if (underscoreIdx > 0) {
|
|
75
|
+
const possibleCaseId = vizId.substring(0, underscoreIdx);
|
|
76
|
+
const casePath = node_path.default.join(home, ".chain-insights", "cases", possibleCaseId, "viz", filename);
|
|
77
|
+
try {
|
|
78
|
+
return await (0, node_fs_promises.readFile)(casePath, "utf-8");
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
const casesDir = node_path.default.join(home, ".chain-insights", "cases");
|
|
82
|
+
try {
|
|
83
|
+
const cases = await (0, node_fs_promises.readdir)(casesDir);
|
|
84
|
+
for (const caseId of cases) {
|
|
85
|
+
const casePath = node_path.default.join(casesDir, caseId, "viz", filename);
|
|
86
|
+
try {
|
|
87
|
+
return await (0, node_fs_promises.readFile)(casePath, "utf-8");
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function isSafeGraphReportFilename(filename) {
|
|
94
|
+
return filename.endsWith(".graph.json") && /^[A-Za-z0-9._-]+$/.test(filename) && !filename.includes("..") && !filename.includes("/") && !filename.includes("\\");
|
|
95
|
+
}
|
|
96
|
+
function createApp() {
|
|
97
|
+
const app = new hono.Hono();
|
|
98
|
+
app.get("/health", (c) => c.json({
|
|
99
|
+
ok: true,
|
|
100
|
+
ts: Date.now()
|
|
101
|
+
}));
|
|
102
|
+
app.get("/status", async (c) => {
|
|
103
|
+
const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
|
|
104
|
+
const config = await loadConfig();
|
|
105
|
+
return c.json({
|
|
106
|
+
dataDir: config.dataDir,
|
|
107
|
+
graphMcpMode: config.graphMcpMode,
|
|
108
|
+
server: "running"
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
app.get("/viz/:id", async (c) => {
|
|
112
|
+
const id = c.req.param("id");
|
|
113
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id)) return c.json({ error: "Invalid visualization ID" }, 400);
|
|
114
|
+
const html = await findVizHtml(id);
|
|
115
|
+
if (!html) return c.json({ error: "Visualization not found" }, 404);
|
|
116
|
+
return c.html(html);
|
|
117
|
+
});
|
|
118
|
+
app.get("/graph-reports/:filename", async (c) => {
|
|
119
|
+
const filename = c.req.param("filename");
|
|
120
|
+
if (!isSafeGraphReportFilename(filename)) return c.json({ error: "Invalid graph report filename" }, 400);
|
|
121
|
+
const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-CFYms3ad.cjs")).then((n) => n.output_root_exports);
|
|
122
|
+
const paths = workspaceOutputPaths();
|
|
123
|
+
const graphPath = node_path.default.resolve(paths.reportGraphsRoot, filename);
|
|
124
|
+
if (!withinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Invalid graph report filename" }, 400);
|
|
125
|
+
if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Graph report not found" }, 404);
|
|
126
|
+
try {
|
|
127
|
+
const graph = await (0, node_fs_promises.readFile)(graphPath, "utf-8");
|
|
128
|
+
return c.body(graph, 200, {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
"Access-Control-Allow-Origin": "*"
|
|
131
|
+
});
|
|
132
|
+
} catch {
|
|
133
|
+
return c.json({ error: "Graph report not found" }, 404);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
app.get("/graph-reports/*", (c) => {
|
|
137
|
+
return c.json({ error: "Invalid graph report filename" }, 400);
|
|
138
|
+
});
|
|
139
|
+
app.get("/workspace/tree", async (c) => {
|
|
140
|
+
const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-CFYms3ad.cjs")).then((n) => n.output_root_exports);
|
|
141
|
+
const paths = workspaceOutputPaths();
|
|
142
|
+
const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS);
|
|
143
|
+
return c.json({
|
|
144
|
+
schema: "chain-insights.workspace-tree.v1",
|
|
145
|
+
root: paths.root,
|
|
146
|
+
entries
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
app.onError((err, c) => {
|
|
150
|
+
console.error(err);
|
|
151
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
152
|
+
});
|
|
153
|
+
return app;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
Object.defineProperty(exports, "createApp", {
|
|
157
|
+
enumerable: true,
|
|
158
|
+
get: function() {
|
|
159
|
+
return createApp;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
const require_app = require("./app-Dq1TdB6p.cjs");
|
|
3
|
+
let _hono_node_server = require("@hono/node-server");
|
|
4
|
+
let node_timers_promises = require("node:timers/promises");
|
|
5
|
+
//#region src/mcp/artifact-server.ts
|
|
6
|
+
const servers = /* @__PURE__ */ new Map();
|
|
7
|
+
async function isHealthy(port) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
10
|
+
try {
|
|
11
|
+
return (await fetch(`http://127.0.0.1:${port}/health`, { signal: controller.signal })).ok;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function waitUntilHealthy(port) {
|
|
19
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
20
|
+
if (await isHealthy(port)) return;
|
|
21
|
+
await (0, node_timers_promises.setTimeout)(50);
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`);
|
|
24
|
+
}
|
|
25
|
+
async function ensureArtifactServer(port) {
|
|
26
|
+
if (servers.has(port)) return;
|
|
27
|
+
if (await isHealthy(port)) return;
|
|
28
|
+
const server = (0, _hono_node_server.serve)({
|
|
29
|
+
fetch: require_app.createApp().fetch,
|
|
30
|
+
hostname: "127.0.0.1",
|
|
31
|
+
port
|
|
32
|
+
});
|
|
33
|
+
servers.set(port, server);
|
|
34
|
+
server.on("error", (err) => {
|
|
35
|
+
servers.delete(port);
|
|
36
|
+
process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${err.message}\n`);
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
await waitUntilHealthy(port);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
servers.delete(port);
|
|
42
|
+
server.close();
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
exports.ensureArtifactServer = ensureArtifactServer;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as createApp } from "./app-BjjuQM0B.mjs";
|
|
2
|
+
import { serve } from "@hono/node-server";
|
|
3
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
4
|
+
//#region src/mcp/artifact-server.ts
|
|
5
|
+
const servers = /* @__PURE__ */ new Map();
|
|
6
|
+
async function isHealthy(port) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
9
|
+
try {
|
|
10
|
+
return (await fetch(`http://127.0.0.1:${port}/health`, { signal: controller.signal })).ok;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
} finally {
|
|
14
|
+
clearTimeout(timeout);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function waitUntilHealthy(port) {
|
|
18
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
19
|
+
if (await isHealthy(port)) return;
|
|
20
|
+
await setTimeout$1(50);
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`);
|
|
23
|
+
}
|
|
24
|
+
async function ensureArtifactServer(port) {
|
|
25
|
+
if (servers.has(port)) return;
|
|
26
|
+
if (await isHealthy(port)) return;
|
|
27
|
+
const server = serve({
|
|
28
|
+
fetch: createApp().fetch,
|
|
29
|
+
hostname: "127.0.0.1",
|
|
30
|
+
port
|
|
31
|
+
});
|
|
32
|
+
servers.set(port, server);
|
|
33
|
+
server.on("error", (err) => {
|
|
34
|
+
servers.delete(port);
|
|
35
|
+
process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${err.message}\n`);
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await waitUntilHealthy(port);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
servers.delete(port);
|
|
41
|
+
server.close();
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { ensureArtifactServer };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=artifact-server-Dxz5YbuQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-server-Dxz5YbuQ.mjs","names":["delay"],"sources":["../src/mcp/artifact-server.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { createApp } from '../server/app.js'\n\ntype ArtifactServer = ReturnType<typeof serve>\n\nconst servers = new Map<number, ArtifactServer>()\n\nasync function isHealthy(port: number): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 500)\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n })\n return response.ok\n } catch {\n return false\n } finally {\n clearTimeout(timeout)\n }\n}\n\nasync function waitUntilHealthy(port: number): Promise<void> {\n for (let attempt = 0; attempt < 20; attempt += 1) {\n if (await isHealthy(port)) return\n await delay(50)\n }\n throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`)\n}\n\nexport async function ensureArtifactServer(port: number): Promise<void> {\n if (servers.has(port)) return\n if (await isHealthy(port)) return\n\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1',\n port,\n })\n servers.set(port, server)\n server.on('error', (err) => {\n servers.delete(port)\n process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${(err as Error).message}\\n`)\n })\n\n try {\n await waitUntilHealthy(port)\n } catch (err) {\n servers.delete(port)\n server.close()\n throw err\n }\n}\n\nexport function closeArtifactServers(): void {\n for (const [port, server] of servers.entries()) {\n server.close()\n servers.delete(port)\n }\n}\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,KAA6B;AAEjD,eAAe,UAAU,MAAgC;CACvD,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,UAAU,iBAAiB,WAAW,OAAO,EAAE,IAAI;AACzD,KAAI;AAIF,UAAO,MAHgB,MAAM,oBAAoB,KAAK,UAAU,EAC9D,QAAQ,WAAW,QACpB,CAAC,EACc;SACV;AACN,SAAO;WACC;AACR,eAAa,QAAQ;;;AAIzB,eAAe,iBAAiB,MAA6B;AAC3D,MAAK,IAAI,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;AAChD,MAAI,MAAM,UAAU,KAAK,CAAE;AAC3B,QAAMA,aAAM,GAAG;;AAEjB,OAAM,IAAI,MAAM,2DAA2D,OAAO;;AAGpF,eAAsB,qBAAqB,MAA6B;AACtE,KAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,KAAI,MAAM,UAAU,KAAK,CAAE;CAG3B,MAAM,SAAS,MAAM;EACnB,OAFU,WAEA,CAAC;EACX,UAAU;EACV;EACD,CAAC;AACF,SAAQ,IAAI,MAAM,OAAO;AACzB,QAAO,GAAG,UAAU,QAAQ;AAC1B,UAAQ,OAAO,KAAK;AACpB,UAAQ,OAAO,MAAM,0DAA0D,KAAK,IAAK,IAAc,QAAQ,IAAI;GACnH;AAEF,KAAI;AACF,QAAM,iBAAiB,KAAK;UACrB,KAAK;AACZ,UAAQ,OAAO,KAAK;AACpB,SAAO,OAAO;AACd,QAAM"}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/mcp/call-args.ts
|
|
2
|
+
const NUMERIC_ARG_KEYS = new Set([
|
|
3
|
+
"per_query_timeout_seconds",
|
|
4
|
+
"incident_timestamp_ms",
|
|
5
|
+
"max_hops",
|
|
6
|
+
"per_address_limit",
|
|
7
|
+
"min_amount_sum"
|
|
8
|
+
]);
|
|
9
|
+
function parseMcpArgValue(key, value) {
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
if (!trimmed) return value;
|
|
12
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return JSON.parse(trimmed);
|
|
13
|
+
if (NUMERIC_ARG_KEYS.has(key) && /^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function parseMcpCallArgs(rawArgs) {
|
|
17
|
+
const args = {};
|
|
18
|
+
for (const pair of rawArgs) {
|
|
19
|
+
const eqIdx = pair.indexOf("=");
|
|
20
|
+
if (eqIdx === -1) throw new Error(`Invalid arg format: ${pair} (expected key=value)`);
|
|
21
|
+
const key = pair.slice(0, eqIdx);
|
|
22
|
+
args[key] = parseMcpArgValue(key, pair.slice(eqIdx + 1));
|
|
23
|
+
}
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
exports.parseMcpCallArgs = parseMcpCallArgs;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/mcp/call-args.ts
|
|
2
|
+
const NUMERIC_ARG_KEYS = new Set([
|
|
3
|
+
"per_query_timeout_seconds",
|
|
4
|
+
"incident_timestamp_ms",
|
|
5
|
+
"max_hops",
|
|
6
|
+
"per_address_limit",
|
|
7
|
+
"min_amount_sum"
|
|
8
|
+
]);
|
|
9
|
+
function parseMcpArgValue(key, value) {
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
if (!trimmed) return value;
|
|
12
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return JSON.parse(trimmed);
|
|
13
|
+
if (NUMERIC_ARG_KEYS.has(key) && /^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function parseMcpCallArgs(rawArgs) {
|
|
17
|
+
const args = {};
|
|
18
|
+
for (const pair of rawArgs) {
|
|
19
|
+
const eqIdx = pair.indexOf("=");
|
|
20
|
+
if (eqIdx === -1) throw new Error(`Invalid arg format: ${pair} (expected key=value)`);
|
|
21
|
+
const key = pair.slice(0, eqIdx);
|
|
22
|
+
args[key] = parseMcpArgValue(key, pair.slice(eqIdx + 1));
|
|
23
|
+
}
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { parseMcpCallArgs };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=call-args-Lk_wOJxd.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-args-Lk_wOJxd.mjs","names":[],"sources":["../src/mcp/call-args.ts"],"sourcesContent":["const NUMERIC_ARG_KEYS = new Set([\n 'per_query_timeout_seconds',\n 'incident_timestamp_ms',\n 'max_hops',\n 'per_address_limit',\n 'min_amount_sum',\n])\n\nfunction parseMcpArgValue(key: string, value: string): unknown {\n const trimmed = value.trim()\n if (!trimmed) return value\n\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n return JSON.parse(trimmed) as unknown\n }\n\n if (NUMERIC_ARG_KEYS.has(key) && /^-?\\d+(?:\\.\\d+)?$/.test(trimmed)) {\n return Number(trimmed)\n }\n\n return value\n}\n\nexport function parseMcpCallArgs(rawArgs: string[]): Record<string, unknown> {\n const args: Record<string, unknown> = {}\n for (const pair of rawArgs) {\n const eqIdx = pair.indexOf('=')\n if (eqIdx === -1) {\n throw new Error(`Invalid arg format: ${pair} (expected key=value)`)\n }\n const key = pair.slice(0, eqIdx)\n args[key] = parseMcpArgValue(key, pair.slice(eqIdx + 1))\n }\n return args\n}\n"],"mappings":";AAAA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,iBAAiB,KAAa,OAAwB;CAC7D,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAS,QAAO;AAErB,KAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,IAAI,CACpD,QAAO,KAAK,MAAM,QAAQ;AAG5B,KAAI,iBAAiB,IAAI,IAAI,IAAI,oBAAoB,KAAK,QAAQ,CAChE,QAAO,OAAO,QAAQ;AAGxB,QAAO;;AAGT,SAAgB,iBAAiB,SAA4C;CAC3E,MAAM,OAAgC,EAAE;AACxC,MAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,MAAI,UAAU,GACZ,OAAM,IAAI,MAAM,uBAAuB,KAAK,uBAAuB;EAErE,MAAM,MAAM,KAAK,MAAM,GAAG,MAAM;AAChC,OAAK,OAAO,iBAAiB,KAAK,KAAK,MAAM,QAAQ,EAAE,CAAC;;AAE1D,QAAO"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const require_client = require("./client-D4fZgIaO.cjs");
|
|
2
|
+
//#region src/mcp/capabilities.ts
|
|
3
|
+
function metadataNetworksUrl(endpoint) {
|
|
4
|
+
const url = new URL(endpoint);
|
|
5
|
+
url.pathname = "/metadata/networks";
|
|
6
|
+
url.search = "";
|
|
7
|
+
url.hash = "";
|
|
8
|
+
return url;
|
|
9
|
+
}
|
|
10
|
+
async function fetchNetworkCapabilities(config) {
|
|
11
|
+
const request = metadataNetworksUrl(require_client.resolveGraphMcpEndpoint(config));
|
|
12
|
+
const headers = new Headers();
|
|
13
|
+
const token = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.trim();
|
|
14
|
+
if (token) {
|
|
15
|
+
headers.set("X-MCP-Debug-Token", token);
|
|
16
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
17
|
+
}
|
|
18
|
+
let response;
|
|
19
|
+
try {
|
|
20
|
+
response = await fetch(request, { headers });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new Error(`network capabilities unavailable at ${request}: ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
if (!response.ok) throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`);
|
|
25
|
+
const parsed = await response.json();
|
|
26
|
+
if (parsed.schema !== "chain-insights.network-capabilities.v1" || !Array.isArray(parsed.networks)) throw new Error("network capabilities response has unsupported schema");
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function layerValue(network, layer) {
|
|
30
|
+
if (!network.layers[layer]?.enabled) return "no";
|
|
31
|
+
return "yes";
|
|
32
|
+
}
|
|
33
|
+
function availableToolsLabel(network) {
|
|
34
|
+
const tools = Object.entries(network.tools ?? {}).filter(([, status]) => status === "available").map(([name]) => name);
|
|
35
|
+
return tools.length > 0 ? tools.join(", ") : "none";
|
|
36
|
+
}
|
|
37
|
+
function shortDate(value) {
|
|
38
|
+
if (!value) return "";
|
|
39
|
+
return value.slice(0, 10);
|
|
40
|
+
}
|
|
41
|
+
function datasetLabel(network) {
|
|
42
|
+
const coverage = network.coverage;
|
|
43
|
+
if (!coverage) return "unknown";
|
|
44
|
+
const blockRange = coverage.from_block !== void 0 && coverage.to_block !== void 0 ? `${coverage.from_block}..${coverage.to_block}` : "blocks unknown";
|
|
45
|
+
const dateRange = coverage.from_timestamp && coverage.to_timestamp ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}` : "dates unknown";
|
|
46
|
+
if (blockRange === "blocks unknown" && dateRange === "dates unknown") return "unknown";
|
|
47
|
+
return `${blockRange} / ${dateRange}`;
|
|
48
|
+
}
|
|
49
|
+
function formatNetworkCapabilities(document) {
|
|
50
|
+
if (document.networks.length === 0) return "No supported networks advertised.";
|
|
51
|
+
const headers = [
|
|
52
|
+
"Network",
|
|
53
|
+
"Topology",
|
|
54
|
+
"Facts",
|
|
55
|
+
"Risk",
|
|
56
|
+
"Dataset",
|
|
57
|
+
"Available tools"
|
|
58
|
+
];
|
|
59
|
+
const widths = [
|
|
60
|
+
14,
|
|
61
|
+
10,
|
|
62
|
+
8,
|
|
63
|
+
8,
|
|
64
|
+
38,
|
|
65
|
+
64
|
|
66
|
+
];
|
|
67
|
+
const row = (values) => values.map((value, index) => value.padEnd(widths[index])).join(" ");
|
|
68
|
+
return [
|
|
69
|
+
row(headers),
|
|
70
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
71
|
+
...document.networks.map((network) => row([
|
|
72
|
+
network.display_name || network.network,
|
|
73
|
+
layerValue(network, "topology"),
|
|
74
|
+
layerValue(network, "facts"),
|
|
75
|
+
layerValue(network, "risk"),
|
|
76
|
+
datasetLabel(network),
|
|
77
|
+
availableToolsLabel(network)
|
|
78
|
+
]))
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
exports.fetchNetworkCapabilities = fetchNetworkCapabilities;
|
|
83
|
+
exports.formatNetworkCapabilities = formatNetworkCapabilities;
|