chain-insights 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/bin/cli.js +10 -0
  4. package/bin/install.cjs +252 -0
  5. package/bin/mcp-proxy.cjs +10 -0
  6. package/dist/active-BSrxLKwn.mjs +50 -0
  7. package/dist/active-BSrxLKwn.mjs.map +1 -0
  8. package/dist/active-Dv7Tu-O4.cjs +68 -0
  9. package/dist/app-BjjuQM0B.mjs +155 -0
  10. package/dist/app-BjjuQM0B.mjs.map +1 -0
  11. package/dist/app-Dq1TdB6p.cjs +161 -0
  12. package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
  13. package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
  14. package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
  15. package/dist/assets/bg-pattern.png +0 -0
  16. package/dist/assets/logo.png +0 -0
  17. package/dist/call-args-DQA2QcRA.cjs +27 -0
  18. package/dist/call-args-Lk_wOJxd.mjs +29 -0
  19. package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
  20. package/dist/capabilities-CB97WMA5.cjs +83 -0
  21. package/dist/capabilities-DliMBim-.mjs +84 -0
  22. package/dist/capabilities-DliMBim-.mjs.map +1 -0
  23. package/dist/cases-By7INiOa.mjs +6 -0
  24. package/dist/cases-CDcNU91B.cjs +9 -0
  25. package/dist/chunk-CZWwpsFl.cjs +43 -0
  26. package/dist/cli.cjs +752 -0
  27. package/dist/cli.d.cts +1 -0
  28. package/dist/cli.d.mts +1 -0
  29. package/dist/cli.mjs +753 -0
  30. package/dist/cli.mjs.map +1 -0
  31. package/dist/client-D4Bq0rp9.mjs +111 -0
  32. package/dist/client-D4Bq0rp9.mjs.map +1 -0
  33. package/dist/client-D4fZgIaO.cjs +132 -0
  34. package/dist/config-Bmdl5hdk.cjs +67 -0
  35. package/dist/config-BwrBYmiC.mjs +44 -0
  36. package/dist/config-BwrBYmiC.mjs.map +1 -0
  37. package/dist/data-extractor-BNGj7ECT.cjs +347 -0
  38. package/dist/data-extractor-DFzsa5CS.mjs +336 -0
  39. package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
  40. package/dist/dossier-BsroDgD3.mjs +76 -0
  41. package/dist/dossier-BsroDgD3.mjs.map +1 -0
  42. package/dist/dossier-DtxREpPm.cjs +76 -0
  43. package/dist/evidence-BGcdKxuV.cjs +200 -0
  44. package/dist/evidence-BhvFW-y_.mjs +195 -0
  45. package/dist/evidence-BhvFW-y_.mjs.map +1 -0
  46. package/dist/format-Ce1RObVl.mjs +22 -0
  47. package/dist/format-Ce1RObVl.mjs.map +1 -0
  48. package/dist/format-DOrPvXEr.cjs +20 -0
  49. package/dist/frontmatter-D8wWCeOa.mjs +26 -0
  50. package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
  51. package/dist/frontmatter-DgAuai7E.cjs +35 -0
  52. package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
  53. package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
  54. package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
  55. package/dist/graph-reports-C4TBjCkM.mjs +63 -0
  56. package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
  57. package/dist/graph-reports-DU05YCei.cjs +64 -0
  58. package/dist/html-generator-CAv81IWH.cjs +85 -0
  59. package/dist/html-generator-V6Bp0uRb.mjs +68 -0
  60. package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
  61. package/dist/index.cjs +31 -0
  62. package/dist/index.d.cts +187 -0
  63. package/dist/index.d.cts.map +1 -0
  64. package/dist/index.d.mts +187 -0
  65. package/dist/index.d.mts.map +1 -0
  66. package/dist/index.mjs +9 -0
  67. package/dist/init-BjuFt54X.cjs +232 -0
  68. package/dist/init-CaOsHTIo.mjs +232 -0
  69. package/dist/init-CaOsHTIo.mjs.map +1 -0
  70. package/dist/mcp-proxy.cjs +1257 -0
  71. package/dist/mcp-proxy.d.cts +12 -0
  72. package/dist/mcp-proxy.d.cts.map +1 -0
  73. package/dist/mcp-proxy.d.mts +12 -0
  74. package/dist/mcp-proxy.d.mts.map +1 -0
  75. package/dist/mcp-proxy.mjs +1255 -0
  76. package/dist/mcp-proxy.mjs.map +1 -0
  77. package/dist/output-root-CFYms3ad.cjs +43 -0
  78. package/dist/output-root-CmWM7aV2.mjs +33 -0
  79. package/dist/output-root-CmWM7aV2.mjs.map +1 -0
  80. package/dist/parser-BUIWW1OH.cjs +182 -0
  81. package/dist/parser-DO0_SssG.mjs +182 -0
  82. package/dist/parser-DO0_SssG.mjs.map +1 -0
  83. package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
  84. package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
  85. package/dist/public-tools-XSpkz2ky.cjs +2556 -0
  86. package/dist/resolver-C2ZS7oC8.mjs +201 -0
  87. package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
  88. package/dist/resolver-zYbu4wDV.cjs +203 -0
  89. package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
  90. package/dist/runner-1Eq55OYb.cjs +148 -0
  91. package/dist/runner-BhUHbiHG.mjs +149 -0
  92. package/dist/runner-BhUHbiHG.mjs.map +1 -0
  93. package/dist/schema-4XpzDFQM.cjs +55 -0
  94. package/dist/schema-8d0rVIdZ.mjs +37 -0
  95. package/dist/schema-8d0rVIdZ.mjs.map +1 -0
  96. package/dist/schema-cache-9CksD7tX.mjs +34 -0
  97. package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
  98. package/dist/schema-cache-CgWRCN2N.cjs +36 -0
  99. package/dist/selector-CkFcTXzz.cjs +10 -0
  100. package/dist/selector-xjm6NTHI.mjs +12 -0
  101. package/dist/selector-xjm6NTHI.mjs.map +1 -0
  102. package/dist/server-BkM5xrXb.mjs +45 -0
  103. package/dist/server-BkM5xrXb.mjs.map +1 -0
  104. package/dist/server-DXowbpfi.cjs +54 -0
  105. package/dist/session-BpNylyuJ.cjs +115 -0
  106. package/dist/session-CcTgYxsj.mjs +115 -0
  107. package/dist/session-CcTgYxsj.mjs.map +1 -0
  108. package/dist/setup-DOpKPrlx.cjs +81 -0
  109. package/dist/setup-DyrWHuwQ.mjs +80 -0
  110. package/dist/setup-DyrWHuwQ.mjs.map +1 -0
  111. package/dist/store-BiUhQOIf.cjs +230 -0
  112. package/dist/store-BoWE-Gtl.mjs +225 -0
  113. package/dist/store-BoWE-Gtl.mjs.map +1 -0
  114. package/dist/templates/graph.html +1406 -0
  115. package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
  116. package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
  117. package/dist/tool-visibility-CwgY205r.cjs +36 -0
  118. package/dist/tools-Cp2jAAAb.mjs +100 -0
  119. package/dist/tools-Cp2jAAAb.mjs.map +1 -0
  120. package/dist/tools-f_vJUZAF.cjs +139 -0
  121. package/dist/topup-server-BZuQifvh.cjs +940 -0
  122. package/dist/topup-server-DUjyFftI.mjs +919 -0
  123. package/dist/topup-server-DUjyFftI.mjs.map +1 -0
  124. package/dist/version-1gP19Lhi.mjs +8 -0
  125. package/dist/version-1gP19Lhi.mjs.map +1 -0
  126. package/dist/version-BNGtdpmH.cjs +18 -0
  127. package/dist/viz-BlCJe6Tk.mjs +35 -0
  128. package/dist/viz-BlCJe6Tk.mjs.map +1 -0
  129. package/dist/viz-ClezVXrJ.cjs +44 -0
  130. package/dist/wallet-BMelXBYP.mjs +104 -0
  131. package/dist/wallet-BMelXBYP.mjs.map +1 -0
  132. package/dist/wallet-RnvvSpV2.cjs +146 -0
  133. package/docs/architecture.md +145 -0
  134. package/docs/contributing.md +68 -0
  135. package/docs/debugging.md +68 -0
  136. package/docs/development.md +44 -0
  137. package/docs/graph-tools.md +251 -0
  138. package/docs/images/graph-mcp-iframe.png +0 -0
  139. package/docs/images/graph-visualization.png +0 -0
  140. package/docs/images/topup-page.png +0 -0
  141. package/docs/investigation-workspaces.md +151 -0
  142. package/docs/mcp-proxy.md +180 -0
  143. package/package.json +59 -0
  144. package/skills/chain-insights-developer-experience/SKILL.md +101 -0
  145. package/skills/chain-insights-investigation/SKILL.md +285 -0
  146. package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
  147. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
  148. package/skills/chain-insights-trace-funds/SKILL.md +249 -0
  149. package/skills/ci-case/SKILL.md +43 -0
  150. package/skills/ci-status/SKILL.md +45 -0
  151. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
  152. package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
  153. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ CHAIN_INSIGHTS_DIR="${CHAIN_INSIGHTS_DIR:-/home/aphex5/work/chain-insights}"
