chain-insights 0.2.16 → 0.2.17
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/README.md +29 -18
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{init-CaOsHTIo.mjs → init-BufWeNP5.mjs} +4 -5
- package/dist/init-BufWeNP5.mjs.map +1 -0
- package/dist/{init-BjuFt54X.cjs → init-BxDVuX6v.cjs} +3 -4
- package/dist/mcp-proxy.cjs +9 -9
- package/dist/mcp-proxy.mjs +9 -9
- package/dist/mcp-proxy.mjs.map +1 -1
- package/docs/architecture.md +1 -2
- package/docs/graph-tools.md +3 -3
- package/package.json +22 -2
- package/skills/chain-insights-investigation/SKILL.md +9 -9
- package/skills/chain-insights-trace-funds/SKILL.md +3 -3
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +1 -1
- package/dist/init-CaOsHTIo.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Chain Insights
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
turns graph access into analyst-ready workflows: address screening, fund-flow
|
|
5
|
-
tracing, scam topology discovery, case files, evidence, dossiers, reports, and
|
|
6
|
-
graph visualizations.
|
|
3
|
+
[Website](https://chain-insights.ai) | [GitHub](https://github.com/chainswarm/chain-insights) | [npm](https://www.npmjs.com/package/chain-insights)
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Chain Insights is an open-source AML investigation toolkit for AI agents and
|
|
6
|
+
analysts. Install it from npm to screen blockchain addresses, trace funds,
|
|
7
|
+
expand scam topologies, manage case evidence, and generate graph reports from
|
|
8
|
+
Chain Insights graph intelligence.
|
|
9
|
+
|
|
10
|
+
The hosted GraphRAG MCP access path is paid through x402. The CLI and MCP proxy
|
|
11
|
+
handle local wallet status, paid graph calls, approved test access, case files,
|
|
12
|
+
evidence pointers, dossiers, and reports.
|
|
10
13
|
|
|
11
14
|
## What You Can Do Today
|
|
12
15
|
|
|
@@ -20,7 +23,13 @@ investigation workflow around them.
|
|
|
20
23
|
|
|
21
24
|
## Quick Start
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
Install from npm:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g chain-insights
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Check the CLI:
|
|
24
33
|
|
|
25
34
|
```bash
|
|
26
35
|
cia --version
|
|
@@ -47,13 +56,15 @@ Check the configured endpoint and current GraphRAG MCP capabilities:
|
|
|
47
56
|
|
|
48
57
|
```bash
|
|
49
58
|
cia config get graphMcpEndpoint
|
|
59
|
+
cia wallet balance
|
|
50
60
|
cia mcp networks
|
|
51
61
|
cia mcp tools --refresh
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
endpoint
|
|
64
|
+
GraphRAG MCP calls use x402 paid mode by default unless you configure approved
|
|
65
|
+
test access or local debug access. If network or tool discovery fails, fix
|
|
66
|
+
endpoint/auth/payment first; the CLI can still initialize workspaces and manage
|
|
67
|
+
cases without a reachable GraphRAG MCP endpoint.
|
|
57
68
|
|
|
58
69
|
Open a case and run a small investigation:
|
|
59
70
|
|
|
@@ -111,25 +122,25 @@ Agent or CLI user
|
|
|
111
122
|
-> Chain Insights CLI / MCP proxy
|
|
112
123
|
-> local config, wallet, workspace, cases, evidence, reports
|
|
113
124
|
-> GraphRAG MCP
|
|
114
|
-
->
|
|
125
|
+
-> graph intelligence for AML workflows
|
|
115
126
|
```
|
|
116
127
|
|
|
117
128
|
Chain Insights stores investigation outputs in initialized local workspaces.
|
|
118
129
|
GraphRAG MCP performs graph-language reads against network-specific graph
|
|
119
130
|
layers.
|
|
120
131
|
|
|
121
|
-
##
|
|
132
|
+
## Graph Access
|
|
122
133
|
|
|
123
|
-
Graph queries must choose
|
|
134
|
+
Graph queries must choose the right read layer explicitly:
|
|
124
135
|
|
|
125
|
-
| Layer |
|
|
136
|
+
| Layer | Use it for |
|
|
126
137
|
| --- | --- |
|
|
127
|
-
| `live_topology` |
|
|
128
|
-
| `archive_topology` |
|
|
129
|
-
| `facts` |
|
|
138
|
+
| `live_topology` | Recent topology and fast traversal |
|
|
139
|
+
| `archive_topology` | Historical fund-flow context |
|
|
140
|
+
| `facts` | Labels, features, risk scores, assets, and enrichment |
|
|
130
141
|
|
|
131
142
|
Use `graph_query_batch` when related reads should share one call and one
|
|
132
|
-
result envelope.
|
|
143
|
+
result envelope. Paid hosted calls are settled through x402.
|
|
133
144
|
|
|
134
145
|
## AML Tools
|
|
135
146
|
|
package/dist/cli.cjs
CHANGED
|
@@ -215,7 +215,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
|
|
|
215
215
|
}));
|
|
216
216
|
program.command("init").description("Initialize an investigation workspace").argument("[dir]", "Workspace directory to initialize", ".").option("--force", "Overwrite existing workspace files").action(async (dir, opts) => {
|
|
217
217
|
try {
|
|
218
|
-
const { initWorkspace } = await Promise.resolve().then(() => require("./init-
|
|
218
|
+
const { initWorkspace } = await Promise.resolve().then(() => require("./init-BxDVuX6v.cjs"));
|
|
219
219
|
const result = await initWorkspace({
|
|
220
220
|
targetDir: dir,
|
|
221
221
|
force: opts.force
|
package/dist/cli.mjs
CHANGED
|
@@ -213,7 +213,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
|
|
|
213
213
|
}));
|
|
214
214
|
program.command("init").description("Initialize an investigation workspace").argument("[dir]", "Workspace directory to initialize", ".").option("--force", "Overwrite existing workspace files").action(async (dir, opts) => {
|
|
215
215
|
try {
|
|
216
|
-
const { initWorkspace } = await import("./init-
|
|
216
|
+
const { initWorkspace } = await import("./init-BufWeNP5.mjs");
|
|
217
217
|
const result = await initWorkspace({
|
|
218
218
|
targetDir: dir,
|
|
219
219
|
force: opts.force
|
|
@@ -144,10 +144,9 @@ property names for the active network.
|
|
|
144
144
|
Rules:
|
|
145
145
|
|
|
146
146
|
- Prefer \`graph_query\` and \`graph_query_batch\` for graph-language reads.
|
|
147
|
-
- Use \`USE live_topology\` for
|
|
148
|
-
for
|
|
149
|
-
|
|
150
|
-
\`RiskScore\`, and \`Asset\`. Address facts can be reached through
|
|
147
|
+
- Use \`USE live_topology\` for recent topology, \`USE archive_topology\`
|
|
148
|
+
for historical topology, and \`USE facts\` for labels, features,
|
|
149
|
+
risk scores, assets, and enrichment. Address facts can be reached through
|
|
151
150
|
relationships such as \`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\`.
|
|
152
151
|
Archived money-flow topology is exposed as
|
|
153
152
|
\`(:Address)-[:FLOWS_TO]->(:Address)\` with \`period_granularity\`,
|
|
@@ -229,4 +228,4 @@ async function initWorkspace(options) {
|
|
|
229
228
|
//#endregion
|
|
230
229
|
export { initWorkspace };
|
|
231
230
|
|
|
232
|
-
//# sourceMappingURL=init-
|
|
231
|
+
//# sourceMappingURL=init-BufWeNP5.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-BufWeNP5.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport interface InitWorkspaceOptions {\n targetDir: string\n force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspaceRoot: string\n filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n '.chain-insights',\n '.chain-insights/schema',\n '.chain-insights/runtime',\n '.chain-insights/runtime/logs',\n '.chain-insights/runtime-skill',\n 'cases',\n 'imports',\n 'reports',\n 'reports/graphs',\n 'reports/tables',\n 'templates',\n]\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n return JSON.stringify({\n schema: 'chain-insights.workspace.v1',\n name: 'Chain Insights Investigations',\n workspace_root: workspaceRoot,\n default_network: 'bittensor',\n graph_mcp_endpoint: 'https://staging-mcp.chain-insights.ai/mcp',\n cases_dir: 'cases',\n imports_dir: 'imports',\n reports_dir: 'reports',\n templates_dir: 'templates',\n created_at: todayIso(),\n }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Investigations\n\nThis is a workspace for Chain Insights AML investigations.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet balance\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/ Workspace metadata\ncases/ Case exports and notes\nimports/ External reports, CSVs, screenshots, raw notes\nreports/ Final or interim analyst reports\nreports/graphs/ Graph JSON for visualization\nreports/tables/ Compact tabular extracts\ntemplates/ Reusable case/report templates\n.chain-insights/schema/ Runtime graph schema captures\n.chain-insights/runtime/ Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/ Workspace-specific agent schema notes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights investigation workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before investigation-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first case query.\n- Save compact evidence with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Evidence files should summarize and point to graph/table outputs; do not paste large raw JSON blobs into evidence Markdown.\n- Investigation output must stay in this initialized workspace.\n- Never write cases, evidence, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Case Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party investigation material here before turning it\ninto case evidence.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into the case evidence manifest and reference\nthe original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, case, prompt, and evidence templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as case state until copied into a case, evidence file, dossier, or\nreport.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Address) RETURN \\\"Address\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Address)-[f:FLOWS_TO]->(:Address) RETURN f.period_granularity AS granularity, f.amount_sum AS amount_sum LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Use \\`USE live_topology\\` for recent topology, \\`USE archive_topology\\`\n for historical topology, and \\`USE facts\\` for labels, features,\n risk scores, assets, and enrichment. Address facts can be reached through\n relationships such as \\`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\\`.\n Archived money-flow topology is exposed as\n \\`(:Address)-[:FLOWS_TO]->(:Address)\\` with \\`period_granularity\\`,\n \\`period_start_date\\`, and \\`period_end_date\\` on the relationship.\n- Preserve source schema field names in evidence and generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n schema or query result explicitly supports that interpretation.\n- Keep evidence compact: select only the fields needed to support the claim.\n Avoid storing whole node or relationship property blobs in evidence unless\n the purpose of the query is schema discovery or debugging.\n- Keep analysis products separate from evidence: graph JSON belongs under\n \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n narrative under \\`reports/\\`.\n- Evidence Markdown should be a short provenance record with key facts and\n pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in evidence.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first case query in a fresh\nworkspace, then referenced by evidence, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n return [\n ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n ['README.md', README],\n ['AGENTS.md', AGENTS],\n ['CLAUDE.md', CLAUDE],\n ['imports/README.md', IMPORTS_README],\n ['templates/README.md', TEMPLATES_README],\n ['templates/case-brief.md', CASE_BRIEF],\n ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n ['.chain-insights/schema/README.md', SCHEMA_README],\n ['.chain-insights/runtime/.keep', ''],\n ['.chain-insights/runtime/logs/.keep', ''],\n ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await access(filePath)\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n continue\n }\n throw err\n }\n }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n const workspaceRoot = path.resolve(options.targetDir)\n if (!options.force) {\n await assertNoFileCollisions(workspaceRoot)\n }\n\n for (const dir of WORKSPACE_DIRS) {\n await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n }\n\n const filesWritten: string[] = []\n const flag = options.force ? 'w' : 'wx'\n for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await writeFile(filePath, content, { mode: 0o600, flag })\n filesWritten.push(relativePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n }\n throw err\n }\n }\n\n return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;AAaA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,WAAmB;AAC1B,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;AAG9C,SAAS,cAAc,eAA+B;AACpD,QAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,aAAa;EACb,aAAa;EACb,eAAe;EACf,YAAY,UAAU;EACvB,EAAE,MAAM,EAAE,GAAG;;AAGhB,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;AA2Bf,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CtB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;AACtE,QAAO;EACL,CAAC,kCAAkC,cAAc,cAAc,CAAC;EAChE,CAAC,aAAa,OAAO;EACrB,CAAC,aAAa,OAAO;EACrB,CAAC,aAAa,OAAO;EACrB,CAAC,qBAAqB,eAAe;EACrC,CAAC,uBAAuB,iBAAiB;EACzC,CAAC,2BAA2B,WAAW;EACvC,CAAC,0CAA0C,cAAc;EACzD,CAAC,oCAAoC,cAAc;EACnD,CAAC,iCAAiC,GAAG;EACrC,CAAC,sCAAsC,GAAG;EAC3C;;AAGH,eAAe,uBAAuB,eAAsC;AAC1E,MAAK,MAAM,CAAC,iBAAiB,eAAe,cAAc,EAAE;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,aAAa;AACvD,MAAI;AACF,SAAM,OAAO,SAAS;AACtB,SAAM,IAAI,MAAM,yBAAyB,SAAS,mDAAmD;WAC9F,KAAK;AACZ,OAAK,IAA8B,SAAS,SAC1C;AAEF,SAAM;;;;AAKZ,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,UAAU;AACrD,KAAI,CAAC,QAAQ,MACX,OAAM,uBAAuB,cAAc;AAG7C,MAAK,MAAM,OAAO,eAChB,OAAM,MAAM,KAAK,KAAK,eAAe,IAAI,EAAE,EAAE,WAAW,MAAM,CAAC;CAGjE,MAAM,eAAyB,EAAE;CACjC,MAAM,OAAO,QAAQ,QAAQ,MAAM;AACnC,MAAK,MAAM,CAAC,cAAc,YAAY,eAAe,cAAc,EAAE;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,aAAa;AACvD,MAAI;AACF,SAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;IAAM,CAAC;AACzD,gBAAa,KAAK,aAAa;WACxB,KAAK;AACZ,OAAK,IAA8B,SAAS,SAC1C,OAAM,IAAI,MAAM,yBAAyB,SAAS,mDAAmD;AAEvG,SAAM;;;AAIV,QAAO;EAAE;EAAe;EAAc"}
|
|
@@ -146,10 +146,9 @@ property names for the active network.
|
|
|
146
146
|
Rules:
|
|
147
147
|
|
|
148
148
|
- Prefer \`graph_query\` and \`graph_query_batch\` for graph-language reads.
|
|
149
|
-
- Use \`USE live_topology\` for
|
|
150
|
-
for
|
|
151
|
-
|
|
152
|
-
\`RiskScore\`, and \`Asset\`. Address facts can be reached through
|
|
149
|
+
- Use \`USE live_topology\` for recent topology, \`USE archive_topology\`
|
|
150
|
+
for historical topology, and \`USE facts\` for labels, features,
|
|
151
|
+
risk scores, assets, and enrichment. Address facts can be reached through
|
|
153
152
|
relationships such as \`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\`.
|
|
154
153
|
Archived money-flow topology is exposed as
|
|
155
154
|
\`(:Address)-[:FLOWS_TO]->(:Address)\` with \`period_granularity\`,
|
package/dist/mcp-proxy.cjs
CHANGED
|
@@ -58,7 +58,7 @@ const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
|
|
|
58
58
|
address_risk: "Screen one full blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with compare_address. This includes the exchange-behavior analysis formerly covered by money_flows_between_exchanges. Use this as the first tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.",
|
|
59
59
|
scam_topology: "Build victim-incident laundering topology from one victim/source address and the earliest known incident timestamp. Traversal uses one explicit activity policy: node_relative_only by default, or global_incident_only when requested. Repeated targets are kept as non-expanding convergence edges. Returns ML-ready scam_labels plus review context and a track_funds-compatible graph report: primary flows, deposits, reverse_leads. Victims, exchange endpoints, and generic labeled context nodes are not automatic scam labels; preserve full addresses exactly.",
|
|
60
60
|
track_funds: "Trace funds from trusted victim/source addresses through intermediaries to exchange deposit addresses. Use this when the user has a victim/source address or known untrusted/scammer addresses. The tool returns an investigator-ready fund-flow report and recommended next actions.",
|
|
61
|
-
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for
|
|
61
|
+
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Cross-layer correlated joins may be limited by the active graph endpoint; preserve full addresses exactly.",
|
|
62
62
|
graph_query_batch: "Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads."
|
|
63
63
|
};
|
|
64
64
|
const NETWORK_DESCRIPTION = "Required network to query. Do not guess; use network_capabilities or ask the user if missing.";
|
|
@@ -80,12 +80,12 @@ const GRAPH_SCHEMA_HINTS = [
|
|
|
80
80
|
"- Risk and ML properties may appear as live hints, but source-of-truth risk rows are RiskScore facts.",
|
|
81
81
|
"- Common relationships include FLOWS_TO, OPERATED_FROM, SERVED_FROM, REGISTERED_NEURON, BELONGS_TO, SYBIL_CLUSTER, LAYERING_HOP, BURST_ACTIVITY, CYCLE_PARTICIPANT, SMURFING_CLUSTER.",
|
|
82
82
|
"- FLOWS_TO properties are scoped to the selected topology graph and commonly carry amount_sum, amount_usd_sum, tx_count, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id. Confirm available fields through runtime schema before relying on them.",
|
|
83
|
-
"- Start schema discovery with
|
|
83
|
+
"- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 20",
|
|
84
84
|
"- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_sum AS amount_sum, r.amount_usd_sum AS amount_usd_sum LIMIT 20",
|
|
85
|
-
"- graph_query uses
|
|
85
|
+
"- graph_query uses the active Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment.",
|
|
86
86
|
"- Archive topology labels include Address and TopologySnapshot. Archived money-flow topology is represented as (:Address)-[:FLOWS_TO]->(:Address) relationships with period_granularity, period_start_date, and period_end_date.",
|
|
87
87
|
"- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.",
|
|
88
|
-
"-
|
|
88
|
+
"- Use USE facts graph patterns for fact and enrichment reads. Do not query internal table namespaces directly."
|
|
89
89
|
].join("\n");
|
|
90
90
|
const GRAPH_REPORT_HINTS = [
|
|
91
91
|
"Graph visualization behavior:",
|
|
@@ -158,7 +158,7 @@ function knownPublicToolInputSchema(toolName) {
|
|
|
158
158
|
case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.")
|
|
159
159
|
};
|
|
160
160
|
case "graph_query": return {
|
|
161
|
-
query: zod.string().min(1).describe("Read-only GQL/Cypher query. Use USE live_topology for
|
|
161
|
+
query: zod.string().min(1).describe("Read-only GQL/Cypher query. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment."),
|
|
162
162
|
network: zod.string().min(1).describe(NETWORK_DESCRIPTION)
|
|
163
163
|
};
|
|
164
164
|
case "graph_query_batch": return {
|
|
@@ -414,7 +414,7 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
414
414
|
});
|
|
415
415
|
server.registerPrompt("graph-query", {
|
|
416
416
|
title: "Federated Graph Query",
|
|
417
|
-
description: "Run a read-only GQL/Cypher query through Chain Insights
|
|
417
|
+
description: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.",
|
|
418
418
|
argsSchema: {
|
|
419
419
|
query: zod.string().describe("Read-only GQL/Cypher query"),
|
|
420
420
|
network: zod.string().describe(NETWORK_DESCRIPTION)
|
|
@@ -426,11 +426,11 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
426
426
|
query,
|
|
427
427
|
"```",
|
|
428
428
|
"",
|
|
429
|
-
"Use USE live_topology for
|
|
429
|
+
"Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses."
|
|
430
430
|
].join("\n"), "Federated graph query"));
|
|
431
431
|
server.registerPrompt("graph-query-batch", {
|
|
432
432
|
title: "Federated Graph Query Batch",
|
|
433
|
-
description: "Run related read-only GQL/Cypher queries through Chain Insights
|
|
433
|
+
description: "Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.",
|
|
434
434
|
argsSchema: {
|
|
435
435
|
queries: zod.string().describe("JSON array of query objects with optional id and required query fields"),
|
|
436
436
|
network: zod.string().describe(NETWORK_DESCRIPTION),
|
|
@@ -444,7 +444,7 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
444
444
|
"```",
|
|
445
445
|
per_query_timeout_seconds ? `per_query_timeout_seconds: ${per_query_timeout_seconds}` : "",
|
|
446
446
|
"",
|
|
447
|
-
"Use USE live_topology for
|
|
447
|
+
"Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses."
|
|
448
448
|
].filter(Boolean).join("\n"), "Federated graph batch query"));
|
|
449
449
|
server.registerPrompt("balance", {
|
|
450
450
|
title: "Wallet Balance",
|
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -54,7 +54,7 @@ const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
|
|
|
54
54
|
address_risk: "Screen one full blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with compare_address. This includes the exchange-behavior analysis formerly covered by money_flows_between_exchanges. Use this as the first tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.",
|
|
55
55
|
scam_topology: "Build victim-incident laundering topology from one victim/source address and the earliest known incident timestamp. Traversal uses one explicit activity policy: node_relative_only by default, or global_incident_only when requested. Repeated targets are kept as non-expanding convergence edges. Returns ML-ready scam_labels plus review context and a track_funds-compatible graph report: primary flows, deposits, reverse_leads. Victims, exchange endpoints, and generic labeled context nodes are not automatic scam labels; preserve full addresses exactly.",
|
|
56
56
|
track_funds: "Trace funds from trusted victim/source addresses through intermediaries to exchange deposit addresses. Use this when the user has a victim/source address or known untrusted/scammer addresses. The tool returns an investigator-ready fund-flow report and recommended next actions.",
|
|
57
|
-
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for
|
|
57
|
+
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Cross-layer correlated joins may be limited by the active graph endpoint; preserve full addresses exactly.",
|
|
58
58
|
graph_query_batch: "Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads."
|
|
59
59
|
};
|
|
60
60
|
const NETWORK_DESCRIPTION = "Required network to query. Do not guess; use network_capabilities or ask the user if missing.";
|
|
@@ -76,12 +76,12 @@ const GRAPH_SCHEMA_HINTS = [
|
|
|
76
76
|
"- Risk and ML properties may appear as live hints, but source-of-truth risk rows are RiskScore facts.",
|
|
77
77
|
"- Common relationships include FLOWS_TO, OPERATED_FROM, SERVED_FROM, REGISTERED_NEURON, BELONGS_TO, SYBIL_CLUSTER, LAYERING_HOP, BURST_ACTIVITY, CYCLE_PARTICIPANT, SMURFING_CLUSTER.",
|
|
78
78
|
"- FLOWS_TO properties are scoped to the selected topology graph and commonly carry amount_sum, amount_usd_sum, tx_count, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id. Confirm available fields through runtime schema before relying on them.",
|
|
79
|
-
"- Start schema discovery with
|
|
79
|
+
"- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 20",
|
|
80
80
|
"- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_sum AS amount_sum, r.amount_usd_sum AS amount_usd_sum LIMIT 20",
|
|
81
|
-
"- graph_query uses
|
|
81
|
+
"- graph_query uses the active Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment.",
|
|
82
82
|
"- Archive topology labels include Address and TopologySnapshot. Archived money-flow topology is represented as (:Address)-[:FLOWS_TO]->(:Address) relationships with period_granularity, period_start_date, and period_end_date.",
|
|
83
83
|
"- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.",
|
|
84
|
-
"-
|
|
84
|
+
"- Use USE facts graph patterns for fact and enrichment reads. Do not query internal table namespaces directly."
|
|
85
85
|
].join("\n");
|
|
86
86
|
const GRAPH_REPORT_HINTS = [
|
|
87
87
|
"Graph visualization behavior:",
|
|
@@ -154,7 +154,7 @@ function knownPublicToolInputSchema(toolName) {
|
|
|
154
154
|
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.")
|
|
155
155
|
};
|
|
156
156
|
case "graph_query": return {
|
|
157
|
-
query: z.string().min(1).describe("Read-only GQL/Cypher query. Use USE live_topology for
|
|
157
|
+
query: z.string().min(1).describe("Read-only GQL/Cypher query. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment."),
|
|
158
158
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION)
|
|
159
159
|
};
|
|
160
160
|
case "graph_query_batch": return {
|
|
@@ -410,7 +410,7 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
410
410
|
});
|
|
411
411
|
server.registerPrompt("graph-query", {
|
|
412
412
|
title: "Federated Graph Query",
|
|
413
|
-
description: "Run a read-only GQL/Cypher query through Chain Insights
|
|
413
|
+
description: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.",
|
|
414
414
|
argsSchema: {
|
|
415
415
|
query: z.string().describe("Read-only GQL/Cypher query"),
|
|
416
416
|
network: z.string().describe(NETWORK_DESCRIPTION)
|
|
@@ -422,11 +422,11 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
422
422
|
query,
|
|
423
423
|
"```",
|
|
424
424
|
"",
|
|
425
|
-
"Use USE live_topology for
|
|
425
|
+
"Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses."
|
|
426
426
|
].join("\n"), "Federated graph query"));
|
|
427
427
|
server.registerPrompt("graph-query-batch", {
|
|
428
428
|
title: "Federated Graph Query Batch",
|
|
429
|
-
description: "Run related read-only GQL/Cypher queries through Chain Insights
|
|
429
|
+
description: "Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.",
|
|
430
430
|
argsSchema: {
|
|
431
431
|
queries: z.string().describe("JSON array of query objects with optional id and required query fields"),
|
|
432
432
|
network: z.string().describe(NETWORK_DESCRIPTION),
|
|
@@ -440,7 +440,7 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
440
440
|
"```",
|
|
441
441
|
per_query_timeout_seconds ? `per_query_timeout_seconds: ${per_query_timeout_seconds}` : "",
|
|
442
442
|
"",
|
|
443
|
-
"Use USE live_topology for
|
|
443
|
+
"Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses."
|
|
444
444
|
].filter(Boolean).join("\n"), "Federated graph batch query"));
|
|
445
445
|
server.registerPrompt("balance", {
|
|
446
446
|
title: "Wallet Balance",
|
package/dist/mcp-proxy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-proxy.mjs","names":[],"sources":["../src/mcp/proxy.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ContentBlock, GetPromptResult, Prompt } from '@modelcontextprotocol/sdk/types.js'\nimport { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'\nimport * as z from 'zod'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { McpTool } from './schema-cache.js'\nimport { HIDDEN_REMOTE_TOOL_NAMES } from './tool-visibility.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'balance',\n 'help',\n 'case_open',\n 'case_list',\n 'case_resume',\n 'case_add_evidence',\n 'case_verify_evidence',\n 'case_update_dossier',\n 'case_start_session',\n 'case_end_session',\n])\nconst PUBLIC_GRAPHRAG_PROMPT_NAMES = new Set(['address-risk', 'track-funds'])\nconst GRAPH_RESOURCE_URI = 'ui://chain-insights/graph'\nconst GRAPH_APP_TOOL_NAMES = new Set([\n 'address_risk',\n 'scam_topology',\n 'track_funds',\n])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nconst COMMA_SEPARATED_ADDRESS_FIELDS = new Set([\n 'trusted_addresses',\n 'untrusted_addresses',\n])\n\nconst KNOWN_PUBLIC_TOOL_REQUIRED_ARGS: Record<string, string[]> = {\n address_risk: ['address', 'network'],\n scam_topology: ['victim_address', 'incident_timestamp_ms', 'network'],\n track_funds: ['trusted_addresses', 'network'],\n graph_query: ['query', 'network'],\n graph_query_batch: ['network', 'queries'],\n}\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n network_capabilities: 'Return supported Chain Insights networks, capability layers, tool availability, data retention windows, and freshness. Use this before choosing network-specific tools.',\n address_risk: 'Screen one full blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with compare_address. This includes the exchange-behavior analysis formerly covered by money_flows_between_exchanges. Use this as the first tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.',\n scam_topology: 'Build victim-incident laundering topology from one victim/source address and the earliest known incident timestamp. Traversal uses one explicit activity policy: node_relative_only by default, or global_incident_only when requested. Repeated targets are kept as non-expanding convergence edges. Returns ML-ready scam_labels plus review context and a track_funds-compatible graph report: primary flows, deposits, reverse_leads. Victims, exchange endpoints, and generic labeled context nodes are not automatic scam labels; preserve full addresses exactly.',\n track_funds: 'Trace funds from trusted victim/source addresses through intermediaries to exchange deposit addresses. Use this when the user has a victim/source address or known untrusted/scammer addresses. The tool returns an investigator-ready fund-flow report and recommended next actions.',\n graph_query: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for Memgraph RAM topology, USE archive_topology for StarRocks historical topology, and USE facts for StarRocks facts. Cross-backend correlated joins are limited by current MemGQL behavior; preserve full addresses exactly.',\n graph_query_batch: 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\n\nconst NETWORK_DESCRIPTION = 'Required network to query. Do not guess; use network_capabilities or ask the user if missing.'\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. If the user is starting or continuing an investigation, use case_open or case_list/case_resume first.',\n '2. Do not call investigation tools until required arguments are known. Network is required; use network_capabilities to check supported networks, data layers, retention, and freshness, or ask the user if missing.',\n '3. Use address_risk first for a single address when facts and topology are available. Use track_funds for victim/source fund tracing when topology is available. Use scam_topology when known victim incident ground truth should become ML-ready scam labels. Use graph_query(_batch) for the universal graph-language path over topology and facts.',\n '4. After a material result, preserve it with case_add_evidence when a case is active or ask whether to create/select a case.',\n '5. Use case_update_dossier for durable address/entity findings and case_start_session/case_end_session for session notes.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints for network=bittensor:',\n '- Common live topology node labels include Address and may include legacy enrichment labels. Do not depend on Exchange/Miner graph labels for correctness; use address properties such as labels and is_exchange when available.',\n '- Address nodes are identity plus traversal hints. Lifetime/global address metrics live in USE facts as AddressFeature, not as topology semantics.',\n '- Facts graph labels include Address, AddressLabel, AddressFeature, RiskScore, and Asset.',\n '- Facts graph relationships include (:Address)-[:HAS_FEATURE]->(:AddressFeature), (:Address)-[:HAS_LABEL]->(:AddressLabel), and (:Address)-[:HAS_RISK_SCORE]->(:RiskScore).',\n '- Risk and ML properties may appear as live hints, but source-of-truth risk rows are RiskScore facts.',\n '- Common relationships include FLOWS_TO, OPERATED_FROM, SERVED_FROM, REGISTERED_NEURON, BELONGS_TO, SYBIL_CLUSTER, LAYERING_HOP, BURST_ACTIVITY, CYCLE_PARTICIPANT, SMURFING_CLUSTER.',\n '- FLOWS_TO properties are scoped to the selected topology graph and commonly carry amount_sum, amount_usd_sum, tx_count, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id. Confirm available fields through runtime schema before relying on them.',\n '- Start schema discovery with MemGQL-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 20',\n '- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_sum AS amount_sum, r.amount_usd_sum AS amount_usd_sum LIMIT 20',\n '- graph_query uses Memgraph Zero / MemGQL when available. Use USE live_topology for Memgraph RAM topology, USE archive_topology for StarRocks historical topology, and USE facts for StarRocks facts.',\n '- Archive topology labels include Address and TopologySnapshot. Archived money-flow topology is represented as (:Address)-[:FLOWS_TO]->(:Address) relationships with period_granularity, period_start_date, and period_end_date.',\n '- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.',\n '- Warehouse facts live behind facts_*_view StarRocks views and are reached through USE facts graph patterns. Do not query core_*, ml_*, analyzers_*, synthetics_*, or _* tables directly.',\n].join('\\n')\n\nconst GRAPH_REPORT_HINTS = [\n 'Graph visualization behavior:',\n '- Graph-backed tools return the investigator report as text content and keep raw graph data out of LLM-visible structuredContent.',\n '- Raw graph data is stored locally under Chain Insights reports/graphs and exposed to the graph app as _meta.chainInsights.graph.url.',\n '- The local graph report server is started automatically by the MCP server when a graph-backed tool returns a report URL; do not ask the user to run chain-insights serve for Claude Desktop graph iframes.',\n '- If an iframe reports that a graph report fetch failed, retry the graph-backed tool call so Chain Insights can recreate the report URL and ensure the local report server is running.',\n].join('\\n')\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is a local AML investigation workspace for AI agents.',\n CHAIN_INSIGHTS_WORKFLOW,\n GRAPH_REPORT_HINTS,\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses; use case tools to preserve evidence when a case exists.',\n].join('\\n\\n')\n\nfunction readGraphAppHtml(): string {\n const candidates = [\n path.resolve(__dirname, 'templates', 'graph.html'),\n path.resolve(__dirname, '..', 'templates', 'graph.html'),\n path.resolve(__dirname, '..', 'viz', 'templates', 'graph.html'),\n ]\n\n for (const candidate of candidates) {\n try {\n return readFileSync(candidate, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n }\n\n throw new Error(`Graph MCP app template not found. Tried: ${candidates.join(', ')}`)\n}\n\nfunction graphArtifactOrigins(config: Pick<InvestigatorConfig, 'serverPort'>): string[] {\n return [\n `http://127.0.0.1:${config.serverPort}`,\n `http://localhost:${config.serverPort}`,\n ]\n}\n\nfunction hasGraphApp(tool: McpTool): boolean {\n const configuredUri = tool._meta?.ui\n if (\n configuredUri &&\n typeof configuredUri === 'object' &&\n 'resourceUri' in configuredUri &&\n configuredUri.resourceUri === GRAPH_RESOURCE_URI\n ) {\n return true\n }\n\n if (tool._meta?.['ui/resourceUri'] === GRAPH_RESOURCE_URI) return true\n if (GRAPH_APP_TOOL_NAMES.has(tool.name)) return true\n return JSON.stringify(tool.outputSchema ?? {}).includes('\"app_data\"')\n}\n\nfunction graphToolMeta(tool: McpTool): Record<string, unknown> & { ui: { resourceUri: string } } {\n const meta = { ...(tool._meta ?? {}) }\n const ui =\n meta.ui && typeof meta.ui === 'object' && !Array.isArray(meta.ui)\n ? { ...(meta.ui as Record<string, unknown>) }\n : {}\n\n return {\n ...meta,\n ui: {\n ...ui,\n resourceUri: GRAPH_RESOURCE_URI,\n },\n }\n}\n\nfunction knownPublicToolInputSchema(toolName: string): ToolInputShape | null {\n switch (toolName) {\n case 'address_risk':\n return {\n address: z.string().min(1).describe('Full blockchain address to screen'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n compare_address: z.string().optional().describe('Optional second full address for comparison'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n }\n case 'track_funds':\n return {\n trusted_addresses: z.string().min(1).describe('Comma-separated full trusted victim addresses. Min 1, max 5.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n untrusted_addresses: z.string().optional().describe('Comma-separated full untrusted/scammer addresses. Max 5.'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n }\n case 'scam_topology':\n return {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n victim_address: z.string().min(1).describe('Full victim/source address that anchors the scam incident. Victims are not risky labels.'),\n incident_timestamp_ms: z.number().min(0).describe('Earliest known incident transfer timestamp in milliseconds. Primary traversal uses node-relative wave-arrival filtering.'),\n max_hops: z.number().int().min(1).max(64).optional().describe('Maximum forward expansion depth. Default 16.'),\n activity_policy: z.enum(['node_relative_only', 'global_incident_only']).optional().describe('Traversal activity policy. Default node_relative_only.'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n }\n case 'graph_query':\n return {\n query: z.string().min(1).describe('Read-only GQL/Cypher query. Use USE live_topology for Memgraph RAM topology, USE archive_topology for StarRocks historical topology, and USE facts for StarRocks facts.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n }\n case 'graph_query_batch':\n return {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n queries: z.array(z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })).min(1).max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n }))\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [{\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n }],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) => isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath = process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() || path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(level: 'info' | 'error', event: string, fields: Record<string, unknown> = {}): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(filePath, JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n', { mode: 0o600 })\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((name: string, config: ToolRegistrationConfig, handler: ToolHandler) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(remoteClient: RemoteToolCaller, logger: ReturnType<typeof createMcpLogger>): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in KNOWN_PUBLIC_TOOL_REQUIRED_ARGS)) return normalized\n\n for (const fieldName of COMMA_SEPARATED_ADDRESS_FIELDS) {\n const value = normalized[fieldName]\n if (Array.isArray(value)) {\n normalized[fieldName] = value\n .map((entry) => String(entry).trim())\n .filter(Boolean)\n .join(',')\n }\n }\n\n return normalized\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>,\n): string | null {\n const requiredArgs = KNOWN_PUBLIC_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = KNOWN_PUBLIC_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\ntype PromptArgs = Record<string, string | undefined>\n\nfunction promptResult(text: string, description?: string): GetPromptResult {\n return {\n description,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ],\n }\n}\n\nfunction compactPromptArguments(args: PromptArgs): Record<string, string> {\n const compact: Record<string, string> = {}\n for (const [key, value] of Object.entries(args)) {\n if (typeof value === 'string' && value.trim() !== '') {\n compact[key] = value\n }\n }\n return compact\n}\n\nfunction promptArgumentSchema(promptName: string, argument: NonNullable<Prompt['arguments']>[number]) {\n const description = PUBLIC_GRAPHRAG_PROMPT_NAMES.has(promptName) && argument.name === 'network'\n ? NETWORK_DESCRIPTION\n : argument.description ?? argument.name\n const schema = z.string().describe(description)\n if (PUBLIC_GRAPHRAG_PROMPT_NAMES.has(promptName) && argument.name === 'network') {\n return schema\n }\n return argument.required === false ? schema.optional() : schema\n}\n\nfunction registerRemotePrompt(server: McpServer, remoteClient: Client, prompt: Prompt): void {\n const argsSchema: Record<string, z.ZodTypeAny> = {}\n for (const argument of prompt.arguments ?? []) {\n argsSchema[argument.name] = promptArgumentSchema(prompt.name, argument)\n }\n\n server.registerPrompt(\n prompt.name,\n {\n title: prompt.title,\n description: prompt.description,\n argsSchema,\n },\n async (args) => remoteClient.getPrompt({\n name: prompt.name,\n arguments: compactPromptArguments(args as PromptArgs),\n }),\n )\n}\n\nfunction registerLocalPrompts(server: McpServer, remotePromptNames: Set<string>): void {\n if (!remotePromptNames.has('address-risk')) {\n server.registerPrompt(\n 'address-risk',\n {\n title: 'Address Risk',\n description: 'Screen an address for AML risk, behavioral patterns, neighborhood profile, and exchange links.',\n argsSchema: {\n address: z.string().describe('Full blockchain address to screen'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ address, network }) => promptResult(\n [\n `Use Chain Insights address_risk on ${network} for:`,\n '',\n `\\`${address}\\``,\n '',\n 'Present the summary as-is. Do not add analysis, verdicts, or risk assessments; the tool output already contains the risk assessment.',\n ].join('\\n'),\n 'Address risk screening',\n ),\n )\n }\n\n if (!remotePromptNames.has('track-funds')) {\n server.registerPrompt(\n 'track-funds',\n {\n title: 'Track Funds',\n description: 'Trace stolen funds from victim addresses through intermediaries to exchange deposit addresses.',\n argsSchema: {\n trusted_addresses: z.string().describe('Victim/trusted addresses, comma-separated full addresses'),\n untrusted_addresses: z.string().optional().describe('Known scammer/untrusted addresses, comma-separated full addresses'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ trusted_addresses, untrusted_addresses, network }) => {\n const untrusted = untrusted_addresses?.trim()\n ? `\\nKnown untrusted addresses:\\n${untrusted_addresses}\\n`\n : ''\n return promptResult(\n [\n `Use Chain Insights track_funds on ${network}.`,\n '',\n 'Trusted victim addresses:',\n trusted_addresses,\n untrusted,\n 'Present the summary as-is and include recommended next actions exactly as returned.',\n ].join('\\n'),\n 'Trace stolen funds',\n )\n },\n )\n }\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Federated Graph Query',\n description: 'Run a read-only GQL/Cypher query through Chain Insights Memgraph Zero.',\n argsSchema: {\n query: z.string().describe('Read-only GQL/Cypher query'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ query, network }) => promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n 'Use USE live_topology for Memgraph RAM topology, USE archive_topology for StarRocks historical topology, and USE facts for StarRocks facts. Return full address properties; never shorten addresses with ellipses.',\n ].join('\\n'),\n 'Federated graph query',\n ),\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Federated Graph Query Batch',\n description: 'Run related read-only GQL/Cypher queries through Chain Insights Memgraph Zero in one paid batch.',\n argsSchema: {\n queries: z.string().describe('JSON array of query objects with optional id and required query fields'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n per_query_timeout_seconds: z.string().optional().describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ queries, network, per_query_timeout_seconds }) => promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds ? `per_query_timeout_seconds: ${per_query_timeout_seconds}` : '',\n '',\n 'Use USE live_topology for Memgraph RAM topology, USE archive_topology for StarRocks historical topology, and USE facts for StarRocks facts. Return full address properties; never shorten addresses with ellipses.',\n ].filter(Boolean).join('\\n'),\n 'Federated graph batch query',\n ),\n )\n\n server.registerPrompt(\n 'balance',\n {\n title: 'Wallet Balance',\n description: 'Show the local Chain Insights payment wallet address and Base USDC balance.',\n argsSchema: {},\n },\n async () => promptResult(\n 'Use Chain Insights balance. Show the wallet address, network, token, and balance exactly as returned.',\n 'Wallet balance',\n ),\n )\n\n server.registerPrompt(\n 'help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and investigation case workflow.',\n argsSchema: {},\n },\n async () => promptResult(\n 'Use Chain Insights help. Summarize the available tools and investigation case workflow without inventing capabilities.',\n 'Chain Insights help',\n ),\n )\n\n server.registerPrompt(\n 'open-investigation-case',\n {\n title: 'Open Investigation Case',\n description: 'Create a local Chain Insights case for an investigation.',\n argsSchema: {\n name: z.string().describe('Case name'),\n tags: z.string().optional().describe('Comma-separated tags'),\n description: z.string().optional().describe('Brief investigation description'),\n },\n },\n async ({ name, tags, description }) => promptResult(\n [\n 'Use Chain Insights case_open to create a local investigation case.',\n '',\n `name: \\`${name}\\``,\n tags ? `tags: \\`${tags}\\`` : '',\n description ? `description: ${description}` : '',\n ].filter(Boolean).join('\\n'),\n 'Open investigation case',\n ),\n )\n\n server.registerPrompt(\n 'resume-investigation-case',\n {\n title: 'Resume Investigation Case',\n description: 'Load local Chain Insights case context, evidence count, dossiers, and latest session.',\n argsSchema: {\n case_id: z.string().describe('Chain Insights case ID'),\n },\n },\n async ({ case_id }) => promptResult(\n `Use Chain Insights case_resume for case_id: \\`${case_id}\\`. Continue from the returned context.`,\n 'Resume investigation case',\n ),\n )\n\n server.registerPrompt(\n 'save-investigation-evidence',\n {\n title: 'Save Investigation Evidence',\n description: 'Append a tool result or analyst note to a local Chain Insights case evidence manifest.',\n argsSchema: {\n case_id: z.string().describe('Chain Insights case ID'),\n source: z.string().describe('Tool or source name'),\n },\n },\n async ({ case_id, source }) => promptResult(\n [\n 'Use Chain Insights case_add_evidence after the next relevant tool result.',\n '',\n `case_id: \\`${case_id}\\``,\n `source: \\`${source}\\``,\n 'content: use the exact report or note that should become evidence.',\n ].join('\\n'),\n 'Save investigation evidence',\n ),\n )\n}\n\nfunction hasGraphArrayFields(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false\n const record = value as Record<string, unknown>\n return GRAPH_ARRAY_KEYS.some((key) => Array.isArray(record[key]))\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) && Array.isArray(childValue)) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\nfunction getRemoteGraphPayload(result: RemoteToolResult): Record<string, unknown> | null {\n const chainInsights = result._meta?.chainInsights\n if (!chainInsights || typeof chainInsights !== 'object' || Array.isArray(chainInsights)) return null\n const graph = (chainInsights as Record<string, unknown>).graph\n if (graph === undefined) return null\n if (!graph || typeof graph !== 'object' || Array.isArray(graph)) {\n throw new Error('Invalid remote graph payload')\n }\n\n const graphRecord = graph as Record<string, unknown>\n if (!('data' in graphRecord)) {\n if ('url' in graphRecord || hasGraphArrayFields(graphRecord)) {\n throw new Error('Invalid remote graph payload')\n }\n return null\n }\n\n const data = graphRecord.data\n if (!data || typeof data !== 'object' || Array.isArray(data)) {\n throw new Error('Invalid remote graph payload')\n }\n\n return data as Record<string, unknown>\n}\n\nasync function normalizeRemoteToolResult(\n result: RemoteToolResult,\n config: Pick<InvestigatorConfig, 'dataDir' | 'serverPort'>,\n toolName = 'remote-graph',\n) {\n const graphPayload = getRemoteGraphPayload(result)\n const meta = { ...(result._meta ?? {}) }\n\n if (graphPayload) {\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const report = await writeGraphReport(graphPayload as never, {\n serverPort: config.serverPort,\n slug: toolName || 'remote-graph',\n })\n await ensureArtifactServer(config.serverPort)\n meta.chainInsights = {\n ...((meta.chainInsights as Record<string, unknown>) ?? {}),\n graph: {\n schema: report.schema,\n url: report.url,\n },\n }\n }\n\n return {\n content: result.content ?? [],\n structuredContent: sanitizeStructuredContentForGraphPayload(result.structuredContent),\n _meta: Object.keys(meta).length > 0 ? meta : undefined,\n isError: result.isError,\n }\n}\n\n/**\n * Core proxy logic — exported so tests can inject dependencies directly.\n * The IIFE at the bottom calls this with real dependencies.\n *\n * stdout purity: NEVER write to stdout in this file. Use console.error() or process.stderr.write() only.\n * All diagnostic output goes to console.error() or process.stderr.write().\n */\nexport async function createProxy(): Promise<void> {\n // Lazy imports to avoid module-load side effects (critical for stdio proxy)\n const { loadConfig } = await import('../config/index.js')\n const { activeDataDir, findActiveWorkspace } = await import('../workspace/active.js')\n const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import('./client.js')\n const { loadSchema, saveSchema } = await import('./schema-cache.js')\n\n const loadedConfig = await loadConfig()\n const activeWorkspace = findActiveWorkspace()\n const config = {\n ...loadedConfig,\n dataDir: activeDataDir(loadedConfig.dataDir),\n }\n const logger = createMcpLogger(config)\n await logger.info('proxy.start', {\n data_dir: config.dataDir,\n workspace_root: activeWorkspace?.root,\n graph_mcp_mode: config.graphMcpMode,\n graph_mcp_endpoint: resolveGraphMcpEndpoint(config),\n log_path: logger.filePath,\n })\n const mcpFetch = await createConfiguredGraphMcpFetch(config)\n const graphMcpEndpoint = resolveGraphMcpEndpoint(config)\n\n // Build remote MCP client. The local Chain Insights MCP surface must still\n // start when the graph endpoint is temporarily unavailable so agents can use\n // help, wallet, and case workflow tools.\n const remoteClient = new Client({ name: 'chain-insights-proxy-client', version: PACKAGE_VERSION })\n let remoteConnected = false\n let remoteUnavailableMessage: string | undefined\n\n try {\n await remoteClient.connect(\n new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch }),\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n } catch {\n await logger.error('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n // StreamableHTTP failed — try SSE fallback (assumption A1 from RESEARCH.md)\n try {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await remoteClient.connect(\n new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch }),\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n })\n } catch (err2) {\n await logger.error('remote.connect_failed', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n error: errorForLog(err2),\n })\n remoteUnavailableMessage = `Graph MCP unreachable at ${graphMcpEndpoint}: ${(err2 as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`,\n )\n }\n }\n if (remoteConnected) installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n\n // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n if (!tools && remoteConnected) {\n // Cache miss — fetch tools from remote (client is already connected above)\n const result = await remoteClient.listTools()\n tools = result.tools as McpTool[]\n await saveSchema(tools, graphMcpEndpoint)\n await logger.info('schema.tools_loaded', {\n source: 'remote',\n count: tools.length,\n })\n } else if (tools) {\n await logger.info('schema.tools_loaded', {\n source: 'cache',\n count: tools.length,\n })\n } else {\n tools = []\n await logger.info('schema.tools_loaded', {\n source: 'unavailable',\n count: 0,\n })\n }\n const remoteToolNames = new Set((tools ?? []).map((tool) => tool.name))\n\n // Build local stdio proxy server\n const server = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n { instructions: SERVER_INSTRUCTIONS },\n )\n installToolLogging(server, logger)\n\n const remotePrompts: Prompt[] = []\n if (remoteConnected) {\n try {\n const promptResult = await remoteClient.listPrompts()\n for (const prompt of promptResult.prompts as Prompt[]) {\n if (PUBLIC_GRAPHRAG_PROMPT_NAMES.has(prompt.name)) {\n remotePrompts.push(prompt)\n }\n }\n } catch (err) {\n await logger.error('remote.prompts_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n process.stderr.write(\n `Chain Insights MCP prompt passthrough unavailable at ${graphMcpEndpoint}: ${(err as Error).message}\\n`,\n )\n }\n }\n\n const remotePromptNames = new Set(remotePrompts.map((prompt) => prompt.name))\n for (const prompt of remotePrompts) {\n registerRemotePrompt(server, remoteClient, prompt)\n }\n registerLocalPrompts(server, remotePromptNames)\n\n const caseToolError = (label: string, err: unknown) => ({\n content: [{ type: 'text' as const, text: `${label} failed: ${(err as Error).message}` }],\n isError: true,\n })\n\n const parseTags = (tags: string | string[] | undefined): string[] => {\n if (Array.isArray(tags)) return tags.map((tag) => tag.trim()).filter(Boolean)\n if (typeof tags === 'string') return tags.split(',').map((tag) => tag.trim()).filter(Boolean)\n return []\n }\n\n server.registerTool(\n 'balance',\n {\n description: 'Show the local Chain Insights payment wallet address and Base USDC balance.',\n inputSchema: z.object({}).passthrough(),\n },\n async () => {\n try {\n const { getWalletAccount, getWalletBalanceText } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return {\n content: [{ type: 'text' as const, text: await getWalletBalanceText(account) }],\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n\n registerAppResource(\n server,\n 'Fund Flow Graph',\n GRAPH_RESOURCE_URI,\n {\n description: 'Interactive D3 force-directed graph for fund flow and pattern visualization. It loads local graph report URLs returned in _meta.chainInsights.graph.url.',\n _meta: {\n ui: {\n csp: {\n resourceDomains: graphArtifactOrigins(config),\n connectDomains: graphArtifactOrigins(config),\n },\n },\n },\n },\n async () => ({\n contents: [\n {\n uri: GRAPH_RESOURCE_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: readGraphAppHtml(),\n _meta: {\n ui: {\n csp: {\n resourceDomains: graphArtifactOrigins(config),\n connectDomains: graphArtifactOrigins(config),\n },\n },\n },\n },\n ],\n }),\n )\n\n server.registerTool(\n 'case_open',\n {\n description: 'Create a local Chain Insights investigation case. Use this before saving evidence, dossiers, or session notes for a new investigation.',\n inputSchema: {\n name: z.string().min(1).describe('Case name'),\n tags: z.union([z.string(), z.array(z.string())]).optional().describe('Comma-separated tags or string array'),\n description: z.string().optional().describe('Brief investigation description'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ name, tags, description }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const created = await CaseStore.create({\n name,\n tags: parseTags(tags),\n description: description ?? '',\n })\n const { casesRoot } = await import('../cases/store.js')\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n case_id: created.id,\n name: created.name,\n status: created.status,\n tags: created.tags,\n directory: `${path.join(casesRoot(), created.id)}/`,\n }, null, 2),\n }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case open', err)\n }\n },\n )\n\n server.registerTool(\n 'case_list',\n {\n description: 'List local Chain Insights investigation cases. Use before resuming when the user does not provide a case ID.',\n inputSchema: {\n status: z.enum(['open', 'active', 'suspended', 'closed']).optional().describe('Optional status filter'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ status }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const cases = await CaseStore.list()\n const filtered = status ? cases.filter((entry) => entry.status === status) : cases\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ cases: filtered }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case list', err)\n }\n },\n )\n\n server.registerTool(\n 'case_resume',\n {\n description: 'Load local Chain Insights case context: metadata, evidence count, dossier summaries, and latest session notes.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const context = await CaseStore.loadContext(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(context, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case resume', err)\n }\n },\n )\n\n server.registerTool(\n 'case_add_evidence',\n {\n description: 'Append a tool result or analyst note to a local case evidence manifest. Use after address_risk, track_funds, graph_query, or manual findings that should be preserved.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n source: z.string().min(1).describe('Source tool or evidence origin'),\n content: z.string().min(1).describe('Evidence markdown/text to store'),\n query_params: z.string().optional().describe('Original query parameters, for example \"network=bittensor address=...\"'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, source, content, query_params }) => {\n try {\n const { EvidenceStore } = await import('../cases/index.js')\n const saved = await EvidenceStore.append(case_id, {\n source,\n content,\n queryParams: query_params ?? '',\n })\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(saved, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Evidence append', err)\n }\n },\n )\n\n server.registerTool(\n 'case_verify_evidence',\n {\n description: 'Verify a local case evidence manifest and report tampered or missing evidence files.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { EvidenceStore } = await import('../cases/index.js')\n const result = await EvidenceStore.verifyManifest(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Evidence verify', err)\n }\n },\n )\n\n server.registerTool(\n 'case_update_dossier',\n {\n description: 'Append a finding to an address/entity dossier inside a local Chain Insights case.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n address: z.string().min(1).describe('Full address or entity identifier'),\n finding: z.string().min(1).describe('Finding to append'),\n entity_type: z.enum(['eoa', 'contract', 'exchange', 'mixer', 'unknown']).optional().describe('Entity type'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, address, finding, entity_type }) => {\n try {\n const { DossierStore } = await import('../cases/index.js')\n await DossierStore.appendFinding(case_id, address, finding, entity_type ?? 'unknown')\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ case_id, address, updated: true }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Dossier update', err)\n }\n },\n )\n\n server.registerTool(\n 'case_start_session',\n {\n description: 'Start a local investigation session file for a Chain Insights case.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { SessionStore } = await import('../cases/index.js')\n const session = await SessionStore.start(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(session, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Session start', err)\n }\n },\n )\n\n server.registerTool(\n 'case_end_session',\n {\n description: 'End the latest local investigation session for a Chain Insights case with findings and next steps.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n findings: z.string().optional().describe('Key findings from this session'),\n next_steps: z.string().optional().describe('Next investigation steps'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, findings, next_steps }) => {\n try {\n const { SessionStore } = await import('../cases/index.js')\n await SessionStore.end(case_id, {\n findings: findings ?? '',\n nextSteps: next_steps ?? '',\n })\n await SessionStore.archiveOldSessions(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ case_id, ended: true }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Session end', err)\n }\n },\n )\n\n if (!remoteToolNames.has('address_risk')) {\n registerAppTool(\n server,\n 'address_risk',\n {\n title: 'Address Risk',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.address_risk,\n inputSchema: {\n address: z.string().min(1).describe('Full blockchain address to screen'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n compare_address: z.string().optional().describe('Optional second full address for comparison'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ address, network, compare_address }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { addressRisk } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await addressRisk(remoteClient, {\n address,\n network,\n compareAddress: compare_address,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `address-risk-${network}-${address}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Address risk failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n if (!remoteToolNames.has('track_funds')) {\n registerAppTool(\n server,\n 'track_funds',\n {\n title: 'Track Funds',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.track_funds,\n inputSchema: {\n trusted_addresses: z.union([z.string().min(1), z.array(z.string().min(1))]).describe('Comma-separated full trusted victim addresses, or an array. Min 1, max 5.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n untrusted_addresses: z.union([z.string(), z.array(z.string())]).optional().describe('Known scammer/untrusted addresses. Max 5.'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n max_hops: z.number().int().min(1).max(5).optional(),\n per_address_limit: z.number().int().min(1).max(10).optional(),\n min_amount_sum: z.number().min(0).optional(),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ trusted_addresses, untrusted_addresses, network, case_id, max_hops, per_address_limit, min_amount_sum }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { trackFunds } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await trackFunds(remoteClient, config, {\n trustedAddresses: trusted_addresses,\n untrustedAddresses: untrusted_addresses,\n network,\n caseId: case_id,\n maxHops: max_hops,\n perAddressLimit: per_address_limit,\n minAmountSum: min_amount_sum,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `track-funds-${network}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Track funds failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n if (!remoteToolNames.has('scam_topology')) {\n registerAppTool(\n server,\n 'scam_topology',\n {\n title: 'Scam Topology',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.scam_topology,\n inputSchema: {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n victim_address: z.string().min(1).describe('Full victim/source address that anchors the scam incident. Victims are not risky labels.'),\n incident_timestamp_ms: z.number().min(0).describe('Earliest known incident transfer timestamp in milliseconds. Primary traversal uses node-relative wave-arrival filtering.'),\n max_hops: z.number().int().min(1).max(64).optional().describe('Maximum forward expansion depth. Default 16.'),\n activity_policy: z.enum(['node_relative_only', 'global_incident_only']).optional().describe('Traversal activity policy. Default node_relative_only.'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ victim_address, incident_timestamp_ms, network, max_hops, activity_policy, case_id }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { scamTopology } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await scamTopology(remoteClient, config, {\n victimAddress: victim_address,\n network,\n maxHops: max_hops,\n incidentTimestampMs: incident_timestamp_ms,\n activityPolicyMode: activity_policy,\n caseId: case_id,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `scam-topology-${network}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Scam topology failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n server.registerTool(\n 'help',\n {\n description: 'Show Chain Insights overview, available tools, and investigation workflow.',\n inputSchema: z.object({}).passthrough(),\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: [\n 'Chain Insights AML investigation workspace for AI agents.',\n '',\n CHAIN_INSIGHTS_WORKFLOW,\n '',\n 'Investigation tools:',\n '- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.',\n '- address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.',\n '- track_funds: trace up to five trusted/victim addresses plus up to five known untrusted/scammer addresses through intermediaries to exchange deposit addresses.',\n '- scam_topology: derive ML-ready scam_labels from one victim incident address and incident_timestamp_ms.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n '',\n 'Case workflow tools:',\n '- case_open: create a local case before preserving evidence.',\n '- case_list: list local cases.',\n '- case_resume: load case context, evidence count, dossiers, and latest session.',\n '- case_add_evidence: append a report or note to the case evidence manifest.',\n '- case_verify_evidence: verify saved evidence integrity.',\n '- case_update_dossier: add a finding to an address/entity dossier.',\n '- case_start_session and case_end_session: record session notes.',\n '',\n 'Wallet tools:',\n '- balance: show the local payment wallet address and Base USDC balance.',\n '- help: show this overview.',\n '',\n GRAPH_REPORT_HINTS,\n '',\n GRAPH_SCHEMA_HINTS,\n ].join('\\n'),\n },\n ],\n isError: false,\n }),\n )\n\n // Register each remote tool locally — passthrough proxy pattern\n for (const tool of tools ?? []) {\n if (HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue\n if (LOCAL_TOOL_NAMES.has(tool.name)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const normalizedArgs = normalizeRemoteToolArguments(tool.name, args)\n const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs)\n if (validationError) {\n return {\n content: [{ type: 'text' as const, text: validationError }],\n isError: true,\n }\n }\n const request = {\n name: tool.name,\n arguments: normalizedArgs,\n }\n const requestOptions = remoteToolRequestOptions(tool.name)\n const result = requestOptions\n ? await remoteClient.callTool(request, undefined, requestOptions)\n : await remoteClient.callTool(request)\n return await normalizeRemoteToolResult(result as RemoteToolResult, config, tool.name)\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `MCP call failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n const toolConfig = {\n title: tool.title,\n description: claudeFacingToolDescription(tool),\n inputSchema,\n }\n\n if (hasGraphApp(tool)) {\n registerAppTool(\n server,\n tool.name,\n {\n ...toolConfig,\n _meta: graphToolMeta(tool),\n },\n handler,\n )\n } else {\n server.registerTool(tool.name, toolConfig, handler)\n }\n }\n\n // Connect to stdio transport — after this line, stdout belongs to MCP\n const transport = new StdioServerTransport()\n await server.connect(transport)\n await logger.info('proxy.ready', {\n tools: [\n ...LOCAL_TOOL_NAMES,\n ...(tools ?? []).map((tool) => tool.name).filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n })\n\n // Signal handling — clean shutdown\n const shutdown = async () => {\n await logger.info('proxy.shutdown')\n transport.close()\n process.exit(0)\n }\n process.on('SIGINT', () => { void shutdown() })\n process.on('SIGTERM', () => { void shutdown() })\n}\n\n// Entry point — only execute when run as the main module (not when imported by tests)\n// Using process.argv check to detect direct execution vs import\nif (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\\\/g, '/'))) {\n createProxy().catch((err) => {\n process.stderr.write(`Chain Insights MCP proxy startup failed: ${(err as Error).message}\\n`)\n process.exit(1)\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-proxy.mjs","names":[],"sources":["../src/mcp/proxy.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ContentBlock, GetPromptResult, Prompt } from '@modelcontextprotocol/sdk/types.js'\nimport { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'\nimport * as z from 'zod'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { McpTool } from './schema-cache.js'\nimport { HIDDEN_REMOTE_TOOL_NAMES } from './tool-visibility.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'balance',\n 'help',\n 'case_open',\n 'case_list',\n 'case_resume',\n 'case_add_evidence',\n 'case_verify_evidence',\n 'case_update_dossier',\n 'case_start_session',\n 'case_end_session',\n])\nconst PUBLIC_GRAPHRAG_PROMPT_NAMES = new Set(['address-risk', 'track-funds'])\nconst GRAPH_RESOURCE_URI = 'ui://chain-insights/graph'\nconst GRAPH_APP_TOOL_NAMES = new Set([\n 'address_risk',\n 'scam_topology',\n 'track_funds',\n])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nconst COMMA_SEPARATED_ADDRESS_FIELDS = new Set([\n 'trusted_addresses',\n 'untrusted_addresses',\n])\n\nconst KNOWN_PUBLIC_TOOL_REQUIRED_ARGS: Record<string, string[]> = {\n address_risk: ['address', 'network'],\n scam_topology: ['victim_address', 'incident_timestamp_ms', 'network'],\n track_funds: ['trusted_addresses', 'network'],\n graph_query: ['query', 'network'],\n graph_query_batch: ['network', 'queries'],\n}\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n network_capabilities: 'Return supported Chain Insights networks, capability layers, tool availability, data retention windows, and freshness. Use this before choosing network-specific tools.',\n address_risk: 'Screen one full blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with compare_address. This includes the exchange-behavior analysis formerly covered by money_flows_between_exchanges. Use this as the first tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.',\n scam_topology: 'Build victim-incident laundering topology from one victim/source address and the earliest known incident timestamp. Traversal uses one explicit activity policy: node_relative_only by default, or global_incident_only when requested. Repeated targets are kept as non-expanding convergence edges. Returns ML-ready scam_labels plus review context and a track_funds-compatible graph report: primary flows, deposits, reverse_leads. Victims, exchange endpoints, and generic labeled context nodes are not automatic scam labels; preserve full addresses exactly.',\n track_funds: 'Trace funds from trusted victim/source addresses through intermediaries to exchange deposit addresses. Use this when the user has a victim/source address or known untrusted/scammer addresses. The tool returns an investigator-ready fund-flow report and recommended next actions.',\n graph_query: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Cross-layer correlated joins may be limited by the active graph endpoint; preserve full addresses exactly.',\n graph_query_batch: 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\n\nconst NETWORK_DESCRIPTION = 'Required network to query. Do not guess; use network_capabilities or ask the user if missing.'\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. If the user is starting or continuing an investigation, use case_open or case_list/case_resume first.',\n '2. Do not call investigation tools until required arguments are known. Network is required; use network_capabilities to check supported networks, data layers, retention, and freshness, or ask the user if missing.',\n '3. Use address_risk first for a single address when facts and topology are available. Use track_funds for victim/source fund tracing when topology is available. Use scam_topology when known victim incident ground truth should become ML-ready scam labels. Use graph_query(_batch) for the universal graph-language path over topology and facts.',\n '4. After a material result, preserve it with case_add_evidence when a case is active or ask whether to create/select a case.',\n '5. Use case_update_dossier for durable address/entity findings and case_start_session/case_end_session for session notes.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints for network=bittensor:',\n '- Common live topology node labels include Address and may include legacy enrichment labels. Do not depend on Exchange/Miner graph labels for correctness; use address properties such as labels and is_exchange when available.',\n '- Address nodes are identity plus traversal hints. Lifetime/global address metrics live in USE facts as AddressFeature, not as topology semantics.',\n '- Facts graph labels include Address, AddressLabel, AddressFeature, RiskScore, and Asset.',\n '- Facts graph relationships include (:Address)-[:HAS_FEATURE]->(:AddressFeature), (:Address)-[:HAS_LABEL]->(:AddressLabel), and (:Address)-[:HAS_RISK_SCORE]->(:RiskScore).',\n '- Risk and ML properties may appear as live hints, but source-of-truth risk rows are RiskScore facts.',\n '- Common relationships include FLOWS_TO, OPERATED_FROM, SERVED_FROM, REGISTERED_NEURON, BELONGS_TO, SYBIL_CLUSTER, LAYERING_HOP, BURST_ACTIVITY, CYCLE_PARTICIPANT, SMURFING_CLUSTER.',\n '- FLOWS_TO properties are scoped to the selected topology graph and commonly carry amount_sum, amount_usd_sum, tx_count, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id. Confirm available fields through runtime schema before relying on them.',\n '- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 20',\n '- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_sum AS amount_sum, r.amount_usd_sum AS amount_usd_sum LIMIT 20',\n '- graph_query uses the active Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment.',\n '- Archive topology labels include Address and TopologySnapshot. Archived money-flow topology is represented as (:Address)-[:FLOWS_TO]->(:Address) relationships with period_granularity, period_start_date, and period_end_date.',\n '- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.',\n '- Use USE facts graph patterns for fact and enrichment reads. Do not query internal table namespaces directly.',\n].join('\\n')\n\nconst GRAPH_REPORT_HINTS = [\n 'Graph visualization behavior:',\n '- Graph-backed tools return the investigator report as text content and keep raw graph data out of LLM-visible structuredContent.',\n '- Raw graph data is stored locally under Chain Insights reports/graphs and exposed to the graph app as _meta.chainInsights.graph.url.',\n '- The local graph report server is started automatically by the MCP server when a graph-backed tool returns a report URL; do not ask the user to run chain-insights serve for Claude Desktop graph iframes.',\n '- If an iframe reports that a graph report fetch failed, retry the graph-backed tool call so Chain Insights can recreate the report URL and ensure the local report server is running.',\n].join('\\n')\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is a local AML investigation workspace for AI agents.',\n CHAIN_INSIGHTS_WORKFLOW,\n GRAPH_REPORT_HINTS,\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses; use case tools to preserve evidence when a case exists.',\n].join('\\n\\n')\n\nfunction readGraphAppHtml(): string {\n const candidates = [\n path.resolve(__dirname, 'templates', 'graph.html'),\n path.resolve(__dirname, '..', 'templates', 'graph.html'),\n path.resolve(__dirname, '..', 'viz', 'templates', 'graph.html'),\n ]\n\n for (const candidate of candidates) {\n try {\n return readFileSync(candidate, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n }\n\n throw new Error(`Graph MCP app template not found. Tried: ${candidates.join(', ')}`)\n}\n\nfunction graphArtifactOrigins(config: Pick<InvestigatorConfig, 'serverPort'>): string[] {\n return [\n `http://127.0.0.1:${config.serverPort}`,\n `http://localhost:${config.serverPort}`,\n ]\n}\n\nfunction hasGraphApp(tool: McpTool): boolean {\n const configuredUri = tool._meta?.ui\n if (\n configuredUri &&\n typeof configuredUri === 'object' &&\n 'resourceUri' in configuredUri &&\n configuredUri.resourceUri === GRAPH_RESOURCE_URI\n ) {\n return true\n }\n\n if (tool._meta?.['ui/resourceUri'] === GRAPH_RESOURCE_URI) return true\n if (GRAPH_APP_TOOL_NAMES.has(tool.name)) return true\n return JSON.stringify(tool.outputSchema ?? {}).includes('\"app_data\"')\n}\n\nfunction graphToolMeta(tool: McpTool): Record<string, unknown> & { ui: { resourceUri: string } } {\n const meta = { ...(tool._meta ?? {}) }\n const ui =\n meta.ui && typeof meta.ui === 'object' && !Array.isArray(meta.ui)\n ? { ...(meta.ui as Record<string, unknown>) }\n : {}\n\n return {\n ...meta,\n ui: {\n ...ui,\n resourceUri: GRAPH_RESOURCE_URI,\n },\n }\n}\n\nfunction knownPublicToolInputSchema(toolName: string): ToolInputShape | null {\n switch (toolName) {\n case 'address_risk':\n return {\n address: z.string().min(1).describe('Full blockchain address to screen'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n compare_address: z.string().optional().describe('Optional second full address for comparison'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n }\n case 'track_funds':\n return {\n trusted_addresses: z.string().min(1).describe('Comma-separated full trusted victim addresses. Min 1, max 5.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n untrusted_addresses: z.string().optional().describe('Comma-separated full untrusted/scammer addresses. Max 5.'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n }\n case 'scam_topology':\n return {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n victim_address: z.string().min(1).describe('Full victim/source address that anchors the scam incident. Victims are not risky labels.'),\n incident_timestamp_ms: z.number().min(0).describe('Earliest known incident transfer timestamp in milliseconds. Primary traversal uses node-relative wave-arrival filtering.'),\n max_hops: z.number().int().min(1).max(64).optional().describe('Maximum forward expansion depth. Default 16.'),\n activity_policy: z.enum(['node_relative_only', 'global_incident_only']).optional().describe('Traversal activity policy. Default node_relative_only.'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n }\n case 'graph_query':\n return {\n query: z.string().min(1).describe('Read-only GQL/Cypher query. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n }\n case 'graph_query_batch':\n return {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n queries: z.array(z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })).min(1).max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n }))\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [{\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n }],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) => isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath = process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() || path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(level: 'info' | 'error', event: string, fields: Record<string, unknown> = {}): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(filePath, JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n', { mode: 0o600 })\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((name: string, config: ToolRegistrationConfig, handler: ToolHandler) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(remoteClient: RemoteToolCaller, logger: ReturnType<typeof createMcpLogger>): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in KNOWN_PUBLIC_TOOL_REQUIRED_ARGS)) return normalized\n\n for (const fieldName of COMMA_SEPARATED_ADDRESS_FIELDS) {\n const value = normalized[fieldName]\n if (Array.isArray(value)) {\n normalized[fieldName] = value\n .map((entry) => String(entry).trim())\n .filter(Boolean)\n .join(',')\n }\n }\n\n return normalized\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>,\n): string | null {\n const requiredArgs = KNOWN_PUBLIC_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = KNOWN_PUBLIC_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\ntype PromptArgs = Record<string, string | undefined>\n\nfunction promptResult(text: string, description?: string): GetPromptResult {\n return {\n description,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ],\n }\n}\n\nfunction compactPromptArguments(args: PromptArgs): Record<string, string> {\n const compact: Record<string, string> = {}\n for (const [key, value] of Object.entries(args)) {\n if (typeof value === 'string' && value.trim() !== '') {\n compact[key] = value\n }\n }\n return compact\n}\n\nfunction promptArgumentSchema(promptName: string, argument: NonNullable<Prompt['arguments']>[number]) {\n const description = PUBLIC_GRAPHRAG_PROMPT_NAMES.has(promptName) && argument.name === 'network'\n ? NETWORK_DESCRIPTION\n : argument.description ?? argument.name\n const schema = z.string().describe(description)\n if (PUBLIC_GRAPHRAG_PROMPT_NAMES.has(promptName) && argument.name === 'network') {\n return schema\n }\n return argument.required === false ? schema.optional() : schema\n}\n\nfunction registerRemotePrompt(server: McpServer, remoteClient: Client, prompt: Prompt): void {\n const argsSchema: Record<string, z.ZodTypeAny> = {}\n for (const argument of prompt.arguments ?? []) {\n argsSchema[argument.name] = promptArgumentSchema(prompt.name, argument)\n }\n\n server.registerPrompt(\n prompt.name,\n {\n title: prompt.title,\n description: prompt.description,\n argsSchema,\n },\n async (args) => remoteClient.getPrompt({\n name: prompt.name,\n arguments: compactPromptArguments(args as PromptArgs),\n }),\n )\n}\n\nfunction registerLocalPrompts(server: McpServer, remotePromptNames: Set<string>): void {\n if (!remotePromptNames.has('address-risk')) {\n server.registerPrompt(\n 'address-risk',\n {\n title: 'Address Risk',\n description: 'Screen an address for AML risk, behavioral patterns, neighborhood profile, and exchange links.',\n argsSchema: {\n address: z.string().describe('Full blockchain address to screen'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ address, network }) => promptResult(\n [\n `Use Chain Insights address_risk on ${network} for:`,\n '',\n `\\`${address}\\``,\n '',\n 'Present the summary as-is. Do not add analysis, verdicts, or risk assessments; the tool output already contains the risk assessment.',\n ].join('\\n'),\n 'Address risk screening',\n ),\n )\n }\n\n if (!remotePromptNames.has('track-funds')) {\n server.registerPrompt(\n 'track-funds',\n {\n title: 'Track Funds',\n description: 'Trace stolen funds from victim addresses through intermediaries to exchange deposit addresses.',\n argsSchema: {\n trusted_addresses: z.string().describe('Victim/trusted addresses, comma-separated full addresses'),\n untrusted_addresses: z.string().optional().describe('Known scammer/untrusted addresses, comma-separated full addresses'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ trusted_addresses, untrusted_addresses, network }) => {\n const untrusted = untrusted_addresses?.trim()\n ? `\\nKnown untrusted addresses:\\n${untrusted_addresses}\\n`\n : ''\n return promptResult(\n [\n `Use Chain Insights track_funds on ${network}.`,\n '',\n 'Trusted victim addresses:',\n trusted_addresses,\n untrusted,\n 'Present the summary as-is and include recommended next actions exactly as returned.',\n ].join('\\n'),\n 'Trace stolen funds',\n )\n },\n )\n }\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Federated Graph Query',\n description: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.',\n argsSchema: {\n query: z.string().describe('Read-only GQL/Cypher query'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n },\n },\n async ({ query, network }) => promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n 'Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses.',\n ].join('\\n'),\n 'Federated graph query',\n ),\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Federated Graph Query Batch',\n description: 'Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.',\n argsSchema: {\n queries: z.string().describe('JSON array of query objects with optional id and required query fields'),\n network: z.string().describe(NETWORK_DESCRIPTION),\n per_query_timeout_seconds: z.string().optional().describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ queries, network, per_query_timeout_seconds }) => promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds ? `per_query_timeout_seconds: ${per_query_timeout_seconds}` : '',\n '',\n 'Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Return full address properties; never shorten addresses with ellipses.',\n ].filter(Boolean).join('\\n'),\n 'Federated graph batch query',\n ),\n )\n\n server.registerPrompt(\n 'balance',\n {\n title: 'Wallet Balance',\n description: 'Show the local Chain Insights payment wallet address and Base USDC balance.',\n argsSchema: {},\n },\n async () => promptResult(\n 'Use Chain Insights balance. Show the wallet address, network, token, and balance exactly as returned.',\n 'Wallet balance',\n ),\n )\n\n server.registerPrompt(\n 'help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and investigation case workflow.',\n argsSchema: {},\n },\n async () => promptResult(\n 'Use Chain Insights help. Summarize the available tools and investigation case workflow without inventing capabilities.',\n 'Chain Insights help',\n ),\n )\n\n server.registerPrompt(\n 'open-investigation-case',\n {\n title: 'Open Investigation Case',\n description: 'Create a local Chain Insights case for an investigation.',\n argsSchema: {\n name: z.string().describe('Case name'),\n tags: z.string().optional().describe('Comma-separated tags'),\n description: z.string().optional().describe('Brief investigation description'),\n },\n },\n async ({ name, tags, description }) => promptResult(\n [\n 'Use Chain Insights case_open to create a local investigation case.',\n '',\n `name: \\`${name}\\``,\n tags ? `tags: \\`${tags}\\`` : '',\n description ? `description: ${description}` : '',\n ].filter(Boolean).join('\\n'),\n 'Open investigation case',\n ),\n )\n\n server.registerPrompt(\n 'resume-investigation-case',\n {\n title: 'Resume Investigation Case',\n description: 'Load local Chain Insights case context, evidence count, dossiers, and latest session.',\n argsSchema: {\n case_id: z.string().describe('Chain Insights case ID'),\n },\n },\n async ({ case_id }) => promptResult(\n `Use Chain Insights case_resume for case_id: \\`${case_id}\\`. Continue from the returned context.`,\n 'Resume investigation case',\n ),\n )\n\n server.registerPrompt(\n 'save-investigation-evidence',\n {\n title: 'Save Investigation Evidence',\n description: 'Append a tool result or analyst note to a local Chain Insights case evidence manifest.',\n argsSchema: {\n case_id: z.string().describe('Chain Insights case ID'),\n source: z.string().describe('Tool or source name'),\n },\n },\n async ({ case_id, source }) => promptResult(\n [\n 'Use Chain Insights case_add_evidence after the next relevant tool result.',\n '',\n `case_id: \\`${case_id}\\``,\n `source: \\`${source}\\``,\n 'content: use the exact report or note that should become evidence.',\n ].join('\\n'),\n 'Save investigation evidence',\n ),\n )\n}\n\nfunction hasGraphArrayFields(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false\n const record = value as Record<string, unknown>\n return GRAPH_ARRAY_KEYS.some((key) => Array.isArray(record[key]))\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) && Array.isArray(childValue)) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\nfunction getRemoteGraphPayload(result: RemoteToolResult): Record<string, unknown> | null {\n const chainInsights = result._meta?.chainInsights\n if (!chainInsights || typeof chainInsights !== 'object' || Array.isArray(chainInsights)) return null\n const graph = (chainInsights as Record<string, unknown>).graph\n if (graph === undefined) return null\n if (!graph || typeof graph !== 'object' || Array.isArray(graph)) {\n throw new Error('Invalid remote graph payload')\n }\n\n const graphRecord = graph as Record<string, unknown>\n if (!('data' in graphRecord)) {\n if ('url' in graphRecord || hasGraphArrayFields(graphRecord)) {\n throw new Error('Invalid remote graph payload')\n }\n return null\n }\n\n const data = graphRecord.data\n if (!data || typeof data !== 'object' || Array.isArray(data)) {\n throw new Error('Invalid remote graph payload')\n }\n\n return data as Record<string, unknown>\n}\n\nasync function normalizeRemoteToolResult(\n result: RemoteToolResult,\n config: Pick<InvestigatorConfig, 'dataDir' | 'serverPort'>,\n toolName = 'remote-graph',\n) {\n const graphPayload = getRemoteGraphPayload(result)\n const meta = { ...(result._meta ?? {}) }\n\n if (graphPayload) {\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const report = await writeGraphReport(graphPayload as never, {\n serverPort: config.serverPort,\n slug: toolName || 'remote-graph',\n })\n await ensureArtifactServer(config.serverPort)\n meta.chainInsights = {\n ...((meta.chainInsights as Record<string, unknown>) ?? {}),\n graph: {\n schema: report.schema,\n url: report.url,\n },\n }\n }\n\n return {\n content: result.content ?? [],\n structuredContent: sanitizeStructuredContentForGraphPayload(result.structuredContent),\n _meta: Object.keys(meta).length > 0 ? meta : undefined,\n isError: result.isError,\n }\n}\n\n/**\n * Core proxy logic — exported so tests can inject dependencies directly.\n * The IIFE at the bottom calls this with real dependencies.\n *\n * stdout purity: NEVER write to stdout in this file. Use console.error() or process.stderr.write() only.\n * All diagnostic output goes to console.error() or process.stderr.write().\n */\nexport async function createProxy(): Promise<void> {\n // Lazy imports to avoid module-load side effects (critical for stdio proxy)\n const { loadConfig } = await import('../config/index.js')\n const { activeDataDir, findActiveWorkspace } = await import('../workspace/active.js')\n const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import('./client.js')\n const { loadSchema, saveSchema } = await import('./schema-cache.js')\n\n const loadedConfig = await loadConfig()\n const activeWorkspace = findActiveWorkspace()\n const config = {\n ...loadedConfig,\n dataDir: activeDataDir(loadedConfig.dataDir),\n }\n const logger = createMcpLogger(config)\n await logger.info('proxy.start', {\n data_dir: config.dataDir,\n workspace_root: activeWorkspace?.root,\n graph_mcp_mode: config.graphMcpMode,\n graph_mcp_endpoint: resolveGraphMcpEndpoint(config),\n log_path: logger.filePath,\n })\n const mcpFetch = await createConfiguredGraphMcpFetch(config)\n const graphMcpEndpoint = resolveGraphMcpEndpoint(config)\n\n // Build remote MCP client. The local Chain Insights MCP surface must still\n // start when the graph endpoint is temporarily unavailable so agents can use\n // help, wallet, and case workflow tools.\n const remoteClient = new Client({ name: 'chain-insights-proxy-client', version: PACKAGE_VERSION })\n let remoteConnected = false\n let remoteUnavailableMessage: string | undefined\n\n try {\n await remoteClient.connect(\n new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch }),\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n } catch {\n await logger.error('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n // StreamableHTTP failed — try SSE fallback (assumption A1 from RESEARCH.md)\n try {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await remoteClient.connect(\n new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch }),\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n })\n } catch (err2) {\n await logger.error('remote.connect_failed', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n error: errorForLog(err2),\n })\n remoteUnavailableMessage = `Graph MCP unreachable at ${graphMcpEndpoint}: ${(err2 as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`,\n )\n }\n }\n if (remoteConnected) installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n\n // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n if (!tools && remoteConnected) {\n // Cache miss — fetch tools from remote (client is already connected above)\n const result = await remoteClient.listTools()\n tools = result.tools as McpTool[]\n await saveSchema(tools, graphMcpEndpoint)\n await logger.info('schema.tools_loaded', {\n source: 'remote',\n count: tools.length,\n })\n } else if (tools) {\n await logger.info('schema.tools_loaded', {\n source: 'cache',\n count: tools.length,\n })\n } else {\n tools = []\n await logger.info('schema.tools_loaded', {\n source: 'unavailable',\n count: 0,\n })\n }\n const remoteToolNames = new Set((tools ?? []).map((tool) => tool.name))\n\n // Build local stdio proxy server\n const server = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n { instructions: SERVER_INSTRUCTIONS },\n )\n installToolLogging(server, logger)\n\n const remotePrompts: Prompt[] = []\n if (remoteConnected) {\n try {\n const promptResult = await remoteClient.listPrompts()\n for (const prompt of promptResult.prompts as Prompt[]) {\n if (PUBLIC_GRAPHRAG_PROMPT_NAMES.has(prompt.name)) {\n remotePrompts.push(prompt)\n }\n }\n } catch (err) {\n await logger.error('remote.prompts_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n process.stderr.write(\n `Chain Insights MCP prompt passthrough unavailable at ${graphMcpEndpoint}: ${(err as Error).message}\\n`,\n )\n }\n }\n\n const remotePromptNames = new Set(remotePrompts.map((prompt) => prompt.name))\n for (const prompt of remotePrompts) {\n registerRemotePrompt(server, remoteClient, prompt)\n }\n registerLocalPrompts(server, remotePromptNames)\n\n const caseToolError = (label: string, err: unknown) => ({\n content: [{ type: 'text' as const, text: `${label} failed: ${(err as Error).message}` }],\n isError: true,\n })\n\n const parseTags = (tags: string | string[] | undefined): string[] => {\n if (Array.isArray(tags)) return tags.map((tag) => tag.trim()).filter(Boolean)\n if (typeof tags === 'string') return tags.split(',').map((tag) => tag.trim()).filter(Boolean)\n return []\n }\n\n server.registerTool(\n 'balance',\n {\n description: 'Show the local Chain Insights payment wallet address and Base USDC balance.',\n inputSchema: z.object({}).passthrough(),\n },\n async () => {\n try {\n const { getWalletAccount, getWalletBalanceText } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return {\n content: [{ type: 'text' as const, text: await getWalletBalanceText(account) }],\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n\n registerAppResource(\n server,\n 'Fund Flow Graph',\n GRAPH_RESOURCE_URI,\n {\n description: 'Interactive D3 force-directed graph for fund flow and pattern visualization. It loads local graph report URLs returned in _meta.chainInsights.graph.url.',\n _meta: {\n ui: {\n csp: {\n resourceDomains: graphArtifactOrigins(config),\n connectDomains: graphArtifactOrigins(config),\n },\n },\n },\n },\n async () => ({\n contents: [\n {\n uri: GRAPH_RESOURCE_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: readGraphAppHtml(),\n _meta: {\n ui: {\n csp: {\n resourceDomains: graphArtifactOrigins(config),\n connectDomains: graphArtifactOrigins(config),\n },\n },\n },\n },\n ],\n }),\n )\n\n server.registerTool(\n 'case_open',\n {\n description: 'Create a local Chain Insights investigation case. Use this before saving evidence, dossiers, or session notes for a new investigation.',\n inputSchema: {\n name: z.string().min(1).describe('Case name'),\n tags: z.union([z.string(), z.array(z.string())]).optional().describe('Comma-separated tags or string array'),\n description: z.string().optional().describe('Brief investigation description'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ name, tags, description }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const created = await CaseStore.create({\n name,\n tags: parseTags(tags),\n description: description ?? '',\n })\n const { casesRoot } = await import('../cases/store.js')\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n case_id: created.id,\n name: created.name,\n status: created.status,\n tags: created.tags,\n directory: `${path.join(casesRoot(), created.id)}/`,\n }, null, 2),\n }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case open', err)\n }\n },\n )\n\n server.registerTool(\n 'case_list',\n {\n description: 'List local Chain Insights investigation cases. Use before resuming when the user does not provide a case ID.',\n inputSchema: {\n status: z.enum(['open', 'active', 'suspended', 'closed']).optional().describe('Optional status filter'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ status }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const cases = await CaseStore.list()\n const filtered = status ? cases.filter((entry) => entry.status === status) : cases\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ cases: filtered }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case list', err)\n }\n },\n )\n\n server.registerTool(\n 'case_resume',\n {\n description: 'Load local Chain Insights case context: metadata, evidence count, dossier summaries, and latest session notes.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { CaseStore } = await import('../cases/index.js')\n const context = await CaseStore.loadContext(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(context, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Case resume', err)\n }\n },\n )\n\n server.registerTool(\n 'case_add_evidence',\n {\n description: 'Append a tool result or analyst note to a local case evidence manifest. Use after address_risk, track_funds, graph_query, or manual findings that should be preserved.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n source: z.string().min(1).describe('Source tool or evidence origin'),\n content: z.string().min(1).describe('Evidence markdown/text to store'),\n query_params: z.string().optional().describe('Original query parameters, for example \"network=bittensor address=...\"'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, source, content, query_params }) => {\n try {\n const { EvidenceStore } = await import('../cases/index.js')\n const saved = await EvidenceStore.append(case_id, {\n source,\n content,\n queryParams: query_params ?? '',\n })\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(saved, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Evidence append', err)\n }\n },\n )\n\n server.registerTool(\n 'case_verify_evidence',\n {\n description: 'Verify a local case evidence manifest and report tampered or missing evidence files.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { EvidenceStore } = await import('../cases/index.js')\n const result = await EvidenceStore.verifyManifest(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Evidence verify', err)\n }\n },\n )\n\n server.registerTool(\n 'case_update_dossier',\n {\n description: 'Append a finding to an address/entity dossier inside a local Chain Insights case.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n address: z.string().min(1).describe('Full address or entity identifier'),\n finding: z.string().min(1).describe('Finding to append'),\n entity_type: z.enum(['eoa', 'contract', 'exchange', 'mixer', 'unknown']).optional().describe('Entity type'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, address, finding, entity_type }) => {\n try {\n const { DossierStore } = await import('../cases/index.js')\n await DossierStore.appendFinding(case_id, address, finding, entity_type ?? 'unknown')\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ case_id, address, updated: true }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Dossier update', err)\n }\n },\n )\n\n server.registerTool(\n 'case_start_session',\n {\n description: 'Start a local investigation session file for a Chain Insights case.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id }) => {\n try {\n const { SessionStore } = await import('../cases/index.js')\n const session = await SessionStore.start(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(session, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Session start', err)\n }\n },\n )\n\n server.registerTool(\n 'case_end_session',\n {\n description: 'End the latest local investigation session for a Chain Insights case with findings and next steps.',\n inputSchema: {\n case_id: z.string().min(1).describe('Chain Insights case ID'),\n findings: z.string().optional().describe('Key findings from this session'),\n next_steps: z.string().optional().describe('Next investigation steps'),\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n },\n async ({ case_id, findings, next_steps }) => {\n try {\n const { SessionStore } = await import('../cases/index.js')\n await SessionStore.end(case_id, {\n findings: findings ?? '',\n nextSteps: next_steps ?? '',\n })\n await SessionStore.archiveOldSessions(case_id)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ case_id, ended: true }, null, 2) }],\n isError: false,\n }\n } catch (err) {\n return caseToolError('Session end', err)\n }\n },\n )\n\n if (!remoteToolNames.has('address_risk')) {\n registerAppTool(\n server,\n 'address_risk',\n {\n title: 'Address Risk',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.address_risk,\n inputSchema: {\n address: z.string().min(1).describe('Full blockchain address to screen'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n compare_address: z.string().optional().describe('Optional second full address for comparison'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ address, network, compare_address }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { addressRisk } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await addressRisk(remoteClient, {\n address,\n network,\n compareAddress: compare_address,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `address-risk-${network}-${address}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Address risk failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n if (!remoteToolNames.has('track_funds')) {\n registerAppTool(\n server,\n 'track_funds',\n {\n title: 'Track Funds',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.track_funds,\n inputSchema: {\n trusted_addresses: z.union([z.string().min(1), z.array(z.string().min(1))]).describe('Comma-separated full trusted victim addresses, or an array. Min 1, max 5.'),\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n untrusted_addresses: z.union([z.string(), z.array(z.string())]).optional().describe('Known scammer/untrusted addresses. Max 5.'),\n include_attachments: z.boolean().optional().describe('Include graph app report metadata'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n max_hops: z.number().int().min(1).max(5).optional(),\n per_address_limit: z.number().int().min(1).max(10).optional(),\n min_amount_sum: z.number().min(0).optional(),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ trusted_addresses, untrusted_addresses, network, case_id, max_hops, per_address_limit, min_amount_sum }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { trackFunds } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await trackFunds(remoteClient, config, {\n trustedAddresses: trusted_addresses,\n untrustedAddresses: untrusted_addresses,\n network,\n caseId: case_id,\n maxHops: max_hops,\n perAddressLimit: per_address_limit,\n minAmountSum: min_amount_sum,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `track-funds-${network}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Track funds failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n if (!remoteToolNames.has('scam_topology')) {\n registerAppTool(\n server,\n 'scam_topology',\n {\n title: 'Scam Topology',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.scam_topology,\n inputSchema: {\n network: z.string().min(1).describe(NETWORK_DESCRIPTION),\n victim_address: z.string().min(1).describe('Full victim/source address that anchors the scam incident. Victims are not risky labels.'),\n incident_timestamp_ms: z.number().min(0).describe('Earliest known incident transfer timestamp in milliseconds. Primary traversal uses node-relative wave-arrival filtering.'),\n max_hops: z.number().int().min(1).max(64).optional().describe('Maximum forward expansion depth. Default 16.'),\n activity_policy: z.enum(['node_relative_only', 'global_incident_only']).optional().describe('Traversal activity policy. Default node_relative_only.'),\n case_id: z.string().optional().describe('Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.'),\n },\n _meta: {\n ui: {\n resourceUri: GRAPH_RESOURCE_URI,\n },\n },\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ victim_address, incident_timestamp_ms, network, max_hops, activity_policy, case_id }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const { scamTopology } = await import('../investigation/public-tools.js')\n const { writeGraphReport } = await import('./graph-reports.js')\n const { ensureArtifactServer } = await import('./artifact-server.js')\n const result = await scamTopology(remoteClient, config, {\n victimAddress: victim_address,\n network,\n maxHops: max_hops,\n incidentTimestampMs: incident_timestamp_ms,\n activityPolicyMode: activity_policy,\n caseId: case_id,\n })\n const report = await writeGraphReport(result.graphData as never, {\n serverPort: config.serverPort,\n slug: `scam-topology-${network}`,\n })\n await ensureArtifactServer(config.serverPort)\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n _meta: {\n chainInsights: {\n graph: {\n schema: report.schema,\n url: report.url,\n },\n },\n },\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Scam topology failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n },\n )\n }\n\n server.registerTool(\n 'help',\n {\n description: 'Show Chain Insights overview, available tools, and investigation workflow.',\n inputSchema: z.object({}).passthrough(),\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: [\n 'Chain Insights AML investigation workspace for AI agents.',\n '',\n CHAIN_INSIGHTS_WORKFLOW,\n '',\n 'Investigation tools:',\n '- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.',\n '- address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.',\n '- track_funds: trace up to five trusted/victim addresses plus up to five known untrusted/scammer addresses through intermediaries to exchange deposit addresses.',\n '- scam_topology: derive ML-ready scam_labels from one victim incident address and incident_timestamp_ms.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n '',\n 'Case workflow tools:',\n '- case_open: create a local case before preserving evidence.',\n '- case_list: list local cases.',\n '- case_resume: load case context, evidence count, dossiers, and latest session.',\n '- case_add_evidence: append a report or note to the case evidence manifest.',\n '- case_verify_evidence: verify saved evidence integrity.',\n '- case_update_dossier: add a finding to an address/entity dossier.',\n '- case_start_session and case_end_session: record session notes.',\n '',\n 'Wallet tools:',\n '- balance: show the local payment wallet address and Base USDC balance.',\n '- help: show this overview.',\n '',\n GRAPH_REPORT_HINTS,\n '',\n GRAPH_SCHEMA_HINTS,\n ].join('\\n'),\n },\n ],\n isError: false,\n }),\n )\n\n // Register each remote tool locally — passthrough proxy pattern\n for (const tool of tools ?? []) {\n if (HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue\n if (LOCAL_TOOL_NAMES.has(tool.name)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n if (!remoteConnected) {\n return {\n content: [{\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }],\n isError: true,\n }\n }\n const normalizedArgs = normalizeRemoteToolArguments(tool.name, args)\n const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs)\n if (validationError) {\n return {\n content: [{ type: 'text' as const, text: validationError }],\n isError: true,\n }\n }\n const request = {\n name: tool.name,\n arguments: normalizedArgs,\n }\n const requestOptions = remoteToolRequestOptions(tool.name)\n const result = requestOptions\n ? await remoteClient.callTool(request, undefined, requestOptions)\n : await remoteClient.callTool(request)\n return await normalizeRemoteToolResult(result as RemoteToolResult, config, tool.name)\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `MCP call failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n const toolConfig = {\n title: tool.title,\n description: claudeFacingToolDescription(tool),\n inputSchema,\n }\n\n if (hasGraphApp(tool)) {\n registerAppTool(\n server,\n tool.name,\n {\n ...toolConfig,\n _meta: graphToolMeta(tool),\n },\n handler,\n )\n } else {\n server.registerTool(tool.name, toolConfig, handler)\n }\n }\n\n // Connect to stdio transport — after this line, stdout belongs to MCP\n const transport = new StdioServerTransport()\n await server.connect(transport)\n await logger.info('proxy.ready', {\n tools: [\n ...LOCAL_TOOL_NAMES,\n ...(tools ?? []).map((tool) => tool.name).filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n })\n\n // Signal handling — clean shutdown\n const shutdown = async () => {\n await logger.info('proxy.shutdown')\n transport.close()\n process.exit(0)\n }\n process.on('SIGINT', () => { void shutdown() })\n process.on('SIGTERM', () => { void shutdown() })\n}\n\n// Entry point — only execute when run as the main module (not when imported by tests)\n// Using process.argv check to detect direct execution vs import\nif (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\\\/g, '/'))) {\n createProxy().catch((err) => {\n process.stderr.write(`Chain Insights MCP proxy startup failed: ${(err as Error).message}\\n`)\n process.exit(1)\n })\n}\n"],"mappings":"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"}
|
package/docs/architecture.md
CHANGED
|
@@ -15,8 +15,7 @@ flowchart LR
|
|
|
15
15
|
CLI --> Workspace[Investigation workspace]
|
|
16
16
|
Proxy --> GraphMCP[GraphRAG MCP]
|
|
17
17
|
CLI --> GraphMCP
|
|
18
|
-
GraphMCP -->
|
|
19
|
-
GraphMCP --> StarRocks[(StarRocks)]
|
|
18
|
+
GraphMCP --> GraphData[(Graph intelligence)]
|
|
20
19
|
Wallet --> Base[Base RPC]
|
|
21
20
|
Workspace --> Browser[Local browser reports]
|
|
22
21
|
```
|
package/docs/graph-tools.md
CHANGED
|
@@ -20,9 +20,9 @@ assumed to exist on the GraphRAG MCP endpoint.
|
|
|
20
20
|
|
|
21
21
|
- `network` is required. Do not guess it in agent workflows.
|
|
22
22
|
- GQL/Cypher must be read-only.
|
|
23
|
-
- Use `USE live_topology` for
|
|
24
|
-
- Use `USE archive_topology` for
|
|
25
|
-
- Use `USE facts` for
|
|
23
|
+
- Use `USE live_topology` for recent topology.
|
|
24
|
+
- Use `USE archive_topology` for historical topology.
|
|
25
|
+
- Use `USE facts` for labels, features, risk scores, assets, and enrichment.
|
|
26
26
|
- Use `graph_query_batch` for related reads that should share one paid call.
|
|
27
27
|
- `per_query_timeout_seconds` is optional and capped at `600`.
|
|
28
28
|
- Returned rows live in `structuredContent.facts`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chain-insights",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "AML investigation
|
|
3
|
+
"version": "0.2.17",
|
|
4
|
+
"description": "AML investigation CLI and MCP proxy for blockchain risk, fund-flow tracing, case reports, and x402-paid GraphRAG MCP access",
|
|
5
|
+
"homepage": "https://chain-insights.ai",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/chainswarm/chain-insights.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/chainswarm/chain-insights/issues"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"aml",
|
|
15
|
+
"blockchain",
|
|
16
|
+
"crypto-compliance",
|
|
17
|
+
"chain-insights",
|
|
18
|
+
"mcp",
|
|
19
|
+
"graphrag",
|
|
20
|
+
"x402",
|
|
21
|
+
"fund-flow",
|
|
22
|
+
"risk-scoring",
|
|
23
|
+
"investigations"
|
|
24
|
+
],
|
|
5
25
|
"type": "module",
|
|
6
26
|
"engines": {
|
|
7
27
|
"node": ">=22.0.0"
|
|
@@ -50,7 +50,7 @@ cia debug off
|
|
|
50
50
|
`graph_query_batch` with `USE live_topology` as appropriate. Use
|
|
51
51
|
`graph_query_batch` with `USE archive_topology`
|
|
52
52
|
for historical money-flow topology, and `USE facts`
|
|
53
|
-
for graph-language
|
|
53
|
+
for graph-language facts and enrichment.
|
|
54
54
|
3. Read workspace runtime schema notes:
|
|
55
55
|
```bash
|
|
56
56
|
test -f .chain-insights/runtime-skill/SKILL.md && sed -n '1,220p' .chain-insights/runtime-skill/SKILL.md
|
|
@@ -89,16 +89,16 @@ cia debug off
|
|
|
89
89
|
their graph semantics must be a faithful port of the Python tools.
|
|
90
90
|
- When the upstream server is Go Graph MCP, high-level Chain Insights tools
|
|
91
91
|
must implement Python-compatible orchestration by calling `graph_query` or
|
|
92
|
-
`graph_query_batch`. Prefer `USE live_topology` for
|
|
93
|
-
`USE archive_topology` for
|
|
94
|
-
for
|
|
92
|
+
`graph_query_batch`. Prefer `USE live_topology` for recent topology,
|
|
93
|
+
`USE archive_topology` for historical topology, and `USE facts`
|
|
94
|
+
for facts and enrichment. Do not replace Python probe semantics with
|
|
95
95
|
simplified local recipes.
|
|
96
96
|
- For exchange-deposit discovery, preserve Python `BFSOps`/`StolenFundsProbe`
|
|
97
97
|
semantics: forward search to `Address` nodes where `is_exchange IS NOT NULL`,
|
|
98
98
|
stop at exchange nodes, treat `path[-2]` as the deposit candidate, then run
|
|
99
|
-
backward/source and reverse-lead stages.
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
backward/source and reverse-lead stages. Some Graph MCP deployments do not
|
|
100
|
+
parse backend-specific BFS or variable-length relationship syntax, so they
|
|
101
|
+
reproduce this with generated fixed-depth `FLOWS_TO` query batches.
|
|
102
102
|
- For `address_risk`, Python `GraphRAGQueryEngine.check_address_risk` is the
|
|
103
103
|
golden behavior: bounded neighborhood expansion with exchange-stopped waves,
|
|
104
104
|
risk/scoring fields, lookalikes, forward exchange
|
|
@@ -181,8 +181,8 @@ workspace-local compact evidence JSON, graph JSON, graph HTML,
|
|
|
181
181
|
label-candidate CSV, and Markdown report files under `reports/`.
|
|
182
182
|
|
|
183
183
|
Use manual `graph_query_batch` for custom topology or fact questions. Use
|
|
184
|
-
`USE live_topology` for
|
|
185
|
-
|
|
184
|
+
`USE live_topology` for recent topology, `USE archive_topology` for historical
|
|
185
|
+
topology, and `USE facts` for facts and enrichment.
|
|
186
186
|
Use `graph_query` or `graph_query_batch` for all graph-language reads.
|
|
187
187
|
|
|
188
188
|
## Query And Evidence Loop
|
|
@@ -92,8 +92,8 @@ into a simple top-K neighbor recipe. When Chain Insights runs against the Go
|
|
|
92
92
|
Graph MCP, it should still reproduce Python `BFSOps` and `StolenFundsProbe`
|
|
93
93
|
semantics by issuing read-only `graph_query_batch` calls with `USE live_topology`.
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
syntax. Against Go Graph MCP, exchange-deposit discovery therefore uses
|
|
95
|
+
Some Graph MCP deployments do not parse backend-specific BFS or variable-length
|
|
96
|
+
relationship syntax. Against Go Graph MCP, exchange-deposit discovery therefore uses
|
|
97
97
|
generated fixed-depth `FLOWS_TO` query batches up to the requested hop limit,
|
|
98
98
|
requires `t.is_exchange IS NOT NULL`, prevents intermediate exchange hops, and
|
|
99
99
|
treats the penultimate address as the deposit candidate.
|
|
@@ -151,7 +151,7 @@ The tool:
|
|
|
151
151
|
- `.chain-insights/schema/<network>.graph-schema.json`
|
|
152
152
|
2. Runs Python-probe-style forward exchange path query batches:
|
|
153
153
|
- generated fixed-depth `MATCH (s)-[r1:FLOWS_TO]->...->(t)` queries
|
|
154
|
-
because
|
|
154
|
+
because some graph endpoints reject backend-specific BFS or variable-length syntax,
|
|
155
155
|
- excludes paths that traverse through an intermediate exchange,
|
|
156
156
|
- records node and relationship projections,
|
|
157
157
|
- treats `path[-2]` as the exchange deposit candidate.
|
|
@@ -58,7 +58,7 @@ The UAT must verify all of these facts:
|
|
|
58
58
|
- Chain Insights proxy `address_risk` returns only local graph report metadata in `_meta.chainInsights.graph = { schema, url }`.
|
|
59
59
|
- Chain Insights proxy response must not include `_meta.chainInsights.graph.data`.
|
|
60
60
|
- The local graph report URL must be served by the Chain Insights Hono server at `/graph-reports/<filename>.graph.json` and return `chain-insights.graph.v1` JSON without `transfers`.
|
|
61
|
-
- `chain-insights mcp call graph_query` with `USE live_topology` must hit the real GraphRAG
|
|
61
|
+
- `chain-insights mcp call graph_query` with `USE live_topology` must hit the real GraphRAG path and return the UAT address.
|
|
62
62
|
- No investigation output is created under `~/.chain-insights/reports` or `~/.chain-insights/cases`.
|
|
63
63
|
</uat_contract>
|
|
64
64
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init-CaOsHTIo.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport interface InitWorkspaceOptions {\n targetDir: string\n force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspaceRoot: string\n filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n '.chain-insights',\n '.chain-insights/schema',\n '.chain-insights/runtime',\n '.chain-insights/runtime/logs',\n '.chain-insights/runtime-skill',\n 'cases',\n 'imports',\n 'reports',\n 'reports/graphs',\n 'reports/tables',\n 'templates',\n]\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n return JSON.stringify({\n schema: 'chain-insights.workspace.v1',\n name: 'Chain Insights Investigations',\n workspace_root: workspaceRoot,\n default_network: 'bittensor',\n graph_mcp_endpoint: 'https://staging-mcp.chain-insights.ai/mcp',\n cases_dir: 'cases',\n imports_dir: 'imports',\n reports_dir: 'reports',\n templates_dir: 'templates',\n created_at: todayIso(),\n }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Investigations\n\nThis is a workspace for Chain Insights AML investigations.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet balance\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/ Workspace metadata\ncases/ Case exports and notes\nimports/ External reports, CSVs, screenshots, raw notes\nreports/ Final or interim analyst reports\nreports/graphs/ Graph JSON for visualization\nreports/tables/ Compact tabular extracts\ntemplates/ Reusable case/report templates\n.chain-insights/schema/ Runtime graph schema captures\n.chain-insights/runtime/ Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/ Workspace-specific agent schema notes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights investigation workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before investigation-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first case query.\n- Save compact evidence with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Evidence files should summarize and point to graph/table outputs; do not paste large raw JSON blobs into evidence Markdown.\n- Investigation output must stay in this initialized workspace.\n- Never write cases, evidence, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Case Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party investigation material here before turning it\ninto case evidence.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into the case evidence manifest and reference\nthe original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, case, prompt, and evidence templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as case state until copied into a case, evidence file, dossier, or\nreport.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Address) RETURN \\\"Address\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Address)-[f:FLOWS_TO]->(:Address) RETURN f.period_granularity AS granularity, f.amount_sum AS amount_sum LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Use \\`USE live_topology\\` for Memgraph RAM topology, \\`USE archive_topology\\`\n for StarRocks historical topology, and \\`USE facts\\` for StarRocks fact\n labels such as \\`AddressLabel\\`, \\`AddressFeature\\`,\n \\`RiskScore\\`, and \\`Asset\\`. Address facts can be reached through\n relationships such as \\`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\\`.\n Archived money-flow topology is exposed as\n \\`(:Address)-[:FLOWS_TO]->(:Address)\\` with \\`period_granularity\\`,\n \\`period_start_date\\`, and \\`period_end_date\\` on the relationship.\n- Preserve source schema field names in evidence and generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n schema or query result explicitly supports that interpretation.\n- Keep evidence compact: select only the fields needed to support the claim.\n Avoid storing whole node or relationship property blobs in evidence unless\n the purpose of the query is schema discovery or debugging.\n- Keep analysis products separate from evidence: graph JSON belongs under\n \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n narrative under \\`reports/\\`.\n- Evidence Markdown should be a short provenance record with key facts and\n pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in evidence.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first case query in a fresh\nworkspace, then referenced by evidence, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n return [\n ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n ['README.md', README],\n ['AGENTS.md', AGENTS],\n ['CLAUDE.md', CLAUDE],\n ['imports/README.md', IMPORTS_README],\n ['templates/README.md', TEMPLATES_README],\n ['templates/case-brief.md', CASE_BRIEF],\n ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n ['.chain-insights/schema/README.md', SCHEMA_README],\n ['.chain-insights/runtime/.keep', ''],\n ['.chain-insights/runtime/logs/.keep', ''],\n ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await access(filePath)\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n continue\n }\n throw err\n }\n }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n const workspaceRoot = path.resolve(options.targetDir)\n if (!options.force) {\n await assertNoFileCollisions(workspaceRoot)\n }\n\n for (const dir of WORKSPACE_DIRS) {\n await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n }\n\n const filesWritten: string[] = []\n const flag = options.force ? 'w' : 'wx'\n for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await writeFile(filePath, content, { mode: 0o600, flag })\n filesWritten.push(relativePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n }\n throw err\n }\n }\n\n return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;AAaA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,WAAmB;AAC1B,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;AAG9C,SAAS,cAAc,eAA+B;AACpD,QAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,aAAa;EACb,aAAa;EACb,eAAe;EACf,YAAY,UAAU;EACvB,EAAE,MAAM,EAAE,GAAG;;AAGhB,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;AA2Bf,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDtB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;AACtE,QAAO;EACL,CAAC,kCAAkC,cAAc,cAAc,CAAC;EAChE,CAAC,aAAa,OAAO;EACrB,CAAC,aAAa,OAAO;EACrB,CAAC,aAAa,OAAO;EACrB,CAAC,qBAAqB,eAAe;EACrC,CAAC,uBAAuB,iBAAiB;EACzC,CAAC,2BAA2B,WAAW;EACvC,CAAC,0CAA0C,cAAc;EACzD,CAAC,oCAAoC,cAAc;EACnD,CAAC,iCAAiC,GAAG;EACrC,CAAC,sCAAsC,GAAG;EAC3C;;AAGH,eAAe,uBAAuB,eAAsC;AAC1E,MAAK,MAAM,CAAC,iBAAiB,eAAe,cAAc,EAAE;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,aAAa;AACvD,MAAI;AACF,SAAM,OAAO,SAAS;AACtB,SAAM,IAAI,MAAM,yBAAyB,SAAS,mDAAmD;WAC9F,KAAK;AACZ,OAAK,IAA8B,SAAS,SAC1C;AAEF,SAAM;;;;AAKZ,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,UAAU;AACrD,KAAI,CAAC,QAAQ,MACX,OAAM,uBAAuB,cAAc;AAG7C,MAAK,MAAM,OAAO,eAChB,OAAM,MAAM,KAAK,KAAK,eAAe,IAAI,EAAE,EAAE,WAAW,MAAM,CAAC;CAGjE,MAAM,eAAyB,EAAE;CACjC,MAAM,OAAO,QAAQ,QAAQ,MAAM;AACnC,MAAK,MAAM,CAAC,cAAc,YAAY,eAAe,cAAc,EAAE;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,aAAa;AACvD,MAAI;AACF,SAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;IAAM,CAAC;AACzD,gBAAa,KAAK,aAAa;WACxB,KAAK;AACZ,OAAK,IAA8B,SAAS,SAC1C,OAAM,IAAI,MAAM,yBAAyB,SAAS,mDAAmD;AAEvG,SAAM;;;AAIV,QAAO;EAAE;EAAe;EAAc"}
|