chain-insights 0.35.1 → 0.36.0

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 CHANGED
@@ -163,9 +163,8 @@ database and `Address` carries no `network` property at all. See
163
163
  [Graph query compatibility](docs/graph-query-compatibility.md).
164
164
 
165
165
  Agent installs include `chain-insights-address-risk` for one-address
166
- screens, `chain-insights-cypher` for graph-query dialect rules,
167
- `chain-insights-schema-evm` for the EVM / Robinhood graph map, and
168
- `chain-insights-schema-bittensor` for the Bittensor graph map.
166
+ screens, `chain-insights-cypher` for graph-query dialect rules, and
167
+ `chain-insights-schema-evm` for the EVM / Robinhood graph map.
169
168
 
170
169
  ## Billing: Billable Units
171
170
 
package/bin/install.cjs CHANGED
@@ -48,7 +48,6 @@ const srcSkillsDir = path.join(__dirname, '..', 'skills')
48
48
  const PUBLIC_SKILL_NAMES = Object.freeze([
49
49
  'chain-insights-address-risk',
50
50
  'chain-insights-cypher',
51
- 'chain-insights-schema-bittensor',
52
51
  'chain-insights-schema-evm',
53
52
  ])
54
53
  const RETIRED_SKILL_NAMES = Object.freeze([
@@ -56,6 +55,7 @@ const RETIRED_SKILL_NAMES = Object.freeze([
56
55
  'chain-insights-developer-experience',
57
56
  'chain-insights-investigation',
58
57
  'chain-insights-monitoring',
58
+ 'chain-insights-schema-bittensor',
59
59
  'ci-status',
60
60
  'test-chain-insights-graph',
61
61
  ])
package/dist/cli.cjs CHANGED
@@ -301,11 +301,11 @@ async function printSubscriptionPassGuidance(pass) {
301
301
  function addAmlAddressRiskCommand(parent, networksCommand) {
302
302
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new commander.Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
303
303
  try {
304
- const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-CZTDEjqJ.cjs"));
304
+ const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
305
305
  const requestedVersion = opts.toolVersion ?? opts.version;
306
306
  resolveAmlAddressRiskVersion(requestedVersion);
307
307
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
308
- const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-CZTDEjqJ.cjs"));
308
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
309
309
  const result = await runAmlAddressRisk(client, {
310
310
  address: opts.address,
311
311
  network: opts.network,
package/dist/cli.mjs CHANGED
@@ -299,11 +299,11 @@ async function printSubscriptionPassGuidance(pass) {
299
299
  function addAmlAddressRiskCommand(parent, networksCommand) {
300
300
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
301
301
  try {
302
- const { resolveAmlAddressRiskVersion } = await import("./public-tools-CsBmOyeQ.mjs");
302
+ const { resolveAmlAddressRiskVersion } = await import("./public-tools-rKIzmn6f.mjs");
303
303
  const requestedVersion = opts.toolVersion ?? opts.version;
304
304
  resolveAmlAddressRiskVersion(requestedVersion);
305
305
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
306
- const { runAmlAddressRisk } = await import("./public-tools-CsBmOyeQ.mjs");
306
+ const { runAmlAddressRisk } = await import("./public-tools-rKIzmn6f.mjs");
307
307
  const result = await runAmlAddressRisk(client, {
308
308
  address: opts.address,
309
309
  network: opts.network,
@@ -62,21 +62,22 @@ const GRAPH_SCHEMA_HINTS = [
62
62
  "Graph query hints:",
63
63
  "- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.",
64
64
  "- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.",
65
- "- Address nodes carry address, network, labels, and is_exchange. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.",
65
+ "- Address nodes carry address, network, labels, and the role flags is_exchange, is_scam, is_victim and is_sanctioned. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.",
66
+ "- Labels hold role words, never detector names: Exchange (plus the exchange name), Scam, Victim and Sanctioned (plus the entity name). Each role is also a node label and a flag on the Address: :Exchange and is_exchange follow the exchange label, :Scam and is_scam the risk label, :Victim and is_victim the protection label, :Sanctioned and is_sanctioned the sanctioned label. Each flag is present only when true and absent otherwise, never false: test IS NOT NULL or IS NULL, not = false. A withdrawn label removes its node label and its flag.",
66
67
  "- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)); transaction anchors resolve through USE facts: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) RETURN t.tx_id ORDER BY t.block_timestamp ASC LIMIT 1.",
67
68
  "- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd).",
68
- "- The risk verdict lives on topology nodes (risk_score float, risk_level string). Labels and per-label risk also live on the address node (labels array + label_risk entries: label, risk_level, updated_timestamp). USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.",
69
+ "- The risk verdict lives on topology nodes (risk_score float, risk_level string). An absent risk_score means UNSCORED: the model gave the address no verdict, which is no signal and never low risk. Labels and per-label risk also live on the address node: the labels array plus three parallel lists, label_risk_labels, label_risk_levels and label_risk_updated_timestamps, where entry i of each list is one label row. USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.",
69
70
  "- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, price_usd, and price_missing. Every TRANSFER query — row-select or count()/sum() aggregate — requires an indexed predicate: address equality on either endpoint (for example {address: \"...\"} on from or to), a WHERE t.tx_id = \"...\" equality (on EVM networks tx_id is the 0x transaction hash), or a bare WHERE t.block_date = \"YYYY-MM-DD\" bound, which t.block_timestamp bounds in epoch milliseconds may narrow to a time window; a bare LIMIT with no indexed predicate is rejected, since facts_transfers_view is a full transfer-history table, not a small per-address dimension view.",
70
71
  "- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.",
71
72
  "- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.",
72
- "- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the approved operator executed transfers on the owner behalf. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.",
73
- "- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. No other edge fields exist: tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.",
74
- "- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened.",
73
+ "- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the transaction sender that moved the owner's tokens (ERC-20/721), or the event operator (ERC-1155); not the approved spender. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.",
74
+ "- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. pair_key and synced_through_height are internal sync bookkeeping, not to be queried. Tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.",
75
+ "- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened. Swap attribution is read from SWAPPED, the aggregate (strength, pools, families), or from the USE facts SWAP row, one route. FLOWS_TO carries value only.",
75
76
  "- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.",
76
77
  "- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.",
77
78
  "- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.",
78
79
  "- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id, pools, families and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. SWAP and LIQUIDITY_* need an address equality on either endpoint or a tx_id equality. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event and needs a bare block_date bound or a tx_id equality. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.",
79
- "- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. At a Pool, follow the pool trace rule above.",
80
+ "- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. is_exchange is absent unless true, so a labelled node with no is_exchange is walked through, and is_scam, is_victim and is_sanctioned do not end a walk. At a Pool, follow the pool trace rule above.",
80
81
  "- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route. ANY SHORTEST and ALL SHORTEST take the same pattern. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.",
81
82
  "- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.address AS address, n.network AS network, n.labels AS labels, n.risk_score AS risk_score, n.risk_level AS risk_level LIMIT 20",
82
83
  "- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count LIMIT 20",
@@ -767,7 +768,7 @@ async function createProxy() {
767
768
  }],
768
769
  isError: true
769
770
  };
770
- const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-CZTDEjqJ.cjs"));
771
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
771
772
  const result = await runAmlAddressRisk(remoteClient, {
772
773
  address,
773
774
  network,
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.d.cts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;YA+BY;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KA2BtE,iBAAiB,eAAe,EAAE;wBAoEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
1
+ {"version":3,"file":"mcp-proxy.d.cts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;YA+BY;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KA2BtE,iBAAiB,eAAe,EAAE;wBAqEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.d.mts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;YA+BY;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KA2BtE,iBAAiB,eAAe,EAAE;wBAoEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
1
+ {"version":3,"file":"mcp-proxy.d.mts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;YA+BY;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KA2BtE,iBAAiB,eAAe,EAAE;wBAqEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
@@ -58,21 +58,22 @@ const GRAPH_SCHEMA_HINTS = [
58
58
  "Graph query hints:",
59
59
  "- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.",
60
60
  "- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.",
61
- "- Address nodes carry address, network, labels, and is_exchange. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.",
61
+ "- Address nodes carry address, network, labels, and the role flags is_exchange, is_scam, is_victim and is_sanctioned. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.",
62
+ "- Labels hold role words, never detector names: Exchange (plus the exchange name), Scam, Victim and Sanctioned (plus the entity name). Each role is also a node label and a flag on the Address: :Exchange and is_exchange follow the exchange label, :Scam and is_scam the risk label, :Victim and is_victim the protection label, :Sanctioned and is_sanctioned the sanctioned label. Each flag is present only when true and absent otherwise, never false: test IS NOT NULL or IS NULL, not = false. A withdrawn label removes its node label and its flag.",
62
63
  "- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)); transaction anchors resolve through USE facts: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) RETURN t.tx_id ORDER BY t.block_timestamp ASC LIMIT 1.",
63
64
  "- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd).",
64
- "- The risk verdict lives on topology nodes (risk_score float, risk_level string). Labels and per-label risk also live on the address node (labels array + label_risk entries: label, risk_level, updated_timestamp). USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.",
65
+ "- The risk verdict lives on topology nodes (risk_score float, risk_level string). An absent risk_score means UNSCORED: the model gave the address no verdict, which is no signal and never low risk. Labels and per-label risk also live on the address node: the labels array plus three parallel lists, label_risk_labels, label_risk_levels and label_risk_updated_timestamps, where entry i of each list is one label row. USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.",
65
66
  "- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, price_usd, and price_missing. Every TRANSFER query — row-select or count()/sum() aggregate — requires an indexed predicate: address equality on either endpoint (for example {address: \"...\"} on from or to), a WHERE t.tx_id = \"...\" equality (on EVM networks tx_id is the 0x transaction hash), or a bare WHERE t.block_date = \"YYYY-MM-DD\" bound, which t.block_timestamp bounds in epoch milliseconds may narrow to a time window; a bare LIMIT with no indexed predicate is rejected, since facts_transfers_view is a full transfer-history table, not a small per-address dimension view.",
66
67
  "- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.",
67
68
  "- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.",
68
- "- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the approved operator executed transfers on the owner behalf. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.",
69
- "- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. No other edge fields exist: tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.",
70
- "- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened.",
69
+ "- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the transaction sender that moved the owner's tokens (ERC-20/721), or the event operator (ERC-1155); not the approved spender. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.",
70
+ "- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. pair_key and synced_through_height are internal sync bookkeeping, not to be queried. Tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.",
71
+ "- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened. Swap attribution is read from SWAPPED, the aggregate (strength, pools, families), or from the USE facts SWAP row, one route. FLOWS_TO carries value only.",
71
72
  "- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.",
72
73
  "- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.",
73
74
  "- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.",
74
75
  "- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id, pools, families and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. SWAP and LIQUIDITY_* need an address equality on either endpoint or a tx_id equality. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event and needs a bare block_date bound or a tx_id equality. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.",
75
- "- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. At a Pool, follow the pool trace rule above.",
76
+ "- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. is_exchange is absent unless true, so a labelled node with no is_exchange is walked through, and is_scam, is_victim and is_sanctioned do not end a walk. At a Pool, follow the pool trace rule above.",
76
77
  "- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route. ANY SHORTEST and ALL SHORTEST take the same pattern. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.",
77
78
  "- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.address AS address, n.network AS network, n.labels AS labels, n.risk_score AS risk_score, n.risk_level AS risk_level LIMIT 20",
78
79
  "- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count LIMIT 20",
@@ -763,7 +764,7 @@ async function createProxy() {
763
764
  }],
764
765
  isError: true
765
766
  };
766
- const { runAmlAddressRisk } = await import("./public-tools-CsBmOyeQ.mjs");
767
+ const { runAmlAddressRisk } = await import("./public-tools-rKIzmn6f.mjs");
767
768
  const result = await runAmlAddressRisk(remoteClient, {
768
769
  address,
769
770
  network,
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.mjs","names":[],"sources":["../src/mcp/proxy.ts"],"sourcesContent":["import { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ContentBlock, GetPromptResult } from '@modelcontextprotocol/sdk/types.js'\nimport * as z from 'zod'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { McpTool } from './schema-cache.js'\nimport {\n HIDDEN_REMOTE_TOOL_NAMES,\n PUBLIC_MCP_TOOL_ALLOWED_ARGS,\n PUBLIC_MCP_TOOL_REQUIRED_ARGS,\n} from './tool-visibility.js'\nimport { PaymentRequiredError } from './client.js'\nimport { primitiveBackendUsageStatus } from './usage-status.js'\nimport { unavailableSubscriptionStatus } from './subscription-status.js'\nimport { mirrorGraphNetworkCapabilities } from './capabilities.js'\nimport { actionLogSignalsFromResult, appendActionLog } from './action-log.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'meta_network_capabilities',\n 'meta_usage_status',\n 'meta_subscription_status',\n 'meta_help',\n 'wallet_balance',\n])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\n\nexport type McpProxyMode = 'workspace' | 'stateless'\n\nexport function resolveMcpProxyMode(env: NodeJS.ProcessEnv = process.env): McpProxyMode {\n const raw = env['CHAIN_INSIGHTS_MCP_PROXY_MODE']?.trim().toLowerCase()\n if (!raw || raw === 'stateless') return 'stateless'\n if (raw === 'workspace') return 'workspace'\n if (raw === 'no-workspace' || raw === 'workspace-less') return 'stateless'\n throw new Error(`CHAIN_INSIGHTS_MCP_PROXY_MODE must be workspace or stateless; got \"${raw}\"`)\n}\n\nconst GRAPH_LAYERS_TEXT =\n 'Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment.'\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n meta_network_capabilities: 'Return the current Chain Insights network and tool support matrix.',\n meta_usage_status: \"Return the caller's public free graph_query quota for the current UTC day.\",\n meta_subscription_status:\n \"Return the caller's CIA subscription window end, daily allowance, consumption, and tier.\",\n meta_help: 'Show a short guide to Chain Insights tools and workflow.',\n wallet_balance:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n aml_address_risk:\n 'Screen one blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with another address. Topology reads cover full lifetime history in one unified graph. Omit version to use the latest contract, or pass version=v1 to pin the v1 contract.',\n graph_query: `Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} Preserve full addresses exactly.`,\n graph_query_batch:\n 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\nconst FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES = ['graph_query', 'graph_query_batch'] as const\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\nconst NETWORK_DESCRIPTION =\n 'Network to query. Call meta_network_capabilities first and pass a name GraphRAG advertised. CIA does not pick a default network.'\nconst NETWORK_SCHEMA = z.string().min(1).describe(NETWORK_DESCRIPTION)\n\nconst EMPTY_INPUT_SCHEMA = z.strictObject({})\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. Do not call investigation tools until required arguments are known. Network is required; use meta_network_capabilities to check supported networks and available tools, or ask the user if missing.',\n '2. Use aml_address_risk for single-address enrichment. Use graph_query(_batch) for graph-level questions that aml_address_risk does not answer.',\n '3. Preserve tool summaries and structured facts as returned. Keep full blockchain addresses intact.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints:',\n '- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.',\n '- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.',\n '- Address nodes carry address, network, labels, and is_exchange. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.',\n '- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)); transaction anchors resolve through USE facts: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) RETURN t.tx_id ORDER BY t.block_timestamp ASC LIMIT 1.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd).',\n '- The risk verdict lives on topology nodes (risk_score float, risk_level string). Labels and per-label risk also live on the address node (labels array + label_risk entries: label, risk_level, updated_timestamp). USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.',\n '- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, price_usd, and price_missing. Every TRANSFER query — row-select or count()/sum() aggregate — requires an indexed predicate: address equality on either endpoint (for example {address: \"...\"} on from or to), a WHERE t.tx_id = \"...\" equality (on EVM networks tx_id is the 0x transaction hash), or a bare WHERE t.block_date = \"YYYY-MM-DD\" bound, which t.block_timestamp bounds in epoch milliseconds may narrow to a time window; a bare LIMIT with no indexed predicate is rejected, since facts_transfers_view is a full transfer-history table, not a small per-address dimension view.',\n '- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.',\n '- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the approved operator executed transfers on the owner behalf. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.',\n '- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. No other edge fields exist: tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.',\n '- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened.',\n \"- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.\",\n '- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.',\n '- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.',\n '- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id, pools, families and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. SWAP and LIQUIDITY_* need an address equality on either endpoint or a tx_id equality. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event and needs a bare block_date bound or a tx_id equality. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.',\n '- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. At a Pool, follow the pool trace rule above.',\n '- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route. ANY SHORTEST and ALL SHORTEST take the same pattern. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.',\n '- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.address AS address, n.network AS network, n.labels AS labels, n.risk_score AS risk_score, n.risk_level AS risk_level LIMIT 20',\n '- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count LIMIT 20',\n '- graph_query uses the active Chain Insights graph endpoint. Select the graph with USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment; address is the node grain, not the topology name.',\n '- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.',\n '- Use USE facts graph patterns for fact and enrichment reads. Do not query internal table namespaces directly.',\n].join('\\n')\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is an AML and graph-analysis MCP server for AI agents.',\n CHAIN_INSIGHTS_WORKFLOW,\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses or identity_resolution audit mappings.',\n].join('\\n\\n')\n\nconst STATELESS_SERVER_INSTRUCTIONS = [\n 'Chain Insights is running as a stateless AML proxy for a host application.',\n 'Use meta_network_capabilities first when network support is unknown, then call aml_address_risk, graph_query, or graph_query_batch as needed.',\n 'Use wallet_balance to inspect the local payment wallet when payment setup is needed.',\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses or identity_resolution audit mappings.',\n].join('\\n\\n')\n\n// Exported so a test can prove, for EVERY public tool, that each declared\n// schema argument also appears in PUBLIC_MCP_TOOL_ALLOWED_ARGS. An argument\n// present here but missing there is silently stripped by\n// normalizeRemoteToolArguments and the caller never learns their override was\n// ignored — the failure mode that shipped with `time_scope`.\nexport function knownPublicToolInputSchema(toolName: string): ToolInputShape | null {\n switch (toolName) {\n case 'aml_address_risk':\n return {\n address: z.string().min(1).describe('Blockchain address to screen.'),\n network: NETWORK_SCHEMA,\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address.'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version.'),\n }\n case 'graph_query':\n return {\n query: z.string().min(1).describe(`Read-only GQL/Cypher query. ${GRAPH_LAYERS_TEXT}`),\n network: NETWORK_SCHEMA,\n }\n case 'graph_query_batch':\n return {\n network: NETWORK_SCHEMA,\n queries: z\n .array(\n z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })\n )\n .min(1)\n .max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction fallbackGraphPrimitiveTools(): McpTool[] {\n return FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES.map((name) => ({\n name,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[name],\n }))\n}\n\n/**\n * Local payment wallet address for tools that address the caller on the\n * server (subscription_status). Returns null — never throws — when no wallet\n * is configured; callers degrade to an unavailable-shape result.\n */\nasync function localSubscriptionWalletAddress(): Promise<string | null> {\n try {\n const { getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return account.address\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n })\n )\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [\n {\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n },\n ],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) =>\n isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }\n ),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath =\n process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() ||\n path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(\n level: 'info' | 'error',\n event: string,\n fields: Record<string, unknown> = {}\n ): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(\n filePath,\n JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n',\n { mode: 0o600 }\n )\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((\n name: string,\n config: ToolRegistrationConfig,\n handler: ToolHandler\n ) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(\n remoteClient: RemoteToolCaller,\n logger: ReturnType<typeof createMcpLogger>\n): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const toolArgs = input.arguments ?? {}\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in PUBLIC_MCP_TOOL_REQUIRED_ARGS)) return normalized\n\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[toolName]\n if (!allowedArgs) return normalized\n return Object.fromEntries(Object.entries(normalized).filter(([key]) => allowedArgs.includes(key)))\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>\n): string | null {\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\nfunction knownPublicToolAnnotations(toolName: string): Record<string, boolean> | undefined {\n if (\n toolName === 'graph_query' ||\n toolName === 'graph_query_batch' ||\n toolName.startsWith('aml_')\n ) {\n return {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n }\n }\n return undefined\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\nfunction promptResult(text: string, description?: string): GetPromptResult {\n return {\n description,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ],\n }\n}\n\nfunction registerLocalPrompts(server: McpServer): void {\n server.registerPrompt(\n 'aml-address-risk',\n {\n title: 'AML Address Risk',\n description:\n 'Screen a blockchain address for AML risk, behavioral patterns, neighborhood profile, member addresses, and exchange links.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n address: z.string().describe('Blockchain address to screen'),\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version'),\n },\n },\n async ({ network, address, compare_address, version }) =>\n promptResult(\n [\n `Use Chain Insights aml_address_risk${version ? ` version ${version}` : ''} on ${network} for:`,\n '',\n `\\`${address}\\``,\n compare_address ? `\\nCompare with: \\`${compare_address}\\`` : '',\n '',\n 'Present the summary as-is. Do not add analysis, verdicts, or risk assessments; the tool output already contains the risk assessment.',\n ]\n .filter(Boolean)\n .join('\\n'),\n 'AML address risk screening'\n )\n )\n\n server.registerPrompt(\n 'meta-network-capabilities',\n {\n title: 'Network Capabilities',\n description: 'Inspect supported networks and available tools before selecting a network.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_network_capabilities. Report only the supported networks and available tools exactly as returned; do not infer unsupported networks.',\n 'Network capabilities'\n )\n )\n\n server.registerPrompt(\n 'meta-usage-status',\n {\n title: 'Usage Status',\n description: \"Check the caller's public free graph_query quota.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_usage_status. Report the quota fields exactly as returned.',\n 'Usage status'\n )\n )\n\n server.registerPrompt(\n 'meta-subscription-status',\n {\n title: 'Subscription Status',\n description: \"Check the caller's CIA subscription window, daily allowance, and tier.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_subscription_status. Report the subscription facts exactly as returned.',\n 'Subscription status'\n )\n )\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Graph Query',\n description: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n query: z.string().describe('Read-only GQL/Cypher query'),\n },\n },\n async ({ network, query }) =>\n promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ].join('\\n'),\n 'Graph query'\n )\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Graph Query Batch',\n description:\n 'Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n queries: z\n .string()\n .describe('JSON array of query objects with optional id and required query fields'),\n per_query_timeout_seconds: z\n .string()\n .optional()\n .describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ network, queries, per_query_timeout_seconds }) =>\n promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds\n ? `per_query_timeout_seconds: ${per_query_timeout_seconds}`\n : '',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ]\n .filter(Boolean)\n .join('\\n'),\n 'Graph query batch'\n )\n )\n\n server.registerPrompt(\n 'wallet-balance',\n {\n title: 'Wallet Balance',\n description:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights wallet_balance. Show the wallet address, payment network, token, and amount exactly as returned.',\n 'Wallet balance'\n )\n )\n\n server.registerPrompt(\n 'meta-help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and workflow guidance.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_help. Summarize the available tools and workflow guidance without inventing capabilities.',\n 'Chain Insights help'\n )\n )\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (\n GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) &&\n Array.isArray(childValue)\n ) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\nfunction sanitizeRemoteMeta(metaValue: RemoteToolResult['_meta']): RemoteToolResult['_meta'] {\n if (!metaValue || typeof metaValue !== 'object' || Array.isArray(metaValue)) return undefined\n\n const meta = { ...metaValue } as Record<string, unknown>\n delete meta.ui\n delete meta['ui/resourceUri']\n\n const chainInsights = meta.chainInsights\n if (chainInsights && typeof chainInsights === 'object' && !Array.isArray(chainInsights)) {\n const { graph: _graph, ...withoutGraph } = chainInsights as Record<string, unknown>\n if (Object.keys(withoutGraph).length > 0) meta.chainInsights = withoutGraph\n else delete meta.chainInsights\n }\n\n return Object.keys(meta).length > 0 ? (meta as RemoteToolResult['_meta']) : undefined\n}\n\nfunction normalizeRemoteToolResult(result: RemoteToolResult) {\n return {\n content: result.content ?? [],\n structuredContent: sanitizeStructuredContentForGraphPayload(result.structuredContent),\n _meta: sanitizeRemoteMeta(result._meta),\n isError: result.isError,\n }\n}\n\nfunction cleanNetworkCapabilities(value: unknown) {\n const structuredContent = isRecord(value) ? value.structuredContent : undefined\n const facts = isRecord(structuredContent) ? structuredContent.facts : undefined\n const capabilities = isRecord(facts) ? facts.capabilities : undefined\n const networks =\n isRecord(capabilities) && Array.isArray(capabilities.networks) ? capabilities.networks : []\n\n return {\n schema: 'chain-insights.result.v1' as const,\n tool: 'meta_network_capabilities',\n hint: null,\n facts: {\n capabilities: mirrorGraphNetworkCapabilities({ networks }),\n },\n }\n}\n\nfunction jsonTextResult(structuredContent: Record<string, unknown>) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n isError: false,\n }\n}\n\n/**\n * Core proxy logic — exported so tests can inject dependencies directly.\n * The IIFE at the bottom calls this with real dependencies.\n *\n * stdout purity: NEVER write to stdout in this file. Use console.error() or process.stderr.write() only.\n * All diagnostic output goes to console.error() or process.stderr.write().\n */\nexport async function createProxy(): Promise<void> {\n // Lazy imports to avoid module-load side effects (critical for stdio proxy)\n const { loadConfig } = await import('../config/index.js')\n const { activeDataDir, findActiveWorkspace } = await import('../workspace/active.js')\n const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import('./client.js')\n const { loadSchema, saveSchema } = await import('./schema-cache.js')\n\n const proxyMode = resolveMcpProxyMode()\n const workspaceArtifactsEnabled = proxyMode === 'workspace'\n const loadedConfig = await loadConfig()\n const activeWorkspace = workspaceArtifactsEnabled ? findActiveWorkspace() : null\n const config = {\n ...loadedConfig,\n dataDir: workspaceArtifactsEnabled ? activeDataDir(loadedConfig.dataDir) : loadedConfig.dataDir,\n }\n const logger = createMcpLogger(config)\n await logger.info('proxy.start', {\n data_dir: config.dataDir,\n workspace_root: activeWorkspace?.root,\n proxy_mode: proxyMode,\n graph_mcp_mode: config.graphMcpMode,\n graph_mcp_endpoint: resolveGraphMcpEndpoint(config),\n log_path: logger.filePath,\n })\n const graphMcpEndpoint = resolveGraphMcpEndpoint(config)\n\n // Build remote MCP client. The local Chain Insights MCP surface must still\n // start when the graph endpoint is temporarily unavailable so agents can use\n // help and wallet tools.\n const remoteClient = new Client({ name: 'chain-insights-proxy-client', version: PACKAGE_VERSION })\n let remoteConnected = false\n let remoteUnavailableMessage: string | undefined\n let mcpFetch: typeof fetch | undefined\n\n try {\n mcpFetch = await createConfiguredGraphMcpFetch(config)\n } catch (err) {\n await logger.error('remote.fetch_setup_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n remoteUnavailableMessage = `Chain Insights Graph setup unavailable at ${graphMcpEndpoint}: ${(err as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`\n )\n }\n\n if (mcpFetch) {\n try {\n await remoteClient.connect(\n new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch })\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n } catch {\n await logger.error('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n // StreamableHTTP failed — try SSE fallback (assumption A1 from RESEARCH.md)\n try {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await remoteClient.connect(\n new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch })\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n })\n } catch (err2) {\n await logger.error('remote.connect_failed', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n error: errorForLog(err2),\n })\n remoteUnavailableMessage = `Chain Insights Graph unreachable at ${graphMcpEndpoint}: ${(err2 as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`\n )\n }\n }\n }\n if (remoteConnected)\n installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n\n // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n if (!tools && remoteConnected) {\n // Cache miss — fetch tools from remote (client is already connected above)\n const result = await remoteClient.listTools()\n tools = result.tools as McpTool[]\n await saveSchema(tools, graphMcpEndpoint)\n await logger.info('schema.tools_loaded', {\n source: 'remote',\n count: tools.length,\n })\n } else if (tools) {\n await logger.info('schema.tools_loaded', {\n source: 'cache',\n count: tools.length,\n })\n } else {\n tools = fallbackGraphPrimitiveTools()\n await logger.info('schema.tools_loaded', {\n source: 'unavailable',\n count: tools.length,\n })\n }\n const remoteToolNames = new Set((tools ?? []).map((tool) => tool.name))\n\n // Build local stdio proxy server\n const server = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n {\n instructions: workspaceArtifactsEnabled ? SERVER_INSTRUCTIONS : STATELESS_SERVER_INSTRUCTIONS,\n }\n )\n installToolLogging(server, logger)\n\n if (remoteConnected) {\n try {\n await remoteClient.listPrompts()\n } catch (err) {\n await logger.error('remote.prompts_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n process.stderr.write(\n `Chain Insights MCP remote prompt metadata unavailable at ${graphMcpEndpoint}: ${(err as Error).message}\\n`\n )\n }\n }\n\n registerLocalPrompts(server)\n\n server.registerTool(\n 'meta_network_capabilities',\n {\n title: 'Network Capabilities',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_network_capabilities,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => {\n if (remoteConnected && remoteToolNames.has('network_capabilities')) {\n try {\n const result = await remoteClient.callTool({\n name: 'network_capabilities',\n arguments: {},\n })\n return jsonTextResult(cleanNetworkCapabilities(result))\n } catch (err) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Network capabilities failed: ${(err as Error).message}`,\n },\n ],\n isError: true,\n }\n }\n }\n return jsonTextResult(cleanNetworkCapabilities(undefined))\n }\n )\n\n server.registerTool(\n 'meta_usage_status',\n {\n title: 'Usage Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_usage_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n if (!remoteToolNames.has('usage_status')) {\n return jsonTextResult(primitiveBackendUsageStatus(graphMcpEndpoint))\n }\n const result = (await remoteClient.callTool({\n name: 'usage_status',\n arguments: {},\n })) as RemoteToolResult\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_usage_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return {\n content: [\n { type: 'text' as const, text: `Usage status failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n\n server.registerTool(\n 'meta_subscription_status',\n {\n title: 'Subscription Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_subscription_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n // Local proxy shape discipline: the tool is always present and never\n // throws. When the server tool is absent or errors, the facts explain\n // the unavailability.\n try {\n if (!remoteConnected) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n remoteUnavailableMessage ??\n `Chain Insights Graph is not connected at ${graphMcpEndpoint}`\n )\n )\n }\n if (!remoteToolNames.has('subscription_status')) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'The graph backend exposes primitive graph tools but no subscription_status tool.'\n )\n )\n }\n const walletAddress = await localSubscriptionWalletAddress()\n if (!walletAddress) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'No local payment wallet is configured; run `cia wallet create` or `cia wallet import` first.'\n )\n )\n }\n const result = (await remoteClient.callTool({\n name: 'subscription_status',\n arguments: { wallet: walletAddress },\n })) as RemoteToolResult\n if (result.isError === true) {\n const firstText = Array.isArray(result.content)\n ? result.content.find(\n (block): block is Extract<ContentBlock, { type: 'text' }> =>\n block.type === 'text' && typeof block.text === 'string'\n )\n : undefined\n const reason = firstText?.text\n ? `subscription_status failed: ${firstText.text}`\n : 'subscription_status failed: the server returned an error'\n return jsonTextResult(unavailableSubscriptionStatus(graphMcpEndpoint, reason))\n }\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_subscription_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n `subscription_status failed: ${(err as Error).message}`\n )\n )\n }\n }\n )\n\n server.registerTool(\n 'wallet_balance',\n {\n title: 'Wallet Balance',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.wallet_balance,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n const { formatWalletBalanceResult, getWalletAccount, getWalletBalanceResult } =\n await import('../wallet/tools.js')\n const account = await getWalletAccount()\n const structuredContent = await getWalletBalanceResult(account)\n return {\n content: [{ type: 'text' as const, text: formatWalletBalanceResult(structuredContent) }],\n structuredContent: structuredContent as unknown as Record<string, unknown>,\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n if (!remoteToolNames.has('aml_address_risk')) {\n server.registerTool(\n 'aml_address_risk',\n {\n title: 'Address Risk',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_address_risk,\n inputSchema: {\n address: z.string().min(1).describe('Blockchain address to screen'),\n network: NETWORK_SCHEMA,\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ address, network, compare_address, version }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n const { runAmlAddressRisk } = await import('../investigation/public-tools.js')\n const result = await runAmlAddressRisk(\n remoteClient,\n {\n address,\n network,\n compareAddress: compare_address,\n writeArtifacts: workspaceArtifactsEnabled,\n },\n version\n )\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n isError: false,\n }\n } catch (err) {\n if (err instanceof PaymentRequiredError) {\n return { content: [{ type: 'text' as const, text: err.message }], isError: true }\n }\n return {\n content: [\n { type: 'text' as const, text: `Address risk failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n }\n\n server.registerTool(\n 'meta_help',\n {\n title: 'Chain Insights Help',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_help,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: workspaceArtifactsEnabled\n ? [\n 'Chain Insights helps AI agents run AML investigation workflows.',\n '',\n CHAIN_INSIGHTS_WORKFLOW,\n '',\n 'Investigation tools:',\n '- meta_network_capabilities: inspect supported networks and available tools.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- aml_address_risk: screen one blockchain address; optionally compare it with another address.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n '',\n 'Wallet tools:',\n '- wallet_balance: show the local payment wallet address, payment network, token, and amount.',\n '- meta_help: show this overview.',\n ].join('\\n')\n : [\n 'Chain Insights stateless AML proxy for host applications.',\n '',\n 'Available graph-backed tools:',\n '- meta_network_capabilities: inspect supported networks and available tools.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- aml_address_risk: screen one blockchain address; optionally compare it with another address.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n ].join('\\n'),\n },\n ],\n isError: false,\n })\n )\n\n // Register each remote tool locally — passthrough proxy pattern\n for (const tool of tools ?? []) {\n if (HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue\n if (LOCAL_TOOL_NAMES.has(tool.name)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n const normalizedArgs = normalizeRemoteToolArguments(tool.name, args)\n const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs)\n if (validationError) {\n return {\n content: [{ type: 'text' as const, text: validationError }],\n isError: true,\n }\n }\n const request = {\n name: tool.name,\n arguments: normalizedArgs,\n }\n const requestOptions = remoteToolRequestOptions(tool.name)\n const result = requestOptions\n ? await remoteClient.callTool(request, undefined, requestOptions)\n : await remoteClient.callTool(request)\n return normalizeRemoteToolResult(result as RemoteToolResult)\n } catch (err) {\n if (err instanceof PaymentRequiredError) {\n return {\n content: [{ type: 'text' as const, text: err.message }],\n isError: true,\n }\n }\n const msg = (err as Error).message ?? String(err)\n const isTransport402 = /\\b402\\b/.test(msg) || msg.toLowerCase().includes('payment')\n if (isTransport402) {\n return {\n content: [\n {\n type: 'text' as const,\n text:\n `Payment required for ${tool.name}. This tool costs USDC on Base via x402 micropayments. ` +\n 'Next steps: run `cia wallet ready` to check funding and finish one-time payment setup, ' +\n 'run `cia wallet topup` if it says the wallet needs USDC, ' +\n 'or `cia access-key set <key>` if you have been given test access.',\n },\n ],\n isError: true,\n }\n }\n return {\n content: [{ type: 'text' as const, text: `MCP call failed: ${msg}` }],\n isError: true,\n }\n }\n }\n const toolConfig = {\n title: tool.title,\n description: claudeFacingToolDescription(tool),\n inputSchema,\n ...(knownPublicToolAnnotations(tool.name)\n ? { annotations: knownPublicToolAnnotations(tool.name) }\n : {}),\n }\n\n server.registerTool(tool.name, toolConfig, handler)\n }\n\n // Connect to stdio transport — after this line, stdout belongs to MCP\n const transport = new StdioServerTransport()\n await server.connect(transport)\n await logger.info('proxy.ready', {\n tools: [\n ...LOCAL_TOOL_NAMES,\n ...(tools ?? [])\n .map((tool) => tool.name)\n .filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n })\n\n // Signal handling — clean shutdown\n const shutdown = async () => {\n await logger.info('proxy.shutdown')\n transport.close()\n process.exit(0)\n }\n process.on('SIGINT', () => {\n void shutdown()\n })\n process.on('SIGTERM', () => {\n void shutdown()\n })\n}\n\n// Entry point — only execute when run as the main module (not when imported by tests)\n// Using process.argv check to detect direct execution vs import\nif (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\\\/g, '/'))) {\n createProxy().catch((err) => {\n process.stderr.write(`Chain Insights MCP proxy startup failed: ${(err as Error).message}\\n`)\n process.exit(1)\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-proxy.mjs","names":[],"sources":["../src/mcp/proxy.ts"],"sourcesContent":["import { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ContentBlock, GetPromptResult } from '@modelcontextprotocol/sdk/types.js'\nimport * as z from 'zod'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { McpTool } from './schema-cache.js'\nimport {\n HIDDEN_REMOTE_TOOL_NAMES,\n PUBLIC_MCP_TOOL_ALLOWED_ARGS,\n PUBLIC_MCP_TOOL_REQUIRED_ARGS,\n} from './tool-visibility.js'\nimport { PaymentRequiredError } from './client.js'\nimport { primitiveBackendUsageStatus } from './usage-status.js'\nimport { unavailableSubscriptionStatus } from './subscription-status.js'\nimport { mirrorGraphNetworkCapabilities } from './capabilities.js'\nimport { actionLogSignalsFromResult, appendActionLog } from './action-log.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'meta_network_capabilities',\n 'meta_usage_status',\n 'meta_subscription_status',\n 'meta_help',\n 'wallet_balance',\n])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\n\nexport type McpProxyMode = 'workspace' | 'stateless'\n\nexport function resolveMcpProxyMode(env: NodeJS.ProcessEnv = process.env): McpProxyMode {\n const raw = env['CHAIN_INSIGHTS_MCP_PROXY_MODE']?.trim().toLowerCase()\n if (!raw || raw === 'stateless') return 'stateless'\n if (raw === 'workspace') return 'workspace'\n if (raw === 'no-workspace' || raw === 'workspace-less') return 'stateless'\n throw new Error(`CHAIN_INSIGHTS_MCP_PROXY_MODE must be workspace or stateless; got \"${raw}\"`)\n}\n\nconst GRAPH_LAYERS_TEXT =\n 'Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment.'\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n meta_network_capabilities: 'Return the current Chain Insights network and tool support matrix.',\n meta_usage_status: \"Return the caller's public free graph_query quota for the current UTC day.\",\n meta_subscription_status:\n \"Return the caller's CIA subscription window end, daily allowance, consumption, and tier.\",\n meta_help: 'Show a short guide to Chain Insights tools and workflow.',\n wallet_balance:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n aml_address_risk:\n 'Screen one blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with another address. Topology reads cover full lifetime history in one unified graph. Omit version to use the latest contract, or pass version=v1 to pin the v1 contract.',\n graph_query: `Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} Preserve full addresses exactly.`,\n graph_query_batch:\n 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\nconst FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES = ['graph_query', 'graph_query_batch'] as const\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\nconst NETWORK_DESCRIPTION =\n 'Network to query. Call meta_network_capabilities first and pass a name GraphRAG advertised. CIA does not pick a default network.'\nconst NETWORK_SCHEMA = z.string().min(1).describe(NETWORK_DESCRIPTION)\n\nconst EMPTY_INPUT_SCHEMA = z.strictObject({})\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. Do not call investigation tools until required arguments are known. Network is required; use meta_network_capabilities to check supported networks and available tools, or ask the user if missing.',\n '2. Use aml_address_risk for single-address enrichment. Use graph_query(_batch) for graph-level questions that aml_address_risk does not answer.',\n '3. Preserve tool summaries and structured facts as returned. Keep full blockchain addresses intact.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints:',\n '- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.',\n '- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.',\n '- Address nodes carry address, network, labels, and the role flags is_exchange, is_scam, is_victim and is_sanctioned. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.',\n '- Labels hold role words, never detector names: Exchange (plus the exchange name), Scam, Victim and Sanctioned (plus the entity name). Each role is also a node label and a flag on the Address: :Exchange and is_exchange follow the exchange label, :Scam and is_scam the risk label, :Victim and is_victim the protection label, :Sanctioned and is_sanctioned the sanctioned label. Each flag is present only when true and absent otherwise, never false: test IS NOT NULL or IS NULL, not = false. A withdrawn label removes its node label and its flag.',\n '- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)); transaction anchors resolve through USE facts: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) RETURN t.tx_id ORDER BY t.block_timestamp ASC LIMIT 1.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd).',\n '- The risk verdict lives on topology nodes (risk_score float, risk_level string). An absent risk_score means UNSCORED: the model gave the address no verdict, which is no signal and never low risk. Labels and per-label risk also live on the address node: the labels array plus three parallel lists, label_risk_labels, label_risk_levels and label_risk_updated_timestamps, where entry i of each list is one label row. USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.',\n '- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, price_usd, and price_missing. Every TRANSFER query — row-select or count()/sum() aggregate — requires an indexed predicate: address equality on either endpoint (for example {address: \"...\"} on from or to), a WHERE t.tx_id = \"...\" equality (on EVM networks tx_id is the 0x transaction hash), or a bare WHERE t.block_date = \"YYYY-MM-DD\" bound, which t.block_timestamp bounds in epoch milliseconds may narrow to a time window; a bare LIMIT with no indexed predicate is rejected, since facts_transfers_view is a full transfer-history table, not a small per-address dimension view.',\n '- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.',\n \"- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the transaction sender that moved the owner's tokens (ERC-20/721), or the event operator (ERC-1155); not the approved spender. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.\",\n '- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. pair_key and synced_through_height are internal sync bookkeeping, not to be queried. Tx ids, coverage, and averages come from USE facts or inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.',\n '- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool matches no reviewed family). A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened. Swap attribution is read from SWAPPED, the aggregate (strength, pools, families), or from the USE facts SWAP row, one route. FLOWS_TO carries value only.',\n \"- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.\",\n '- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.',\n '- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.',\n '- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id, pools, families and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. SWAP and LIQUIDITY_* need an address equality on either endpoint or a tx_id equality. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event and needs a bare block_date bound or a tx_id equality. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.',\n '- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. is_exchange is absent unless true, so a labelled node with no is_exchange is walked through, and is_scam, is_victim and is_sanctioned do not end a walk. At a Pool, follow the pool trace rule above.',\n '- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route. ANY SHORTEST and ALL SHORTEST take the same pattern. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.',\n '- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.address AS address, n.network AS network, n.labels AS labels, n.risk_score AS risk_score, n.risk_level AS risk_level LIMIT 20',\n '- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count LIMIT 20',\n '- graph_query uses the active Chain Insights graph endpoint. Select the graph with USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment; address is the node grain, not the topology name.',\n '- All graph_query calls are read-only. Never use CREATE, INSERT, MERGE, SET, DELETE, REMOVE, DROP, DETACH, ADD, CONNECT, DISCONNECT, ALTER, TRUNCATE, GRANT, or REVOKE.',\n '- Use USE facts graph patterns for fact and enrichment reads. Do not query internal table namespaces directly.',\n].join('\\n')\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is an AML and graph-analysis MCP server for AI agents.',\n CHAIN_INSIGHTS_WORKFLOW,\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses or identity_resolution audit mappings.',\n].join('\\n\\n')\n\nconst STATELESS_SERVER_INSTRUCTIONS = [\n 'Chain Insights is running as a stateless AML proxy for a host application.',\n 'Use meta_network_capabilities first when network support is unknown, then call aml_address_risk, graph_query, or graph_query_batch as needed.',\n 'Use wallet_balance to inspect the local payment wallet when payment setup is needed.',\n GRAPH_SCHEMA_HINTS,\n 'Presentation rules: preserve tool summaries as returned; never truncate blockchain addresses or identity_resolution audit mappings.',\n].join('\\n\\n')\n\n// Exported so a test can prove, for EVERY public tool, that each declared\n// schema argument also appears in PUBLIC_MCP_TOOL_ALLOWED_ARGS. An argument\n// present here but missing there is silently stripped by\n// normalizeRemoteToolArguments and the caller never learns their override was\n// ignored — the failure mode that shipped with `time_scope`.\nexport function knownPublicToolInputSchema(toolName: string): ToolInputShape | null {\n switch (toolName) {\n case 'aml_address_risk':\n return {\n address: z.string().min(1).describe('Blockchain address to screen.'),\n network: NETWORK_SCHEMA,\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address.'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version.'),\n }\n case 'graph_query':\n return {\n query: z.string().min(1).describe(`Read-only GQL/Cypher query. ${GRAPH_LAYERS_TEXT}`),\n network: NETWORK_SCHEMA,\n }\n case 'graph_query_batch':\n return {\n network: NETWORK_SCHEMA,\n queries: z\n .array(\n z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })\n )\n .min(1)\n .max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction fallbackGraphPrimitiveTools(): McpTool[] {\n return FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES.map((name) => ({\n name,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[name],\n }))\n}\n\n/**\n * Local payment wallet address for tools that address the caller on the\n * server (subscription_status). Returns null — never throws — when no wallet\n * is configured; callers degrade to an unavailable-shape result.\n */\nasync function localSubscriptionWalletAddress(): Promise<string | null> {\n try {\n const { getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return account.address\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n })\n )\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [\n {\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n },\n ],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) =>\n isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }\n ),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath =\n process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() ||\n path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(\n level: 'info' | 'error',\n event: string,\n fields: Record<string, unknown> = {}\n ): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(\n filePath,\n JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n',\n { mode: 0o600 }\n )\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((\n name: string,\n config: ToolRegistrationConfig,\n handler: ToolHandler\n ) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(\n remoteClient: RemoteToolCaller,\n logger: ReturnType<typeof createMcpLogger>\n): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const toolArgs = input.arguments ?? {}\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in PUBLIC_MCP_TOOL_REQUIRED_ARGS)) return normalized\n\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[toolName]\n if (!allowedArgs) return normalized\n return Object.fromEntries(Object.entries(normalized).filter(([key]) => allowedArgs.includes(key)))\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>\n): string | null {\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\nfunction knownPublicToolAnnotations(toolName: string): Record<string, boolean> | undefined {\n if (\n toolName === 'graph_query' ||\n toolName === 'graph_query_batch' ||\n toolName.startsWith('aml_')\n ) {\n return {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n }\n }\n return undefined\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\nfunction promptResult(text: string, description?: string): GetPromptResult {\n return {\n description,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text,\n },\n },\n ],\n }\n}\n\nfunction registerLocalPrompts(server: McpServer): void {\n server.registerPrompt(\n 'aml-address-risk',\n {\n title: 'AML Address Risk',\n description:\n 'Screen a blockchain address for AML risk, behavioral patterns, neighborhood profile, member addresses, and exchange links.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n address: z.string().describe('Blockchain address to screen'),\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version'),\n },\n },\n async ({ network, address, compare_address, version }) =>\n promptResult(\n [\n `Use Chain Insights aml_address_risk${version ? ` version ${version}` : ''} on ${network} for:`,\n '',\n `\\`${address}\\``,\n compare_address ? `\\nCompare with: \\`${compare_address}\\`` : '',\n '',\n 'Present the summary as-is. Do not add analysis, verdicts, or risk assessments; the tool output already contains the risk assessment.',\n ]\n .filter(Boolean)\n .join('\\n'),\n 'AML address risk screening'\n )\n )\n\n server.registerPrompt(\n 'meta-network-capabilities',\n {\n title: 'Network Capabilities',\n description: 'Inspect supported networks and available tools before selecting a network.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_network_capabilities. Report only the supported networks and available tools exactly as returned; do not infer unsupported networks.',\n 'Network capabilities'\n )\n )\n\n server.registerPrompt(\n 'meta-usage-status',\n {\n title: 'Usage Status',\n description: \"Check the caller's public free graph_query quota.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_usage_status. Report the quota fields exactly as returned.',\n 'Usage status'\n )\n )\n\n server.registerPrompt(\n 'meta-subscription-status',\n {\n title: 'Subscription Status',\n description: \"Check the caller's CIA subscription window, daily allowance, and tier.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_subscription_status. Report the subscription facts exactly as returned.',\n 'Subscription status'\n )\n )\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Graph Query',\n description: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n query: z.string().describe('Read-only GQL/Cypher query'),\n },\n },\n async ({ network, query }) =>\n promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ].join('\\n'),\n 'Graph query'\n )\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Graph Query Batch',\n description:\n 'Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n queries: z\n .string()\n .describe('JSON array of query objects with optional id and required query fields'),\n per_query_timeout_seconds: z\n .string()\n .optional()\n .describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ network, queries, per_query_timeout_seconds }) =>\n promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds\n ? `per_query_timeout_seconds: ${per_query_timeout_seconds}`\n : '',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ]\n .filter(Boolean)\n .join('\\n'),\n 'Graph query batch'\n )\n )\n\n server.registerPrompt(\n 'wallet-balance',\n {\n title: 'Wallet Balance',\n description:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights wallet_balance. Show the wallet address, payment network, token, and amount exactly as returned.',\n 'Wallet balance'\n )\n )\n\n server.registerPrompt(\n 'meta-help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and workflow guidance.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_help. Summarize the available tools and workflow guidance without inventing capabilities.',\n 'Chain Insights help'\n )\n )\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (\n GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) &&\n Array.isArray(childValue)\n ) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\nfunction sanitizeRemoteMeta(metaValue: RemoteToolResult['_meta']): RemoteToolResult['_meta'] {\n if (!metaValue || typeof metaValue !== 'object' || Array.isArray(metaValue)) return undefined\n\n const meta = { ...metaValue } as Record<string, unknown>\n delete meta.ui\n delete meta['ui/resourceUri']\n\n const chainInsights = meta.chainInsights\n if (chainInsights && typeof chainInsights === 'object' && !Array.isArray(chainInsights)) {\n const { graph: _graph, ...withoutGraph } = chainInsights as Record<string, unknown>\n if (Object.keys(withoutGraph).length > 0) meta.chainInsights = withoutGraph\n else delete meta.chainInsights\n }\n\n return Object.keys(meta).length > 0 ? (meta as RemoteToolResult['_meta']) : undefined\n}\n\nfunction normalizeRemoteToolResult(result: RemoteToolResult) {\n return {\n content: result.content ?? [],\n structuredContent: sanitizeStructuredContentForGraphPayload(result.structuredContent),\n _meta: sanitizeRemoteMeta(result._meta),\n isError: result.isError,\n }\n}\n\nfunction cleanNetworkCapabilities(value: unknown) {\n const structuredContent = isRecord(value) ? value.structuredContent : undefined\n const facts = isRecord(structuredContent) ? structuredContent.facts : undefined\n const capabilities = isRecord(facts) ? facts.capabilities : undefined\n const networks =\n isRecord(capabilities) && Array.isArray(capabilities.networks) ? capabilities.networks : []\n\n return {\n schema: 'chain-insights.result.v1' as const,\n tool: 'meta_network_capabilities',\n hint: null,\n facts: {\n capabilities: mirrorGraphNetworkCapabilities({ networks }),\n },\n }\n}\n\nfunction jsonTextResult(structuredContent: Record<string, unknown>) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }],\n structuredContent,\n isError: false,\n }\n}\n\n/**\n * Core proxy logic — exported so tests can inject dependencies directly.\n * The IIFE at the bottom calls this with real dependencies.\n *\n * stdout purity: NEVER write to stdout in this file. Use console.error() or process.stderr.write() only.\n * All diagnostic output goes to console.error() or process.stderr.write().\n */\nexport async function createProxy(): Promise<void> {\n // Lazy imports to avoid module-load side effects (critical for stdio proxy)\n const { loadConfig } = await import('../config/index.js')\n const { activeDataDir, findActiveWorkspace } = await import('../workspace/active.js')\n const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import('./client.js')\n const { loadSchema, saveSchema } = await import('./schema-cache.js')\n\n const proxyMode = resolveMcpProxyMode()\n const workspaceArtifactsEnabled = proxyMode === 'workspace'\n const loadedConfig = await loadConfig()\n const activeWorkspace = workspaceArtifactsEnabled ? findActiveWorkspace() : null\n const config = {\n ...loadedConfig,\n dataDir: workspaceArtifactsEnabled ? activeDataDir(loadedConfig.dataDir) : loadedConfig.dataDir,\n }\n const logger = createMcpLogger(config)\n await logger.info('proxy.start', {\n data_dir: config.dataDir,\n workspace_root: activeWorkspace?.root,\n proxy_mode: proxyMode,\n graph_mcp_mode: config.graphMcpMode,\n graph_mcp_endpoint: resolveGraphMcpEndpoint(config),\n log_path: logger.filePath,\n })\n const graphMcpEndpoint = resolveGraphMcpEndpoint(config)\n\n // Build remote MCP client. The local Chain Insights MCP surface must still\n // start when the graph endpoint is temporarily unavailable so agents can use\n // help and wallet tools.\n const remoteClient = new Client({ name: 'chain-insights-proxy-client', version: PACKAGE_VERSION })\n let remoteConnected = false\n let remoteUnavailableMessage: string | undefined\n let mcpFetch: typeof fetch | undefined\n\n try {\n mcpFetch = await createConfiguredGraphMcpFetch(config)\n } catch (err) {\n await logger.error('remote.fetch_setup_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n remoteUnavailableMessage = `Chain Insights Graph setup unavailable at ${graphMcpEndpoint}: ${(err as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`\n )\n }\n\n if (mcpFetch) {\n try {\n await remoteClient.connect(\n new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch })\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n } catch {\n await logger.error('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: graphMcpEndpoint,\n })\n // StreamableHTTP failed — try SSE fallback (assumption A1 from RESEARCH.md)\n try {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await remoteClient.connect(\n new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: mcpFetch })\n )\n remoteConnected = true\n await logger.info('remote.connect', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n })\n } catch (err2) {\n await logger.error('remote.connect_failed', {\n transport: 'sse',\n endpoint: graphMcpEndpoint,\n error: errorForLog(err2),\n })\n remoteUnavailableMessage = `Chain Insights Graph unreachable at ${graphMcpEndpoint}: ${(err2 as Error).message}`\n process.stderr.write(\n `Chain Insights MCP graph tools unavailable: ${remoteUnavailableMessage}. Local Chain Insights tools are still available.\\n`\n )\n }\n }\n }\n if (remoteConnected)\n installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n\n // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n if (!tools && remoteConnected) {\n // Cache miss — fetch tools from remote (client is already connected above)\n const result = await remoteClient.listTools()\n tools = result.tools as McpTool[]\n await saveSchema(tools, graphMcpEndpoint)\n await logger.info('schema.tools_loaded', {\n source: 'remote',\n count: tools.length,\n })\n } else if (tools) {\n await logger.info('schema.tools_loaded', {\n source: 'cache',\n count: tools.length,\n })\n } else {\n tools = fallbackGraphPrimitiveTools()\n await logger.info('schema.tools_loaded', {\n source: 'unavailable',\n count: tools.length,\n })\n }\n const remoteToolNames = new Set((tools ?? []).map((tool) => tool.name))\n\n // Build local stdio proxy server\n const server = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n {\n instructions: workspaceArtifactsEnabled ? SERVER_INSTRUCTIONS : STATELESS_SERVER_INSTRUCTIONS,\n }\n )\n installToolLogging(server, logger)\n\n if (remoteConnected) {\n try {\n await remoteClient.listPrompts()\n } catch (err) {\n await logger.error('remote.prompts_failed', {\n endpoint: graphMcpEndpoint,\n error: errorForLog(err),\n })\n process.stderr.write(\n `Chain Insights MCP remote prompt metadata unavailable at ${graphMcpEndpoint}: ${(err as Error).message}\\n`\n )\n }\n }\n\n registerLocalPrompts(server)\n\n server.registerTool(\n 'meta_network_capabilities',\n {\n title: 'Network Capabilities',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_network_capabilities,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => {\n if (remoteConnected && remoteToolNames.has('network_capabilities')) {\n try {\n const result = await remoteClient.callTool({\n name: 'network_capabilities',\n arguments: {},\n })\n return jsonTextResult(cleanNetworkCapabilities(result))\n } catch (err) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Network capabilities failed: ${(err as Error).message}`,\n },\n ],\n isError: true,\n }\n }\n }\n return jsonTextResult(cleanNetworkCapabilities(undefined))\n }\n )\n\n server.registerTool(\n 'meta_usage_status',\n {\n title: 'Usage Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_usage_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n if (!remoteToolNames.has('usage_status')) {\n return jsonTextResult(primitiveBackendUsageStatus(graphMcpEndpoint))\n }\n const result = (await remoteClient.callTool({\n name: 'usage_status',\n arguments: {},\n })) as RemoteToolResult\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_usage_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return {\n content: [\n { type: 'text' as const, text: `Usage status failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n\n server.registerTool(\n 'meta_subscription_status',\n {\n title: 'Subscription Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_subscription_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n // Local proxy shape discipline: the tool is always present and never\n // throws. When the server tool is absent or errors, the facts explain\n // the unavailability.\n try {\n if (!remoteConnected) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n remoteUnavailableMessage ??\n `Chain Insights Graph is not connected at ${graphMcpEndpoint}`\n )\n )\n }\n if (!remoteToolNames.has('subscription_status')) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'The graph backend exposes primitive graph tools but no subscription_status tool.'\n )\n )\n }\n const walletAddress = await localSubscriptionWalletAddress()\n if (!walletAddress) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'No local payment wallet is configured; run `cia wallet create` or `cia wallet import` first.'\n )\n )\n }\n const result = (await remoteClient.callTool({\n name: 'subscription_status',\n arguments: { wallet: walletAddress },\n })) as RemoteToolResult\n if (result.isError === true) {\n const firstText = Array.isArray(result.content)\n ? result.content.find(\n (block): block is Extract<ContentBlock, { type: 'text' }> =>\n block.type === 'text' && typeof block.text === 'string'\n )\n : undefined\n const reason = firstText?.text\n ? `subscription_status failed: ${firstText.text}`\n : 'subscription_status failed: the server returned an error'\n return jsonTextResult(unavailableSubscriptionStatus(graphMcpEndpoint, reason))\n }\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_subscription_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n `subscription_status failed: ${(err as Error).message}`\n )\n )\n }\n }\n )\n\n server.registerTool(\n 'wallet_balance',\n {\n title: 'Wallet Balance',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.wallet_balance,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n const { formatWalletBalanceResult, getWalletAccount, getWalletBalanceResult } =\n await import('../wallet/tools.js')\n const account = await getWalletAccount()\n const structuredContent = await getWalletBalanceResult(account)\n return {\n content: [{ type: 'text' as const, text: formatWalletBalanceResult(structuredContent) }],\n structuredContent: structuredContent as unknown as Record<string, unknown>,\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n if (!remoteToolNames.has('aml_address_risk')) {\n server.registerTool(\n 'aml_address_risk',\n {\n title: 'Address Risk',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.aml_address_risk,\n inputSchema: {\n address: z.string().min(1).describe('Blockchain address to screen'),\n network: NETWORK_SCHEMA,\n compare_address: z\n .string()\n .optional()\n .describe('Optional address to compare against the screened address'),\n version: z\n .string()\n .optional()\n .describe('Optional AML tool contract version. Omit to use the latest version'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ address, network, compare_address, version }) => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n const { runAmlAddressRisk } = await import('../investigation/public-tools.js')\n const result = await runAmlAddressRisk(\n remoteClient,\n {\n address,\n network,\n compareAddress: compare_address,\n writeArtifacts: workspaceArtifactsEnabled,\n },\n version\n )\n return {\n content: [{ type: 'text' as const, text: result.summaryText }],\n structuredContent: result.structuredContent,\n isError: false,\n }\n } catch (err) {\n if (err instanceof PaymentRequiredError) {\n return { content: [{ type: 'text' as const, text: err.message }], isError: true }\n }\n return {\n content: [\n { type: 'text' as const, text: `Address risk failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n }\n\n server.registerTool(\n 'meta_help',\n {\n title: 'Chain Insights Help',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_help,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => ({\n content: [\n {\n type: 'text' as const,\n text: workspaceArtifactsEnabled\n ? [\n 'Chain Insights helps AI agents run AML investigation workflows.',\n '',\n CHAIN_INSIGHTS_WORKFLOW,\n '',\n 'Investigation tools:',\n '- meta_network_capabilities: inspect supported networks and available tools.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- aml_address_risk: screen one blockchain address; optionally compare it with another address.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n '',\n 'Wallet tools:',\n '- wallet_balance: show the local payment wallet address, payment network, token, and amount.',\n '- meta_help: show this overview.',\n ].join('\\n')\n : [\n 'Chain Insights stateless AML proxy for host applications.',\n '',\n 'Available graph-backed tools:',\n '- meta_network_capabilities: inspect supported networks and available tools.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- aml_address_risk: screen one blockchain address; optionally compare it with another address.',\n '- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.',\n '- graph_query_batch: run related read-only graph-language queries through one paid graph call.',\n ].join('\\n'),\n },\n ],\n isError: false,\n })\n )\n\n // Register each remote tool locally — passthrough proxy pattern\n for (const tool of tools ?? []) {\n if (HIDDEN_REMOTE_TOOL_NAMES.has(tool.name)) continue\n if (LOCAL_TOOL_NAMES.has(tool.name)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n const normalizedArgs = normalizeRemoteToolArguments(tool.name, args)\n const validationError = validateKnownPublicToolArguments(tool.name, normalizedArgs)\n if (validationError) {\n return {\n content: [{ type: 'text' as const, text: validationError }],\n isError: true,\n }\n }\n const request = {\n name: tool.name,\n arguments: normalizedArgs,\n }\n const requestOptions = remoteToolRequestOptions(tool.name)\n const result = requestOptions\n ? await remoteClient.callTool(request, undefined, requestOptions)\n : await remoteClient.callTool(request)\n return normalizeRemoteToolResult(result as RemoteToolResult)\n } catch (err) {\n if (err instanceof PaymentRequiredError) {\n return {\n content: [{ type: 'text' as const, text: err.message }],\n isError: true,\n }\n }\n const msg = (err as Error).message ?? String(err)\n const isTransport402 = /\\b402\\b/.test(msg) || msg.toLowerCase().includes('payment')\n if (isTransport402) {\n return {\n content: [\n {\n type: 'text' as const,\n text:\n `Payment required for ${tool.name}. This tool costs USDC on Base via x402 micropayments. ` +\n 'Next steps: run `cia wallet ready` to check funding and finish one-time payment setup, ' +\n 'run `cia wallet topup` if it says the wallet needs USDC, ' +\n 'or `cia access-key set <key>` if you have been given test access.',\n },\n ],\n isError: true,\n }\n }\n return {\n content: [{ type: 'text' as const, text: `MCP call failed: ${msg}` }],\n isError: true,\n }\n }\n }\n const toolConfig = {\n title: tool.title,\n description: claudeFacingToolDescription(tool),\n inputSchema,\n ...(knownPublicToolAnnotations(tool.name)\n ? { annotations: knownPublicToolAnnotations(tool.name) }\n : {}),\n }\n\n server.registerTool(tool.name, toolConfig, handler)\n }\n\n // Connect to stdio transport — after this line, stdout belongs to MCP\n const transport = new StdioServerTransport()\n await server.connect(transport)\n await logger.info('proxy.ready', {\n tools: [\n ...LOCAL_TOOL_NAMES,\n ...(tools ?? [])\n .map((tool) => tool.name)\n .filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n })\n\n // Signal handling — clean shutdown\n const shutdown = async () => {\n await logger.info('proxy.shutdown')\n transport.close()\n process.exit(0)\n }\n process.on('SIGINT', () => {\n void shutdown()\n })\n process.on('SIGTERM', () => {\n void shutdown()\n })\n}\n\n// Entry point — only execute when run as the main module (not when imported by tests)\n// Using process.argv check to detect direct execution vs import\nif (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\\\/g, '/'))) {\n createProxy().catch((err) => {\n process.stderr.write(`Chain Insights MCP proxy startup failed: ${(err as Error).message}\\n`)\n process.exit(1)\n })\n}\n"],"mappings":"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"}
@@ -360,7 +360,7 @@ function addressProfileQuery(address) {
360
360
  id: "address_profile",
361
361
  query: [
362
362
  `MATCH (a:Address {address: "${escapeCypherString(address)}"})`,
363
- `RETURN a.address AS address, a.network AS network, a.labels AS display_labels, a.labels AS system_labels, a.is_exchange AS is_exchange, a.risk_score AS live_risk_score, a.risk_level AS live_risk_level, a.label_risk AS label_risk`,
363
+ `RETURN a.address AS address, a.network AS network, a.labels AS display_labels, a.labels AS system_labels, a.is_exchange AS is_exchange, a.risk_score AS live_risk_score, a.risk_level AS live_risk_level, a.label_risk_labels AS label_risk_labels, a.label_risk_levels AS label_risk_levels, a.label_risk_updated_timestamps AS label_risk_updated_timestamps`,
364
364
  "LIMIT 1"
365
365
  ].join(" ")
366
366
  };