chain-insights 0.3.9 → 0.3.18

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 (137) hide show
  1. package/README.md +49 -52
  2. package/dist/{active-ByNgjuAg.mjs → active-BQopLul8.mjs} +6 -8
  3. package/dist/active-BQopLul8.mjs.map +1 -0
  4. package/dist/{active-BVr55kvW.cjs → active-XWv72R1X.cjs} +4 -12
  5. package/dist/{app-BxojXjtB.cjs → app-DBrqk_iP.cjs} +12 -28
  6. package/dist/{app-CRd39JJ8.mjs → app-DXwILI_a.mjs} +13 -28
  7. package/dist/app-DXwILI_a.mjs.map +1 -0
  8. package/dist/{artifact-server-CP6LXQ9d.mjs → artifact-server-CcmLBv1j.mjs} +2 -2
  9. package/dist/{artifact-server-CP6LXQ9d.mjs.map → artifact-server-CcmLBv1j.mjs.map} +1 -1
  10. package/dist/{artifact-server-XbN16DwU.cjs → artifact-server-v0WgTPFT.cjs} +1 -1
  11. package/dist/{capabilities-BCvkTkIu.mjs → capabilities-CM72SErE.mjs} +2 -2
  12. package/dist/{capabilities-BCvkTkIu.mjs.map → capabilities-CM72SErE.mjs.map} +1 -1
  13. package/dist/{capabilities-DOa6EFO-.cjs → capabilities-DGeF-oHc.cjs} +1 -1
  14. package/dist/cli.cjs +149 -405
  15. package/dist/cli.mjs +149 -405
  16. package/dist/cli.mjs.map +1 -1
  17. package/dist/{client-Y_zqKqJT.cjs → client-BY-56ojr.cjs} +0 -17
  18. package/dist/{client-BgmHjBHQ.mjs → client-ytTO0mcZ.mjs} +2 -13
  19. package/dist/{client-BgmHjBHQ.mjs.map → client-ytTO0mcZ.mjs.map} +1 -1
  20. package/dist/{config-Drgc2HuF.mjs → config-C6zM8Xir.mjs} +3 -3
  21. package/dist/{config-Drgc2HuF.mjs.map → config-C6zM8Xir.mjs.map} +1 -1
  22. package/dist/{config-BwVx19Og.cjs → config-CkW404Cs.cjs} +2 -2
  23. package/dist/{graph-reports-BDELxmpi.mjs → graph-reports-CEq-Mvx0.mjs} +2 -2
  24. package/dist/{graph-reports-BDELxmpi.mjs.map → graph-reports-CEq-Mvx0.mjs.map} +1 -1
  25. package/dist/{graph-reports-B3mkLP8Z.cjs → graph-reports-CkglRtg4.cjs} +1 -1
  26. package/dist/{html-generator-Bx3UcLTB.cjs → html-generator-BFKafL8y.cjs} +5 -6
  27. package/dist/{html-generator-AowOmzyi.mjs → html-generator-D4fX71hI.mjs} +6 -6
  28. package/dist/html-generator-D4fX71hI.mjs.map +1 -0
  29. package/dist/index.cjs +5 -5
  30. package/dist/index.d.cts +1 -2
  31. package/dist/index.d.cts.map +1 -1
  32. package/dist/index.d.mts +1 -2
  33. package/dist/index.d.mts.map +1 -1
  34. package/dist/index.mjs +5 -5
  35. package/dist/{init-CKQ6F07J.mjs → init-BGDvGreX.mjs} +52 -55
  36. package/dist/init-BGDvGreX.mjs.map +1 -0
  37. package/dist/{init-Dhw8F23z.cjs → init-Cuw9TznI.cjs} +51 -54
  38. package/dist/{mcp-endpoint-DHs1cRFH.mjs → mcp-endpoint-QQ5Lbqc2.mjs} +5 -2
  39. package/dist/mcp-endpoint-QQ5Lbqc2.mjs.map +1 -0
  40. package/dist/{mcp-endpoint-BaV8h_lq.cjs → mcp-endpoint-cQIZSjkK.cjs} +4 -1
  41. package/dist/mcp-proxy.cjs +650 -771
  42. package/dist/mcp-proxy.d.cts.map +1 -1
  43. package/dist/mcp-proxy.d.mts.map +1 -1
  44. package/dist/mcp-proxy.mjs +651 -772
  45. package/dist/mcp-proxy.mjs.map +1 -1
  46. package/dist/{output-root-BRhzhhXZ.mjs → output-root-BK4pdjyz.mjs} +6 -3
  47. package/dist/output-root-BK4pdjyz.mjs.map +1 -0
  48. package/dist/{output-root-YIbl6PwF.cjs → output-root-DI0tzA0X.cjs} +5 -2
  49. package/dist/{public-tools-BY3PTw6x.cjs → public-tools-BREojpU7.cjs} +1244 -426
  50. package/dist/{public-tools-CvlZcysd.mjs → public-tools-brHmHGYm.mjs} +1240 -428
  51. package/dist/public-tools-brHmHGYm.mjs.map +1 -0
  52. package/dist/{schema-BFEWhzg7.mjs → schema-D_qwaQA5.mjs} +2 -2
  53. package/dist/{schema-BFEWhzg7.mjs.map → schema-D_qwaQA5.mjs.map} +1 -1
  54. package/dist/{schema-Vl9yuOFO.cjs → schema-Dr6JXSOF.cjs} +1 -1
  55. package/dist/{server-BXLX2j_A.mjs → server-BK4bfOiv.mjs} +2 -2
  56. package/dist/{server-BXLX2j_A.mjs.map → server-BK4bfOiv.mjs.map} +1 -1
  57. package/dist/{server-BqVdWath.cjs → server-ColyTG1t.cjs} +1 -1
  58. package/dist/templates/graph.html +1 -1
  59. package/dist/{tool-visibility-Buq7YdUZ.cjs → tool-visibility--QPgrRE5.cjs} +5 -1
  60. package/dist/{tool-visibility-BpyZHRBi.mjs → tool-visibility-nr6XqO1F.mjs} +6 -2
  61. package/dist/tool-visibility-nr6XqO1F.mjs.map +1 -0
  62. package/dist/viz-BBvY-wXz.cjs +210 -0
  63. package/dist/viz-D8umSF-t.mjs +199 -0
  64. package/dist/viz-D8umSF-t.mjs.map +1 -0
  65. package/docs/architecture.md +4 -3
  66. package/docs/contributing.md +12 -6
  67. package/docs/graph-tools.md +93 -68
  68. package/docs/investigation-workspaces.md +38 -124
  69. package/docs/mcp-proxy.md +23 -34
  70. package/package.json +2 -2
  71. package/skills/chain-insights-address-risk/SKILL.md +92 -0
  72. package/skills/chain-insights-bittensor-cypher/SKILL.md +2 -22
  73. package/skills/chain-insights-developer-experience/SKILL.md +8 -28
  74. package/skills/chain-insights-exposure-analysis/SKILL.md +83 -0
  75. package/skills/chain-insights-investigation/SKILL.md +59 -211
  76. package/skills/chain-insights-investigation/agents/openai.yaml +1 -1
  77. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +37 -55
  78. package/skills/chain-insights-trace-funds/SKILL.md +14 -14
  79. package/skills/ci-status/SKILL.md +9 -15
  80. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +5 -4
  81. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +272 -18
  82. package/dist/active-ByNgjuAg.mjs.map +0 -1
  83. package/dist/app-CRd39JJ8.mjs.map +0 -1
  84. package/dist/canvas-Cn-maEIh.mjs +0 -203
  85. package/dist/canvas-Cn-maEIh.mjs.map +0 -1
  86. package/dist/canvas-p-oKCMjc.cjs +0 -251
  87. package/dist/cases-Bz_9XKEw.cjs +0 -19
  88. package/dist/cases-TVcAifxu.mjs +0 -16
  89. package/dist/cases-TVcAifxu.mjs.map +0 -1
  90. package/dist/data-extractor-B4nHw1wZ.mjs +0 -336
  91. package/dist/data-extractor-B4nHw1wZ.mjs.map +0 -1
  92. package/dist/data-extractor-DS4rzy3M.cjs +0 -353
  93. package/dist/dossier-BXy57V4-.cjs +0 -88
  94. package/dist/dossier-Bjpcbcxa.mjs +0 -78
  95. package/dist/dossier-Bjpcbcxa.mjs.map +0 -1
  96. package/dist/evidence-CvEesemA.cjs +0 -200
  97. package/dist/evidence-D96PTzOQ.mjs +0 -195
  98. package/dist/evidence-D96PTzOQ.mjs.map +0 -1
  99. package/dist/export-CBhcJuZ6.mjs +0 -394
  100. package/dist/export-CBhcJuZ6.mjs.map +0 -1
  101. package/dist/export-D4v4-6F4.cjs +0 -394
  102. package/dist/frontmatter-D0ccQnUM.mjs +0 -26
  103. package/dist/frontmatter-D0ccQnUM.mjs.map +0 -1
  104. package/dist/frontmatter-Dvqa5HX6.cjs +0 -35
  105. package/dist/html-generator-AowOmzyi.mjs.map +0 -1
  106. package/dist/init-CKQ6F07J.mjs.map +0 -1
  107. package/dist/mcp-endpoint-DHs1cRFH.mjs.map +0 -1
  108. package/dist/output-root-BRhzhhXZ.mjs.map +0 -1
  109. package/dist/parser-BXLAHYnZ.cjs +0 -182
  110. package/dist/parser-CJfMsOl6.mjs +0 -182
  111. package/dist/parser-CJfMsOl6.mjs.map +0 -1
  112. package/dist/public-tools-CvlZcysd.mjs.map +0 -1
  113. package/dist/resolver-2jXNtWQO.mjs +0 -184
  114. package/dist/resolver-2jXNtWQO.mjs.map +0 -1
  115. package/dist/resolver-CZdQwKvh.cjs +0 -186
  116. package/dist/runner-CVnjpqc-.mjs +0 -149
  117. package/dist/runner-CVnjpqc-.mjs.map +0 -1
  118. package/dist/runner-bLy0pTr_.cjs +0 -147
  119. package/dist/selector-BvXM9jbe.mjs +0 -12
  120. package/dist/selector-BvXM9jbe.mjs.map +0 -1
  121. package/dist/selector-Dps_ZFxq.cjs +0 -10
  122. package/dist/session-BT7VpbAd.cjs +0 -127
  123. package/dist/session-DROyhebe.mjs +0 -117
  124. package/dist/session-DROyhebe.mjs.map +0 -1
  125. package/dist/store-C2B_AssI.mjs +0 -231
  126. package/dist/store-C2B_AssI.mjs.map +0 -1
  127. package/dist/store-CQhU8dz8.cjs +0 -242
  128. package/dist/tool-visibility-BpyZHRBi.mjs.map +0 -1
  129. package/dist/vault-B2y78Ypu.cjs +0 -560
  130. package/dist/vault-z35Dohdq.mjs +0 -560
  131. package/dist/vault-z35Dohdq.mjs.map +0 -1
  132. package/dist/viz-D1620cBX.cjs +0 -44
  133. package/dist/viz-DB5XFG1z.mjs +0 -35
  134. package/dist/viz-DB5XFG1z.mjs.map +0 -1
  135. package/docs/knowledge-exports.md +0 -204
  136. package/docs/obsidian-vault.md +0 -130
  137. package/skills/ci-case/SKILL.md +0 -43
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-DakpK96I.cjs");
3
3
  const require_version = require("./version-CO9Or_YV.cjs");
4
- const require_client = require("./client-Y_zqKqJT.cjs");
5
- const require_tool_visibility = require("./tool-visibility-Buq7YdUZ.cjs");
4
+ const require_client = require("./client-BY-56ojr.cjs");
5
+ const require_tool_visibility = require("./tool-visibility--QPgrRE5.cjs");
6
6
  let node_url = require("node:url");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path, 1);
@@ -10,33 +10,21 @@ let node_fs = require("node:fs");
10
10
  let node_fs_promises = require("node:fs/promises");
