chain-insights 0.36.0 → 0.37.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/dist/{capabilities-B-M8vAYp.cjs → capabilities-BbAQ4Tfa.cjs} +36 -0
- package/dist/{capabilities-ByKP5l0G.mjs → capabilities-C8DQSfyW.mjs} +37 -1
- package/dist/capabilities-C8DQSfyW.mjs.map +1 -0
- package/dist/cli.cjs +3 -3
- package/dist/cli.mjs +3 -3
- package/dist/mcp-proxy.cjs +2 -1
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +2 -1
- package/dist/mcp-proxy.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/capabilities-ByKP5l0G.mjs.map +0 -1
|
@@ -19,6 +19,38 @@ function advertisedTools(raw) {
|
|
|
19
19
|
for (const [name, status] of Object.entries(raw)) if (name.trim() !== "" && typeof status === "string") tools[name] = status;
|
|
20
20
|
return tools;
|
|
21
21
|
}
|
|
22
|
+
function advertisedLaneProgress(raw) {
|
|
23
|
+
if (!Array.isArray(raw)) return void 0;
|
|
24
|
+
const lanes = [];
|
|
25
|
+
for (const entry of raw) {
|
|
26
|
+
if (!isRecord(entry) || typeof entry.lane !== "string" || entry.lane.trim() === "") continue;
|
|
27
|
+
if (typeof entry.height === "number") lanes.push({
|
|
28
|
+
lane: entry.lane,
|
|
29
|
+
height: entry.height
|
|
30
|
+
});
|
|
31
|
+
else if (entry.height === null) lanes.push({
|
|
32
|
+
lane: entry.lane,
|
|
33
|
+
height: null
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return lanes;
|
|
37
|
+
}
|
|
38
|
+
function advertisedGraphProgress(raw) {
|
|
39
|
+
if (!isRecord(raw) || typeof raw.complete_through_block !== "number") return void 0;
|
|
40
|
+
if (!Array.isArray(raw.layers)) return void 0;
|
|
41
|
+
const layers = [];
|
|
42
|
+
for (const entry of raw.layers) {
|
|
43
|
+
if (!isRecord(entry) || typeof entry.layer !== "string" || typeof entry.position !== "number") continue;
|
|
44
|
+
layers.push({
|
|
45
|
+
layer: entry.layer,
|
|
46
|
+
position: entry.position
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
complete_through_block: raw.complete_through_block,
|
|
51
|
+
layers
|
|
52
|
+
};
|
|
53
|
+
}
|
|
22
54
|
function advertisedNetwork(raw) {
|
|
23
55
|
if (!isRecord(raw) || typeof raw.network !== "string" || raw.network.trim() === "") return null;
|
|
24
56
|
const network = raw.network.trim();
|
|
@@ -33,6 +65,10 @@ function advertisedNetwork(raw) {
|
|
|
33
65
|
if (raw.default === false) capability.default = false;
|
|
34
66
|
if (isRecord(raw.coverage)) capability.coverage = raw.coverage;
|
|
35
67
|
if (isRecord(raw.freshness)) capability.freshness = raw.freshness;
|
|
68
|
+
const laneProgress = advertisedLaneProgress(raw.lane_progress);
|
|
69
|
+
if (laneProgress) capability.lane_progress = laneProgress;
|
|
70
|
+
const graphProgress = advertisedGraphProgress(raw.graph_progress);
|
|
71
|
+
if (graphProgress) capability.graph_progress = graphProgress;
|
|
36
72
|
return capability;
|
|
37
73
|
}
|
|
38
74
|
/** Repeat GraphRAG's network list. CIA does not add, drop, or invent names. */
|
|
@@ -19,6 +19,38 @@ function advertisedTools(raw) {
|
|
|
19
19
|
for (const [name, status] of Object.entries(raw)) if (name.trim() !== "" && typeof status === "string") tools[name] = status;
|
|
20
20
|
return tools;
|
|
21
21
|
}
|
|
22
|
+
function advertisedLaneProgress(raw) {
|
|
23
|
+
if (!Array.isArray(raw)) return void 0;
|
|
24
|
+
const lanes = [];
|
|
25
|
+
for (const entry of raw) {
|
|
26
|
+
if (!isRecord(entry) || typeof entry.lane !== "string" || entry.lane.trim() === "") continue;
|
|
27
|
+
if (typeof entry.height === "number") lanes.push({
|
|
28
|
+
lane: entry.lane,
|
|
29
|
+
height: entry.height
|
|
30
|
+
});
|
|
31
|
+
else if (entry.height === null) lanes.push({
|
|
32
|
+
lane: entry.lane,
|
|
33
|
+
height: null
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return lanes;
|
|
37
|
+
}
|
|
38
|
+
function advertisedGraphProgress(raw) {
|
|
39
|
+
if (!isRecord(raw) || typeof raw.complete_through_block !== "number") return void 0;
|
|
40
|
+
if (!Array.isArray(raw.layers)) return void 0;
|
|
41
|
+
const layers = [];
|
|
42
|
+
for (const entry of raw.layers) {
|
|
43
|
+
if (!isRecord(entry) || typeof entry.layer !== "string" || typeof entry.position !== "number") continue;
|
|
44
|
+
layers.push({
|
|
45
|
+
layer: entry.layer,
|
|
46
|
+
position: entry.position
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
complete_through_block: raw.complete_through_block,
|
|
51
|
+
layers
|
|
52
|
+
};
|
|
53
|
+
}
|
|
22
54
|
function advertisedNetwork(raw) {
|
|
23
55
|
if (!isRecord(raw) || typeof raw.network !== "string" || raw.network.trim() === "") return null;
|
|
24
56
|
const network = raw.network.trim();
|
|
@@ -33,6 +65,10 @@ function advertisedNetwork(raw) {
|
|
|
33
65
|
if (raw.default === false) capability.default = false;
|
|
34
66
|
if (isRecord(raw.coverage)) capability.coverage = raw.coverage;
|
|
35
67
|
if (isRecord(raw.freshness)) capability.freshness = raw.freshness;
|
|
68
|
+
const laneProgress = advertisedLaneProgress(raw.lane_progress);
|
|
69
|
+
if (laneProgress) capability.lane_progress = laneProgress;
|
|
70
|
+
const graphProgress = advertisedGraphProgress(raw.graph_progress);
|
|
71
|
+
if (graphProgress) capability.graph_progress = graphProgress;
|
|
36
72
|
return capability;
|
|
37
73
|
}
|
|
38
74
|
/** Repeat GraphRAG's network list. CIA does not add, drop, or invent names. */
|
|
@@ -160,4 +196,4 @@ function formatNetworkCapability(network) {
|
|
|
160
196
|
//#endregion
|
|
161
197
|
export { mirrorGraphNetworkCapabilities as n, capabilities_exports as t };
|
|
162
198
|
|
|
163
|
-
//# sourceMappingURL=capabilities-
|
|
199
|
+
//# sourceMappingURL=capabilities-C8DQSfyW.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities-C8DQSfyW.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { applyMcpAuthHeaders, resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkTopologyLayer {\n enabled: boolean\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n /** Lowest height among the raw-data lanes that have started. Above it an empty answer may mean \"not indexed yet\". */\n complete_through_block?: number\n }\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n live?: NetworkTopologyLayer\n archive?: NetworkTopologyLayer\n}\n\nexport interface NetworkCapability {\n network: string\n display_name?: string\n status: string\n default?: boolean\n layers: Record<string, NetworkLayerCapability>\n tools: Record<string, string>\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n /** Lowest height among the raw-data lanes that have started. Above it an empty answer may mean \"not indexed yet\". */\n complete_through_block?: number\n }\n freshness?: {\n last_processed_at?: string\n last_successful_sync_at?: string\n max_data_age_seconds?: number\n last_processing_duration_seconds?: number\n }\n lane_progress?: NetworkLaneProgress[]\n graph_progress?: NetworkGraphProgress\n}\n\n/** One writer lane's committed height. null: the lane has not started. */\nexport interface NetworkLaneProgress {\n lane: string\n height: number | null\n}\n\n/** How far each block layer of the topology graph reaches, and their lowest. */\nexport interface NetworkGraphProgress {\n complete_through_block: number\n layers: { layer: string; position: number }[]\n}\n\nexport interface NetworkCapabilitiesDocument {\n schema: 'chain-insights.network-capabilities.v1'\n networks: NetworkCapability[]\n}\n\nconst AVAILABLE_TOOLS_PER_LINE = 3\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction advertisedTools(raw: unknown): Record<string, string> {\n if (!isRecord(raw)) return {}\n const tools: Record<string, string> = {}\n for (const [name, status] of Object.entries(raw)) {\n if (name.trim() !== '' && typeof status === 'string') tools[name] = status\n }\n return tools\n}\n\nfunction advertisedLaneProgress(raw: unknown): NetworkLaneProgress[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const lanes: NetworkLaneProgress[] = []\n for (const entry of raw) {\n if (!isRecord(entry) || typeof entry.lane !== 'string' || entry.lane.trim() === '') continue\n if (typeof entry.height === 'number') lanes.push({ lane: entry.lane, height: entry.height })\n else if (entry.height === null) lanes.push({ lane: entry.lane, height: null })\n }\n return lanes\n}\n\nfunction advertisedGraphProgress(raw: unknown): NetworkGraphProgress | undefined {\n if (!isRecord(raw) || typeof raw.complete_through_block !== 'number') return undefined\n if (!Array.isArray(raw.layers)) return undefined\n const layers: NetworkGraphProgress['layers'] = []\n for (const entry of raw.layers) {\n if (!isRecord(entry) || typeof entry.layer !== 'string' || typeof entry.position !== 'number')\n continue\n layers.push({ layer: entry.layer, position: entry.position })\n }\n return { complete_through_block: raw.complete_through_block, layers }\n}\n\nfunction advertisedNetwork(raw: unknown): NetworkCapability | null {\n if (!isRecord(raw) || typeof raw.network !== 'string' || raw.network.trim() === '') {\n return null\n }\n const network = raw.network.trim()\n const capability: NetworkCapability = {\n network,\n display_name:\n typeof raw.display_name === 'string' && raw.display_name.trim() !== ''\n ? raw.display_name\n : network,\n status: typeof raw.status === 'string' && raw.status.trim() !== '' ? raw.status : 'live',\n layers: {},\n tools: advertisedTools(raw.tools),\n }\n if (raw.default === true) capability.default = true\n if (raw.default === false) capability.default = false\n if (isRecord(raw.coverage)) capability.coverage = raw.coverage as NetworkCapability['coverage']\n if (isRecord(raw.freshness))\n capability.freshness = raw.freshness as NetworkCapability['freshness']\n const laneProgress = advertisedLaneProgress(raw.lane_progress)\n if (laneProgress) capability.lane_progress = laneProgress\n const graphProgress = advertisedGraphProgress(raw.graph_progress)\n if (graphProgress) capability.graph_progress = graphProgress\n return capability\n}\n\n/** Repeat GraphRAG's network list. CIA does not add, drop, or invent names. */\nexport function mirrorGraphNetworkCapabilities(document: {\n networks: unknown[]\n}): NetworkCapabilitiesDocument {\n return {\n schema: 'chain-insights.network-capabilities.v1',\n networks: document.networks\n .map((network) => advertisedNetwork(network))\n .filter((network): network is NetworkCapability => network !== null),\n }\n}\n\nfunction publicNetworkCapabilities(\n document: NetworkCapabilitiesDocument\n): NetworkCapabilitiesDocument {\n return mirrorGraphNetworkCapabilities(document)\n}\n\nfunction metadataNetworksUrl(endpoint: string): URL {\n const url = new URL(endpoint)\n url.pathname = '/metadata/networks'\n url.search = ''\n url.hash = ''\n return url\n}\n\nexport async function fetchNetworkCapabilities(\n config: Pick<InvestigatorConfig, 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint'>\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim()\n if (token) {\n applyMcpAuthHeaders(headers, token)\n }\n let response: Response\n try {\n response = await fetch(request, { headers })\n } catch (err) {\n throw new Error(`network capabilities unavailable at ${request}: ${(err as Error).message}`)\n }\n if (!response.ok) {\n throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`)\n }\n const parsed = (await response.json()) as NetworkCapabilitiesDocument\n if (\n parsed.schema !== 'chain-insights.network-capabilities.v1' ||\n !Array.isArray(parsed.networks)\n ) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return publicNetworkCapabilities(parsed)\n}\n\nfunction availableTools(network: NetworkCapability): string[] {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.sort()\n}\n\nfunction availableToolLines(network: NetworkCapability): string[] {\n const tools = availableTools(network)\n if (tools.length === 0) return ['none']\n const lines: string[] = []\n for (let index = 0; index < tools.length; index += AVAILABLE_TOOLS_PER_LINE) {\n lines.push(tools.slice(index, index + AVAILABLE_TOOLS_PER_LINE).join(', '))\n }\n return lines\n}\n\nfunction shortDate(value?: string): string {\n if (!value) return ''\n return value.slice(0, 10)\n}\n\nfunction datasetLabel(network: NetworkCapability): string {\n const coverage = network.coverage\n if (!coverage) return 'unknown'\n const blockRange =\n coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange =\n coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : ''\n if (blockRange === 'blocks unknown' && dateRange === '') return 'unknown'\n if (blockRange === 'blocks unknown') return dateRange\n if (dateRange === '') return blockRange\n return `${blockRange} / ${dateRange}`\n}\n\nfunction displayName(network: NetworkCapability): string {\n return network.display_name || network.network\n}\n\nfunction statusLabel(network: NetworkCapability): string {\n return network.default ? `${network.status} (default)` : network.status\n}\n\nfunction formatTable(headers: string[], rows: string[][], minimumWidths: number[]): string {\n const widths = headers.map((header, index) =>\n Math.max(\n minimumWidths[index] ?? 0,\n header.length,\n ...rows.map((row) => row[index]?.length ?? 0)\n )\n )\n const row = (values: string[]) =>\n values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [row(headers), widths.map((width) => '-'.repeat(width)).join(' '), ...rows.map(row)].join(\n '\\n'\n )\n}\n\nexport function formatNetworkOverview(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const rows = document.networks.map((network) => [\n displayName(network),\n statusLabel(network),\n datasetLabel(network),\n ])\n return formatTable(['Network', 'Status', 'Dataset'], rows, [14, 10, 38])\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const networkRows = document.networks.flatMap((network) => {\n const toolLines = availableToolLines(network)\n return toolLines.map((toolLine, index) => [\n index === 0 ? displayName(network) : '',\n index === 0 ? datasetLabel(network) : '',\n toolLine,\n ])\n })\n return formatTable(['Network', 'Dataset', 'Chain Insights tools'], networkRows, [14, 38, 64])\n}\n\nexport function findNetworkCapability(\n document: NetworkCapabilitiesDocument,\n name: string\n): NetworkCapability | undefined {\n const normalizedName = name.trim().toLowerCase()\n if (!normalizedName) return undefined\n return document.networks.find((network) =>\n [network.network, network.display_name].some(\n (candidate) => candidate?.trim().toLowerCase() === normalizedName\n )\n )\n}\n\nexport function formatNetworkCapability(network: NetworkCapability): string {\n const rows = [\n ['Network', network.display_name || network.network],\n ['Identifier', network.network],\n ['Status', network.default ? `${network.status} (default)` : network.status],\n ['Dataset', datasetLabel(network)],\n ['Available tools', availableTools(network).join(', ') || 'none'],\n ]\n const labelWidth = Math.max(...rows.map(([label]) => label.length))\n return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join('\\n')\n}\n"],"mappings":"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"}
|
package/dist/cli.cjs
CHANGED
|
@@ -213,21 +213,21 @@ async function withGraphMcpClient(name, fn) {
|
|
|
213
213
|
}
|
|
214
214
|
async function printNetworkCapabilities(opts) {
|
|
215
215
|
const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
|
|
216
|
-
const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-
|
|
216
|
+
const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-BbAQ4Tfa.cjs")).then((n) => n.capabilities_exports);
|
|
217
217
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
218
218
|
if (opts.json) console.log(JSON.stringify(document, null, 2));
|
|
219
219
|
else console.log(formatNetworkCapabilities(document));
|
|
220
220
|
}
|
|
221
221
|
async function printNetworkOverview(opts) {
|
|
222
222
|
const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
|
|
223
|
-
const { fetchNetworkCapabilities, formatNetworkOverview } = await Promise.resolve().then(() => require("./capabilities-
|
|
223
|
+
const { fetchNetworkCapabilities, formatNetworkOverview } = await Promise.resolve().then(() => require("./capabilities-BbAQ4Tfa.cjs")).then((n) => n.capabilities_exports);
|
|
224
224
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
225
225
|
if (opts.json) console.log(JSON.stringify(document, null, 2));
|
|
226
226
|
else console.log(formatNetworkOverview(document));
|
|
227
227
|
}
|
|
228
228
|
async function printNetworkCapability(name, opts) {
|
|
229
229
|
const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
|
|
230
|
-
const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await Promise.resolve().then(() => require("./capabilities-
|
|
230
|
+
const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await Promise.resolve().then(() => require("./capabilities-BbAQ4Tfa.cjs")).then((n) => n.capabilities_exports);
|
|
231
231
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
232
232
|
const network = findNetworkCapability(document, name);
|
|
233
233
|
if (!network) {
|
package/dist/cli.mjs
CHANGED
|
@@ -211,21 +211,21 @@ async function withGraphMcpClient(name, fn) {
|
|
|
211
211
|
}
|
|
212
212
|
async function printNetworkCapabilities(opts) {
|
|
213
213
|
const { loadConfig } = await import("./config-DbbPiGk0.mjs").then((n) => n.t);
|
|
214
|
-
const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-
|
|
214
|
+
const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-C8DQSfyW.mjs").then((n) => n.t);
|
|
215
215
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
216
216
|
if (opts.json) console.log(JSON.stringify(document, null, 2));
|
|
217
217
|
else console.log(formatNetworkCapabilities(document));
|
|
218
218
|
}
|
|
219
219
|
async function printNetworkOverview(opts) {
|
|
220
220
|
const { loadConfig } = await import("./config-DbbPiGk0.mjs").then((n) => n.t);
|
|
221
|
-
const { fetchNetworkCapabilities, formatNetworkOverview } = await import("./capabilities-
|
|
221
|
+
const { fetchNetworkCapabilities, formatNetworkOverview } = await import("./capabilities-C8DQSfyW.mjs").then((n) => n.t);
|
|
222
222
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
223
223
|
if (opts.json) console.log(JSON.stringify(document, null, 2));
|
|
224
224
|
else console.log(formatNetworkOverview(document));
|
|
225
225
|
}
|
|
226
226
|
async function printNetworkCapability(name, opts) {
|
|
227
227
|
const { loadConfig } = await import("./config-DbbPiGk0.mjs").then((n) => n.t);
|
|
228
|
-
const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await import("./capabilities-
|
|
228
|
+
const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await import("./capabilities-C8DQSfyW.mjs").then((n) => n.t);
|
|
229
229
|
const document = await fetchNetworkCapabilities(await loadConfig());
|
|
230
230
|
const network = findNetworkCapability(document, name);
|
|
231
231
|
if (!network) {
|
package/dist/mcp-proxy.cjs
CHANGED
|
@@ -5,7 +5,7 @@ const require_client = require("./client-Ypw9BNbn.cjs");
|
|
|
5
5
|
const require_tool_visibility = require("./tool-visibility-CyZqYnQA.cjs");
|
|
6
6
|
const require_usage_status = require("./usage-status-BdwwgYsu.cjs");
|
|
7
7
|
const require_subscription_status = require("./subscription-status-Cm7LqY_h.cjs");
|
|
8
|
-
const require_capabilities = require("./capabilities-
|
|
8
|
+
const require_capabilities = require("./capabilities-BbAQ4Tfa.cjs");
|
|
9
9
|
const require_action_log = require("./action-log-HniMMF7f.cjs");
|
|
10
10
|
let node_path = require("node:path");
|
|
11
11
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
@@ -61,6 +61,7 @@ const CHAIN_INSIGHTS_WORKFLOW = [
|
|
|
61
61
|
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
|
+
"- Coverage is per kind of data. coverage.complete_through_block is the floor of USE facts: below it every raw relationship is indexed. graph_progress.complete_through_block is the floor of USE topology, and graph_progress.layers gives each link kind its own height. An empty answer above the floor may mean not indexed yet, not absent on chain. An absent chain_tip_block means the indexers are catching up: read freshness.max_data_age_seconds for the lag.",
|
|
64
65
|
"- 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
66
|
"- 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
67
|
"- 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.",
|
package/dist/mcp-proxy.d.cts.map
CHANGED
|
@@ -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;
|
|
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;wBAsEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
|
package/dist/mcp-proxy.d.mts.map
CHANGED
|
@@ -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;
|
|
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;wBAsEvB,2BAA2B,mBAAmB;;;;;;;;wBAwlBxC,eAAe"}
|
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { t as PaymentRequiredError } from "./client-CHYskPnG.mjs";
|
|
|
3
3
|
import { n as PUBLIC_MCP_TOOL_ALLOWED_ARGS, r as PUBLIC_MCP_TOOL_REQUIRED_ARGS, t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-CJV9HezW.mjs";
|
|
4
4
|
import { t as primitiveBackendUsageStatus } from "./usage-status-CHiOPA-2.mjs";
|
|
5
5
|
import { n as unavailableSubscriptionStatus } from "./subscription-status-MyeqOmbA.mjs";
|
|
6
|
-
import { n as mirrorGraphNetworkCapabilities } from "./capabilities-
|
|
6
|
+
import { n as mirrorGraphNetworkCapabilities } from "./capabilities-C8DQSfyW.mjs";
|
|
7
7
|
import { r as appendActionLog, t as actionLogSignalsFromResult } from "./action-log-diTHyMbV.mjs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
@@ -57,6 +57,7 @@ const CHAIN_INSIGHTS_WORKFLOW = [
|
|
|
57
57
|
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
|
+
"- Coverage is per kind of data. coverage.complete_through_block is the floor of USE facts: below it every raw relationship is indexed. graph_progress.complete_through_block is the floor of USE topology, and graph_progress.layers gives each link kind its own height. An empty answer above the floor may mean not indexed yet, not absent on chain. An absent chain_tip_block means the indexers are catching up: read freshness.max_data_age_seconds for the lag.",
|
|
60
61
|
"- 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
62
|
"- 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
63
|
"- 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.",
|
package/dist/mcp-proxy.mjs.map
CHANGED
|
@@ -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 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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"}
|
|
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 '- Coverage is per kind of data. coverage.complete_through_block is the floor of USE facts: below it every raw relationship is indexed. graph_progress.complete_through_block is the floor of USE topology, and graph_progress.layers gives each link kind its own height. An empty answer above the floor may mean not indexed yet, not absent on chain. An absent chain_tip_block means the indexers are catching up: read freshness.max_data_age_seconds for the lag.',\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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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities-ByKP5l0G.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { applyMcpAuthHeaders, resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkTopologyLayer {\n enabled: boolean\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n live?: NetworkTopologyLayer\n archive?: NetworkTopologyLayer\n}\n\nexport interface NetworkCapability {\n network: string\n display_name?: string\n status: string\n default?: boolean\n layers: Record<string, NetworkLayerCapability>\n tools: Record<string, string>\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n freshness?: {\n last_processed_at?: string\n last_successful_sync_at?: string\n max_data_age_seconds?: number\n last_processing_duration_seconds?: number\n }\n}\n\nexport interface NetworkCapabilitiesDocument {\n schema: 'chain-insights.network-capabilities.v1'\n networks: NetworkCapability[]\n}\n\nconst AVAILABLE_TOOLS_PER_LINE = 3\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction advertisedTools(raw: unknown): Record<string, string> {\n if (!isRecord(raw)) return {}\n const tools: Record<string, string> = {}\n for (const [name, status] of Object.entries(raw)) {\n if (name.trim() !== '' && typeof status === 'string') tools[name] = status\n }\n return tools\n}\n\nfunction advertisedNetwork(raw: unknown): NetworkCapability | null {\n if (!isRecord(raw) || typeof raw.network !== 'string' || raw.network.trim() === '') {\n return null\n }\n const network = raw.network.trim()\n const capability: NetworkCapability = {\n network,\n display_name:\n typeof raw.display_name === 'string' && raw.display_name.trim() !== ''\n ? raw.display_name\n : network,\n status: typeof raw.status === 'string' && raw.status.trim() !== '' ? raw.status : 'live',\n layers: {},\n tools: advertisedTools(raw.tools),\n }\n if (raw.default === true) capability.default = true\n if (raw.default === false) capability.default = false\n if (isRecord(raw.coverage)) capability.coverage = raw.coverage as NetworkCapability['coverage']\n if (isRecord(raw.freshness))\n capability.freshness = raw.freshness as NetworkCapability['freshness']\n return capability\n}\n\n/** Repeat GraphRAG's network list. CIA does not add, drop, or invent names. */\nexport function mirrorGraphNetworkCapabilities(document: {\n networks: unknown[]\n}): NetworkCapabilitiesDocument {\n return {\n schema: 'chain-insights.network-capabilities.v1',\n networks: document.networks\n .map((network) => advertisedNetwork(network))\n .filter((network): network is NetworkCapability => network !== null),\n }\n}\n\nfunction publicNetworkCapabilities(\n document: NetworkCapabilitiesDocument\n): NetworkCapabilitiesDocument {\n return mirrorGraphNetworkCapabilities(document)\n}\n\nfunction metadataNetworksUrl(endpoint: string): URL {\n const url = new URL(endpoint)\n url.pathname = '/metadata/networks'\n url.search = ''\n url.hash = ''\n return url\n}\n\nexport async function fetchNetworkCapabilities(\n config: Pick<InvestigatorConfig, 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint'>\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim()\n if (token) {\n applyMcpAuthHeaders(headers, token)\n }\n let response: Response\n try {\n response = await fetch(request, { headers })\n } catch (err) {\n throw new Error(`network capabilities unavailable at ${request}: ${(err as Error).message}`)\n }\n if (!response.ok) {\n throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`)\n }\n const parsed = (await response.json()) as NetworkCapabilitiesDocument\n if (\n parsed.schema !== 'chain-insights.network-capabilities.v1' ||\n !Array.isArray(parsed.networks)\n ) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return publicNetworkCapabilities(parsed)\n}\n\nfunction availableTools(network: NetworkCapability): string[] {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.sort()\n}\n\nfunction availableToolLines(network: NetworkCapability): string[] {\n const tools = availableTools(network)\n if (tools.length === 0) return ['none']\n const lines: string[] = []\n for (let index = 0; index < tools.length; index += AVAILABLE_TOOLS_PER_LINE) {\n lines.push(tools.slice(index, index + AVAILABLE_TOOLS_PER_LINE).join(', '))\n }\n return lines\n}\n\nfunction shortDate(value?: string): string {\n if (!value) return ''\n return value.slice(0, 10)\n}\n\nfunction datasetLabel(network: NetworkCapability): string {\n const coverage = network.coverage\n if (!coverage) return 'unknown'\n const blockRange =\n coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange =\n coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : ''\n if (blockRange === 'blocks unknown' && dateRange === '') return 'unknown'\n if (blockRange === 'blocks unknown') return dateRange\n if (dateRange === '') return blockRange\n return `${blockRange} / ${dateRange}`\n}\n\nfunction displayName(network: NetworkCapability): string {\n return network.display_name || network.network\n}\n\nfunction statusLabel(network: NetworkCapability): string {\n return network.default ? `${network.status} (default)` : network.status\n}\n\nfunction formatTable(headers: string[], rows: string[][], minimumWidths: number[]): string {\n const widths = headers.map((header, index) =>\n Math.max(\n minimumWidths[index] ?? 0,\n header.length,\n ...rows.map((row) => row[index]?.length ?? 0)\n )\n )\n const row = (values: string[]) =>\n values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [row(headers), widths.map((width) => '-'.repeat(width)).join(' '), ...rows.map(row)].join(\n '\\n'\n )\n}\n\nexport function formatNetworkOverview(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const rows = document.networks.map((network) => [\n displayName(network),\n statusLabel(network),\n datasetLabel(network),\n ])\n return formatTable(['Network', 'Status', 'Dataset'], rows, [14, 10, 38])\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const networkRows = document.networks.flatMap((network) => {\n const toolLines = availableToolLines(network)\n return toolLines.map((toolLine, index) => [\n index === 0 ? displayName(network) : '',\n index === 0 ? datasetLabel(network) : '',\n toolLine,\n ])\n })\n return formatTable(['Network', 'Dataset', 'Chain Insights tools'], networkRows, [14, 38, 64])\n}\n\nexport function findNetworkCapability(\n document: NetworkCapabilitiesDocument,\n name: string\n): NetworkCapability | undefined {\n const normalizedName = name.trim().toLowerCase()\n if (!normalizedName) return undefined\n return document.networks.find((network) =>\n [network.network, network.display_name].some(\n (candidate) => candidate?.trim().toLowerCase() === normalizedName\n )\n )\n}\n\nexport function formatNetworkCapability(network: NetworkCapability): string {\n const rows = [\n ['Network', network.display_name || network.network],\n ['Identifier', network.network],\n ['Status', network.default ? `${network.status} (default)` : network.status],\n ['Dataset', datasetLabel(network)],\n ['Available tools', availableTools(network).join(', ') || 'none'],\n ]\n const labelWidth = Math.max(...rows.map(([label]) => label.length))\n return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join('\\n')\n}\n"],"mappings":"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"}
|