5
+ GRAPHRAG_ML_DIR="${GRAPHRAG_ML_DIR:-/home/aphex5/work/rbmk/repos/ml}"
6
+ GRAPHRAG_DIR="${GRAPHRAG_DIR:-${GRAPHRAG_ML_DIR}/graphrag}"
7
+ RBMK_DIR="${RBMK_DIR:-$(cd "${GRAPHRAG_ML_DIR}/../.." && pwd)}"
8
+ MCP_ENDPOINT="${GRAPHRAG_MCP_ENDPOINT:-http://localhost:8012/mcp}"
9
+ DEBUG_TOKEN="${GRAPHRAG_DEBUG_TOKEN:-chain-insights-dev-debug}"
10
+ SERVER_PORT="${CHAIN_INSIGHTS_SERVER_PORT:-4321}"
11
+ NETWORK="${NETWORK:-bittensor}"
12
+ UAT_ADDRESS="${UAT_ADDRESS:-5Ccmf1dJKzGtXX7h17eN72MVMRsFwvYjPVmkXPUaapczECf6}"
13
+ REPORT_DIR="${REPORT_DIR:-${CHAIN_INSIGHTS_DIR}/.tmp/uat}"
14
+ RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
15
+ RUN_DIR="${REPORT_DIR}/${RUN_ID}"
16
+ WORKSPACE_ROOT="${WORKSPACE_ROOT:-${RUN_DIR}/workspace}"
17
+ CHAIN_INSIGHTS_CLI="${CHAIN_INSIGHTS_DIR}/bin/cli.js"
18
+ CHAIN_INSIGHTS_PROXY="${CHAIN_INSIGHTS_DIR}/bin/mcp-proxy.cjs"
19
+ GLOBAL_REPORTS="${HOME}/.chain-insights/reports"
20
+ GLOBAL_CASES="${HOME}/.chain-insights/cases"
21
+ GLOBAL_SNAPSHOT_BEFORE="${RUN_DIR}/global-output-before.txt"
22
+ GLOBAL_SNAPSHOT_AFTER="${RUN_DIR}/global-output-after.txt"
23
+ SERVER_PID=""
24
+ CONFIG_SNAPSHOT_READY=0
25
+
26
+ mkdir -p "${RUN_DIR}"
27
+
28
+ log() {
29
+ printf '[uat] %s\n' "$*"
30
+ }
31
+
32
+ require_cmd() {
33
+ if ! command -v "$1" >/dev/null 2>&1; then
34
+ printf '[uat] missing required command: %s\n' "$1" >&2
35
+ exit 127
36
+ fi
37
+ }
38
+
39
+ snapshot_global_outputs() {
40
+ local output_file="$1"
41
+ : >"${output_file}"
42
+ for dir in "${GLOBAL_REPORTS}" "${GLOBAL_CASES}"; do
43
+ {
44
+ printf '[%s]\n' "${dir}"
45
+ if [[ -d "${dir}" ]]; then
46
+ (
47
+ cd "${dir}"
48
+ find . -mindepth 1 -type d -print | LC_ALL=C sort | sed 's/^/dir /'
49
+ find . -mindepth 1 -type f -print0 \
50
+ | LC_ALL=C sort -z \
51
+ | xargs -0 -r sha256sum \
52
+ | sed 's/^/file /'
53
+ )
54
+ else
55
+ printf '<missing>\n'
56
+ fi
57
+ } >>"${output_file}"
58
+ done
59
+ }
60
+
61
+ assert_no_global_outputs_changed() {
62
+ snapshot_global_outputs "${GLOBAL_SNAPSHOT_AFTER}"
63
+ if ! cmp -s "${GLOBAL_SNAPSHOT_BEFORE}" "${GLOBAL_SNAPSHOT_AFTER}"; then
64
+ log "global investigation output roots changed; reports/cases must stay workspace-local"
65
+ diff -u "${GLOBAL_SNAPSHOT_BEFORE}" "${GLOBAL_SNAPSHOT_AFTER}" >&2 || true
66
+ return 1
67
+ fi
68
+ }
69
+
70
+ cleanup() {
71
+ if [[ -n "${SERVER_PID}" ]]; then
72
+ kill "${SERVER_PID}" >/dev/null 2>&1 || true
73
+ wait "${SERVER_PID}" >/dev/null 2>&1 || true
74
+ fi
75
+ }
76
+
77
+ restore_config() {
78
+ if [[ "${CONFIG_SNAPSHOT_READY}" != "1" ]]; then
79
+ return
80
+ fi
81
+ if [[ -n "${OLD_GRAPH_MCP_MODE:-}" ]]; then
82
+ node "${CHAIN_INSIGHTS_CLI}" config set graphMcpMode "${OLD_GRAPH_MCP_MODE}" >/dev/null || true
83
+ fi
84
+ if [[ -n "${OLD_GRAPH_MCP_ENDPOINT:-}" ]]; then
85
+ node "${CHAIN_INSIGHTS_CLI}" config set graphMcpEndpoint "${OLD_GRAPH_MCP_ENDPOINT}" >/dev/null || true
86
+ fi
87
+ node "${CHAIN_INSIGHTS_CLI}" config set graphMcpAuthToken "${OLD_GRAPH_MCP_AUTH_TOKEN:-}" >/dev/null || true
88
+ if [[ -n "${OLD_SERVER_PORT:-}" ]]; then
89
+ node "${CHAIN_INSIGHTS_CLI}" config set serverPort "${OLD_SERVER_PORT}" >/dev/null || true
90
+ fi
91
+ }
92
+
93
+ finish() {
94
+ local status="$?"
95
+ set +e
96
+ cleanup
97
+ if [[ -f "${GLOBAL_SNAPSHOT_BEFORE}" ]]; then
98
+ assert_no_global_outputs_changed || status=1
99
+ fi
100
+ restore_config
101
+ exit "${status}"
102
+ }
103
+ trap finish EXIT
104
+
105
+ require_cmd node
106
+ require_cmd npm
107
+ require_cmd npx
108
+ require_cmd docker
109
+ require_cmd curl
110
+ require_cmd sha256sum
111
+
112
+ if [[ ! -d "${CHAIN_INSIGHTS_DIR}" ]]; then
113
+ log "missing Chain Insights repo: ${CHAIN_INSIGHTS_DIR}"
114
+ exit 1
115
+ fi
116
+
117
+ if [[ ! -d "${GRAPHRAG_ML_DIR}" ]]; then
118
+ log "missing GraphRAG compose root: ${GRAPHRAG_ML_DIR}"
119
+ exit 1
120
+ fi
121
+
122
+ if [[ ! -d "${RBMK_DIR}" ]]; then
123
+ log "missing RBMK dev stack root: ${RBMK_DIR}"
124
+ exit 1
125
+ fi
126
+
127
+ if [[ ! -d "${GRAPHRAG_DIR}" ]]; then
128
+ log "missing GraphRAG repo: ${GRAPHRAG_DIR}"
129
+ exit 1
130
+ fi
131
+
132
+ log "report directory: ${RUN_DIR}"
133
+ snapshot_global_outputs "${GLOBAL_SNAPSHOT_BEFORE}"
134
+ OLD_GRAPH_MCP_MODE="$(node "${CHAIN_INSIGHTS_CLI}" config get graphMcpMode || true)"
135
+ OLD_GRAPH_MCP_ENDPOINT="$(node "${CHAIN_INSIGHTS_CLI}" config get graphMcpEndpoint || true)"
136
+ OLD_GRAPH_MCP_AUTH_TOKEN="$(node "${CHAIN_INSIGHTS_CLI}" config get graphMcpAuthToken || true)"
137
+ OLD_SERVER_PORT="$(node "${CHAIN_INSIGHTS_CLI}" config get serverPort || true)"
138
+ CONFIG_SNAPSHOT_READY=1
139
+ log "starting RBMK dev stack"
140
+ (cd "${RBMK_DIR}" && bash dev.sh --bg)
141
+
142
+ cd "${CHAIN_INSIGHTS_DIR}"
143
+
144
+ if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
145
+ log "building Chain Insights dist"
146
+ npm run build
147
+ fi
148
+
149
+ log "initializing Chain Insights UAT workspace: ${WORKSPACE_ROOT}"
150
+ node "${CHAIN_INSIGHTS_CLI}" init "${WORKSPACE_ROOT}" --force >/dev/null
151
+ export CHAIN_INSIGHTS_WORKSPACE="${WORKSPACE_ROOT}"
152
+
153
+ log "configuring Chain Insights MCP endpoint and debug bearer token"
154
+ (
155
+ cd "${WORKSPACE_ROOT}"
156
+ node "${CHAIN_INSIGHTS_CLI}" debug on --token "${DEBUG_TOKEN}" --endpoint "${MCP_ENDPOINT}" >/dev/null
157
+ node "${CHAIN_INSIGHTS_CLI}" config set serverPort "${SERVER_PORT}" >/dev/null
158
+ )
159
+
160
+ if curl -sf "http://127.0.0.1:${SERVER_PORT}/health" >/dev/null 2>&1; then
161
+ log "reusing healthy Chain Insights server on port ${SERVER_PORT}"
162
+ else
163
+ log "starting Chain Insights server on port ${SERVER_PORT}"
164
+ (
165
+ cd "${WORKSPACE_ROOT}"
166
+ CHAIN_INSIGHTS_WORKSPACE="${WORKSPACE_ROOT}" node "${CHAIN_INSIGHTS_CLI}" serve -p "${SERVER_PORT}"
167
+ ) >"${RUN_DIR}/chain-insights-server.log" 2>&1 &
168
+ SERVER_PID="$!"
169
+ for _ in $(seq 1 30); do
170
+ if curl -sf "http://127.0.0.1:${SERVER_PORT}/health" >/dev/null 2>&1; then
171
+ break
172
+ fi
173
+ sleep 0.5
174
+ done
175
+ curl -sf "http://127.0.0.1:${SERVER_PORT}/health" >"${RUN_DIR}/server-health.json"
176
+ fi
177
+
178
+ log "refreshing Chain Insights remote tool schema cache"
179
+ (
180
+ cd "${WORKSPACE_ROOT}"
181
+ node "${CHAIN_INSIGHTS_CLI}" mcp tools --refresh
182
+ ) >"${RUN_DIR}/chain-insights-tools.txt"
183
+
184
+ DIRECT_TOOLS_JSON="${RUN_DIR}/direct-tools-list.json"
185
+ log "checking direct GraphRAG tools/list"
186
+ npx @modelcontextprotocol/inspector \
187
+ --cli "${MCP_ENDPOINT}" \
188
+ --transport http \
189
+ --header "Authorization: Bearer ${DEBUG_TOKEN}" \
190
+ --header "X-MCP-Debug-Token: ${DEBUG_TOKEN}" \
191
+ --method tools/list >"${DIRECT_TOOLS_JSON}"
192
+
193
+ node - "${DIRECT_TOOLS_JSON}" "${RUN_DIR}/direct-high-level-tools.txt" <<'NODE'
194
+ const fs = require('node:fs')
195
+ const file = process.argv[2]
196
+ const highLevelFile = process.argv[3]
197
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
198
+ const tools = data.tools || []
199
+ const names = new Set(tools.map((tool) => tool.name))
200
+ const required = ['network_capabilities', 'graph_query', 'graph_query_batch']
201
+ const missing = required.filter((name) => !names.has(name))
202
+ if (missing.length) throw new Error(`direct tools/list missing tools: ${missing.join(', ')}`)
203
+ if (JSON.stringify(tools).includes('app_data')) throw new Error('direct tools/list still contains app_data')
204
+ const hasHighLevel = ['address_risk', 'track_funds'].every((name) => names.has(name))
205
+ fs.writeFileSync(highLevelFile, hasHighLevel ? 'yes\n' : 'no\n')
206
+ console.log(`[uat] direct tools/list ok: ${tools.length} tools (${hasHighLevel ? 'high-level' : 'primitive-only'})`)
207
+ NODE
208
+
209
+ DIRECT_JSON="${RUN_DIR}/direct-address-risk.json"
210
+ DIRECT_ADDRESS_RISK_SUMMARY="- direct address_risk skipped: direct endpoint is primitive-only"
211
+ if [[ "$(cat "${RUN_DIR}/direct-high-level-tools.txt")" == "yes" ]]; then
212
+ log "calling direct GraphRAG address_risk"
213
+ npx @modelcontextprotocol/inspector \
214
+ --cli "${MCP_ENDPOINT}" \
215
+ --transport http \
216
+ --header "Authorization: Bearer ${DEBUG_TOKEN}" \
217
+ --header "X-MCP-Debug-Token: ${DEBUG_TOKEN}" \
218
+ --method tools/call \
219
+ --tool-name address_risk \
220
+ --tool-arg "network=${NETWORK}" \
221
+ --tool-arg "address=${UAT_ADDRESS}" \
222
+ --tool-arg include_attachments=true >"${DIRECT_JSON}"
223
+
224
+ node - "${DIRECT_JSON}" <<'NODE'
225
+ const fs = require('node:fs')
226
+ const file = process.argv[2]
227
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
228
+ const errors = []
229
+ const content = data.content || []
230
+ const sc = data.structuredContent || {}
231
+ const graphData = data._meta?.chainInsights?.graph?.data
232
+ const graphArrayKeys = ['app_data', 'nodes', 'edges', 'flows', 'edge_anchors', 'transfers']
233
+ if (data.isError) errors.push('direct address_risk returned isError=true')
234
+ if (content[0]?.type !== 'text') errors.push('direct content[0] is not text')
235
+ if (sc.schema !== 'chain-insights.result.v1') errors.push(`direct structuredContent schema mismatch: ${sc.schema}`)
236
+ for (const key of graphArrayKeys) {
237
+ if (Object.prototype.hasOwnProperty.call(sc, key)) errors.push(`direct structuredContent leaks ${key}`)
238
+ }
239
+ if (!graphData) errors.push('direct _meta.chainInsights.graph.data missing')
240
+ if (graphData?.schema !== 'chain-insights.graph.v1') errors.push(`direct graph schema mismatch: ${graphData?.schema}`)
241
+ for (const key of ['nodes', 'edges', 'flows', 'edge_anchors']) {
242
+ if (!Array.isArray(graphData?.[key])) errors.push(`direct graph ${key} is not an array`)
243
+ }
244
+ if (Object.prototype.hasOwnProperty.call(graphData || {}, 'transfers')) errors.push('direct graph includes transfers')
245
+ if (errors.length) throw new Error(errors.join('; '))
246
+ console.log(`[uat] direct address_risk ok: nodes=${graphData.nodes.length} edges=${graphData.edges.length} flows=${graphData.flows.length} edge_anchors=${graphData.edge_anchors.length}`)
247
+ NODE
248
+ DIRECT_ADDRESS_RISK_SUMMARY="- ${DIRECT_JSON}"
249
+ else
250
+ log "direct GraphRAG high-level tools absent; primitive-only endpoint, skipping direct address_risk check"
251
+ fi
252
+
253
+ PROXY_TOOLS_JSON="${RUN_DIR}/proxy-tools-list.json"
254
+ log "checking Chain Insights proxy tools/list"
255
+ npx @modelcontextprotocol/inspector \
256
+ --cli node "${CHAIN_INSIGHTS_PROXY}" \
257
+ --transport stdio \
258
+ --method tools/list >"${PROXY_TOOLS_JSON}"
259
+
260
+ node - "${PROXY_TOOLS_JSON}" <<'NODE'
261
+ const fs = require('node:fs')
262
+ const file = process.argv[2]
263
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
264
+ const tools = data.tools || []
265
+ const names = new Set(tools.map((tool) => tool.name))
266
+ const required = ['balance', 'help', 'address_risk', 'track_funds', 'scam_topology', 'network_capabilities', 'graph_query', 'graph_query_batch']
267
+ const missing = required.filter((name) => !names.has(name))
268
+ if (missing.length) throw new Error(`proxy tools/list missing tools: ${missing.join(', ')}`)
269
+ for (const hidden of ['topup', 'trace_funds', 'money_flows_between_exchanges', 'address_connection_risk']) {
270
+ if (names.has(hidden)) throw new Error(`proxy tools/list exposed hidden tool: ${hidden}`)
271
+ }
272
+ if (JSON.stringify(tools).includes('app_data')) throw new Error('proxy tools/list still contains app_data')
273
+ const graphTools = tools.filter((tool) => tool._meta?.ui?.resourceUri === 'ui://chain-insights/graph').map((tool) => tool.name)
274
+ for (const name of ['address_risk', 'track_funds', 'scam_topology']) {
275
+ if (!graphTools.includes(name)) throw new Error(`proxy graph app metadata missing for ${name}`)
276
+ }
277
+ console.log(`[uat] proxy tools/list ok: ${tools.length} tools`)
278
+ NODE
279
+
280
+ PROXY_JSON="${RUN_DIR}/proxy-address-risk.json"
281
+ log "calling Chain Insights proxy address_risk"
282
+ node --input-type=module - "${CHAIN_INSIGHTS_PROXY}" "${NETWORK}" "${UAT_ADDRESS}" "${PROXY_JSON}" <<'NODE'
283
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
284
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
285
+ import fs from 'node:fs'
286
+
287
+ const proxy = process.argv[2]
288
+ const network = process.argv[3]
289
+ const address = process.argv[4]
290
+ const outputFile = process.argv[5]
291
+ const requestTimeoutMs = 5 * 60 * 1000
292
+
293
+ const client = new Client({ name: 'chain-insights-uat', version: '0.0.0' })
294
+ const transport = new StdioClientTransport({
295
+ command: process.execPath,
296
+ args: [proxy],
297
+ env: process.env,
298
+ })
299
+
300
+ try {
301
+ await client.connect(transport)
302
+ const result = await client.callTool(
303
+ {
304
+ name: 'address_risk',
305
+ arguments: {
306
+ network,
307
+ address,
308
+ include_attachments: true,
309
+ },
310
+ },
311
+ undefined,
312
+ {
313
+ timeout: requestTimeoutMs,
314
+ maxTotalTimeout: requestTimeoutMs,
315
+ },
316
+ )
317
+ fs.writeFileSync(outputFile, `${JSON.stringify(result, null, 2)}\n`)
318
+ } finally {
319
+ await client.close()
320
+ }
321
+ NODE
322
+
323
+ GRAPH_REPORT_URL="$(
324
+ node - "${PROXY_JSON}" <<'NODE'
325
+ const fs = require('node:fs')
326
+ const file = process.argv[2]
327
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
328
+ const errors = []
329
+ const content = data.content || []
330
+ const sc = data.structuredContent || {}
331
+ const graph = data._meta?.chainInsights?.graph
332
+ if (data.isError) errors.push('proxy address_risk returned isError=true')
333
+ if (content[0]?.type !== 'text') errors.push('proxy content[0] is not text')
334
+ if (sc.schema !== 'chain-insights.result.v1') errors.push(`proxy structuredContent schema mismatch: ${sc.schema}`)
335
+ for (const key of ['app_data', 'nodes', 'edges', 'flows', 'edge_anchors', 'transfers']) {
336
+ if (JSON.stringify(sc).includes(`"${key}"`)) errors.push(`proxy structuredContent leaks ${key}`)
337
+ }
338
+ if (!graph) errors.push('proxy _meta.chainInsights.graph missing')
339
+ if (graph?.data) errors.push('proxy _meta.chainInsights.graph.data leaked')
340
+ if (graph?.schema !== 'chain-insights.graph.v1') errors.push(`proxy graph schema mismatch: ${graph?.schema}`)
341
+ if (graph?.id) errors.push('proxy graph id should not be returned')
342
+ if (!/^http:\/\/127\.0\.0\.1:\d+\/graph-reports\/[A-Za-z0-9._-]+\.graph\.json$/.test(graph?.url || '')) {
343
+ errors.push(`proxy graph url is not a local graph report URL: ${graph?.url}`)
344
+ }
345
+ if (errors.length) throw new Error(errors.join('; '))
346
+ console.error(`[uat] proxy address_risk ok: graph_report=${graph.url}`)
347
+ process.stdout.write(graph.url)
348
+ NODE
349
+ )"
350
+ printf '%s\n' "${GRAPH_REPORT_URL}" >"${RUN_DIR}/graph-report-url.txt"
351
+
352
+ GRAPH_REPORT_JSON="${RUN_DIR}/graph-report.json"
353
+ log "fetching local graph report"
354
+ curl -sf "${GRAPH_REPORT_URL}" >"${GRAPH_REPORT_JSON}"
355
+
356
+ node - "${GRAPH_REPORT_JSON}" <<'NODE'
357
+ const fs = require('node:fs')
358
+ const file = process.argv[2]
359
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'))
360
+ const errors = []
361
+ if (data.schema !== 'chain-insights.graph.v1') errors.push(`graph report schema mismatch: ${data.schema}`)
362
+ for (const key of ['nodes', 'edges', 'flows', 'edge_anchors']) {
363
+ if (!Array.isArray(data[key])) errors.push(`graph report ${key} is not an array`)
364
+ }
365
+ if (Object.prototype.hasOwnProperty.call(data, 'transfers')) errors.push('graph report includes transfers')
366
+ if (errors.length) throw new Error(errors.join('; '))
367
+ console.log(`[uat] graph report ok: nodes=${data.nodes.length} edges=${data.edges.length} flows=${data.flows.length} edge_anchors=${data.edge_anchors.length}`)
368
+ NODE
369
+
370
+ GRAPH_QUERY_TEXT="${RUN_DIR}/graph-query-address.txt"
371
+ log "calling Chain Insights CLI graph_query against real MCP"
372
+ (
373
+ cd "${WORKSPACE_ROOT}"
374
+ node "${CHAIN_INSIGHTS_CLI}" mcp call graph_query \
375
+ "network=${NETWORK}" \
376
+ "query=USE live_topology MATCH (n) WHERE n.address = '${UAT_ADDRESS}' RETURN n.labels AS labels, n.address AS address LIMIT 1"
377
+ ) >"${GRAPH_QUERY_TEXT}"
378
+
379
+ node - "${GRAPH_QUERY_TEXT}" "${UAT_ADDRESS}" <<'NODE'
380
+ const fs = require('node:fs')
381
+ const file = process.argv[2]
382
+ const address = process.argv[3]
383
+ const text = fs.readFileSync(file, 'utf8').trim()
384
+ const data = JSON.parse(text)
385
+ const first = data.facts?.query?.results?.[0] || data.results?.[0]
386
+ if (!first || first.address !== address) {
387
+ throw new Error(`graph_query did not return expected address ${address}`)
388
+ }
389
+ console.log(`[uat] graph_query ok: ${first.address}`)
390
+ NODE
391
+
392
+ SUMMARY="${RUN_DIR}/summary.txt"
393
+ cat >"${SUMMARY}" <<EOF
394
+ Chain Insights vs GraphRAG MCP UAT PASS
395
+
396
+ Endpoint: ${MCP_ENDPOINT}
397
+ Network: ${NETWORK}
398
+ Address: ${UAT_ADDRESS}
399
+ Graph report URL: ${GRAPH_REPORT_URL}
400
+
401
+ Raw outputs:
402
+ - ${DIRECT_TOOLS_JSON}
403
+ ${DIRECT_ADDRESS_RISK_SUMMARY}
404
+ - ${PROXY_TOOLS_JSON}
405
+ - ${PROXY_JSON}
406
+ - ${GRAPH_REPORT_JSON}
407
+ - ${GRAPH_QUERY_TEXT}
408
+
409
+ Workspace:
410
+ - ${WORKSPACE_ROOT}
411
+ EOF
412
+
413
+ log "PASS"
414
+ log "summary: ${SUMMARY}"