11
11
  let zod = require("zod");
12
12
  zod = require_chunk.__toESM(zod, 1);
13
+ let node_crypto = require("node:crypto");
13
14
  let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
14
15
  let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
15
16
  let _modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
16
17
  let _modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
17
18
  let _modelcontextprotocol_ext_apps_server = require("@modelcontextprotocol/ext-apps/server");
18
19
  //#region src/mcp/proxy.ts
19
- const LOCAL_TOOL_NAMES = new Set([
20
- "balance",
21
- "help",
22
- "case_open",
23
- "case_list",
24
- "case_resume",
25
- "case_add_evidence",
26
- "case_verify_evidence",
27
- "case_export",
28
- "case_update_dossier",
29
- "case_start_session",
30
- "case_end_session"
31
- ]);
20
+ const LOCAL_TOOL_NAMES = new Set(["balance", "help"]);
32
21
  const PUBLIC_GRAPHRAG_PROMPT_NAMES = new Set(["address-risk", "trace-tools"]);
33
22
  const GRAPH_RESOURCE_URI = "ui://chain-insights/graph";
34
23
  const GRAPH_APP_TOOL_NAMES = new Set([
35
- "address_risk",
36
- "stake_insights",
37
- "trace_victim_funds",
38
- "trace_suspect_funds",
39
- "trace_deposit_sources"
24
+ "aml_address_risk",
25
+ "aml_trace_victim_funds",
26
+ "aml_trace_suspect_funds",
27
+ "aml_trace_deposit_sources"
40
28
  ]);
41
29
  const GRAPH_ARRAY_KEYS = [
42
30
  "nodes",
@@ -58,21 +46,33 @@ const COMMA_SEPARATED_ADDRESS_FIELDS = new Set([
58
46
  "deposit_addresses"
59
47
  ]);
60
48
  const KNOWN_PUBLIC_TOOL_REQUIRED_ARGS = {
61
- address_risk: ["address", "network"],
62
- stake_insights: ["network"],
63
- trace_victim_funds: ["victim_addresses", "network"],
64
- trace_suspect_funds: ["suspect_addresses", "network"],
65
- trace_deposit_sources: ["deposit_addresses", "network"],
49
+ aml_address_risk: ["address", "network"],
50
+ exposure_profile: ["network"],
51
+ exposure_quality: ["network"],
52
+ exposure_carry: ["network"],
53
+ exposure_crowding: ["network", "instrument"],
54
+ exposure_exit_pressure: ["network"],
55
+ exposure_correlation: ["network"],
56
+ exposure_explain: ["network"],
57
+ aml_trace_victim_funds: ["victim_addresses", "network"],
58
+ aml_trace_suspect_funds: ["suspect_addresses", "network"],
59
+ aml_trace_deposit_sources: ["deposit_addresses", "network"],
66
60
  graph_query: ["query", "network"],
67
61
  graph_query_batch: ["network", "queries"]
68
62
  };
69
63
  const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
70
64
  network_capabilities: "Return supported Chain Insights networks, capability layers, tool availability, data retention windows, and freshness. Use this before choosing network-specific tools.",
71
- 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.",
72
- stake_insights: "Explain Bittensor staking behavior around one full address, coldkey, or hotkey. Requires network plus exactly one of address, coldkey, or hotkey. Returns net staked/unstaked amounts, active coldkey-hotkey-netuid relationships, aggregate stake movement amounts, top counterparties, first/last activity, source backend, query evidence, and optional graph report metadata.",
73
- trace_victim_funds: "Trace victim/source funds forward through intermediaries to exchange deposit candidates. Use only when the input addresses are victims or trusted stolen-source addresses; do not use for suspected deposit addresses because traceback belongs to trace_deposit_sources. Exchange hot wallets are terminal only, never candidate deposits. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
74
- trace_suspect_funds: "Trace suspected scammer, mule, operator, or laundering-ring funds forward to cashout topology. Use when the input addresses are suspect-controlled seeds; incident_timestamp_ms is optional. Do not use for victim/source addresses or suspected deposit endpoints. Exchange hot wallets are terminal only, never candidate suspects or intermediates. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
75
- trace_deposit_sources: "Trace backward from suspected deposit/cashout addresses to upstream sources, shared funders, and convergence. Use only when the input addresses are suspected non-exchange deposit endpoints; do not treat these seeds as scammers and do not continue forward from discovered suspects here. Exchange hot wallets are excluded as seeds and upstream sources. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
65
+ aml_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 AML tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.",
66
+ exposure_profile: "Explain exposure around one account, owner, or counterparty. Supports Bittensor staking exposure and Hyperliquid trading exposure through one generic response shape with venues, instruments, position changes, carry/risk fields when available, public support events, and caveats.",
67
+ exposure_quality: "Score whether exposure behavior looks disciplined, fragile, lucky, or noisy across Bittensor staking, Hyperliquid trading, and future exposure venues. Returns deterministic components, sample-size warnings, evidence, and caveats; it is not trading advice.",
68
+ exposure_carry: "Explain carry earned or paid by exposure, including funding, fees, emissions, dividends, validator take, or equivalent venue-native carry when indexed. Returns carry breakdowns, evidence, and missing-data caveats.",
69
+ exposure_crowding: "Measure whether a market, subnet, hotkey, vault, or strategy is crowded. Returns side concentration, top exposure rows, confidence, and caveats from generic exposure rows.",
70
+ exposure_exit_pressure: "Explain what could force or incentivize exits, including liquidation pressure, slippage/unstake pressure, funding pain, or missing risk coverage. Accepts either an account-style subject or an instrument/market subject.",
71
+ exposure_correlation: "Compare exposure behavior across accounts to find possible copy, overlap, or strategy-cluster relationships. Correlation is not proof of shared control.",
72
+ exposure_explain: "Explain the lifecycle of a specific exposure, position, trade, stake, or rotation using public support events, position changes, carry, risk fields, and caveats.",
73
+ aml_trace_victim_funds: "Trace victim/source funds forward through intermediaries to exchange deposit candidates. Use only when the input addresses are victims or trusted stolen-source addresses; do not use for suspected deposit addresses because traceback belongs to aml_trace_deposit_sources. Exchange hot wallets are terminal only, never candidate deposits. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
74
+ aml_trace_suspect_funds: "Trace suspected scammer, mule, operator, or laundering-ring funds forward to cashout topology. Use when the input addresses are suspect-controlled seeds; incident_timestamp_ms is optional. Do not use for victim/source addresses or suspected deposit endpoints. Exchange hot wallets are terminal only, never candidate suspects or intermediates. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
75
+ aml_trace_deposit_sources: "Trace backward from suspected deposit/cashout addresses to upstream sources, shared funders, and convergence. Use only when the input addresses are suspected non-exchange deposit endpoints; do not treat these seeds as scammers and do not continue forward from discovered suspects here. Exchange hot wallets are excluded as seeds and upstream sources. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
76
76
  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.",
77
77
  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."
78
78
  };
@@ -83,14 +83,22 @@ const FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES = [
83
83
  ];
84
84
  const NETWORK_DESCRIPTION = "Required network to query. Do not guess; use network_capabilities or ask the user if missing.";
85
85
  const REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 900 * 1e3;
86
+ const EXPOSURE_TABLE_ROW_KEYS = [
87
+ "exposures",
88
+ "venues",
89
+ "top_exposures",
90
+ "pressure_bands",
91
+ "relationships",
92
+ "evidence",
93
+ "sides"
94
+ ];
86
95
  const CHAIN_INSIGHTS_WORKFLOW = [
87
96
  "Workflow:",
88
- "1. Chain Insights workspaces are Obsidian-compatible vaults. If the user is starting or continuing an investigation, use case_open or case_list/case_resume first.",
97
+ "1. Chain Insights workspaces are append-only local working directories. Bootstrap with cia init before workflows that persist artifacts.",
89
98
  "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.",
90
- "3. Use address_risk for single-address enrichment. Use trace_victim_funds for victim/source forward tracing, trace_deposit_sources for reverse traceback from suspected deposit endpoints, and trace_suspect_funds for suspect-controlled outbound laundering/cashout topology. Use stake_insights for Bittensor staking behavior. Use graph_query(_batch) only when the high-level trace tools do not answer the exact question.",
91
- "4. After a material result, preserve it with case_add_evidence when a case is active or ask whether to create/select a case.",
92
- "5. Use case_update_dossier for durable address/entity findings and case_start_session/case_end_session for session notes.",
93
- "6. For local review, use live vault notes refreshed with cia case vault refresh. When a case reaches a sharing or archive checkpoint, use case_verify_evidence and case_export to produce Obsidian, LLM Wiki, Codex, Claude Code, and ChatGPT-ready handoff bundles."
99
+ "3. Use aml_address_risk for single-address enrichment. Use exposure_profile, exposure_quality, exposure_carry, exposure_crowding, exposure_exit_pressure, exposure_correlation, and exposure_explain for exposure analysis. Use aml_trace_victim_funds for victim/source forward tracing, aml_trace_deposit_sources for reverse traceback from suspected deposit endpoints, and aml_trace_suspect_funds for suspect-controlled outbound laundering/cashout topology. Use graph_query(_batch) only when the high-level tools do not answer the exact question.",
100
+ "4. Persisted outputs belong in the initialized workspace under reports/, reports/graphs/, reports/tables/, artifacts/, entities/, sessions/, and published/.",
101
+ "5. For local review, inspect the generated Markdown and graph/table artifacts directly in the workspace."
94
102
  ].join("\n");
