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.
- package/README.md +49 -52
- package/dist/{active-ByNgjuAg.mjs → active-BQopLul8.mjs} +6 -8
- package/dist/active-BQopLul8.mjs.map +1 -0
- package/dist/{active-BVr55kvW.cjs → active-XWv72R1X.cjs} +4 -12
- package/dist/{app-BxojXjtB.cjs → app-DBrqk_iP.cjs} +12 -28
- package/dist/{app-CRd39JJ8.mjs → app-DXwILI_a.mjs} +13 -28
- package/dist/app-DXwILI_a.mjs.map +1 -0
- package/dist/{artifact-server-CP6LXQ9d.mjs → artifact-server-CcmLBv1j.mjs} +2 -2
- package/dist/{artifact-server-CP6LXQ9d.mjs.map → artifact-server-CcmLBv1j.mjs.map} +1 -1
- package/dist/{artifact-server-XbN16DwU.cjs → artifact-server-v0WgTPFT.cjs} +1 -1
- package/dist/{capabilities-BCvkTkIu.mjs → capabilities-CM72SErE.mjs} +2 -2
- package/dist/{capabilities-BCvkTkIu.mjs.map → capabilities-CM72SErE.mjs.map} +1 -1
- package/dist/{capabilities-DOa6EFO-.cjs → capabilities-DGeF-oHc.cjs} +1 -1
- package/dist/cli.cjs +149 -405
- package/dist/cli.mjs +149 -405
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-Y_zqKqJT.cjs → client-BY-56ojr.cjs} +0 -17
- package/dist/{client-BgmHjBHQ.mjs → client-ytTO0mcZ.mjs} +2 -13
- package/dist/{client-BgmHjBHQ.mjs.map → client-ytTO0mcZ.mjs.map} +1 -1
- package/dist/{config-Drgc2HuF.mjs → config-C6zM8Xir.mjs} +3 -3
- package/dist/{config-Drgc2HuF.mjs.map → config-C6zM8Xir.mjs.map} +1 -1
- package/dist/{config-BwVx19Og.cjs → config-CkW404Cs.cjs} +2 -2
- package/dist/{graph-reports-BDELxmpi.mjs → graph-reports-CEq-Mvx0.mjs} +2 -2
- package/dist/{graph-reports-BDELxmpi.mjs.map → graph-reports-CEq-Mvx0.mjs.map} +1 -1
- package/dist/{graph-reports-B3mkLP8Z.cjs → graph-reports-CkglRtg4.cjs} +1 -1
- package/dist/{html-generator-Bx3UcLTB.cjs → html-generator-BFKafL8y.cjs} +5 -6
- package/dist/{html-generator-AowOmzyi.mjs → html-generator-D4fX71hI.mjs} +6 -6
- package/dist/html-generator-D4fX71hI.mjs.map +1 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{init-CKQ6F07J.mjs → init-BGDvGreX.mjs} +52 -55
- package/dist/init-BGDvGreX.mjs.map +1 -0
- package/dist/{init-Dhw8F23z.cjs → init-Cuw9TznI.cjs} +51 -54
- package/dist/{mcp-endpoint-DHs1cRFH.mjs → mcp-endpoint-QQ5Lbqc2.mjs} +5 -2
- package/dist/mcp-endpoint-QQ5Lbqc2.mjs.map +1 -0
- package/dist/{mcp-endpoint-BaV8h_lq.cjs → mcp-endpoint-cQIZSjkK.cjs} +4 -1
- package/dist/mcp-proxy.cjs +650 -771
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +651 -772
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{output-root-BRhzhhXZ.mjs → output-root-BK4pdjyz.mjs} +6 -3
- package/dist/output-root-BK4pdjyz.mjs.map +1 -0
- package/dist/{output-root-YIbl6PwF.cjs → output-root-DI0tzA0X.cjs} +5 -2
- package/dist/{public-tools-BY3PTw6x.cjs → public-tools-BREojpU7.cjs} +1244 -426
- package/dist/{public-tools-CvlZcysd.mjs → public-tools-brHmHGYm.mjs} +1240 -428
- package/dist/public-tools-brHmHGYm.mjs.map +1 -0
- package/dist/{schema-BFEWhzg7.mjs → schema-D_qwaQA5.mjs} +2 -2
- package/dist/{schema-BFEWhzg7.mjs.map → schema-D_qwaQA5.mjs.map} +1 -1
- package/dist/{schema-Vl9yuOFO.cjs → schema-Dr6JXSOF.cjs} +1 -1
- package/dist/{server-BXLX2j_A.mjs → server-BK4bfOiv.mjs} +2 -2
- package/dist/{server-BXLX2j_A.mjs.map → server-BK4bfOiv.mjs.map} +1 -1
- package/dist/{server-BqVdWath.cjs → server-ColyTG1t.cjs} +1 -1
- package/dist/templates/graph.html +1 -1
- package/dist/{tool-visibility-Buq7YdUZ.cjs → tool-visibility--QPgrRE5.cjs} +5 -1
- package/dist/{tool-visibility-BpyZHRBi.mjs → tool-visibility-nr6XqO1F.mjs} +6 -2
- package/dist/tool-visibility-nr6XqO1F.mjs.map +1 -0
- package/dist/viz-BBvY-wXz.cjs +210 -0
- package/dist/viz-D8umSF-t.mjs +199 -0
- package/dist/viz-D8umSF-t.mjs.map +1 -0
- package/docs/architecture.md +4 -3
- package/docs/contributing.md +12 -6
- package/docs/graph-tools.md +93 -68
- package/docs/investigation-workspaces.md +38 -124
- package/docs/mcp-proxy.md +23 -34
- package/package.json +2 -2
- package/skills/chain-insights-address-risk/SKILL.md +92 -0
- package/skills/chain-insights-bittensor-cypher/SKILL.md +2 -22
- package/skills/chain-insights-developer-experience/SKILL.md +8 -28
- package/skills/chain-insights-exposure-analysis/SKILL.md +83 -0
- package/skills/chain-insights-investigation/SKILL.md +59 -211
- package/skills/chain-insights-investigation/agents/openai.yaml +1 -1
- package/skills/chain-insights-investigation/scripts/run-target-uat.sh +37 -55
- package/skills/chain-insights-trace-funds/SKILL.md +14 -14
- package/skills/ci-status/SKILL.md +9 -15
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +5 -4
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +272 -18
- package/dist/active-ByNgjuAg.mjs.map +0 -1
- package/dist/app-CRd39JJ8.mjs.map +0 -1
- package/dist/canvas-Cn-maEIh.mjs +0 -203
- package/dist/canvas-Cn-maEIh.mjs.map +0 -1
- package/dist/canvas-p-oKCMjc.cjs +0 -251
- package/dist/cases-Bz_9XKEw.cjs +0 -19
- package/dist/cases-TVcAifxu.mjs +0 -16
- package/dist/cases-TVcAifxu.mjs.map +0 -1
- package/dist/data-extractor-B4nHw1wZ.mjs +0 -336
- package/dist/data-extractor-B4nHw1wZ.mjs.map +0 -1
- package/dist/data-extractor-DS4rzy3M.cjs +0 -353
- package/dist/dossier-BXy57V4-.cjs +0 -88
- package/dist/dossier-Bjpcbcxa.mjs +0 -78
- package/dist/dossier-Bjpcbcxa.mjs.map +0 -1
- package/dist/evidence-CvEesemA.cjs +0 -200
- package/dist/evidence-D96PTzOQ.mjs +0 -195
- package/dist/evidence-D96PTzOQ.mjs.map +0 -1
- package/dist/export-CBhcJuZ6.mjs +0 -394
- package/dist/export-CBhcJuZ6.mjs.map +0 -1
- package/dist/export-D4v4-6F4.cjs +0 -394
- package/dist/frontmatter-D0ccQnUM.mjs +0 -26
- package/dist/frontmatter-D0ccQnUM.mjs.map +0 -1
- package/dist/frontmatter-Dvqa5HX6.cjs +0 -35
- package/dist/html-generator-AowOmzyi.mjs.map +0 -1
- package/dist/init-CKQ6F07J.mjs.map +0 -1
- package/dist/mcp-endpoint-DHs1cRFH.mjs.map +0 -1
- package/dist/output-root-BRhzhhXZ.mjs.map +0 -1
- package/dist/parser-BXLAHYnZ.cjs +0 -182
- package/dist/parser-CJfMsOl6.mjs +0 -182
- package/dist/parser-CJfMsOl6.mjs.map +0 -1
- package/dist/public-tools-CvlZcysd.mjs.map +0 -1
- package/dist/resolver-2jXNtWQO.mjs +0 -184
- package/dist/resolver-2jXNtWQO.mjs.map +0 -1
- package/dist/resolver-CZdQwKvh.cjs +0 -186
- package/dist/runner-CVnjpqc-.mjs +0 -149
- package/dist/runner-CVnjpqc-.mjs.map +0 -1
- package/dist/runner-bLy0pTr_.cjs +0 -147
- package/dist/selector-BvXM9jbe.mjs +0 -12
- package/dist/selector-BvXM9jbe.mjs.map +0 -1
- package/dist/selector-Dps_ZFxq.cjs +0 -10
- package/dist/session-BT7VpbAd.cjs +0 -127
- package/dist/session-DROyhebe.mjs +0 -117
- package/dist/session-DROyhebe.mjs.map +0 -1
- package/dist/store-C2B_AssI.mjs +0 -231
- package/dist/store-C2B_AssI.mjs.map +0 -1
- package/dist/store-CQhU8dz8.cjs +0 -242
- package/dist/tool-visibility-BpyZHRBi.mjs.map +0 -1
- package/dist/vault-B2y78Ypu.cjs +0 -560
- package/dist/vault-z35Dohdq.mjs +0 -560
- package/dist/vault-z35Dohdq.mjs.map +0 -1
- package/dist/viz-D1620cBX.cjs +0 -44
- package/dist/viz-DB5XFG1z.mjs +0 -35
- package/dist/viz-DB5XFG1z.mjs.map +0 -1
- package/docs/knowledge-exports.md +0 -204
- package/docs/obsidian-vault.md +0 -130
- package/skills/ci-case/SKILL.md +0 -43
package/dist/mcp-proxy.cjs
CHANGED
|
@@ -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-
|
|
5
|
-
const require_tool_visibility = require("./tool-visibility
|
|
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
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
91
|
-
"4.
|
|
92
|
-
"5.
|
|
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
|
|
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
|
|
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
|
|
129
|
-
"Use network_capabilities first when network support is unknown, then call
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
202
|
+
case "exposure_profile": return {
|
|
197
203
|
network: zod.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
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
|
|
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" ? "
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
591
|
-
const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-
|
|
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-
|
|
618
|
-
const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-
|
|
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-
|
|
641
|
-
const { activeDataDir, findActiveWorkspace } = await Promise.resolve().then(() => require("./active-
|
|
642
|
-
const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-
|
|
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
|
-
|
|
801
|
-
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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:
|
|
905
|
+
readOnlyHint: true,
|
|
809
906
|
destructiveHint: false,
|
|
810
|
-
idempotentHint:
|
|
811
|
-
openWorldHint:
|
|
907
|
+
idempotentHint: true,
|
|
908
|
+
openWorldHint: true
|
|
812
909
|
}
|
|
813
|
-
}, async ({
|
|
910
|
+
}, async ({ address, network, compare_address, include_attachments }) => {
|
|
814
911
|
try {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
940
|
+
text: err.message
|
|
862
941
|
}],
|
|
863
|
-
isError:
|
|
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:
|
|
947
|
+
text: `Address risk failed: ${err.message}`
|
|
886
948
|
}],
|
|
887
|
-
isError:
|
|
949
|
+
isError: true
|
|
888
950
|
};
|
|
889
|
-
} catch (err) {
|
|
890
|
-
return caseToolError("Case resume", err);
|
|
891
951
|
}
|
|
892
952
|
});
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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:
|
|
971
|
+
openWorldHint: true
|
|
906
972
|
}
|
|
907
|
-
}, async ({
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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:
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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:
|
|
1014
|
+
text: `Trace victim funds failed: ${err.message}`
|
|
943
1015
|
}],
|
|
944
|
-
isError:
|
|
1016
|
+
isError: true
|
|
945
1017
|
};
|
|
946
|
-
} catch (err) {
|
|
947
|
-
return caseToolError("Evidence verify", err);
|
|
948
1018
|
}
|
|
949
1019
|
});
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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:
|
|
1037
|
+
openWorldHint: true
|
|
967
1038
|
}
|
|
968
|
-
}, async ({
|
|
1039
|
+
}, async ({ suspect_addresses, incident_timestamp_ms, network, max_hops, per_address_limit, min_amount_sum, include_attachments }) => {
|
|
969
1040
|
try {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
-
|
|
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:
|
|
1023
|
-
case_id,
|
|
1024
|
-
address,
|
|
1025
|
-
updated: true
|
|
1026
|
-
}, null, 2)
|
|
1072
|
+
text: err.message
|
|
1027
1073
|
}],
|
|
1028
|
-
isError:
|
|
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:
|
|
1079
|
+
text: `Trace suspect funds failed: ${err.message}`
|
|
1051
1080
|
}],
|
|
1052
|
-
isError:
|
|
1081
|
+
isError: true
|
|
1053
1082
|
};
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
return caseToolError("Session start", err);
|
|
1056
1083
|
}
|
|
1057
1084
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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:
|
|
1099
|
+
openWorldHint: true
|
|
1070
1100
|
}
|
|
1071
|
-
}, async ({
|
|
1101
|
+
}, async ({ deposit_addresses, network, max_hops, include_attachments }) => {
|
|
1072
1102
|
try {
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
|
1515
|
-
const
|
|
1516
|
-
|
|
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:
|
|
1196
|
+
text: result.summaryText
|
|
1520
1197
|
}],
|
|
1521
|
-
|
|
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: `
|
|
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
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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`);
|