chain-insights 0.24.2 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/{capabilities-CjpGnrVg.cjs → capabilities-B-M8vAYp.cjs} +8 -16
- package/dist/{capabilities-CY3Ok5ls.mjs → capabilities-ByKP5l0G.mjs} +9 -17
- package/dist/capabilities-ByKP5l0G.mjs.map +1 -0
- package/dist/cli.cjs +124 -23
- package/dist/cli.mjs +124 -23
- package/dist/cli.mjs.map +1 -1
- package/dist/mcp-proxy.cjs +15 -12
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +15 -12
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-CpOLkME5.mjs → public-tools-CMI_0y87.mjs} +28 -2
- package/dist/public-tools-CMI_0y87.mjs.map +1 -0
- package/dist/{public-tools-Bou25cki.cjs → public-tools-DDGHRazh.cjs} +28 -1
- package/dist/{tool-visibility-DA4pOuJt.cjs → tool-visibility-BBEbpKt4.cjs} +2 -1
- package/dist/{tool-visibility-wDMzmHU-.mjs → tool-visibility-CJqjmDF5.mjs} +3 -2
- package/dist/tool-visibility-CJqjmDF5.mjs.map +1 -0
- package/docs/graph-tools.md +16 -3
- package/docs/mcp-proxy.md +33 -2
- package/docs/stability.md +4 -0
- package/package.json +1 -1
- package/skills/chain-insights-address-risk/SKILL.md +6 -0
- package/dist/capabilities-CY3Ok5ls.mjs.map +0 -1
- package/dist/public-tools-CpOLkME5.mjs.map +0 -1
- package/dist/tool-visibility-wDMzmHU-.mjs.map +0 -1
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
|
|
2
2
|
import { t as PaymentRequiredError } from "./client-CHYskPnG.mjs";
|
|
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-
|
|
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-CJqjmDF5.mjs";
|
|
4
4
|
import { t as primitiveBackendUsageStatus } from "./usage-status-CHiOPA-2.mjs";
|
|
5
|
-
import { n as mirrorGraphNetworkCapabilities } from "./capabilities-
|
|
5
|
+
import { n as mirrorGraphNetworkCapabilities } from "./capabilities-ByKP5l0G.mjs";
|
|
6
6
|
import { r as appendActionLog, t as actionLogSignalsFromResult } from "./action-log-diTHyMbV.mjs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
@@ -36,7 +36,7 @@ const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
|
|
|
36
36
|
meta_usage_status: "Return the caller's public free graph_query quota for the current UTC day.",
|
|
37
37
|
meta_help: "Show a short guide to Chain Insights tools and workflow.",
|
|
38
38
|
wallet_balance: "Show the local Chain Insights payment wallet address, payment network, token, and amount.",
|
|
39
|
-
aml_address_risk: "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.",
|
|
39
|
+
aml_address_risk: "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.",
|
|
40
40
|
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. Preserve full addresses exactly.",
|
|
41
41
|
graph_query_batch: "Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads."
|
|
42
42
|
};
|
|
@@ -87,7 +87,8 @@ function knownPublicToolInputSchema(toolName) {
|
|
|
87
87
|
case "aml_address_risk": return {
|
|
88
88
|
address: z.string().min(1).describe("Blockchain address to screen."),
|
|
89
89
|
network: NETWORK_SCHEMA,
|
|
90
|
-
compare_address: z.string().optional().describe("Optional address to compare against the screened address.")
|
|
90
|
+
compare_address: z.string().optional().describe("Optional address to compare against the screened address."),
|
|
91
|
+
version: z.string().optional().describe("Optional AML tool contract version. Omit to use the latest version.")
|
|
91
92
|
};
|
|
92
93
|
case "graph_query": return {
|
|
93
94
|
query: z.string().min(1).describe("Read-only GQL/Cypher query. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment."),
|
|
@@ -325,10 +326,11 @@ function registerLocalPrompts(server) {
|
|
|
325
326
|
argsSchema: {
|
|
326
327
|
network: NETWORK_SCHEMA,
|
|
327
328
|
address: z.string().describe("Blockchain address to screen"),
|
|
328
|
-
compare_address: z.string().optional().describe("Optional address to compare against the screened address")
|
|
329
|
+
compare_address: z.string().optional().describe("Optional address to compare against the screened address"),
|
|
330
|
+
version: z.string().optional().describe("Optional AML tool contract version. Omit to use the latest version")
|
|
329
331
|
}
|
|
330
|
-
}, async ({ network, address, compare_address }) => promptResult([
|
|
331
|
-
`Use Chain Insights aml_address_risk on ${network} for:`,
|
|
332
|
+
}, async ({ network, address, compare_address, version }) => promptResult([
|
|
333
|
+
`Use Chain Insights aml_address_risk${version ? ` version ${version}` : ""} on ${network} for:`,
|
|
332
334
|
"",
|
|
333
335
|
`\`${address}\``,
|
|
334
336
|
compare_address ? `\nCompare with: \`${compare_address}\`` : "",
|
|
@@ -672,7 +674,8 @@ async function createProxy() {
|
|
|
672
674
|
inputSchema: {
|
|
673
675
|
address: z.string().min(1).describe("Blockchain address to screen"),
|
|
674
676
|
network: NETWORK_SCHEMA,
|
|
675
|
-
compare_address: z.string().optional().describe("Optional address to compare against the screened address")
|
|
677
|
+
compare_address: z.string().optional().describe("Optional address to compare against the screened address"),
|
|
678
|
+
version: z.string().optional().describe("Optional AML tool contract version. Omit to use the latest version")
|
|
676
679
|
},
|
|
677
680
|
annotations: {
|
|
678
681
|
readOnlyHint: true,
|
|
@@ -680,7 +683,7 @@ async function createProxy() {
|
|
|
680
683
|
idempotentHint: true,
|
|
681
684
|
openWorldHint: true
|
|
682
685
|
}
|
|
683
|
-
}, async ({ address, network, compare_address }) => {
|
|
686
|
+
}, async ({ address, network, compare_address, version }) => {
|
|
684
687
|
try {
|
|
685
688
|
if (!remoteConnected) return {
|
|
686
689
|
content: [{
|
|
@@ -689,13 +692,13 @@ async function createProxy() {
|
|
|
689
692
|
}],
|
|
690
693
|
isError: true
|
|
691
694
|
};
|
|
692
|
-
const {
|
|
693
|
-
const result = await
|
|
695
|
+
const { runAmlAddressRisk } = await import("./public-tools-CMI_0y87.mjs");
|
|
696
|
+
const result = await runAmlAddressRisk(remoteClient, {
|
|
694
697
|
address,
|
|
695
698
|
network,
|
|
696
699
|
compareAddress: compare_address,
|
|
697
700
|
writeArtifacts: workspaceArtifactsEnabled
|
|
698
|
-
});
|
|
701
|
+
}, version);
|
|
699
702
|
return {
|
|
700
703
|
content: [{
|
|
701
704
|
type: "text",
|
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 { 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_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 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_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.',\n graph_query:\n 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. Preserve full addresses exactly.',\n graph_query_batch:\n 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\nconst FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES = ['graph_query', 'graph_query_batch'] as const\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\nconst NETWORK_DESCRIPTION =\n 'Network to query. Call meta_network_capabilities first and pass a name GraphRAG advertised. CIA does not pick a default network.'\nconst NETWORK_SCHEMA = z.string().min(1).describe(NETWORK_DESCRIPTION)\n\nconst EMPTY_INPUT_SCHEMA = z.strictObject({})\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. Do not call investigation tools until required arguments are known. Network is required; use meta_network_capabilities to check supported networks and available tools, or ask the user if missing.',\n '2. Use aml_address_risk for single-address enrichment. Use graph_query(_batch) for graph-level questions that aml_address_risk does not answer.',\n '3. Preserve tool summaries and structured facts as returned. Keep full blockchain addresses intact.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints:',\n '- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.',\n '- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.',\n '- Address nodes carry address, network, labels, and is_exchange. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.',\n '- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry tx_count, amount_usd_sum, avg_tx_size_usd (understates when price_coverage_ratio < 1), first/last_seen_timestamp, first/last_tx_id, price_coverage_ratio. Lifetime aggregates are the only serving window.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO 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]-(b:Address) WHERE owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, r.amount_usd_sum.',\n '- The risk verdict lives on topology nodes (risk_score float, risk_level string). Labels and per-label risk also live on the address node (labels array + label_risk entries: label, risk_level, updated_timestamp). USE facts serves bounded individual transfer rows (TRANSFER 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) or a WHERE t.tx_id = \"...\" equality; 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 relationship connects two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, LINKED, and RISK_PROXIMITY between Address nodes.',\n '- FLOWS_TO properties commonly carry tx_count, amount_usd_sum, avg_tx_size_usd, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id, price_coverage_ratio. Confirm available fields through runtime schema before relying on them.',\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.',\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/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER 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 }\n case 'graph_query':\n return {\n query: z\n .string()\n .min(1)\n .describe(\n 'Read-only GQL/Cypher query. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment.'\n ),\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\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 },\n },\n async ({ network, address, compare_address }) =>\n promptResult(\n [\n `Use Chain Insights aml_address_risk 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 '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 'Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. 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 'Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. 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 '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 },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ address, network, compare_address }) => {\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 { addressRisk } = await import('../investigation/public-tools.js')\n const result = await addressRisk(remoteClient, {\n address,\n network,\n compareAddress: compare_address,\n writeArtifacts: workspaceArtifactsEnabled,\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 '- 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 '- 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 { 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_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 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_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:\n 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. Preserve full addresses exactly.',\n graph_query_batch:\n 'Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads.',\n}\nconst FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES = ['graph_query', 'graph_query_batch'] as const\n\ntype ToolInputShape = Record<string, z.ZodTypeAny>\ntype ToolHandler = (args: unknown, extra?: unknown) => Promise<unknown> | unknown\ntype ToolRegistrationConfig = Parameters<McpServer['registerTool']>[1]\ntype ToolCallInput = { name: string; arguments?: Record<string, unknown> }\ntype RemoteToolCaller = {\n callTool: Client['callTool']\n}\nconst NETWORK_DESCRIPTION =\n 'Network to query. Call meta_network_capabilities first and pass a name GraphRAG advertised. CIA does not pick a default network.'\nconst NETWORK_SCHEMA = z.string().min(1).describe(NETWORK_DESCRIPTION)\n\nconst EMPTY_INPUT_SCHEMA = z.strictObject({})\nconst REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS = 15 * 60 * 1000\n\nconst CHAIN_INSIGHTS_WORKFLOW = [\n 'Workflow:',\n '1. Do not call investigation tools until required arguments are known. Network is required; use meta_network_capabilities to check supported networks and available tools, or ask the user if missing.',\n '2. Use aml_address_risk for single-address enrichment. Use graph_query(_batch) for graph-level questions that aml_address_risk does not answer.',\n '3. Preserve tool summaries and structured facts as returned. Keep full blockchain addresses intact.',\n].join('\\n')\n\nconst GRAPH_SCHEMA_HINTS = [\n 'Graph query hints:',\n '- Call meta_network_capabilities first. Pass network= exactly as GraphRAG advertised it. CIA does not pick a default network.',\n '- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. The network value on Address nodes matches the tool argument. There is no separate identity key.',\n '- Address nodes carry address, network, labels, and is_exchange. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner) asserting the two addresses are controlled by the same actor. LINKED is served on the topology graph only. Enumerate LINKED neighbors with MATCH (a:Address {address: $addr})-[l:LINKED]-(b:Address) RETURN b.address, b.network, l.basis, l.confidence.',\n '- Address nodes also carry a risk verdict (risk_score float, risk_level string) plus base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry tx_count, amount_usd_sum, avg_tx_size_usd (understates when price_coverage_ratio < 1), first/last_seen_timestamp, first/last_tx_id, price_coverage_ratio. Lifetime aggregates are the only serving window.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO 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]-(b:Address) WHERE owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, r.amount_usd_sum.',\n '- The risk verdict lives on topology nodes (risk_score float, risk_level string). Labels and per-label risk also live on the address node (labels array + label_risk entries: label, risk_level, updated_timestamp). USE facts serves bounded individual transfer rows (TRANSFER 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) or a WHERE t.tx_id = \"...\" equality; 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 relationship connects two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, LINKED, and RISK_PROXIMITY between Address nodes.',\n '- FLOWS_TO properties commonly carry tx_count, amount_usd_sum, avg_tx_size_usd, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id, price_coverage_ratio. Confirm available fields through runtime schema before relying on them.',\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.',\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/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER 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\n .string()\n .min(1)\n .describe(\n 'Read-only GQL/Cypher query. Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment.'\n ),\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\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 '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 'Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. 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 'Use USE topology for topology (address/FLOWS_TO/LINKED graph, unified recent+historical, plus the node risk_score/risk_level verdict) and USE facts for bounded TRANSFER rows and enrichment. 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 '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 '- 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 '- 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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"}
|
|
@@ -448,6 +448,13 @@ function wrapClientForUsageTracking(client, totals) {
|
|
|
448
448
|
//#region src/investigation/public-tools.ts
|
|
449
449
|
const GRAPH_QUERY_BATCH_TIMEOUT_SECONDS = 10;
|
|
450
450
|
const GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
451
|
+
const AML_ADDRESS_RISK_SUPPORTED_VERSIONS = ["v1"];
|
|
452
|
+
function resolveAmlAddressRiskVersion(requested) {
|
|
453
|
+
const normalized = requested?.trim().toLowerCase();
|
|
454
|
+
if (!normalized) return "v1";
|
|
455
|
+
if (AML_ADDRESS_RISK_SUPPORTED_VERSIONS.includes(normalized)) return normalized;
|
|
456
|
+
throw new Error(`Unsupported aml_address_risk version "${requested?.trim() ?? ""}". Supported versions: ${AML_ADDRESS_RISK_SUPPORTED_VERSIONS.join(", ")}.`);
|
|
457
|
+
}
|
|
451
458
|
function escapeCypherString(value) {
|
|
452
459
|
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
453
460
|
}
|
|
@@ -1144,6 +1151,25 @@ async function addressRisk(remoteClient, options) {
|
|
|
1144
1151
|
graphData
|
|
1145
1152
|
};
|
|
1146
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Routes the public AML tool contract to the requested implementation. An
|
|
1156
|
+
* omitted version always follows the latest supported contract.
|
|
1157
|
+
*/
|
|
1158
|
+
async function runAmlAddressRisk(remoteClient, options, requestedVersion) {
|
|
1159
|
+
const version = resolveAmlAddressRiskVersion(requestedVersion);
|
|
1160
|
+
switch (version) {
|
|
1161
|
+
case "v1": {
|
|
1162
|
+
const result = await addressRisk(remoteClient, options);
|
|
1163
|
+
return {
|
|
1164
|
+
...result,
|
|
1165
|
+
structuredContent: {
|
|
1166
|
+
...result.structuredContent,
|
|
1167
|
+
tool_version: version
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1147
1173
|
function toCsvValue(value) {
|
|
1148
1174
|
return value === void 0 || value === null ? "" : String(value);
|
|
1149
1175
|
}
|
|
@@ -1298,6 +1324,6 @@ function htmlEscape(value) {
|
|
|
1298
1324
|
return String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1299
1325
|
}
|
|
1300
1326
|
//#endregion
|
|
1301
|
-
export {
|
|
1327
|
+
export { resolveAmlAddressRiskVersion, runAmlAddressRisk };
|
|
1302
1328
|
|
|
1303
|
-
//# sourceMappingURL=public-tools-
|
|
1329
|
+
//# sourceMappingURL=public-tools-CMI_0y87.mjs.map
|