95
103
  const GRAPH_SCHEMA_HINTS = [
96
104
  "Graph query hints for network=bittensor:",
@@ -117,16 +125,16 @@ const GRAPH_REPORT_HINTS = [
117
125
  "- 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."
118
126
  ].join("\n");
119
127
  const SERVER_INSTRUCTIONS = [
120
- "Chain Insights is a local AML investigation workspace for AI agents.",
128
+ "Chain Insights is a local graph-analysis workspace for AI agents.",
121
129
  CHAIN_INSIGHTS_WORKFLOW,
122
130
  GRAPH_REPORT_HINTS,
123
131
  GRAPH_SCHEMA_HINTS,
124
- "Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses; use case tools to preserve evidence when a case exists."
132
+ "Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses."
125
133
  ].join("\n\n");
126
134
  const STATELESS_SERVER_INSTRUCTIONS = [
127
135
  "Chain Insights is running as a stateless AML proxy for a host application.",
128
- "Do not use local case, evidence, dossier, session, wallet, or graph report workflows in this mode.",
129
- "Use network_capabilities first when network support is unknown, then call address_risk, stake_insights, trace_victim_funds, trace_suspect_funds, trace_deposit_sources, graph_query, or graph_query_batch as needed.",
136
+ "Do not use local workspace persistence, wallet, or graph report workflows in this mode.",
137
+ "Use network_capabilities first when network support is unknown, then call aml_address_risk, exposure_profile, exposure_quality, exposure_carry, exposure_crowding, exposure_exit_pressure, exposure_correlation, exposure_explain, aml_trace_victim_funds, aml_trace_suspect_funds, aml_trace_deposit_sources, graph_query, or graph_query_batch as needed.",
130
138
  GRAPH_SCHEMA_HINTS,
131
139
  "Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses."
132
140
  ].join("\n\n");
@@ -166,45 +174,105 @@ function graphToolMeta(tool) {
166
174
  }
167
175
  function knownPublicToolInputSchema(toolName) {
168
176
  switch (toolName) {
169
- case "address_risk": return {
177
+ case "aml_address_risk": return {
170
178
  address: zod.string().min(1).describe("Full blockchain address to screen"),
171
179
  network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
172
180
  compare_address: zod.string().optional().describe("Optional second full address for comparison"),
173
181
  include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
174
182
  };
175
- case "trace_victim_funds": return {
183
+ case "aml_trace_victim_funds": return {
176
184
  victim_addresses: zod.string().min(1).describe("Comma-separated full victim/source addresses. Min 1, max 5."),
177
185
  network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
178
186
  known_suspect_addresses: zod.string().optional().describe("Optional known suspect addresses for context only. They are not reverse-traced by this tool. Max 5."),
179
187
  incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds."),
180
188
  include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
181
189
  };
182
- case "trace_suspect_funds": return {
190
+ case "aml_trace_suspect_funds": return {
183
191
  network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
184
192
  suspect_addresses: zod.string().min(1).describe("Comma-separated full suspected scammer, mule, operator, or laundering-ring addresses. Min 1, max 5."),
185
193
  incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds. This tool also works without a timestamp."),
186
- max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3."),
187
- case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.")
194
+ max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3.")
188
195
  };
189
- case "trace_deposit_sources": return {
196
+ case "aml_trace_deposit_sources": return {
190
197
  network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
191
198
  deposit_addresses: zod.string().min(1).describe("Comma-separated full suspected deposit/cashout addresses. Min 1, max 5."),
192
199
  max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum reverse traceback hops. Default 2."),
193
- case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
194
200
  include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
195
201
  };
196
- case "stake_insights": return {
202
+ case "exposure_profile": return {
197
203
  network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
198
- address: zod.string().optional().describe("Full Bittensor address to inspect as either coldkey or hotkey. Provide exactly one of address, coldkey, or hotkey."),
199
- coldkey: zod.string().optional().describe("Full Bittensor coldkey address to inspect. Provide exactly one of address, coldkey, or hotkey."),
200
- hotkey: zod.string().optional().describe("Full Bittensor hotkey address to inspect. Provide exactly one of address, coldkey, or hotkey."),
201
- netuid: zod.number().int().min(0).optional().describe("Optional subnet netuid filter."),
204
+ account: zod.string().optional().describe("Full account address to inspect. Provide exactly one of account, owner, or counterparty."),
205
+ owner: zod.string().optional().describe("Full owner address to inspect. Provide exactly one of account, owner, or counterparty."),
206
+ counterparty: zod.string().optional().describe("Full counterparty address to inspect. Provide exactly one of account, owner, or counterparty."),
207
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
208
+ instrument: zod.string().optional().describe("Optional instrument display or durable identifier filter, such as Subnet 19 or BTC-PERP."),
209
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
202
210
  start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
203
211
  end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
204
- start_block: zod.number().int().min(0).optional().describe("Optional start block. Current stake graph parity may require timestamp windows instead."),
205
- end_block: zod.number().int().min(0).optional().describe("Optional end block. Current stake graph parity may require timestamp windows instead."),
206
- depth: zod.number().int().min(1).max(3).optional().describe("Optional expansion depth limit. First release returns direct STAKES_IN relationships; default 1, max 3."),
207
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
212
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
213
+ };
214
+ case "exposure_quality":
215
+ case "exposure_carry": return {
216
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
217
+ account: zod.string().optional().describe("Full account address to inspect. Provide exactly one of account, owner, or counterparty."),
218
+ owner: zod.string().optional().describe("Full owner address to inspect. Provide exactly one of account, owner, or counterparty."),
219
+ counterparty: zod.string().optional().describe("Full counterparty address to inspect. Provide exactly one of account, owner, or counterparty."),
220
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
221
+ instrument: zod.string().optional().describe("Optional instrument display or durable identifier filter, such as Subnet 19 or BTC-PERP."),
222
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
223
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
224
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
225
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
226
+ };
227
+ case "exposure_crowding": return {
228
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
229
+ instrument: zod.string().min(1).describe("Instrument, market, subnet, hotkey, vault, or durable exposure target identifier to inspect."),
230
+ market: zod.string().optional().describe("Alias for instrument when using market language."),
231
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
232
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
233
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
234
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
235
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
236
+ };
237
+ case "exposure_exit_pressure": return {
238
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
239
+ account: zod.string().optional().describe("Optional account address to inspect. Provide an account-style subject or an instrument/market."),
240
+ owner: zod.string().optional().describe("Optional owner address to inspect. Provide an account-style subject or an instrument/market."),
241
+ counterparty: zod.string().optional().describe("Optional counterparty address to inspect. Provide an account-style subject or an instrument/market."),
242
+ instrument: zod.string().optional().describe("Instrument, market, subnet, hotkey, vault, or durable exposure target identifier to inspect."),
243
+ market: zod.string().optional().describe("Alias for instrument when using market language."),
244
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
245
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
246
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
247
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
248
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
249
+ };
250
+ case "exposure_correlation": return {
251
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
252
+ account: zod.string().optional().describe("Full account address to inspect. Provide exactly one of account, owner, or counterparty."),
253
+ owner: zod.string().optional().describe("Full owner address to inspect. Provide exactly one of account, owner, or counterparty."),
254
+ counterparty: zod.string().optional().describe("Full counterparty address to inspect. Provide exactly one of account, owner, or counterparty."),
255
+ candidate_accounts: zod.union([zod.string(), zod.array(zod.string())]).optional().describe("Optional comma-separated or array candidate accounts to compare against."),
256
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
257
+ instrument: zod.string().optional().describe("Optional instrument display or durable identifier filter."),
258
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
259
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
260
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
261
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
262
+ };
263
+ case "exposure_explain": return {
264
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
265
+ account: zod.string().optional().describe("Full account address to inspect. Provide exactly one of account, owner, or counterparty."),
266
+ owner: zod.string().optional().describe("Full owner address to inspect. Provide exactly one of account, owner, or counterparty."),
267
+ counterparty: zod.string().optional().describe("Full counterparty address to inspect. Provide exactly one of account, owner, or counterparty."),
268
+ instrument: zod.string().optional().describe("Optional instrument display or durable identifier filter."),
269
+ market: zod.string().optional().describe("Alias for instrument when using market language."),
270
+ position_id: zod.string().optional().describe("Optional venue-native position, trade, stake, rotation, or lifecycle identifier when available."),
271
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
272
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
273
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
274
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
275
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 25, max 500.")
208
276
  };
209
277
  case "graph_query": return {
210
278
  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."),
@@ -442,7 +510,7 @@ function registerLocalPrompts(server, remotePromptNames) {
442
510
  network: zod.string().describe(NETWORK_DESCRIPTION)
443
511
  }
444
512
  }, async ({ address, network }) => promptResult([
445
- `Use Chain Insights address_risk on ${network} for:`,
513
+ `Use Chain Insights aml_address_risk on ${network} for:`,
446
514
  "",
447
515
  `\`${address}\``,
448
516
  "",
@@ -450,7 +518,7 @@ function registerLocalPrompts(server, remotePromptNames) {
450
518
  ].join("\n"), "Address risk screening"));
451
519
  if (!remotePromptNames.has("trace-tools")) server.registerPrompt("trace-tools", {
452
520
  title: "Trace Tools",
453
- description: "Choose trace_victim_funds, trace_deposit_sources, or trace_suspect_funds based on the evidence role.",
521
+ description: "Choose aml_trace_victim_funds, aml_trace_deposit_sources, or aml_trace_suspect_funds based on the evidence role.",
454
522
  argsSchema: {
455
523
  addresses: zod.string().describe("Input addresses, comma-separated full addresses"),
456
524
  role: zod.enum([
@@ -462,12 +530,12 @@ function registerLocalPrompts(server, remotePromptNames) {
462
530
  }
463
531
  }, async ({ addresses, role, network }) => {
464
532
  return promptResult([
465
- `Use Chain Insights ${role === "deposit" ? "trace_deposit_sources" : `trace_${role}_funds`} on ${network}.`,
533
+ `Use Chain Insights ${role === "deposit" ? "aml_trace_deposit_sources" : `aml_trace_${role}_funds`} on ${network}.`,
466
534
  "",
467
535
  "Full addresses:",
468
536
  addresses,
469
537
  "",
470
- role === "deposit" ? "For deposit role, use trace_deposit_sources rather than trace_deposit_funds." : "Present the summary as-is and use continuation.recommended_next_tools for follow-up."
538
+ role === "deposit" ? "For deposit role, use aml_trace_deposit_sources rather than aml_trace_deposit_funds." : "Present the summary as-is and use continuation.recommended_next_tools for follow-up."
471
539
  ].join("\n"), "Trace role-specific funds");
472
540
  });
473
541
  server.registerPrompt("graph-query", {
@@ -511,49 +579,42 @@ function registerLocalPrompts(server, remotePromptNames) {
511
579
  }, async () => promptResult("Use Chain Insights balance. Show the wallet address, network, token, and balance exactly as returned.", "Wallet balance"));
512
580
  server.registerPrompt("help", {
513
581
  title: "Chain Insights Help",
514
- description: "Show available Chain Insights tools and investigation case workflow.",
582
+ description: "Show available Chain Insights tools and workspace workflow.",
515
583
  argsSchema: {}
516
- }, async () => promptResult("Use Chain Insights help. Summarize the available tools and investigation case workflow without inventing capabilities.", "Chain Insights help"));
517
- server.registerPrompt("open-investigation-case", {
518
- title: "Open Investigation Case",
519
- description: "Create a local Chain Insights case for an investigation.",
520
- argsSchema: {
521
- name: zod.string().describe("Case name"),
522
- tags: zod.string().optional().describe("Comma-separated tags"),
523
- description: zod.string().optional().describe("Brief investigation description")
524
- }
525
- }, async ({ name, tags, description }) => promptResult([
526
- "Use Chain Insights case_open to create a local investigation case.",
527
- "",
528
- `name: \`${name}\``,
529
- tags ? `tags: \`${tags}\`` : "",
530
- description ? `description: ${description}` : ""
531
- ].filter(Boolean).join("\n"), "Open investigation case"));
532
- server.registerPrompt("resume-investigation-case", {
533
- title: "Resume Investigation Case",
534
- description: "Load local Chain Insights case context, evidence count, dossiers, and latest session.",
535
- argsSchema: { case_id: zod.string().describe("Chain Insights case ID") }
536
- }, async ({ case_id }) => promptResult(`Use Chain Insights case_resume for case_id: \`${case_id}\`. Continue from the returned context.`, "Resume investigation case"));
537
- server.registerPrompt("save-investigation-evidence", {
538
- title: "Save Investigation Evidence",
539
- description: "Append a tool result or analyst note to a local Chain Insights case evidence manifest.",
540
- argsSchema: {
541
- case_id: zod.string().describe("Chain Insights case ID"),
542
- source: zod.string().describe("Tool or source name")
543
- }
544
- }, async ({ case_id, source }) => promptResult([
545
- "Use Chain Insights case_add_evidence after the next relevant tool result.",
546
- "",
547
- `case_id: \`${case_id}\``,
548
- `source: \`${source}\``,
549
- "content: use the exact report or note that should become evidence."
550
- ].join("\n"), "Save investigation evidence"));
584
+ }, async () => promptResult("Use Chain Insights help. Summarize the available tools and workspace workflow without inventing capabilities.", "Chain Insights help"));
551
585
  }
552
586
  function hasGraphArrayFields(value) {
553
587
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
554
588
  const record = value;
555
589
  return GRAPH_ARRAY_KEYS.some((key) => Array.isArray(record[key]));
556
590
  }
591
+ function sanitizeSlug(value) {
592
+ return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "exposure";
593
+ }
594
+ function exposureArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
595
+ return date.toISOString().replace(/[-:.]/g, "").replace(/\.[0-9]{3}Z$/, "Z");
596
+ }
597
+ function csvEscape(value) {
598
+ if (value === void 0 || value === null) return "\"\"";
599
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return JSON.stringify(String(value));
600
+ return JSON.stringify(value);
601
+ }
602
+ function tableRowsFromExposureContent(structuredContent) {
603
+ for (const key of EXPOSURE_TABLE_ROW_KEYS) {
604
+ const value = structuredContent[key];
605
+ if (!Array.isArray(value) || value.length === 0) continue;
606
+ if (!value.every((row) => isRecord(row))) continue;
607
+ return value;
608
+ }
609
+ }
610
+ function exposureRowsToCsv(rows) {
611
+ const headers = /* @__PURE__ */ new Set();
612
+ for (const row of rows) for (const key of Object.keys(row)) headers.add(key);
613
+ const headerList = [...headers];
614
+ const lines = [headerList.map(csvEscape).join(",")];
615
+ for (const row of rows) lines.push(headerList.map((header) => csvEscape(row[header])).join(","));
616
+ return lines.join("\n") + "\n";
617
+ }
557
618
  function sanitizeStructuredContentForGraphPayload(structuredContent) {
558
619
  if (!structuredContent) return void 0;
559
620
  return sanitizeStructuredValue(structuredContent);
@@ -587,8 +648,8 @@ async function normalizeRemoteToolResult(result, config, toolName = "remote-grap
587
648
  const graphPayload = getRemoteGraphPayload(result);
588
649
  const meta = { ...result._meta ?? {} };
589
650
  if (graphPayload && includeAttachments) {
590
- const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-B3mkLP8Z.cjs"));
591
- const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-XbN16DwU.cjs"));
651
+ const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-CkglRtg4.cjs"));
652
+ const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-v0WgTPFT.cjs"));
592
653
  const report = await writeGraphReport(graphPayload, {
593
654
  serverPort: config.serverPort,
594
655
  slug: toolName || "remote-graph"
@@ -614,8 +675,8 @@ function shouldIncludeAttachments(args, workspaceArtifactsEnabled) {
614
675
  }
615
676
  async function writeLocalGraphMeta(graphData, config, slug, includeAttachments) {
616
677
  if (!includeAttachments) return void 0;
617
- const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-B3mkLP8Z.cjs"));
618
- const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-XbN16DwU.cjs"));
678
+ const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-CkglRtg4.cjs"));
679
+ const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-v0WgTPFT.cjs"));
619
680
  const report = await writeGraphReport(graphData, {
620
681
  serverPort: config.serverPort,
621
682
  slug
@@ -629,6 +690,51 @@ async function writeLocalGraphMeta(graphData, config, slug, includeAttachments)
629
690
  function graphMetaResult(graph) {
630
691
  return graph ? { chainInsights: { graph } } : void 0;
631
692
  }
693
+ async function writeExposureArtifacts(result, toolName, subject, network, includeAttachments) {
694
+ if (!includeAttachments) return void 0;
695
+ const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-DI0tzA0X.cjs")).then((n) => n.output_root_exports);
696
+ const paths = workspaceOutputPaths();
697
+ const now = /* @__PURE__ */ new Date();
698
+ const slug = `${exposureArtifactTimestamp(now)}-${sanitizeSlug(toolName)}-${sanitizeSlug(subject)}-${(0, node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12)}`;
699
+ await Promise.all([(0, node_fs_promises.mkdir)(paths.reportsRoot, {
700
+ recursive: true,
701
+ mode: 448
702
+ }), (0, node_fs_promises.mkdir)(paths.reportTablesRoot, {
703
+ recursive: true,
704
+ mode: 448
705
+ })]);
706
+ const reportPath = node_path.default.join(paths.reportsRoot, `${slug}.exposure-report.md`);
707
+ const compactFactsPath = node_path.default.join(paths.reportTablesRoot, `${slug}.compact-facts.json`);
708
+ const compactFacts = {
709
+ schema: result.structuredContent.schema,
710
+ tool: result.structuredContent.tool,
711
+ network,
712
+ subject,
713
+ generated_at: now.toISOString(),
714
+ summary_text: result.summaryText,
715
+ facts: result.structuredContent
716
+ };
717
+ const reportBody = [
718
+ `# ${toolName} Report`,
719
+ "",
720
+ `Network: ${network}`,
721
+ `Generated: ${now.toISOString()}`,
722
+ "",
723
+ result.summaryText,
724
+ "",
725
+ "## Artifacts",
726
+ `- Report: ${reportPath}`,
727
+ `- Compact facts: ${compactFactsPath}`
728
+ ];
729
+ const tableRows = tableRowsFromExposureContent(result.structuredContent);
730
+ if (tableRows) {
731
+ const tablePath = node_path.default.join(paths.reportTablesRoot, `${slug}.table.csv`);
732
+ reportBody.push(`- Table: ${tablePath}`);
733
+ await (0, node_fs_promises.writeFile)(tablePath, exposureRowsToCsv(tableRows), { mode: 384 });
734
+ }
735
+ await (0, node_fs_promises.writeFile)(reportPath, reportBody.join("\n") + "\n", { mode: 384 });
736
+ await (0, node_fs_promises.writeFile)(compactFactsPath, JSON.stringify(compactFacts, null, 2) + "\n", { mode: 384 });
737
+ }
632
738
  /**
633
739
  * Core proxy logic — exported so tests can inject dependencies directly.
634
740
  * The IIFE at the bottom calls this with real dependencies.
@@ -637,9 +743,9 @@ function graphMetaResult(graph) {
637
743
  * All diagnostic output goes to console.error() or process.stderr.write().
638
744
  */
639
745
  async function createProxy() {
640
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
641
- const { activeDataDir, findActiveWorkspace } = await Promise.resolve().then(() => require("./active-BVr55kvW.cjs")).then((n) => n.active_exports);
642
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Y_zqKqJT.cjs")).then((n) => n.client_exports);
746
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
747
+ const { activeDataDir, findActiveWorkspace } = await Promise.resolve().then(() => require("./active-XWv72R1X.cjs")).then((n) => n.active_exports);
748
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-BY-56ojr.cjs")).then((n) => n.client_exports);
643
749
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-CJk1EL3L.cjs"));
644
750
  const proxyMode = resolveMcpProxyMode();
645
751
  const workspaceArtifactsEnabled = proxyMode === "workspace";
@@ -746,18 +852,6 @@ async function createProxy() {
746
852
  const remotePromptNames = new Set(remotePrompts.map((prompt) => prompt.name));
747
853
  for (const prompt of remotePrompts) registerRemotePrompt(server, remoteClient, prompt);
748
854
  registerLocalPrompts(server, remotePromptNames);
749
- const caseToolError = (label, err) => ({
750
- content: [{
751
- type: "text",
752
- text: `${label} failed: ${err.message}`
753
- }],
754
- isError: true
755
- });
756
- const parseTags = (tags) => {
757
- if (Array.isArray(tags)) return tags.map((tag) => tag.trim()).filter(Boolean);
758
- if (typeof tags === "string") return tags.split(",").map((tag) => tag.trim()).filter(Boolean);
759
- return [];
760
- };
761
855
  if (workspaceArtifactsEnabled) {
762
856
  server.registerTool("balance", {
763
857
  description: "Show the local Chain Insights payment wallet address and Base USDC balance.",
@@ -797,712 +891,279 @@ async function createProxy() {
797
891
  connectDomains: graphArtifactOrigins(config)
798
892
  } } }
799
893
  }] }));
800
- server.registerTool("case_open", {
801
- description: "Create a local Chain Insights investigation case. Use this before saving evidence, dossiers, or session notes for a new investigation.",
894
+ if (!remoteToolNames.has("aml_address_risk")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "aml_address_risk", {
895
+ title: "Address Risk",
896
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_address_risk,
802
897
  inputSchema: {
803
- name: zod.string().min(1).describe("Case name"),
804
- tags: zod.union([zod.string(), zod.array(zod.string())]).optional().describe("Comma-separated tags or string array"),
805
- description: zod.string().optional().describe("Brief investigation description")
898
+ address: zod.string().min(1).describe("Full blockchain address to screen"),
899
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
900
+ compare_address: zod.string().optional().describe("Optional second full address for comparison"),
901
+ include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
806
902
  },
903
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
807
904
  annotations: {
808
- readOnlyHint: false,
905
+ readOnlyHint: true,
809
906
  destructiveHint: false,
810
- idempotentHint: false,
811
- openWorldHint: false
907
+ idempotentHint: true,
908
+ openWorldHint: true
812
909
  }
813
- }, async ({ name, tags, description }) => {
910
+ }, async ({ address, network, compare_address, include_attachments }) => {
814
911
  try {
815
- const { CaseStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
816
- const created = await CaseStore.create({
817
- name,
818
- tags: parseTags(tags),
819
- description: description ?? ""
912
+ if (!remoteConnected) return {
913
+ content: [{
914
+ type: "text",
915
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
916
+ }],
917
+ isError: true
918
+ };
919
+ const { addressRisk } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
920
+ const result = await addressRisk(remoteClient, {
921
+ address,
922
+ network,
923
+ compareAddress: compare_address,
924
+ writeArtifacts: workspaceArtifactsEnabled
820
925
  });
821
- const { casesRoot } = await Promise.resolve().then(() => require("./store-CQhU8dz8.cjs")).then((n) => n.store_exports);
926
+ const graph = await writeLocalGraphMeta(result.graphData, config, `address-risk-${network}-${address}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
822
927
  return {
823
928
  content: [{
824
929
  type: "text",
825
- text: JSON.stringify({
826
- case_id: created.id,
827
- name: created.name,
828
- status: created.status,
829
- tags: created.tags,
830
- directory: `${node_path.default.join(casesRoot(), created.id)}/`
831
- }, null, 2)
930
+ text: result.summaryText
832
931
  }],
932
+ structuredContent: result.structuredContent,
933
+ _meta: graphMetaResult(graph),
833
934
  isError: false
834
935
  };
835
936
  } catch (err) {
836
- return caseToolError("Case open", err);
837
- }
838
- });
839
- server.registerTool("case_list", {
840
- description: "List local Chain Insights investigation cases. Use before resuming when the user does not provide a case ID.",
841
- inputSchema: { status: zod.enum([
842
- "open",
843
- "active",
844
- "suspended",
845
- "closed"
846
- ]).optional().describe("Optional status filter") },
847
- annotations: {
848
- readOnlyHint: true,
849
- destructiveHint: false,
850
- idempotentHint: true,
851
- openWorldHint: false
852
- }
853
- }, async ({ status }) => {
854
- try {
855
- const { CaseStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
856
- const cases = await CaseStore.list();
857
- const filtered = status ? cases.filter((entry) => entry.status === status) : cases;
858
- return {
937
+ if (err instanceof require_client.PaymentRequiredError) return {
859
938
  content: [{
860
939
  type: "text",
861
- text: JSON.stringify({ cases: filtered }, null, 2)
940
+ text: err.message
862
941
  }],
863
- isError: false
942
+ isError: true
864
943
  };
865
- } catch (err) {
866
- return caseToolError("Case list", err);
867
- }
868
- });
869
- server.registerTool("case_resume", {
870
- description: "Load local Chain Insights case context: metadata, evidence count, dossier summaries, and latest session notes.",
871
- inputSchema: { case_id: zod.string().min(1).describe("Chain Insights case ID") },
872
- annotations: {
873
- readOnlyHint: true,
874
- destructiveHint: false,
875
- idempotentHint: true,
876
- openWorldHint: false
877
- }
878
- }, async ({ case_id }) => {
879
- try {
880
- const { CaseStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
881
- const context = await CaseStore.loadContext(case_id);
882
944
  return {
883
945
  content: [{
884
946
  type: "text",
885
- text: JSON.stringify(context, null, 2)
947
+ text: `Address risk failed: ${err.message}`
886
948
  }],
887
- isError: false
949
+ isError: true
888
950
  };
889
- } catch (err) {
890
- return caseToolError("Case resume", err);
891
951
  }
892
952
  });
893
- server.registerTool("case_add_evidence", {
894
- description: "Append a tool result or analyst note to a local case evidence manifest. Use after address_risk, trace_victim_funds, trace_suspect_funds, trace_deposit_sources, graph_query, or manual findings that should be preserved.",
953
+ if (!remoteToolNames.has("aml_trace_victim_funds")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "aml_trace_victim_funds", {
954
+ title: "Trace Victim Funds",
955
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_trace_victim_funds,
895
956
  inputSchema: {
896
- case_id: zod.string().min(1).describe("Chain Insights case ID"),
897
- source: zod.string().min(1).describe("Source tool or evidence origin"),
898
- content: zod.string().min(1).describe("Evidence markdown/text to store"),
899
- query_params: zod.string().optional().describe("Original query parameters, for example \"network=bittensor address=...\"")
957
+ victim_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full victim/source addresses, or an array. Min 1, max 5."),
958
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
959
+ known_suspect_addresses: zod.union([zod.string(), zod.array(zod.string())]).optional().describe("Known suspect addresses for context only. This tool does not reverse-trace them. Max 5."),
960
+ include_attachments: zod.boolean().optional().describe("Include graph app report metadata"),
961
+ incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds."),
962
+ max_hops: zod.number().int().min(1).max(5).optional(),
963
+ per_address_limit: zod.number().int().min(1).max(10).optional(),
964
+ min_amount_sum: zod.number().min(0).optional()
900
965
  },
966
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
901
967
  annotations: {
902
968
  readOnlyHint: false,
903
969
  destructiveHint: false,
904
970
  idempotentHint: false,
905
- openWorldHint: false
971
+ openWorldHint: true
906
972
  }
907
- }, async ({ case_id, source, content, query_params }) => {
973
+ }, async ({ victim_addresses, known_suspect_addresses, network, incident_timestamp_ms, max_hops, per_address_limit, min_amount_sum, include_attachments }) => {
908
974
  try {
909
- const { EvidenceStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
910
- const saved = await EvidenceStore.append(case_id, {
911
- source,
912
- content,
913
- queryParams: query_params ?? ""
975
+ if (!remoteConnected) return {
976
+ content: [{
977
+ type: "text",
978
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
979
+ }],
980
+ isError: true
981
+ };
982
+ const { traceVictimFunds } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
983
+ const result = await traceVictimFunds(remoteClient, config, {
984
+ victimAddresses: victim_addresses,
985
+ knownSuspectAddresses: known_suspect_addresses,
986
+ network,
987
+ incidentTimestampMs: incident_timestamp_ms,
988
+ maxHops: max_hops,
989
+ perAddressLimit: per_address_limit,
990
+ minAmountSum: min_amount_sum,
991
+ writeArtifacts: workspaceArtifactsEnabled
914
992
  });
993
+ const graph = await writeLocalGraphMeta(result.graphData, config, `trace-victim-funds-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
915
994
  return {
916
995
  content: [{
917
996
  type: "text",
918
- text: JSON.stringify(saved, null, 2)
997
+ text: result.summaryText
919
998
  }],
999
+ structuredContent: result.structuredContent,
1000
+ _meta: graphMetaResult(graph),
920
1001
  isError: false
921
1002
  };
922
1003
  } catch (err) {
923
- return caseToolError("Evidence append", err);
924
- }
925
- });
926
- server.registerTool("case_verify_evidence", {
927
- description: "Verify a local case evidence manifest and report tampered or missing evidence files.",
928
- inputSchema: { case_id: zod.string().min(1).describe("Chain Insights case ID") },
929
- annotations: {
930
- readOnlyHint: true,
931
- destructiveHint: false,
932
- idempotentHint: true,
933
- openWorldHint: false
934
- }
935
- }, async ({ case_id }) => {
936
- try {
937
- const { EvidenceStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
938
- const result = await EvidenceStore.verifyManifest(case_id);
1004
+ if (err instanceof require_client.PaymentRequiredError) return {
1005
+ content: [{
1006
+ type: "text",
1007
+ text: err.message
1008
+ }],
1009
+ isError: true
1010
+ };
939
1011
  return {
940
1012
  content: [{
941
1013
  type: "text",
942
- text: JSON.stringify(result, null, 2)
1014
+ text: `Trace victim funds failed: ${err.message}`
943
1015
  }],
944
- isError: false
1016
+ isError: true
945
1017
  };
946
- } catch (err) {
947
- return caseToolError("Evidence verify", err);
948
1018
  }
949
1019
  });
950
- server.registerTool("case_export", {
951
- description: "Export a Chain Insights case to an Obsidian, LLM Wiki, Codex, Claude Code, and ChatGPT-friendly handoff bundle.",
1020
+ if (!remoteToolNames.has("aml_trace_suspect_funds")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "aml_trace_suspect_funds", {
1021
+ title: "Trace Suspect Funds",
1022
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_trace_suspect_funds,
952
1023
  inputSchema: {
953
- case_id: zod.string().min(1).describe("Chain Insights case ID to export"),
954
- target: zod.enum(["obsidian-llmwiki"]).optional().describe("Export target. Default obsidian-llmwiki."),
955
- mode: zod.enum([
956
- "private",
957
- "partner",
958
- "public"
959
- ]).optional().describe("Redaction mode. Default private."),
960
- output_dir: zod.string().optional().describe("Optional output directory. Defaults to published/<case-slug>.")
1024
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1025
+ suspect_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full suspect-controlled addresses, or an array. Min 1, max 5."),
1026
+ incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds. This tool works without it."),
1027
+ max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3."),
1028
+ per_address_limit: zod.number().int().min(1).max(10).optional(),
1029
+ min_amount_sum: zod.number().min(0).optional(),
1030
+ include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
961
1031
  },
1032
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
962
1033
  annotations: {
963
1034
  readOnlyHint: false,
964
1035
  destructiveHint: false,
965
1036
  idempotentHint: false,
966
- openWorldHint: false
1037
+ openWorldHint: true
967
1038
  }
968
- }, async ({ case_id, target, mode, output_dir }) => {
1039
+ }, async ({ suspect_addresses, incident_timestamp_ms, network, max_hops, per_address_limit, min_amount_sum, include_attachments }) => {
969
1040
  try {
970
- const { exportCase } = await Promise.resolve().then(() => require("./export-D4v4-6F4.cjs"));
971
- const result = await exportCase({
972
- caseId: case_id,
973
- target: target ?? "obsidian-llmwiki",
974
- mode: mode ?? "private",
975
- outputDir: output_dir
1041
+ if (!remoteConnected) return {
1042
+ content: [{
1043
+ type: "text",
1044
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1045
+ }],
1046
+ isError: true
1047
+ };
1048
+ const { traceSuspectFunds } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
1049
+ const result = await traceSuspectFunds(remoteClient, config, {
1050
+ suspectAddresses: suspect_addresses,
1051
+ network,
1052
+ maxHops: max_hops,
1053
+ perAddressLimit: per_address_limit,
1054
+ minAmountSum: min_amount_sum,
1055
+ incidentTimestampMs: incident_timestamp_ms,
1056
+ writeArtifacts: workspaceArtifactsEnabled
976
1057
  });
1058
+ const graph = await writeLocalGraphMeta(result.graphData, config, `trace-suspect-funds-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
977
1059
  return {
978
1060
  content: [{
979
1061
  type: "text",
980
- text: [
981
- `Case exported: ${result.outputDir}`,
982
- `Manifest: ${result.manifestPath}`,
983
- `Files: ${result.fileCount}`,
984
- `Open first: ${result.nextFile}`,
985
- ...result.warnings.map((warning) => `Warning: ${warning}`)
986
- ].join("\n")
1062
+ text: result.summaryText
987
1063
  }],
988
- structuredContent: result,
1064
+ structuredContent: result.structuredContent,
1065
+ _meta: graphMetaResult(graph),
989
1066
  isError: false
990
1067
  };
991
1068
  } catch (err) {
992
- return caseToolError("Case export", err);
993
- }
994
- });
995
- server.registerTool("case_update_dossier", {
996
- description: "Append a finding to an address/entity dossier inside a local Chain Insights case.",
997
- inputSchema: {
998
- case_id: zod.string().min(1).describe("Chain Insights case ID"),
999
- address: zod.string().min(1).describe("Full address or entity identifier"),
1000
- finding: zod.string().min(1).describe("Finding to append"),
1001
- entity_type: zod.enum([
1002
- "eoa",
1003
- "contract",
1004
- "exchange",
1005
- "mixer",
1006
- "unknown"
1007
- ]).optional().describe("Entity type")
1008
- },
1009
- annotations: {
1010
- readOnlyHint: false,
1011
- destructiveHint: false,
1012
- idempotentHint: false,
1013
- openWorldHint: false
1014
- }
1015
- }, async ({ case_id, address, finding, entity_type }) => {
1016
- try {
1017
- const { DossierStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
1018
- await DossierStore.appendFinding(case_id, address, finding, entity_type ?? "unknown");
1019
- return {
1069
+ if (err instanceof require_client.PaymentRequiredError) return {
1020
1070
  content: [{
1021
1071
  type: "text",
1022
- text: JSON.stringify({
1023
- case_id,
1024
- address,
1025
- updated: true
1026
- }, null, 2)
1072
+ text: err.message
1027
1073
  }],
1028
- isError: false
1074
+ isError: true
1029
1075
  };
1030
- } catch (err) {
1031
- return caseToolError("Dossier update", err);
1032
- }
1033
- });
1034
- server.registerTool("case_start_session", {
1035
- description: "Start a local investigation session file for a Chain Insights case.",
1036
- inputSchema: { case_id: zod.string().min(1).describe("Chain Insights case ID") },
1037
- annotations: {
1038
- readOnlyHint: false,
1039
- destructiveHint: false,
1040
- idempotentHint: false,
1041
- openWorldHint: false
1042
- }
1043
- }, async ({ case_id }) => {
1044
- try {
1045
- const { SessionStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
1046
- const session = await SessionStore.start(case_id);
1047
1076
  return {
1048
1077
  content: [{
1049
1078
  type: "text",
1050
- text: JSON.stringify(session, null, 2)
1079
+ text: `Trace suspect funds failed: ${err.message}`
1051
1080
  }],
1052
- isError: false
1081
+ isError: true
1053
1082
  };
1054
- } catch (err) {
1055
- return caseToolError("Session start", err);
1056
1083
  }
1057
1084
  });
1058
- server.registerTool("case_end_session", {
1059
- description: "End the latest local investigation session for a Chain Insights case with findings and next steps.",
1085
+ if (!remoteToolNames.has("aml_trace_deposit_sources")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "aml_trace_deposit_sources", {
1086
+ title: "Trace Deposit Sources",
1087
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_trace_deposit_sources,
1060
1088
  inputSchema: {
1061
- case_id: zod.string().min(1).describe("Chain Insights case ID"),
1062
- findings: zod.string().optional().describe("Key findings from this session"),
1063
- next_steps: zod.string().optional().describe("Next investigation steps")
1089
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1090
+ deposit_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full suspected deposit/cashout addresses, or an array. Min 1, max 5."),
1091
+ max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum reverse traceback hops. Default 2."),
1092
+ include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
1064
1093
  },
1094
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1065
1095
  annotations: {
1066
1096
  readOnlyHint: false,
1067
1097
  destructiveHint: false,
1068
1098
  idempotentHint: false,
1069
- openWorldHint: false
1099
+ openWorldHint: true
1070
1100
  }
1071
- }, async ({ case_id, findings, next_steps }) => {
1101
+ }, async ({ deposit_addresses, network, max_hops, include_attachments }) => {
1072
1102
  try {
1073
- const { SessionStore } = await Promise.resolve().then(() => require("./cases-Bz_9XKEw.cjs")).then((n) => n.cases_exports);
1074
- await SessionStore.end(case_id, {
1075
- findings: findings ?? "",
1076
- nextSteps: next_steps ?? ""
1103
+ if (!remoteConnected) return {
1104
+ content: [{
1105
+ type: "text",
1106
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1107
+ }],
1108
+ isError: true
1109
+ };
1110
+ const { traceDepositSources } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
1111
+ const result = await traceDepositSources(remoteClient, config, {
1112
+ depositAddresses: deposit_addresses,
1113
+ network,
1114
+ maxHops: max_hops,
1115
+ writeArtifacts: workspaceArtifactsEnabled
1077
1116
  });
1078
- await SessionStore.archiveOldSessions(case_id);
1117
+ const graph = await writeLocalGraphMeta(result.graphData, config, `trace-deposit-sources-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1079
1118
  return {
1080
1119
  content: [{
1081
1120
  type: "text",
1082
- text: JSON.stringify({
1083
- case_id,
1084
- ended: true
1085
- }, null, 2)
1121
+ text: result.summaryText
1086
1122
  }],
1123
+ structuredContent: result.structuredContent,
1124
+ _meta: graphMetaResult(graph),
1087
1125
  isError: false
1088
1126
  };
1089
1127
  } catch (err) {
1090
- return caseToolError("Session end", err);
1128
+ if (err instanceof require_client.PaymentRequiredError) return {
1129
+ content: [{
1130
+ type: "text",
1131
+ text: err.message
1132
+ }],
1133
+ isError: true
1134
+ };
1135
+ return {
1136
+ content: [{
1137
+ type: "text",
1138
+ text: `Trace deposit sources failed: ${err.message}`
1139
+ }],
1140
+ isError: true
1141
+ };
1091
1142
  }
1092
1143
  });
1093
- }
1094
- if (!remoteToolNames.has("address_risk")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "address_risk", {
1095
- title: "Address Risk",
1096
- description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.address_risk,
1097
- inputSchema: {
1098
- address: zod.string().min(1).describe("Full blockchain address to screen"),
1099
- network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1100
- compare_address: zod.string().optional().describe("Optional second full address for comparison"),
1101
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
1102
- },
1103
- _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1104
- annotations: {
1105
- readOnlyHint: true,
1106
- destructiveHint: false,
1107
- idempotentHint: true,
1108
- openWorldHint: true
1109
- }
1110
- }, async ({ address, network, compare_address, include_attachments }) => {
1111
- try {
1112
- if (!remoteConnected) return {
1113
- content: [{
1114
- type: "text",
1115
- text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1116
- }],
1117
- isError: true
1118
- };
1119
- const { addressRisk } = await Promise.resolve().then(() => require("./public-tools-BY3PTw6x.cjs"));
1120
- const result = await addressRisk(remoteClient, {
1121
- address,
1122
- network,
1123
- compareAddress: compare_address
1124
- });
1125
- const graph = await writeLocalGraphMeta(result.graphData, config, `address-risk-${network}-${address}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1126
- return {
1127
- content: [{
1128
- type: "text",
1129
- text: result.summaryText
1130
- }],
1131
- structuredContent: result.structuredContent,
1132
- _meta: graphMetaResult(graph),
1133
- isError: false
1134
- };
1135
- } catch (err) {
1136
- if (err instanceof require_client.PaymentRequiredError) return {
1137
- content: [{
1138
- type: "text",
1139
- text: err.message
1140
- }],
1141
- isError: true
1142
- };
1143
- return {
1144
- content: [{
1145
- type: "text",
1146
- text: `Address risk failed: ${err.message}`
1147
- }],
1148
- isError: true
1149
- };
1150
- }
1151
- });
1152
- if (!remoteToolNames.has("trace_victim_funds")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "trace_victim_funds", {
1153
- title: "Trace Victim Funds",
1154
- description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_victim_funds,
1155
- inputSchema: {
1156
- victim_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full victim/source addresses, or an array. Min 1, max 5."),
1157
- network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1158
- known_suspect_addresses: zod.union([zod.string(), zod.array(zod.string())]).optional().describe("Known suspect addresses for context only. This tool does not reverse-trace them. Max 5."),
1159
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata"),
1160
- case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
1161
- incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds."),
1162
- max_hops: zod.number().int().min(1).max(5).optional(),
1163
- per_address_limit: zod.number().int().min(1).max(10).optional(),
1164
- min_amount_sum: zod.number().min(0).optional()
1165
- },
1166
- _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1167
- annotations: {
1168
- readOnlyHint: false,
1169
- destructiveHint: false,
1170
- idempotentHint: false,
1171
- openWorldHint: true
1172
- }
1173
- }, async ({ victim_addresses, known_suspect_addresses, network, case_id, incident_timestamp_ms, max_hops, per_address_limit, min_amount_sum, include_attachments }) => {
1174
- try {
1175
- if (!remoteConnected) return {
1176
- content: [{
1177
- type: "text",
1178
- text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1179
- }],
1180
- isError: true
1181
- };
1182
- if (!workspaceArtifactsEnabled && case_id) return {
1183
- content: [{
1184
- type: "text",
1185
- text: "case_id requires Chain Insights workspace mode; omit case_id when CHAIN_INSIGHTS_MCP_PROXY_MODE=stateless."
1186
- }],
1187
- isError: true
1188
- };
1189
- const { traceVictimFunds } = await Promise.resolve().then(() => require("./public-tools-BY3PTw6x.cjs"));
1190
- const result = await traceVictimFunds(remoteClient, config, {
1191
- victimAddresses: victim_addresses,
1192
- knownSuspectAddresses: known_suspect_addresses,
1193
- network,
1194
- caseId: case_id,
1195
- incidentTimestampMs: incident_timestamp_ms,
1196
- maxHops: max_hops,
1197
- perAddressLimit: per_address_limit,
1198
- minAmountSum: min_amount_sum,
1199
- writeArtifacts: workspaceArtifactsEnabled
1200
- });
1201
- const graph = await writeLocalGraphMeta(result.graphData, config, `trace-victim-funds-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1202
- return {
1203
- content: [{
1204
- type: "text",
1205
- text: result.summaryText
1206
- }],
1207
- structuredContent: result.structuredContent,
1208
- _meta: graphMetaResult(graph),
1209
- isError: false
1210
- };
1211
- } catch (err) {
1212
- if (err instanceof require_client.PaymentRequiredError) return {
1213
- content: [{
1214
- type: "text",
1215
- text: err.message
1216
- }],
1217
- isError: true
1218
- };
1219
- return {
1220
- content: [{
1221
- type: "text",
1222
- text: `Trace victim funds failed: ${err.message}`
1223
- }],
1224
- isError: true
1225
- };
1226
- }
1227
- });
1228
- if (!remoteToolNames.has("trace_suspect_funds")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "trace_suspect_funds", {
1229
- title: "Trace Suspect Funds",
1230
- description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_suspect_funds,
1231
- inputSchema: {
1232
- network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1233
- suspect_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full suspect-controlled addresses, or an array. Min 1, max 5."),
1234
- incident_timestamp_ms: zod.number().min(0).optional().describe("Optional incident timestamp in milliseconds. This tool works without it."),
1235
- max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3."),
1236
- per_address_limit: zod.number().int().min(1).max(10).optional(),
1237
- min_amount_sum: zod.number().min(0).optional(),
1238
- case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
1239
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
1240
- },
1241
- _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1242
- annotations: {
1243
- readOnlyHint: false,
1244
- destructiveHint: false,
1245
- idempotentHint: false,
1246
- openWorldHint: true
1247
- }
1248
- }, async ({ suspect_addresses, incident_timestamp_ms, network, max_hops, per_address_limit, min_amount_sum, case_id, include_attachments }) => {
1249
- try {
1250
- if (!remoteConnected) return {
1251
- content: [{
1252
- type: "text",
1253
- text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1254
- }],
1255
- isError: true
1256
- };
1257
- if (!workspaceArtifactsEnabled && case_id) return {
1258
- content: [{
1259
- type: "text",
1260
- text: "case_id requires Chain Insights workspace mode; omit case_id when CHAIN_INSIGHTS_MCP_PROXY_MODE=stateless."
1261
- }],
1262
- isError: true
1263
- };
1264
- const { traceSuspectFunds } = await Promise.resolve().then(() => require("./public-tools-BY3PTw6x.cjs"));
1265
- const result = await traceSuspectFunds(remoteClient, config, {
1266
- suspectAddresses: suspect_addresses,
1267
- network,
1268
- maxHops: max_hops,
1269
- perAddressLimit: per_address_limit,
1270
- minAmountSum: min_amount_sum,
1271
- incidentTimestampMs: incident_timestamp_ms,
1272
- caseId: case_id,
1273
- writeArtifacts: workspaceArtifactsEnabled
1274
- });
1275
- const graph = await writeLocalGraphMeta(result.graphData, config, `trace-suspect-funds-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1276
- return {
1277
- content: [{
1278
- type: "text",
1279
- text: result.summaryText
1280
- }],
1281
- structuredContent: result.structuredContent,
1282
- _meta: graphMetaResult(graph),
1283
- isError: false
1284
- };
1285
- } catch (err) {
1286
- if (err instanceof require_client.PaymentRequiredError) return {
1287
- content: [{
1288
- type: "text",
1289
- text: err.message
1290
- }],
1291
- isError: true
1292
- };
1293
- return {
1294
- content: [{
1295
- type: "text",
1296
- text: `Trace suspect funds failed: ${err.message}`
1297
- }],
1298
- isError: true
1299
- };
1300
- }
1301
- });
1302
- if (!remoteToolNames.has("trace_deposit_sources")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "trace_deposit_sources", {
1303
- title: "Trace Deposit Sources",
1304
- description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_deposit_sources,
1305
- inputSchema: {
1306
- network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1307
- deposit_addresses: zod.union([zod.string().min(1), zod.array(zod.string().min(1))]).describe("Comma-separated full suspected deposit/cashout addresses, or an array. Min 1, max 5."),
1308
- max_hops: zod.number().int().min(1).max(5).optional().describe("Maximum reverse traceback hops. Default 2."),
1309
- case_id: zod.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
1310
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
1311
- },
1312
- _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1313
- annotations: {
1314
- readOnlyHint: false,
1315
- destructiveHint: false,
1316
- idempotentHint: false,
1317
- openWorldHint: true
1318
- }
1319
- }, async ({ deposit_addresses, network, max_hops, case_id, include_attachments }) => {
1320
- try {
1321
- if (!remoteConnected) return {
1322
- content: [{
1323
- type: "text",
1324
- text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1325
- }],
1326
- isError: true
1327
- };
1328
- if (!workspaceArtifactsEnabled && case_id) return {
1329
- content: [{
1330
- type: "text",
1331
- text: "case_id requires Chain Insights workspace mode; omit case_id when CHAIN_INSIGHTS_MCP_PROXY_MODE=stateless."
1332
- }],
1333
- isError: true
1334
- };
1335
- const { traceDepositSources } = await Promise.resolve().then(() => require("./public-tools-BY3PTw6x.cjs"));
1336
- const result = await traceDepositSources(remoteClient, config, {
1337
- depositAddresses: deposit_addresses,
1338
- network,
1339
- maxHops: max_hops,
1340
- caseId: case_id,
1341
- writeArtifacts: workspaceArtifactsEnabled
1342
- });
1343
- const graph = await writeLocalGraphMeta(result.graphData, config, `trace-deposit-sources-${network}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1344
- return {
1345
- content: [{
1346
- type: "text",
1347
- text: result.summaryText
1348
- }],
1349
- structuredContent: result.structuredContent,
1350
- _meta: graphMetaResult(graph),
1351
- isError: false
1352
- };
1353
- } catch (err) {
1354
- if (err instanceof require_client.PaymentRequiredError) return {
1355
- content: [{
1356
- type: "text",
1357
- text: err.message
1358
- }],
1359
- isError: true
1360
- };
1361
- return {
1362
- content: [{
1363
- type: "text",
1364
- text: `Trace deposit sources failed: ${err.message}`
1365
- }],
1366
- isError: true
1367
- };
1368
- }
1369
- });
1370
- if (!remoteToolNames.has("stake_insights")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "stake_insights", {
1371
- title: "Stake Insights",
1372
- description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.stake_insights,
1373
- inputSchema: {
1374
- network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1375
- address: zod.string().optional().describe("Full Bittensor address to inspect as either coldkey or hotkey. Provide exactly one of address, coldkey, or hotkey."),
1376
- coldkey: zod.string().optional().describe("Full Bittensor coldkey address to inspect. Provide exactly one of address, coldkey, or hotkey."),
1377
- hotkey: zod.string().optional().describe("Full Bittensor hotkey address to inspect. Provide exactly one of address, coldkey, or hotkey."),
1378
- netuid: zod.number().int().min(0).optional().describe("Optional subnet netuid filter."),
1379
- start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
1380
- end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
1381
- start_block: zod.number().int().min(0).optional().describe("Optional start block. Current stake graph parity may require timestamp windows instead."),
1382
- end_block: zod.number().int().min(0).optional().describe("Optional end block. Current stake graph parity may require timestamp windows instead."),
1383
- depth: zod.number().int().min(1).max(3).optional().describe("Optional expansion depth limit. First release returns direct STAKES_IN relationships; default 1, max 3."),
1384
- include_attachments: zod.boolean().optional().describe("Include graph app report metadata")
1385
- },
1386
- _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1387
- annotations: {
1388
- readOnlyHint: true,
1389
- destructiveHint: false,
1390
- idempotentHint: true,
1391
- openWorldHint: true
1392
- }
1393
- }, async ({ network, address, coldkey, hotkey, netuid, start_timestamp_ms, end_timestamp_ms, start_block, end_block, depth, include_attachments }) => {
1394
- try {
1395
- if (!remoteConnected) return {
1396
- content: [{
1397
- type: "text",
1398
- text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1399
- }],
1400
- isError: true
1401
- };
1402
- const { stakeInsights } = await Promise.resolve().then(() => require("./public-tools-BY3PTw6x.cjs"));
1403
- const result = await stakeInsights(remoteClient, {
1404
- network,
1405
- address,
1406
- coldkey,
1407
- hotkey,
1408
- netuid,
1409
- startTimestampMs: start_timestamp_ms,
1410
- endTimestampMs: end_timestamp_ms,
1411
- startBlock: start_block,
1412
- endBlock: end_block,
1413
- depth
1414
- });
1415
- const subject = address ?? coldkey ?? hotkey ?? "subject";
1416
- const graph = await writeLocalGraphMeta(result.graphData, config, `stake-insights-${network}-${subject}`, shouldIncludeAttachments({ include_attachments }, workspaceArtifactsEnabled));
1417
- return {
1418
- content: [{
1419
- type: "text",
1420
- text: result.summaryText
1421
- }],
1422
- structuredContent: result.structuredContent,
1423
- _meta: graphMetaResult(graph),
1424
- isError: false
1425
- };
1426
- } catch (err) {
1427
- if (err instanceof require_client.PaymentRequiredError) return {
1428
- content: [{
1429
- type: "text",
1430
- text: err.message
1431
- }],
1432
- isError: true
1433
- };
1434
- return {
1435
- content: [{
1436
- type: "text",
1437
- text: `Stake insights failed: ${err.message}`
1438
- }],
1439
- isError: true
1440
- };
1441
- }
1442
- });
1443
- server.registerTool("help", {
1444
- description: "Show Chain Insights overview, available tools, and investigation workflow.",
1445
- inputSchema: zod.object({}).passthrough()
1446
- }, async () => ({
1447
- content: [{
1448
- type: "text",
1449
- text: workspaceArtifactsEnabled ? [
1450
- "Chain Insights AML investigation workspace for AI agents. Workspaces are Obsidian-compatible vaults backed by plain local files.",
1451
- "",
1452
- CHAIN_INSIGHTS_WORKFLOW,
1453
- "",
1454
- "Investigation tools:",
1455
- "- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.",
1456
- "- address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.",
1457
- "- stake_insights: explain Bittensor staking around one address, coldkey, or hotkey with net stake, movement amounts, counterparties, backend, and query evidence.",
1458
- "- trace_victim_funds: trace up to five victim/source addresses forward to exchange deposit candidates.",
1459
- "- trace_deposit_sources: trace backward from suspected deposit/cashout addresses to upstream funders and shared-source convergence.",
1460
- "- trace_suspect_funds: trace up to five suspected scammer, mule, operator, or laundering-ring addresses forward to cashout topology.",
1461
- "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.",
1462
- "- graph_query_batch: run related read-only graph-language queries through one paid graph call.",
1463
- "",
1464
- "Case workflow tools:",
1465
- "- case_open: create a local case before preserving evidence.",
1466
- "- case_list: list local cases.",
1467
- "- case_resume: load case context, evidence count, dossiers, and latest session.",
1468
- "- case_add_evidence: append a report or note to the case evidence manifest.",
1469
- "- case_verify_evidence: verify saved evidence integrity.",
1470
- "- case_export: export a case for Obsidian, LLM Wiki, Codex, Claude Code, and ChatGPT handoff bundles.",
1471
- "- case_update_dossier: add a finding to an address/entity dossier.",
1472
- "- case_start_session and case_end_session: record session notes.",
1473
- "",
1474
- "Wallet tools:",
1475
- "- balance: show the local payment wallet address and Base USDC balance.",
1476
- "- help: show this overview.",
1477
- "",
1478
- GRAPH_REPORT_HINTS,
1479
- "",
1480
- GRAPH_SCHEMA_HINTS
1481
- ].join("\n") : [
1482
- "Chain Insights stateless AML proxy for host applications.",
1483
- "",
1484
- "Local workspace, case, evidence, dossier, session, wallet, and graph report attachment tools are disabled in this mode.",
1485
- "",
1486
- "Available graph-backed tools:",
1487
- "- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.",
1488
- "- address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.",
1489
- "- stake_insights: explain Bittensor staking around one address, coldkey, or hotkey with net stake, movement amounts, counterparties, backend, and query evidence.",
1490
- "- trace_victim_funds: trace up to five victim/source addresses forward to exchange deposit candidates.",
1491
- "- trace_deposit_sources: trace backward from suspected deposit/cashout addresses to upstream funders and shared-source convergence.",
1492
- "- trace_suspect_funds: trace up to five suspected scammer, mule, operator, or laundering-ring addresses forward to cashout topology.",
1493
- "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.",
1494
- "- graph_query_batch: run related read-only graph-language queries through one paid graph call.",
1495
- "",
1496
- GRAPH_SCHEMA_HINTS
1497
- ].join("\n")
1498
- }],
1499
- isError: false
1500
- }));
1501
- for (const tool of tools ?? []) {
1502
- if (require_tool_visibility.HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue;
1503
- if (LOCAL_TOOL_NAMES.has(tool.name)) continue;
1504
- const inputSchema = knownPublicToolInputSchema(tool.name) ?? zod.object({}).passthrough();
1505
- const handler = async (args) => {
1144
+ if (!remoteToolNames.has("exposure_profile")) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, "exposure_profile", {
1145
+ title: "Exposure Profile",
1146
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.exposure_profile,
1147
+ inputSchema: {
1148
+ network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
1149
+ account: zod.string().optional().describe("Full account address to inspect. Provide exactly one of account, owner, or counterparty."),
1150
+ owner: zod.string().optional().describe("Full owner address to inspect. Provide exactly one of account, owner, or counterparty."),
1151
+ counterparty: zod.string().optional().describe("Full counterparty address to inspect. Provide exactly one of account, owner, or counterparty."),
1152
+ venue: zod.string().optional().describe("Optional venue filter, such as Bittensor or Hyperliquid."),
1153
+ instrument: zod.string().optional().describe("Optional instrument display or durable identifier filter, such as Subnet 19 or BTC-PERP."),
1154
+ instrument_type: zod.string().optional().describe("Optional instrument type filter, such as subnet, perp, spot, vault, staking, or other."),
1155
+ start_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive lower activity timestamp bound in milliseconds."),
1156
+ end_timestamp_ms: zod.number().min(0).optional().describe("Optional inclusive upper activity timestamp bound in milliseconds."),
1157
+ limit: zod.number().int().min(1).max(500).optional().describe("Maximum exposure rows to inspect. Default 100, max 500.")
1158
+ },
1159
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1160
+ annotations: {
1161
+ readOnlyHint: true,
1162
+ destructiveHint: false,
1163
+ idempotentHint: true,
1164
+ openWorldHint: true
1165
+ }
1166
+ }, async ({ network, account, owner, counterparty, venue, instrument, instrument_type, start_timestamp_ms, end_timestamp_ms, limit }) => {
1506
1167
  try {
1507
1168
  if (!remoteConnected) return {
1508
1169
  content: [{
@@ -1511,21 +1172,32 @@ async function createProxy() {
1511
1172
  }],
1512
1173
  isError: true
1513
1174
  };
1514
- const normalizedArgs = normalizeRemoteToolArguments(tool.name, args);
1515
- const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs);
1516
- if (validationError) return {
1175
+ const { exposureProfile } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
1176
+ const result = await exposureProfile(remoteClient, {
1177
+ network,
1178
+ account,
1179
+ owner,
1180
+ counterparty,
1181
+ venue,
1182
+ instrument,
1183
+ instrumentType: instrument_type,
1184
+ startTimestampMs: start_timestamp_ms,
1185
+ endTimestampMs: end_timestamp_ms,
1186
+ limit
1187
+ });
1188
+ const subject = account ?? owner ?? counterparty ?? "subject";
1189
+ await writeExposureArtifacts({
1190
+ summaryText: result.summaryText,
1191
+ structuredContent: result.structuredContent
1192
+ }, "exposure_profile", subject, network, workspaceArtifactsEnabled);
1193
+ return {
1517
1194
  content: [{
1518
1195
  type: "text",
1519
- text: validationError
1196
+ text: result.summaryText
1520
1197
  }],
1521
- isError: true
1522
- };
1523
- const request = {
1524
- name: tool.name,
1525
- arguments: normalizedArgs
1198
+ structuredContent: result.structuredContent,
1199
+ isError: false
1526
1200
  };
1527
- const requestOptions = remoteToolRequestOptions(tool.name);
1528
- return await normalizeRemoteToolResult(requestOptions ? await remoteClient.callTool(request, void 0, requestOptions) : await remoteClient.callTool(request), config, tool.name, shouldIncludeAttachments(normalizedArgs, workspaceArtifactsEnabled));
1529
1201
  } catch (err) {
1530
1202
  if (err instanceof require_client.PaymentRequiredError) return {
1531
1203
  content: [{
@@ -1534,48 +1206,255 @@ async function createProxy() {
1534
1206
  }],
1535
1207
  isError: true
1536
1208
  };
1537
- const msg = err.message ?? String(err);
1538
- if (/\b402\b/.test(msg) || msg.toLowerCase().includes("payment")) return {
1539
- content: [{
1540
- type: "text",
1541
- text: `Payment required for ${tool.name}. This tool costs USDC on Base via x402 micropayments. Next steps: run \`chain-insights wallet ready\` to check funding and finish one-time payment setup, run \`chain-insights wallet topup\` if it says the wallet needs USDC, or \`chain-insights access-key set <key>\` if you have been given test access.`
1542
- }],
1543
- isError: true
1544
- };
1545
1209
  return {
1546
1210
  content: [{
1547
1211
  type: "text",
1548
- text: `MCP call failed: ${msg}`
1212
+ text: `Exposure profile failed: ${err.message}`
1549
1213
  }],
1550
1214
  isError: true
1551
1215
  };
1552
1216
  }
1217
+ });
1218
+ for (const tool of [
1219
+ {
1220
+ name: "exposure_quality",
1221
+ title: "Exposure Quality",
1222
+ failure: "Exposure quality failed"
1223
+ },
1224
+ {
1225
+ name: "exposure_carry",
1226
+ title: "Exposure Carry",
1227
+ failure: "Exposure carry failed"
1228
+ },
1229
+ {
1230
+ name: "exposure_crowding",
1231
+ title: "Exposure Crowding",
1232
+ failure: "Exposure crowding failed"
1233
+ },
1234
+ {
1235
+ name: "exposure_exit_pressure",
1236
+ title: "Exposure Exit Pressure",
1237
+ failure: "Exposure exit pressure failed"
1238
+ },
1239
+ {
1240
+ name: "exposure_correlation",
1241
+ title: "Exposure Correlation",
1242
+ failure: "Exposure correlation failed"
1243
+ },
1244
+ {
1245
+ name: "exposure_explain",
1246
+ title: "Exposure Explain",
1247
+ failure: "Exposure explain failed"
1248
+ }
1249
+ ]) {
1250
+ if (remoteToolNames.has(tool.name)) continue;
1251
+ (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, tool.name, {
1252
+ title: tool.title,
1253
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name],
1254
+ inputSchema: knownPublicToolInputSchema(tool.name) ?? {},
1255
+ _meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
1256
+ annotations: {
1257
+ readOnlyHint: true,
1258
+ destructiveHint: false,
1259
+ idempotentHint: true,
1260
+ openWorldHint: true
1261
+ }
1262
+ }, async (args) => {
1263
+ try {
1264
+ if (!remoteConnected) return {
1265
+ content: [{
1266
+ type: "text",
1267
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1268
+ }],
1269
+ isError: true
1270
+ };
1271
+ const input = args;
1272
+ const { exposureCarry, exposureCorrelation, exposureCrowding, exposureExitPressure, exposureExplain, exposureQuality } = await Promise.resolve().then(() => require("./public-tools-BREojpU7.cjs"));
1273
+ const options = {
1274
+ network: String(input["network"] ?? ""),
1275
+ account: input["account"] === void 0 ? void 0 : String(input["account"]),
1276
+ owner: input["owner"] === void 0 ? void 0 : String(input["owner"]),
1277
+ counterparty: input["counterparty"] === void 0 ? void 0 : String(input["counterparty"]),
1278
+ venue: input["venue"] === void 0 ? void 0 : String(input["venue"]),
1279
+ instrument: input["instrument"] === void 0 ? void 0 : String(input["instrument"]),
1280
+ market: input["market"] === void 0 ? void 0 : String(input["market"]),
1281
+ instrumentType: input["instrument_type"] === void 0 ? void 0 : String(input["instrument_type"]),
1282
+ startTimestampMs: typeof input["start_timestamp_ms"] === "number" ? input["start_timestamp_ms"] : void 0,
1283
+ endTimestampMs: typeof input["end_timestamp_ms"] === "number" ? input["end_timestamp_ms"] : void 0,
1284
+ limit: typeof input["limit"] === "number" ? input["limit"] : void 0,
1285
+ candidateAccounts: input["candidate_accounts"],
1286
+ positionId: input["position_id"] === void 0 ? void 0 : String(input["position_id"])
1287
+ };
1288
+ const result = tool.name === "exposure_quality" ? await exposureQuality(remoteClient, options) : tool.name === "exposure_carry" ? await exposureCarry(remoteClient, options) : tool.name === "exposure_crowding" ? await exposureCrowding(remoteClient, options) : tool.name === "exposure_exit_pressure" ? await exposureExitPressure(remoteClient, options) : tool.name === "exposure_correlation" ? await exposureCorrelation(remoteClient, options) : await exposureExplain(remoteClient, options);
1289
+ const subject = options.account ?? options.owner ?? options.counterparty ?? options.instrument ?? options.market ?? "subject";
1290
+ await writeExposureArtifacts({
1291
+ summaryText: result.summaryText,
1292
+ structuredContent: result.structuredContent
1293
+ }, tool.name, subject, options.network, workspaceArtifactsEnabled);
1294
+ return {
1295
+ content: [{
1296
+ type: "text",
1297
+ text: result.summaryText
1298
+ }],
1299
+ structuredContent: result.structuredContent,
1300
+ isError: false
1301
+ };
1302
+ } catch (err) {
1303
+ if (err instanceof require_client.PaymentRequiredError) return {
1304
+ content: [{
1305
+ type: "text",
1306
+ text: err.message
1307
+ }],
1308
+ isError: true
1309
+ };
1310
+ return {
1311
+ content: [{
1312
+ type: "text",
1313
+ text: `${tool.failure}: ${err.message}`
1314
+ }],
1315
+ isError: true
1316
+ };
1317
+ }
1318
+ });
1319
+ }
1320
+ server.registerTool("help", {
1321
+ description: "Show Chain Insights overview, available tools, and investigation workflow.",
1322
+ inputSchema: zod.object({}).passthrough()
1323
+ }, async () => ({
1324
+ content: [{
1325
+ type: "text",
1326
+ text: workspaceArtifactsEnabled ? [
1327
+ "Chain Insights workspace for AI agents. Workspaces are plain local files for reports, artifacts, graphs, and published outputs.",
1328
+ "",
1329
+ CHAIN_INSIGHTS_WORKFLOW,
1330
+ "",
1331
+ "Investigation tools:",
1332
+ "- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.",
1333
+ "- aml_address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.",
1334
+ "- exposure_profile: explain staking or trading exposure around one account, owner, or counterparty.",
1335
+ "- exposure_quality: score whether exposure behavior looks disciplined, fragile, lucky, or noisy.",
1336
+ "- exposure_carry: explain carry earned or paid from staking, trading, funding, fees, emissions, or dividends.",
1337
+ "- exposure_crowding: measure side concentration for a market, subnet, hotkey, vault, or strategy.",
1338
+ "- exposure_exit_pressure: explain liquidation, slippage, unstake, funding pain, or other exit pressure.",
1339
+ "- exposure_correlation: compare accounts for possible copy, overlap, or strategy-cluster behavior.",
1340
+ "- exposure_explain: explain a specific exposure lifecycle, trade, position, stake, rotation, or incident.",
1341
+ "- aml_trace_victim_funds: trace up to five victim/source addresses forward to exchange deposit candidates.",
1342
+ "- aml_trace_deposit_sources: trace backward from suspected deposit/cashout addresses to upstream funders and shared-source convergence.",
1343
+ "- aml_trace_suspect_funds: trace up to five suspected scammer, mule, operator, or laundering-ring addresses forward to cashout topology.",
1344
+ "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.",
1345
+ "- graph_query_batch: run related read-only graph-language queries through one paid graph call.",
1346
+ "Wallet tools:",
1347
+ "- balance: show the local payment wallet address and Base USDC balance.",
1348
+ "- help: show this overview.",
1349
+ "",
1350
+ GRAPH_REPORT_HINTS,
1351
+ "",
1352
+ GRAPH_SCHEMA_HINTS
1353
+ ].join("\n") : [
1354
+ "Chain Insights stateless AML proxy for host applications.",
1355
+ "",
1356
+ "Local workspace persistence, wallet, and graph report attachment tools are disabled in this mode.",
1357
+ "",
1358
+ "Available graph-backed tools:",
1359
+ "- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.",
1360
+ "- aml_address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.",
1361
+ "- exposure_profile: explain staking or trading exposure around one account, owner, or counterparty.",
1362
+ "- exposure_quality: score whether exposure behavior looks disciplined, fragile, lucky, or noisy.",
1363
+ "- exposure_carry: explain carry earned or paid from staking, trading, funding, fees, emissions, or dividends.",
1364
+ "- exposure_crowding: measure side concentration for a market, subnet, hotkey, vault, or strategy.",
1365
+ "- exposure_exit_pressure: explain liquidation, slippage, unstake, funding pain, or other exit pressure.",
1366
+ "- exposure_correlation: compare accounts for possible copy, overlap, or strategy-cluster behavior.",
1367
+ "- exposure_explain: explain a specific exposure lifecycle, trade, position, stake, rotation, or incident.",
1368
+ "- aml_trace_victim_funds: trace up to five victim/source addresses forward to exchange deposit candidates.",
1369
+ "- aml_trace_deposit_sources: trace backward from suspected deposit/cashout addresses to upstream funders and shared-source convergence.",
1370
+ "- aml_trace_suspect_funds: trace up to five suspected scammer, mule, operator, or laundering-ring addresses forward to cashout topology.",
1371
+ "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.",
1372
+ "- graph_query_batch: run related read-only graph-language queries through one paid graph call.",
1373
+ "",
1374
+ GRAPH_SCHEMA_HINTS
1375
+ ].join("\n")
1376
+ }],
1377
+ isError: false
1378
+ }));
1379
+ for (const tool of tools ?? []) {
1380
+ if (require_tool_visibility.HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue;
1381
+ if (LOCAL_TOOL_NAMES.has(tool.name)) continue;
1382
+ const inputSchema = knownPublicToolInputSchema(tool.name) ?? zod.object({}).passthrough();
1383
+ const handler = async (args) => {
1384
+ try {
1385
+ if (!remoteConnected) return {
1386
+ content: [{
1387
+ type: "text",
1388
+ text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
1389
+ }],
1390
+ isError: true
1391
+ };
1392
+ const normalizedArgs = normalizeRemoteToolArguments(tool.name, args);
1393
+ const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs);
1394
+ if (validationError) return {
1395
+ content: [{
1396
+ type: "text",
1397
+ text: validationError
1398
+ }],
1399
+ isError: true
1400
+ };
1401
+ const request = {
1402
+ name: tool.name,
1403
+ arguments: normalizedArgs
1404
+ };
1405
+ const requestOptions = remoteToolRequestOptions(tool.name);
1406
+ return await normalizeRemoteToolResult(requestOptions ? await remoteClient.callTool(request, void 0, requestOptions) : await remoteClient.callTool(request), config, tool.name, shouldIncludeAttachments(normalizedArgs, workspaceArtifactsEnabled));
1407
+ } catch (err) {
1408
+ if (err instanceof require_client.PaymentRequiredError) return {
1409
+ content: [{
1410
+ type: "text",
1411
+ text: err.message
1412
+ }],
1413
+ isError: true
1414
+ };
1415
+ const msg = err.message ?? String(err);
1416
+ if (/\b402\b/.test(msg) || msg.toLowerCase().includes("payment")) return {
1417
+ content: [{
1418
+ type: "text",
1419
+ text: `Payment required for ${tool.name}. This tool costs USDC on Base via x402 micropayments. Next steps: run \`chain-insights wallet ready\` to check funding and finish one-time payment setup, run \`chain-insights wallet topup\` if it says the wallet needs USDC, or \`chain-insights access-key set <key>\` if you have been given test access.`
1420
+ }],
1421
+ isError: true
1422
+ };
1423
+ return {
1424
+ content: [{
1425
+ type: "text",
1426
+ text: `MCP call failed: ${msg}`
1427
+ }],
1428
+ isError: true
1429
+ };
1430
+ }
1431
+ };
1432
+ const toolConfig = {
1433
+ title: tool.title,
1434
+ description: claudeFacingToolDescription(tool),
1435
+ inputSchema
1436
+ };
1437
+ if (hasGraphApp(tool)) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, tool.name, {
1438
+ ...toolConfig,
1439
+ _meta: graphToolMeta(tool)
1440
+ }, handler);
1441
+ else server.registerTool(tool.name, toolConfig, handler);
1442
+ }
1443
+ const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
1444
+ await server.connect(transport);
1445
+ await logger.info("proxy.ready", { tools: [...LOCAL_TOOL_NAMES, ...(tools ?? []).map((tool) => tool.name).filter((name) => !require_tool_visibility.HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name))].length });
1446
+ const shutdown = async () => {
1447
+ await logger.info("proxy.shutdown");
1448
+ transport.close();
1449
+ process.exit(0);
1553
1450
  };
1554
- const toolConfig = {
1555
- title: tool.title,
1556
- description: claudeFacingToolDescription(tool),
1557
- inputSchema
1558
- };
1559
- if (hasGraphApp(tool)) (0, _modelcontextprotocol_ext_apps_server.registerAppTool)(server, tool.name, {
1560
- ...toolConfig,
1561
- _meta: graphToolMeta(tool)
1562
- }, handler);
1563
- else server.registerTool(tool.name, toolConfig, handler);
1451
+ process.on("SIGINT", () => {
1452
+ shutdown();
1453
+ });
1454
+ process.on("SIGTERM", () => {
1455
+ shutdown();
1456
+ });
1564
1457
  }
1565
- const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
1566
- await server.connect(transport);
1567
- await logger.info("proxy.ready", { tools: [...LOCAL_TOOL_NAMES, ...(tools ?? []).map((tool) => tool.name).filter((name) => !require_tool_visibility.HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name))].length });
1568
- const shutdown = async () => {
1569
- await logger.info("proxy.shutdown");
1570
- transport.close();
1571
- process.exit(0);
1572
- };
1573
- process.on("SIGINT", () => {
1574
- shutdown();
1575
- });
1576
- process.on("SIGTERM", () => {
1577
- shutdown();
1578
- });
1579
1458
  }
1580
1459
  if (process.argv[1] && require("url").pathToFileURL(__filename).href.includes(process.argv[1].replace(/\\/g, "/"))) createProxy().catch((err) => {
1581
1460
  process.stderr.write(`Chain Insights MCP proxy startup failed: ${err.message}\n`);