chain-insights 0.56.16 → 0.56.17

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.
@@ -182,6 +182,26 @@ async function withBusyMemberRetry(args, call, sleep = defaultSleep) {
182
182
  }
183
183
  return result;
184
184
  }
185
+ /** A single graph_query refused as chain_busy: an error result whose text starts with the code. */
186
+ function isBusyResult(result) {
187
+ if (!result.isError) return false;
188
+ const first = Array.isArray(result.content) ? result.content[0] : void 0;
189
+ const text = isRecord$3(first) && typeof first.text === "string" ? first.text : "";
190
+ return /^(Query execution failed: )?chain_busy\b/.test(text.trim());
191
+ }
192
+ /**
193
+ * Runs a single graph_query and, when the server refuses it as chain_busy,
194
+ * waits and sends it again, up to MAX_ROUNDS times. Seven of 100 loop rounds
195
+ * on prompt set v2 showed a model giving up on the first such refusal.
196
+ */
197
+ async function withBusyRetry(args, call, sleep = defaultSleep) {
198
+ let result = await call(args);
199
+ for (let round = 0; round < 3 && isBusyResult(result); round += 1) {
200
+ await sleep(BUSY_WAIT_MS);
201
+ result = await call(args);
202
+ }
203
+ return result;
204
+ }
185
205
  //#endregion
186
206
  //#region src/mcp/layer-routing.ts
187
207
  /** The routing rule, in one sentence. The server states it in the same words. */
@@ -1752,7 +1772,7 @@ async function createProxy() {
1752
1772
  name: tool.name,
1753
1773
  arguments: requestArgs
1754
1774
  }));
1755
- const result = tool.name === "graph_query_batch" ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a)) : await callRemote(request.arguments);
1775
+ const result = tool.name === "graph_query_batch" ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a)) : tool.name === "graph_query" ? await withBusyRetry(normalizedArgs, (a) => callRemote(a)) : await callRemote(request.arguments);
1756
1776
  const answer = normalizeRemoteToolResult(tool.name, result);
1757
1777
  return BROWSER_VIEW_TOOL_NAMES.has(tool.name) ? await withViewUrl(tool.name, normalizedArgs, answer) : answer;
1758
1778
  } catch (err) {
@@ -178,6 +178,26 @@ async function withBusyMemberRetry(args, call, sleep = defaultSleep) {
178
178
  }
179
179
  return result;
180
180
  }
181
+ /** A single graph_query refused as chain_busy: an error result whose text starts with the code. */
182
+ function isBusyResult(result) {
183
+ if (!result.isError) return false;
184
+ const first = Array.isArray(result.content) ? result.content[0] : void 0;
185
+ const text = isRecord$3(first) && typeof first.text === "string" ? first.text : "";
186
+ return /^(Query execution failed: )?chain_busy\b/.test(text.trim());
187
+ }
188
+ /**
189
+ * Runs a single graph_query and, when the server refuses it as chain_busy,
190
+ * waits and sends it again, up to MAX_ROUNDS times. Seven of 100 loop rounds
191
+ * on prompt set v2 showed a model giving up on the first such refusal.
192
+ */
193
+ async function withBusyRetry(args, call, sleep = defaultSleep) {
194
+ let result = await call(args);
195
+ for (let round = 0; round < 3 && isBusyResult(result); round += 1) {
196
+ await sleep(BUSY_WAIT_MS);
197
+ result = await call(args);
198
+ }
199
+ return result;
200
+ }
181
201
  //#endregion
182
202
  //#region src/mcp/layer-routing.ts
183
203
  /** The routing rule, in one sentence. The server states it in the same words. */
@@ -1748,7 +1768,7 @@ async function createProxy() {
1748
1768
  name: tool.name,
1749
1769
  arguments: requestArgs
1750
1770
  }));
1751
- const result = tool.name === "graph_query_batch" ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a)) : await callRemote(request.arguments);
1771
+ const result = tool.name === "graph_query_batch" ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a)) : tool.name === "graph_query" ? await withBusyRetry(normalizedArgs, (a) => callRemote(a)) : await callRemote(request.arguments);
1752
1772
  const answer = normalizeRemoteToolResult(tool.name, result);
1753
1773
  return BROWSER_VIEW_TOOL_NAMES.has(tool.name) ? await withViewUrl(tool.name, normalizedArgs, answer) : answer;
1754
1774
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.mjs","names":["defaultSleep","isRecord","isRecord","isRecord"],"sources":["../src/mcp/rate-limit.ts","../src/mcp/batch-busy-retry.ts","../src/mcp/layer-routing.ts","../src/mcp/view-server.ts","../src/mcp/graph-row-columns.ts","../src/mcp/remote-connect.ts","../src/mcp/timestamp-text.ts","../src/mcp/proxy.ts"],"sourcesContent":["/**\n * The graph endpoint limits requests per address. Its refusal arrives as a\n * thrown HTTP error, \"too many requests from this address; retry after 3\n * seconds\", not as a typed tool reply. Every proxied tool reads only, and a\n * refused request is not billed, so one wait and one resend are safe.\n *\n * When the resend is refused too, or the endpoint asks for a longer wait than\n * the proxy holds a call for, the caller gets the refusal envelope the graph\n * server uses for every other limit: class `capacity`. The skills tell an\n * agent what to do with that class.\n */\n\n/** The longest wait the proxy holds a call for before it sends it once more. */\nexport const MAX_RETRY_WAIT_MS = 10_000\n/** The wait when the endpoint names none. The skills say at least 5 seconds. */\nexport const DEFAULT_RETRY_WAIT_MS = 5_000\nconst MIN_RETRY_WAIT_MS = 1_000\n\nconst RATE_LIMITED = /\\b429\\b|too many requests|rate.?limit/i\nconst RETRY_AFTER = /retry after (\\d+) seconds?/i\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * The wait the endpoint asks for, in milliseconds, when err is its rate\n * limit, and null for any other failure.\n */\nexport function rateLimitWaitMs(err: unknown): number | null {\n const code = err && typeof err === 'object' ? (err as { code?: unknown }).code : undefined\n const message = messageOf(err)\n if (code !== 429 && !RATE_LIMITED.test(message)) return null\n const seconds = RETRY_AFTER.exec(message)?.[1]\n return seconds ? Math.max(Number(seconds) * 1000, MIN_RETRY_WAIT_MS) : DEFAULT_RETRY_WAIT_MS\n}\n\nexport type Sleep = (ms: number) => Promise<void>\n\nconst defaultSleep: Sleep = (ms) => new Promise((done) => setTimeout(done, ms))\n\n/**\n * Runs call. When the endpoint refuses it for the rate limit and asks for a\n * wait of at most MAX_RETRY_WAIT_MS, waits that long and runs it once more.\n * Any other failure, and the second refusal, is thrown to the caller.\n */\nexport async function withRateLimitRetry<T>(\n call: () => Promise<T>,\n sleep: Sleep = defaultSleep\n): Promise<T> {\n try {\n return await call()\n } catch (err) {\n const wait = rateLimitWaitMs(err)\n if (wait === null || wait > MAX_RETRY_WAIT_MS) throw err\n await sleep(wait)\n return await call()\n }\n}\n\n/**\n * The reply for a call the endpoint refused for the rate limit: the text the\n * agent reads and the `capacity` envelope the skills act on. Null when err is\n * not the rate limit.\n */\nexport function rateLimitedResult(toolName: string, err: unknown) {\n const wait = rateLimitWaitMs(err)\n if (wait === null) return null\n const seconds = Math.ceil(wait / 1000)\n const fix = `Wait ${seconds} seconds, then send the same request once.`\n return {\n content: [\n {\n type: 'text' as const,\n text: `${toolName} was not run: Chain Insights Graph is limiting requests from this address. ${fix}`,\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: toolName,\n hint: null,\n error_detail: {\n code: 'rate_limited',\n rule: 'requests_per_address',\n class: 'capacity',\n fix,\n example: '',\n },\n },\n isError: true,\n }\n}\n","/**\n * A graph_query_batch member that reads the chain node costs several node\n * calls (an Address read costs four), and the graph server caps node calls\n * per caller per second. Three chain reads in one batch come back as one\n * answer and two `chain_busy` refusals: \"this caller used its share of node\n * calls this second. Retry after a short wait.\" A refused member is not\n * billed and every read is safe to repeat, so the proxy waits and resends\n * only the busy members, up to MAX_ROUNDS times, and merges the answers back\n * into the batch under their ids. The model never sees a `chain_busy` unless\n * the server stays busy for every round.\n */\n\nconst BUSY_CODE = 'chain_busy'\nexport const BUSY_WAIT_MS = 1_100\nexport const MAX_ROUNDS = 3\n\ntype Member = Record<string, unknown>\ntype Query = { id?: string; query: string }\n\nexport type BatchResult = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n _meta?: unknown\n}\n\ntype CallBatch = (args: Record<string, unknown>) => Promise<BatchResult>\ntype Sleep = (ms: number) => Promise<void>\n\nconst defaultSleep: Sleep = (ms) => new Promise((done) => setTimeout(done, ms))\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction membersOf(result: BatchResult): Member[] | null {\n const facts = isRecord(result.structuredContent) ? result.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n return Array.isArray(queries) && queries.every(isRecord) ? (queries as Member[]) : null\n}\n\nexport function isBusyMember(member: Member): boolean {\n const detail = member.error_detail\n return isRecord(detail) && detail.code === BUSY_CODE\n}\n\n/** The ids of the members the server refused as chain_busy. */\nexport function busyMemberIds(result: BatchResult): string[] {\n const members = membersOf(result)\n if (!members) return []\n return members.filter(isBusyMember).map((m) => String(m.id))\n}\n\nfunction recount(batch: Record<string, unknown>, members: Member[]): Record<string, unknown> {\n const completed = members.filter((m) => m.ok === true).length\n const billable = members.reduce(\n (sum, m) => sum + (typeof m.billable_units === 'number' ? m.billable_units : 0),\n 0\n )\n return { ...batch, completed, failed: members.length - completed, billable_units: billable }\n}\n\n/**\n * Replaces the busy members of `first` with the answers `again` carries for\n * the same ids, and recounts the batch envelope. The text block is rebuilt\n * from the structured content so the two never disagree.\n */\nexport function mergeBatchAnswers(first: BatchResult, again: BatchResult): BatchResult {\n const members = membersOf(first)\n const fresh = membersOf(again)\n if (!members || !fresh) return first\n const byId = new Map(fresh.map((m) => [String(m.id), m]))\n const merged = members.map((m) => (isBusyMember(m) && byId.has(String(m.id)) ? byId.get(String(m.id))! : m))\n const structured = first.structuredContent as Record<string, unknown>\n const facts = structured.facts as Record<string, unknown>\n const batch = isRecord(facts.batch) ? recount(facts.batch, merged) : facts.batch\n const nextStructured = { ...structured, facts: { ...facts, queries: merged, batch } }\n return {\n ...first,\n structuredContent: nextStructured,\n content: [{ type: 'text', text: JSON.stringify(nextStructured) }],\n isError: merged.some((m) => m.ok !== true) ? first.isError : false,\n }\n}\n\n/**\n * Runs the batch, then resends only its chain_busy members after a short wait,\n * up to MAX_ROUNDS times, merging each answer back under its id.\n */\nexport async function withBusyMemberRetry(\n args: Record<string, unknown>,\n call: CallBatch,\n sleep: Sleep = defaultSleep\n): Promise<BatchResult> {\n let result = await call(args)\n const queries = Array.isArray(args.queries) ? (args.queries as Query[]) : []\n for (let round = 0; round < MAX_ROUNDS; round += 1) {\n const busy = new Set(busyMemberIds(result))\n if (busy.size === 0) break\n const resend = queries.filter((q, index) => busy.has(String(q.id ?? `q${index + 1}`)))\n if (resend.length === 0) break\n await sleep(BUSY_WAIT_MS)\n const again = await call({ ...args, queries: resend })\n result = mergeBatchAnswers(result, again)\n }\n return result\n}\n","// The one source of the routing rule of the three read layers, and of the move\n// that each class of refusal asks of an agent.\n//\n// Three places teach them: the graph hints that the MCP proxy serves\n// (GRAPH_SCHEMA_HINTS in proxy.ts builds its two routing hints from this file),\n// the `chain-insights-cypher` skill and docs/graph-tools.md. The skill and the\n// guide are Markdown and hold the lines word for word. tests/layer-routing.test.ts\n// fails when one of the three differs from this file by one word.\n//\n// The rule follows the graph server's own: topology searches outward from one\n// known address, while facts and chain look up one known thing. The wording asks\n// the agent to say first what the question names (an address, a pair with a day,\n// a transaction, a block), because a question that names none of them is served\n// on no layer today. Nothing here is a limit. A limit that the server publishes\n// is read from `meta_network_capabilities`, and the text names the field, never\n// the number.\n//\n// `USE chain` serves the four labels of the server's chain catalogue and no\n// other: a transaction by its `tx_id`, a block by its `block_height` or its\n// `block_hash`, an address at one block (`Address`, with an optional `at_block`),\n// and the head, which takes no key. A kind (`:Account`, `:Contract`) is never a\n// lookup label: the `Address` lookup reads it as `is_contract` and `nonce`.\n// tests/fixtures/chain-catalogue.json pins the labels, and\n// tests/chain-catalogue-pin.test.ts holds every chain recipe and the chain\n// routing line to them. The depth below which a past block is served is a limit\n// that the server publishes, so the text names its field and never its number.\n\n/** The routing rule, in one sentence. The server states it in the same words. */\nexport const ROUTING_RULE =\n 'Route by what the question names: an address, a pair with a day, a transaction or a block. Topology searches outward from an address, while facts and chain look up one known thing.'\n\n/** The rule in three lines, one for each layer. */\nexport const ROUTING_LINES = [\n 'I know an address and want its links, senders, receivers, hops or a route: `USE topology`, anchored on that address.',\n 'I know both addresses of a pair and one day, and want the rows of that day: `USE facts`.',\n \"I want the chain's own record of a transaction by its `tx_id`, a block by its `block_height` or `block_hash`, an address at one block (`Address`), or the head: `USE chain`.\",\n] as const\n\n/** The chain layer reads one address at one block. A search for counterparties stays on topology. */\nexport const ROUTING_ADDRESS_ON_CHAIN =\n '`USE chain` with `Address` reads the balance, the nonce or the kind of one address, now or at a past block with `at_block`. A past block must be at least `chain_admission.at_block_min_depth` blocks below the tip. To find the counterparties of an address, search `USE topology`, then read the pair and one day on `USE facts`.'\n\n/** What to do when two layers fit. */\nexport const ROUTING_TWO_FIT =\n 'A transaction is a chain question: read it on `USE chain` by its `tx_id`. Chain gives its `block_date`; the transfers between a pair on that day are a `USE facts` read.'\n\n/** The key that each layer hands to the next. Each key keeps its name. */\nexport const ROUTING_HANDOFF =\n 'The layers hand each other keys, and each key keeps its name. Topology gives the pair and the first and last seen time. Facts gives the `tx_id` and the `block_height`. Chain takes an `address`, a `tx_id`, a `block_height` or a `block_hash`, and gives the `block_date` of a transaction or a block.'\n\n/** A whole-chain question is a time-bounded topology search; a server without search mode refuses it once. */\nexport const ROUTING_NOT_SERVED =\n 'A question about the whole chain (recent activity, the biggest senders, a top list) is a `USE topology` search: bound it by time with `f.last_seen_timestamp >= <epoch ms>` on the `FLOWS_TO` link, then sort and `LIMIT`. A search has 10 s. Read `Head` first (`USE chain MATCH (h:Head) RETURN h.graph_blocks_behind`, under a second): when `graph_blocks_behind` is above 100000 (a few hours of blocks: a robinhood block is about a tenth of a second apart from the next), the search cannot finish, so do not send it; say the graph is that many blocks behind the head and ask for an address. Never turn the block count into days or hours by head. To say how fresh the data is, give the newest `_utc` time you read on the layer you used: `graph_blocks_behind` counts the slowest graph layer, and the links of an address and the facts rows are often newer than it. If the server refuses the search with `anchor_missing`, or stops it with `query_timeout`, it cannot search the whole chain now: say so in one line and ask for an address. A narrower window times out the same way, so do not send it again.'\n\n/** What a refusal carries, and which part of it decides the move. */\nexport const MOVE_INTRO =\n 'A refused, killed, busy or failed query comes back with `error_detail`: `code`, `rule`, `class`, `fix` and `example`. The `class` decides your next move.'\n\n/** The move for each class of the refusal envelope. */\nexport const MOVE_BY_CLASS = {\n refused: 'Class `refused`: read `fix`, rewrite the query from `example`, and send it once.',\n killed: 'Class `killed`: narrow the query (fewer properties, rows or hops) and send it once.',\n capacity: 'Class `capacity`: wait at least 5 seconds, then send the same query once.',\n failed: 'Class `failed`: tell the user what is down. Do not retry.',\n} as const\n\n/** A `fix` that names another layer. */\nexport const MOVE_OTHER_LAYER = 'A `fix` that names another layer means move to that layer.'\n\n/** One try, then the user. Never a loop. */\nexport const MOVE_ONCE =\n 'One rewrite or one retry for each query. When it is refused or busy again, stop and tell the user. Quote the `fix` text and send nothing more for that question.'\n\n/** After a refusal or a kill the server has already said no. */\nexport const MOVE_NEVER_SAME = 'Never send the same text again after `refused` or `killed`.'\n\n/** A batch has good members that already ran. */\nexport const MOVE_BATCH = 'In a batch, send again only the members that came back `capacity`.'\n\n/** The routing sentences, in the order a reader meets them. */\nexport function routingLines(): string[] {\n return [\n ROUTING_RULE,\n ...ROUTING_LINES,\n ROUTING_ADDRESS_ON_CHAIN,\n ROUTING_TWO_FIT,\n ROUTING_HANDOFF,\n ROUTING_NOT_SERVED,\n ]\n}\n\n/** The move-by-class sentences, in the order a reader meets them. */\nexport function moveLines(): string[] {\n return [\n MOVE_INTRO,\n MOVE_BY_CLASS.refused,\n MOVE_BY_CLASS.killed,\n MOVE_BY_CLASS.capacity,\n MOVE_BY_CLASS.failed,\n MOVE_OTHER_LAYER,\n MOVE_ONCE,\n MOVE_NEVER_SAME,\n MOVE_BATCH,\n ]\n}\n\n/**\n * The routing rule and its three lines, with the question that no layer serves,\n * as one paragraph. The proxy serves it first among its instructions: a host\n * that keeps only the start of the instructions (Claude Code keeps 2 KB) still\n * shows the agent where each kind of question goes, and which kind goes nowhere.\n */\nexport function routingHead(): string {\n return `Pick the layer first. ${ROUTING_RULE} 1. ${ROUTING_LINES[0]} 2. ${ROUTING_LINES[1]} 3. ${ROUTING_LINES[2]} ${ROUTING_NOT_SERVED}`\n}\n\n/**\n * The two hints that the MCP proxy serves for routing and for refusals. Each is\n * one line of the served instructions.\n */\nexport function routingHintLines(): string[] {\n return [\n `- Pick the layer first. ${ROUTING_RULE} 1. ${ROUTING_LINES[0]} 2. ${ROUTING_LINES[1]} 3. ${ROUTING_LINES[2]} ${ROUTING_NOT_SERVED} ${ROUTING_ADDRESS_ON_CHAIN} ${ROUTING_TWO_FIT} ${ROUTING_HANDOFF} The lookups that USE chain serves are listed in chain_admission.lookups of meta_network_capabilities. Read every limit that the server publishes from meta_network_capabilities (chain_admission and, when the server sends them, topology_admission and facts_admission). Never write a limit down.`,\n `- Act on a refusal by its class. ${moveLines().join(' ')}`,\n ]\n}\n","import { randomBytes } from 'node:crypto'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\n\n/**\n * The Chain Insights view as a browser page on the user's own PC.\n *\n * Claude Desktop draws the view in a small frame beside the answer and offers\n * no full screen. The proxy therefore serves the same view file over HTTP on\n * 127.0.0.1, and every drawn answer carries a link to it (view_url). The page\n * reads the answer back from this server by its id and calls graph_expand\n * through it, so a click in the browser costs what a click in the host costs.\n *\n * Routes, all same-origin, no CORS:\n * GET /view?view=<id> the view file; when the id is held, the answer\n * is written into it as\n * <script type=\"application/json\" data-chain-insights-view=\"<id>\">\n * so a window that may load the page but not call\n * back to it (Claude Desktop's browser pane blocks\n * requests to local hosts) still draws\n * GET /api/view/<id> {\"arguments\": ..., \"result\": ...}, or 404\n * POST /api/tools/graph_expand {\"arguments\": {...}} -> the CallToolResult\n *\n * The server binds to 127.0.0.1 only, refuses a Host header that does not\n * name it (DNS rebinding), and keeps at most 50 answers in memory.\n */\n\nexport const VIEW_STORE_LIMIT = 50\nexport const VIEW_BODY_LIMIT_BYTES = 64 * 1024\n// The one tool the page may call: the view's own click tool.\nexport const VIEW_PAGE_TOOLS = new Set(['graph_expand'])\n\nexport interface ViewEntry {\n toolName: string\n arguments: unknown\n result: unknown\n}\n\n/** A fresh answer id: 12 random bytes, URL-safe base64 (16 characters). */\nexport function newViewId(): string {\n return randomBytes(12).toString('base64url')\n}\n\n/** At most `limit` answers, the oldest evicted first. */\nexport class ViewStore {\n private readonly entries = new Map<string, ViewEntry>()\n\n constructor(private readonly limit: number = VIEW_STORE_LIMIT) {}\n\n put(entry: ViewEntry, id: string = newViewId()): string {\n this.entries.delete(id)\n this.entries.set(id, entry)\n while (this.entries.size > this.limit) {\n const oldest = this.entries.keys().next().value\n if (oldest === undefined) break\n this.entries.delete(oldest)\n }\n return id\n }\n\n get(id: string): ViewEntry | undefined {\n return this.entries.get(id)\n }\n\n get size(): number {\n return this.entries.size\n }\n}\n\n/** The attribute the page reads the written-in answer from; the view (chain-insights-ui browser.ts) names the same one. */\nexport const EMBEDDED_VIEW_ATTR = 'data-chain-insights-view'\n\n/** JSON safe inside a script element: no sequence that could close it, no line separators. */\nfunction scriptJson(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n}\n\n/**\n * The view file with the answer written in: one JSON script element before\n * </head> (else at the top). The id is URL-safe base64, attribute-safe as is.\n */\nexport function pageWithEntry(html: string, id: string, entry: ViewEntry): string {\n const script = `<script type=\"application/json\" ${EMBEDDED_VIEW_ATTR}=\"${id}\">${scriptJson({ arguments: entry.arguments, result: entry.result })}</script>`\n const head = html.indexOf('</head>')\n if (head >= 0) return `${html.slice(0, head)}${script}\\n${html.slice(head)}`\n return `${script}\\n${html}`\n}\n\nexport interface ViewServerOptions {\n /** The view file; the held answer of ?view=<id> is written into it. */\n html: () => string\n /** Runs a page tool (graph_expand only) and returns its CallToolResult. */\n runTool: (name: string, args: Record<string, unknown>) => Promise<unknown>\n /** One structured log line per event; never throws. */\n log?: (event: string, fields: Record<string, unknown>) => void\n limit?: number\n}\n\nexport interface ViewServer {\n readonly port: number\n /** Stores an answer and returns its id. */\n put(entry: ViewEntry, id?: string): string\n /** The browser link of one answer. */\n viewUrl(id: string): string\n close(): Promise<void>\n}\n\nconst COMMON_HEADERS = {\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n}\n\nfunction send(\n res: ServerResponse,\n status: number,\n contentType: string,\n body: string | Buffer\n): void {\n res.writeHead(status, { ...COMMON_HEADERS, 'Content-Type': contentType })\n res.end(body)\n}\n\nfunction sendJson(res: ServerResponse, status: number, value: unknown): void {\n send(res, status, 'application/json; charset=utf-8', JSON.stringify(value))\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nclass BodyTooLarge extends Error {}\n\nfunction readBody(req: IncomingMessage, limit: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n let failed = false\n req.on('data', (chunk: Buffer) => {\n if (failed) return\n size += chunk.length\n if (size > limit) {\n failed = true\n reject(new BodyTooLarge())\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (!failed) resolve(Buffer.concat(chunks).toString('utf8'))\n })\n req.on('error', (err) => {\n if (!failed) {\n failed = true\n reject(err)\n }\n })\n })\n}\n\n/**\n * Starts the server on 127.0.0.1 and a port the system picks. The server does\n * not keep the process alive: the proxy ends when its host closes stdio.\n */\nexport async function startViewServer(options: ViewServerOptions): Promise<ViewServer> {\n const store = new ViewStore(options.limit ?? VIEW_STORE_LIMIT)\n let port = 0\n const allowedHosts = () => new Set([`127.0.0.1:${port}`, `localhost:${port}`])\n const allowedOrigins = () => new Set([`http://127.0.0.1:${port}`, `http://localhost:${port}`])\n\n async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const host = (req.headers.host ?? '').toLowerCase()\n if (!allowedHosts().has(host)) {\n sendJson(res, 403, { error: 'forbidden host' })\n return\n }\n const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`)\n const method = req.method ?? 'GET'\n const pathname = url.pathname\n\n if (method === 'GET' && pathname === '/view') {\n const id = url.searchParams.get('view') ?? ''\n const entry = /^[A-Za-z0-9_-]+$/.test(id) ? store.get(id) : undefined\n send(res, 200, 'text/html; charset=utf-8', entry ? pageWithEntry(options.html(), id, entry) : options.html())\n return\n }\n\n const viewMatch = /^\\/api\\/view\\/([A-Za-z0-9_-]+)$/.exec(pathname)\n if (method === 'GET' && viewMatch) {\n const entry = store.get(viewMatch[1])\n if (!entry) {\n sendJson(res, 404, { error: 'unknown view' })\n return\n }\n sendJson(res, 200, { arguments: entry.arguments, result: entry.result })\n return\n }\n\n const toolMatch = /^\\/api\\/tools\\/([A-Za-z0-9_-]+)$/.exec(pathname)\n if (method === 'POST' && toolMatch && VIEW_PAGE_TOOLS.has(toolMatch[1])) {\n // A page on another site can post to 127.0.0.1 with the right Host\n // header. Its browser sends its own Origin, and a JSON body needs a\n // preflight this server never answers, so both are required here.\n const origin = req.headers.origin\n if (origin !== undefined && !allowedOrigins().has(origin.toLowerCase())) {\n sendJson(res, 403, { error: 'forbidden origin' })\n return\n }\n const contentType = (req.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase()\n if (contentType !== 'application/json') {\n sendJson(res, 415, { error: 'send application/json' })\n return\n }\n let body: unknown\n try {\n const text = await readBody(req, VIEW_BODY_LIMIT_BYTES)\n body = text.trim() === '' ? {} : JSON.parse(text)\n } catch (err) {\n if (err instanceof BodyTooLarge) {\n sendJson(res, 413, { error: 'body too large' })\n req.resume()\n return\n }\n sendJson(res, 400, { error: 'invalid JSON' })\n return\n }\n const args = isRecord(body) ? (body.arguments ?? {}) : undefined\n if (!isRecord(args)) {\n sendJson(res, 400, { error: 'arguments must be an object' })\n return\n }\n const result = await options.runTool(toolMatch[1], args)\n sendJson(res, 200, result)\n return\n }\n\n sendJson(res, 404, { error: 'not found' })\n }\n\n const server: Server = createServer((req, res) => {\n handle(req, res).catch((err: unknown) => {\n options.log?.('view.server.request_failed', {\n error: err instanceof Error ? err.message : String(err),\n })\n if (!res.headersSent) sendJson(res, 500, { error: 'internal error' })\n else res.end()\n })\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(0, '127.0.0.1', () => {\n server.off('error', reject)\n resolve()\n })\n })\n const address = server.address()\n if (!address || typeof address !== 'object') {\n server.close()\n throw new Error('The view server has no port')\n }\n port = address.port\n server.unref()\n options.log?.('view.server.started', { port })\n\n return {\n port,\n put: (entry, id) => store.put(entry, id),\n viewUrl: (id) => `http://127.0.0.1:${port}/view?view=${encodeURIComponent(id)}`,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n server.closeAllConnections?.()\n }),\n }\n}\n","/**\n * The columns the Chain Insights view reads from a graph_query row to draw a\n * graph. The view (Chain Insights UI, apps/claude-view/pick.ts, graphFromRows)\n * reads them by name; the proxy's picture hint names them to the model, and\n * every FLOWS_TO recipe this package teaches must return all of them, or the\n * drawn link panel shows \"Unavailable\" for the column the recipe forgot\n * (0.54.2: first_seen_timestamp). tests/graph-row-columns.test.ts holds the\n * three sides together: the bundled view, the hint and the recipes.\n */\n\n/** A row draws a link only with both of these. */\nexport const GRAPH_REQUIRED_COLUMNS = Object.freeze(['from_address', 'to_address'] as const)\n\n/** What a FLOWS_TO link carries: the link panel of the view shows each one. */\nexport const GRAPH_LINK_COLUMNS = Object.freeze([\n 'amount_usd_sum',\n 'tx_count',\n 'first_seen_timestamp',\n 'last_seen_timestamp',\n] as const)\n\n/** What the two addresses of a link carry: labels and the exchange mark on the node. */\nexport const GRAPH_NODE_COLUMNS = Object.freeze([\n 'from_labels',\n 'to_labels',\n 'from_is_exchange',\n 'to_is_exchange',\n] as const)\n\n/** The kind of a link, when a query joins several link types. */\nexport const GRAPH_KIND_COLUMNS = Object.freeze(['link_kind'] as const)\n\n/** Every optional column, in the order the hint names them. */\nexport const GRAPH_OPTIONAL_COLUMNS = Object.freeze([\n ...GRAPH_LINK_COLUMNS,\n ...GRAPH_KIND_COLUMNS,\n ...GRAPH_NODE_COLUMNS,\n])\n\n/** The columns a FLOWS_TO recipe must return beside from_address and to_address. */\nexport const FLOWS_RECIPE_COLUMNS = Object.freeze([...GRAPH_LINK_COLUMNS, ...GRAPH_NODE_COLUMNS])\n\n/** The `RETURN` tail of a FLOWS_TO recipe over link `f` between `from` and `to`. */\nexport function flowsRecipeReturn(from: string, to: string, link = 'f'): string {\n return (\n `${from}.address AS from_address, ${to}.address AS to_address, ` +\n `${link}.amount_usd_sum AS amount_usd_sum, ${link}.tx_count AS tx_count, ` +\n `${link}.first_seen_timestamp AS first_seen_timestamp, ${link}.last_seen_timestamp AS last_seen_timestamp, ` +\n `${from}.labels AS from_labels, ${to}.labels AS to_labels, ` +\n `${from}.is_exchange AS from_is_exchange, ${to}.is_exchange AS to_is_exchange`\n )\n}\n\n/** The `AS name` columns a query returns. */\nexport function returnedColumns(query: string): string[] {\n return [...query.matchAll(/\\bAS\\s+([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1])\n}\n\n/** A query that draws a graph of FLOWS_TO links: it matches the link and returns both addresses. */\nexport function isFlowsGraphQuery(query: string): boolean {\n const columns = new Set(returnedColumns(query))\n return /\\[\\w+:FLOWS_TO\\]/.test(query) && GRAPH_REQUIRED_COLUMNS.every((c) => columns.has(c))\n}\n","/**\n * Connecting the proxy to the Chain Insights Graph endpoint.\n *\n * The hosted endpoint sheds load with HTTP 429 (an in-flight cap per IP, 8\n * anonymous, with Retry-After: 1). Before this module, a 429 on the first\n * POST made the proxy fall back to the SSE transport, which the endpoint\n * answers with 405, and the proxy then reported \"Graph unreachable ... SSE\n * error: Non-200 status code (405)\" for every graph query of its lifetime,\n * even once the endpoint had room again. Seen 2026-10-09 with nine proxies\n * on one workstation: two of ten test conversations lost every topology and\n * facts query while chain lookups kept working.\n *\n * Rules:\n * - 429, 502, 503 and 504 are \"busy\": wait (Retry-After, else a doubling\n * backoff from one second) and try the same transport again, up to\n * `attempts` times. Never fall back to SSE for a busy endpoint.\n * - 404 and 405 mean the endpoint is not streamable: try SSE once.\n * - Anything else (a refused connection, DNS, TLS) tries SSE once as before.\n * - A failure that was busy at the end is `retryable`: the proxy stays up and\n * connects again on the next graph call instead of answering dead forever.\n */\n\nexport type RemoteTransport = 'streamable_http' | 'sse'\n\n/** The least of an MCP client this module needs: connect, and close what a failed connect left attached. */\nexport interface ConnectableClient {\n connect(transport: unknown): Promise<void>\n close?: () => Promise<void>\n}\n\n/**\n * Connects a client to a fresh transport. The SDK leaves a transport attached\n * when `connect` fails after `start` (an initialize answered 429), and the\n * next `connect` then throws \"Already connected to a transport\", so every try\n * closes first. Seen in round 83 of the 2026-10-10 test loop.\n */\nexport async function connectFresh(client: ConnectableClient, transport: unknown): Promise<void> {\n if (typeof client.close === 'function') await client.close().catch(() => undefined)\n await client.connect(transport)\n}\n\nexport interface RemoteConnectResult {\n connected: boolean\n transport: RemoteTransport | null\n /** The user-facing reason when not connected. */\n message?: string\n /** True when a later attempt may succeed (the endpoint was busy). */\n retryable: boolean\n attempts: number\n}\n\nexport interface RemoteConnectOptions {\n endpoint: string\n /** Opens a fresh streamable-HTTP transport and connects. Throws on failure. */\n connectStreamable: () => Promise<void>\n /** Opens a fresh SSE transport and connects. Throws on failure. */\n connectSse: () => Promise<void>\n /** How many streamable tries while the endpoint is busy. */\n attempts?: number\n sleep?: (ms: number) => Promise<void>\n log?: (event: string, fields: Record<string, unknown>) => Promise<void> | void\n}\n\nconst BUSY = new Set([429, 502, 503, 504])\nconst NOT_STREAMABLE = new Set([404, 405])\n\n/** The HTTP status an SDK transport error carries, from its `code` or its text. */\nexport function httpStatusOf(err: unknown): number | null {\n if (err && typeof err === 'object') {\n const code = (err as { code?: unknown }).code\n if (typeof code === 'number' && code >= 100 && code < 600) return code\n const message = (err as { message?: unknown }).message\n if (typeof message === 'string') {\n const m = /\\b(4\\d\\d|5\\d\\d)\\b/.exec(message)\n if (m) return Number(m[1])\n }\n }\n return null\n}\n\n/** Milliseconds to wait before the next try: Retry-After when the error names one, else 1 s doubling. */\nexport function busyDelayMs(err: unknown, attempt: number): number {\n const retryAfter = (err as { retryAfter?: unknown } | null)?.retryAfter\n if (typeof retryAfter === 'number' && retryAfter > 0) return Math.min(retryAfter * 1000, 30_000)\n return Math.min(1000 * 2 ** (attempt - 1), 8000)\n}\n\nexport function describeRemoteFailure(endpoint: string, err: unknown, status: number | null): string {\n if (status !== null && BUSY.has(status)) {\n return (\n `Chain Insights Graph is at capacity at ${endpoint} (HTTP ${status}, class capacity): ` +\n 'wait at least 5 seconds, then send the same query once.'\n )\n }\n const text = err instanceof Error ? err.message : String(err)\n return `Chain Insights Graph unreachable at ${endpoint}: ${text}`\n}\n\nexport async function connectRemote(options: RemoteConnectOptions): Promise<RemoteConnectResult> {\n const attempts = Math.max(1, options.attempts ?? 5)\n const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))\n const log = options.log ?? (() => undefined)\n let lastErr: unknown = null\n let lastStatus: number | null = null\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await options.connectStreamable()\n await log('remote.connect', { transport: 'streamable_http', endpoint: options.endpoint, attempt })\n return { connected: true, transport: 'streamable_http', retryable: false, attempts: attempt }\n } catch (err) {\n lastErr = err\n lastStatus = httpStatusOf(err)\n await log('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: options.endpoint,\n attempt,\n status: lastStatus,\n })\n if (lastStatus !== null && BUSY.has(lastStatus) && attempt < attempts) {\n await sleep(busyDelayMs(err, attempt))\n continue\n }\n break\n }\n }\n const busy = lastStatus !== null && BUSY.has(lastStatus)\n if (!busy) {\n // Not streamable, or not reachable at all: one SSE try, as before.\n try {\n await options.connectSse()\n await log('remote.connect', { transport: 'sse', endpoint: options.endpoint })\n return { connected: true, transport: 'sse', retryable: false, attempts }\n } catch (err2) {\n await log('remote.connect_failed', { transport: 'sse', endpoint: options.endpoint, status: httpStatusOf(err2) })\n // The SSE 405 says nothing the user can act on; name the first failure\n // unless the streamable failure was only \"not streamable\".\n const named = lastStatus !== null && NOT_STREAMABLE.has(lastStatus) ? err2 : lastErr\n return {\n connected: false,\n transport: null,\n retryable: false,\n attempts,\n message: describeRemoteFailure(options.endpoint, named, null),\n }\n }\n }\n return {\n connected: false,\n transport: null,\n retryable: true,\n attempts,\n message: describeRemoteFailure(options.endpoint, lastErr, lastStatus),\n }\n}\n","/**\n * A UTC text twin beside every timestamp column of a graph answer.\n *\n * The graph serves integer milliseconds. A model that converts them by head\n * gets the time of day wrong about one time in three (loop 3, 2026-10-10:\n * four of twelve answers about one block named 00:15, 09:35, 06:35 or 09:15\n * for 23:55:43 UTC). Hosts without a shell (Claude Desktop) have no other\n * way to convert, so the proxy writes `<column>_utc` next to each one.\n */\n\nconst TIMESTAMP_KEY = /(_timestamp|_time|_at|_seen|_active)$/\nconst MIN_MS = 1_000_000_000_000 // 2001-09-09\nconst MAX_MS = 4_000_000_000_000 // 2096-10-02\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** `2026-08-05 23:55:43 UTC` for epoch milliseconds. */\nexport function utcText(ms: number): string {\n return `${new Date(ms).toISOString().slice(0, 19).replace('T', ' ')} UTC`\n}\n\nfunction isEpochMs(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= MIN_MS && value <= MAX_MS\n}\n\n/**\n * One row with a `<key>_utc` string after every epoch-millisecond timestamp\n * column. A column the model itself named `*_utc` (its query asked for the\n * twin as a property, see stripUtcProperties) arrives as milliseconds and is\n * rendered as UTC text in place: the model gets the column it asked for.\n */\nexport function addTimestampText(row: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(row)) {\n out[key] = key.endsWith('_utc') && isEpochMs(value) ? utcText(value) : value\n if (TIMESTAMP_KEY.test(key) && isEpochMs(value) && !(`${key}_utc` in row)) out[`${key}_utc`] = utcText(value)\n }\n return out\n}\n\n/**\n * The `_utc` twin is added to a reply by the proxy; it is no graph property.\n * Thirteen of 48 loop-5 rounds wrote `f.first_seen_timestamp_utc` into a\n * query anyway: topology answers null, a SWAP read is refused. The property\n * reference is rewritten to its millisecond column, `x.<column>_utc` to\n * `x.<column>`; an alias after AS is not a property and stays as written, so\n * `f.first_seen_timestamp_utc AS first_seen_utc` yields a `first_seen_utc`\n * column that addTimestampText renders as text.\n */\nexport function stripUtcProperties(query: string): string {\n return query.replace(/\\.([A-Za-z_][A-Za-z0-9_]*?)_utc\\b/g, '.$1')\n}\n\n/** graph_query and graph_query_batch arguments with stripUtcProperties applied to every query. */\nexport function withoutUtcProperties(toolName: string, args: Record<string, unknown>): Record<string, unknown> {\n if (toolName === 'graph_query' && typeof args.query === 'string') return { ...args, query: stripUtcProperties(args.query) }\n if (toolName === 'graph_query_batch' && Array.isArray(args.queries)) {\n return {\n ...args,\n queries: args.queries.map((entry) =>\n isRecord(entry) && typeof entry.query === 'string' ? { ...entry, query: stripUtcProperties(entry.query) } : entry\n ),\n }\n }\n return args\n}\n\nfunction mapResults(holder: unknown): unknown {\n if (!isRecord(holder) || !Array.isArray(holder.results)) return holder\n return { ...holder, results: holder.results.map((row) => (isRecord(row) ? addTimestampText(row) : row)) }\n}\n\n/**\n * The structured content of graph_query (facts.query.results) or\n * graph_query_batch (facts.queries[].results) with the UTC twins added.\n * Anything else comes back as it was.\n */\nexport function withTimestampText(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n const facts = structuredContent.facts\n if (!isRecord(facts.query) && !Array.isArray(facts.queries)) return structuredContent\n const next: Record<string, unknown> = { ...facts }\n if (isRecord(facts.query)) next.query = mapResults(facts.query)\n if (Array.isArray(facts.queries)) next.queries = facts.queries.map(mapResults)\n return { ...structuredContent, facts: next }\n}\n\ntype TextBlock = { type: string; text?: string }\n\n/**\n * A tool result whose structured content and single JSON text block both carry\n * the UTC twins. A text block that is not the JSON of the structured content\n * is left alone.\n */\nexport function withTimestampTextResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withTimestampText(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured =\n parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return {\n ...result,\n structuredContent: structured,\n content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content,\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\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 { rateLimitedResult, withRateLimitRetry } from './rate-limit.js'\nimport { withBusyMemberRetry, type BatchResult } from './batch-busy-retry.js'\nimport {\n HIDDEN_REMOTE_TOOL_NAMES,\n PUBLIC_MCP_TOOL_ALLOWED_ARGS,\n PUBLIC_MCP_TOOL_REQUIRED_ARGS,\n isAppOnlyTool,\n} from './tool-visibility.js'\nimport { PaymentRequiredError } from './client.js'\nimport { primitiveBackendUsageStatus } from './usage-status.js'\nimport { unavailableSubscriptionStatus } from './subscription-status.js'\nimport { mirrorGraphNetworkCapabilities } from './capabilities.js'\nimport { ROUTING_NOT_SERVED, routingHead, routingHintLines } from './layer-routing.js'\nimport {\n GraphSchemaError,\n META_SCHEMA_DESCRIPTION,\n META_SCHEMA_TITLE,\n handleMetaSchema,\n type GraphSchemaDependencies,\n} from './graph-schema.js'\nimport { actionLogSignalsFromResult, appendActionLog } from './action-log.js'\nimport {\n FLOWS_MAX_OFFSET,\n GRAPH_EXPAND_DESCRIPTION,\n GRAPH_EXPAND_TITLE,\n GRAPH_EXPAND_TOOL,\n handleGraphExpand,\n type FlowsDependencies,\n type GraphQueryAnswer,\n} from './flows.js'\nimport { newViewId, startViewServer, type ViewServer } from './view-server.js'\nimport { GRAPH_OPTIONAL_COLUMNS, flowsRecipeReturn } from './graph-row-columns.js'\nimport { connectFresh, connectRemote } from './remote-connect.js'\nimport { withTimestampTextResult, withoutUtcProperties } from './timestamp-text.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'meta_network_capabilities',\n 'meta_schema',\n 'meta_usage_status',\n 'meta_subscription_status',\n 'meta_help',\n 'meta_rules',\n 'wallet_balance',\n 'wallet_topup',\n GRAPH_EXPAND_TOOL,\n])\n// Local tools only the view calls (visibility [\"app\"]): never named to the model.\nconst APP_ONLY_LOCAL_TOOL_NAMES = new Set([GRAPH_EXPAND_TOOL])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\n\n// The Chain Insights view: one HTML file, served by this proxy and by nothing\n// else. The graph endpoint serves no view; a view, a ui:// resource or a\n// _meta.ui it advertises is never forwarded. The tool result says which view\n// the file draws: a graph, a chart or a table from the columns of a graph_query\n// answer, the balance (meta_usage_status, meta_subscription_status), or the\n// answer of a click (graph_expand). The metadata below is the wire contract of\n// the Claude views, character for character.\nexport const CLAUDE_VIEW_URI = 'ui://chain-insights/view'\nexport const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app'\n// A tool whose answer the view draws: the resource under the current key and\n// the legacy flat key hosts still read.\nconst VIEW_TOOL_META = {\n ui: { resourceUri: CLAUDE_VIEW_URI },\n 'ui/resourceUri': CLAUDE_VIEW_URI,\n}\n// A tool only the view calls: the host keeps it away from the model.\nconst APP_ONLY_TOOL_META = { ui: { resourceUri: CLAUDE_VIEW_URI, visibility: ['app'] } }\nconst VIEW_RESOURCE_META = {\n ui: { prefersBorder: false, csp: { resourceDomains: ['https://assets.claude.ai'] } },\n}\nconst VIEW_TOOL_ANNOTATIONS = { readOnlyHint: true }\n// The answers that also open as a browser window on the user's PC\n// (src/mcp/view-server.ts): each carries view_url.\nconst BROWSER_VIEW_TOOL_NAMES = new Set(['graph_query', 'graph_query_batch', GRAPH_EXPAND_TOOL])\nconst BROWSER_VIEW_LINE = 'Open the graph in a browser window:'\nconst CLAUDE_VIEW_FILE = 'claude-view.html'\n\nlet claudeViewHtml: string | undefined\n\n/**\n * The view file shipped in the package: dist/apps/claude-view.html next to the\n * built proxy, or src/mcp/apps/claude-view.html when run from source. Read once.\n */\nexport function readClaudeViewHtml(): string {\n if (claudeViewHtml !== undefined) return claudeViewHtml\n const here = path.dirname(fileURLToPath(import.meta.url))\n const candidates = [\n path.join(here, 'apps', CLAUDE_VIEW_FILE),\n path.join(here, '..', 'src', 'mcp', 'apps', CLAUDE_VIEW_FILE),\n ]\n for (const candidate of candidates) {\n try {\n claudeViewHtml = readFileSync(candidate, 'utf8')\n return claudeViewHtml\n } catch {\n /* try the next place */\n }\n }\n throw new Error(`The Chain Insights view file ${CLAUDE_VIEW_FILE} is missing from the package`)\n}\n\nexport type McpProxyMode = 'workspace' | 'stateless'\n\nexport function resolveMcpProxyMode(env: NodeJS.ProcessEnv = process.env): McpProxyMode {\n const raw = env['CHAIN_INSIGHTS_MCP_PROXY_MODE']?.trim().toLowerCase()\n if (!raw || raw === 'stateless') return 'stateless'\n if (raw === 'workspace') return 'workspace'\n if (raw === 'no-workspace' || raw === 'workspace-less') return 'stateless'\n throw new Error(`CHAIN_INSIGHTS_MCP_PROXY_MODE must be workspace or stateless; got \"${raw}\"`)\n}\n\nconst GRAPH_LAYERS_TEXT =\n \"Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for the chain node's own record of one known transaction, block, address or the head.\"\n\n// The rules where every host reads them, the tool description: a host that\n// loads no skill and keeps no server instructions still sees this first.\nconst TOOL_RULES_FIRST =\n 'Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once before this tool. For a graph, alias the columns from_address and to_address; the host draws the picture from the answer itself. Never draw it again with another tool, such as a visualize or widget tool.'\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n meta_network_capabilities:\n 'Return the current Chain Insights network and tool support matrix. Takes no arguments: send {}.',\n meta_schema: META_SCHEMA_DESCRIPTION,\n meta_usage_status: \"Return the caller's public free graph_query quota for the current UTC day.\",\n meta_subscription_status:\n \"Return the caller's CIA subscription window end, daily allowance, consumption, and tier.\",\n meta_help: 'Show a short guide to Chain Insights tools and workflow.',\n meta_rules:\n 'Return the Chain Insights rules: how to write graph_query and graph_query_batch reads that are served, how to name columns so the answer is drawn as a graph, a time series or a table, and how to write the answer. Call it once per chat, before the first graph query, when the chain-insights-cypher skill is not loaded. Takes no arguments: send {}.',\n wallet_balance:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,\n graph_query_batch: `${TOOL_RULES_FIRST} Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads. For a time series across days, send one USE facts total per day with the query id set to the day, \"YYYY-MM-DD\": the view joins them into one series. Write every address in full in your answer, all 42 characters.`,\n}\n// Titles for proxied tools whose graph endpoint definition carries none.\nconst KNOWN_PUBLIC_TOOL_TITLES: Record<string, string> = {\n graph_query: 'Graph Query',\n graph_query_batch: 'Graph Query Batch',\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: robinhood, the one public network. CIA does not pick a default network, so always pass it. meta_network_capabilities (no arguments) lists the live networks.'\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\n// What the Chain Insights view draws from a graph_query answer, told by column\n// name. The same rules are in skills/chain-insights-cypher/SKILL.md.\nexport const PICTURE_RULES = [\n 'Pictures: hosts that draw views, such as Claude Desktop and the Codex app, draw a graph_query answer from its column names. No other tool is needed. Choose the picture by the question: who sent to whom, flows and routes are a graph; how something changed over time is a time series; a list, a lookup or the details of one thing is a table.',\n `- Rows with from_address and to_address columns draw a graph of at most 5,000 addresses. Optional columns: ${GRAPH_OPTIONAL_COLUMNS.join(', ')}. A recipe that draws a graph returns all of them; a column it leaves out shows as Unavailable in the drawn link. Example: USE topology MATCH (a:Address {address: $addr})-[f:FLOWS_TO]->(b:Address) WHERE NOT a:Pool RETURN ${flowsRecipeReturn('a', 'b')} ORDER BY f.last_seen_timestamp DESC LIMIT 25`,\n '- Rows with a day, date, hour, week, month or *_timestamp column, number columns, no address in any value and at least 3 distinct times draw a time series. Within one day, the transfers of a known pair: USE facts MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN t.block_timestamp AS block_timestamp, t.amount_usd AS amount_usd LIMIT 200',\n '- Across days: facts reads one day at a time, so send graph_query_batch with one query per day (3 to 20 days), each query id the day as \"YYYY-MM-DD\", each returning one total row: USE facts MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN count(t) AS tx_count, sum(t.amount_usd) AS amount_usd_sum. The view joins the days into one time series. Do not group by day or name several days in one read: both are refused.',\n '- Any other rows draw a table.',\n '- The host draws the picture from the answer itself. Do not draw it again with another tool, such as a visualize or widget tool: the user would see the same answer drawn twice.',\n \"- Every drawn answer carries view_url, a page on the user's own PC. When the user asks to see the graph bigger, in a window, or full screen, or says the Open in browser button did nothing: if this host has a browser tool (Claude Desktop: the Claude_Browser navigate tool), open view_url there; otherwise give the user the view_url link as a plain link. Claude Desktop opens no local link from the button itself, only from its browser tool.\",\n].join('\\n')\n\n// The same rule in one sentence, for meta_help.\nconst PICTURE_HELP_LINE =\n 'In hosts that draw views, a graph_query answer with from_address and to_address columns draws a graph, a day or time column with numbers draws a time series (across days: a graph_query_batch of one-day totals, each query id the day), and any other rows draw a table.'\n\n// Hosts that read skills from local files (Claude Code, Codex) load the\n// chain-insights-cypher skill that setup copies and the proxy keeps current.\n// Claude Desktop chat reads skills only from the user's claude.ai account, so\n// the proxy serves the same text through meta_rules: no upload, and always the\n// version of the installed package.\nconst RULES_FIRST =\n 'Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once (send {}) before the first graph_query or graph_query_batch, and follow what it returns. When the skill is loaded, do not call meta_rules.'\n\n// What every reply to the user follows. First in the instructions, so a host\n// that keeps only their start still has them; the skill says the same.\nconst ANSWER_RULES = [\n 'Answer rules, for every reply to the user:',\n '1. Write every address in full: 0x plus 40 hex characters, 42 in all, in sentences, lists, tables and summaries. Never shorten one with ... or …, not even a repeat mention of an address named earlier. The drawn picture may shorten its labels; your text never does. A table column that needs a short label names the thing (Pair A, Wallet Y) and the full address is listed above the table.',\n '2. Do not print the query, its USE line, its LIMIT, the epoch numbers you computed, or a Ref: line, unless the user asks how a number was found. Write a time from the <column>_utc twin the answer carries, never by converting milliseconds by head; the twin is in the reply only, so ask the query for the _timestamp column. Say what a value means in plain words and never print a column name such as is_contract, delegated_to or nonce in the answer.',\n '3. Tokens are not dollars: a transfer moves tokens worth an amount at the day price. Say \"tokens worth about 183 USD on 2026-10-06\", never \"sent 183 USD\". Write the unit as USD after the number, never a $ sign; head a table column \"Tokens worth (USD)\".',\n '4. Never draw a graph or a chart in text, ASCII or Mermaid: the host draws the picture from the columns. Write a short answer beside it, and never promise a picture: in a host that draws nothing, such as Claude Code or a terminal, the text answer carries everything.',\n].join('\\n')\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 graph_query(_batch) for every question about addresses, money flow and transactions.',\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 '- Pass network=robinhood, the one public network, on every graph tool call. CIA does not pick a default network. meta_network_capabilities takes no arguments (send {}); call it only to check which tools and layers are live.',\n ...routingHintLines(),\n '- Coverage is per kind of data. coverage.complete_through_block is the floor of USE facts: below it every raw relationship is indexed. graph_progress.complete_through_block is the floor of USE topology, and graph_progress.layers gives each link kind its own height. An empty answer above the floor may mean not indexed yet, not absent on chain. An absent chain_tip_block means the indexers are catching up: read freshness.max_data_age_seconds for the lag.',\n \"- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. There is no separate identity key. network is the query's network on every node and relationship, never stored, except on :Chain, where it names the remote chain.\",\n '- An Address carries a kind from chain facts. topology carries :Account or :Contract as a second label on an Address (:Contract:SmartAccount for a contract wallet), with is_contract kept beside :Contract, and an address that only received has none. facts serves no kind, and a facts read that names :Account or :Contract is refused: ask USE topology or USE chain. chain Address returns is_contract, nonce and delegated_to at the newest block, or at the block named by at_block. block_timestamp is epoch milliseconds on every layer.',\n '- Address nodes carry address, network, labels, and the role flags is_exchange, is_scam, is_victim and is_sanctioned. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner, owner_state) 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 LIMIT 25.',\n '- Labels hold role words, never detector names: Exchange (plus the exchange name), Scam, Victim and Sanctioned (plus the entity name). Each role is also a node label and a flag on the Address: :Exchange and is_exchange follow the exchange label, :Scam and is_scam the risk label, :Victim and is_victim the protection label, :Sanctioned and is_sanctioned the sanctioned label. Each flag is present only when true and absent otherwise, never false: test IS NOT NULL or IS NULL, not = false. A withdrawn label removes its node label and its flag.',\n '- Address nodes carry base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)). tx_count counts token and native transfers plus internal native transfers (a contract sending ETH during a call), and amount_usd_sum prices them all. USE facts TRANSFER lists all three, so the rows of a pair are the transfers its link counts, up to the height the link was built to. A link covers all time and a USE facts read covers one day: read the transfers of a pair one day at a time. A transaction anchor of a pair resolves through USE facts, on the UTC day of the first_seen_timestamp or last_seen_timestamp of the link: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN t.tx_id, t.block_timestamp LIMIT 1.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd) LIMIT 50.',\n '- Labels and per-label risk live on the address node: the labels array plus three parallel lists, label_risk_labels, label_risk_levels and label_risk_updated_timestamps, where entry i of each list is one label row. No overall risk score is served. USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.',\n '- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, kind, price_usd, and price_missing. Every TRANSFER row has a kind: token, native or internal. An internal row is ETH a contract sends while it runs a call, such as the ETH leg of a wrap. Add t.kind = \"internal\" to the pair and the day to read only the internal rows. A facts read names an address pair with one day, whether it is a TRANSFER row-select or a count()/sum() aggregate: both endpoint addresses as {address: \"...\"} on from and to, with WHERE t.block_date = \"YYYY-MM-DD\". A transaction is read on USE chain by its tx_id, not on facts. t.block_timestamp bounds in epoch milliseconds may narrow the day to a time window. One address, a day alone, a window of days, a block range or a bare LIMIT is not enough. A facts read holds at most 200 rows in a reply, has one relationship and no hop, and takes no ORDER BY: sort the page yourself.',\n '- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.',\n \"- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the transaction sender that moved the owner's tokens (ERC-20/721), or the event operator (ERC-1155); not the approved spender. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). Valuation fields: valuation_tracked_count, valued_count and the reason counters missing_valuation_price_count, unknown_quantity_count, unrepresentable_quantity_count and usd_range_count. valuation_complete is true when valued_count equals valuation_tracked_count, valuation_coverage_ratio is valued_count divided by valuation_tracked_count, and amount_usd_sum counts a transfer with no USD value as 0, so it is a floor unless valuation_complete is true. One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.\",\n '- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. pair_key and synced_through_height are internal sync bookkeeping, not to be queried. synced_through_height is also on the other summed links (SWAPPED, OPERATED_BY, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED, APPROVED, SPONSORED, BUNDLED, SIGNED_FOR) and pair_key on SWAPPED: bookkeeping there too. Tx ids of every transfer, internal ones included, come from USE facts TRANSFER. Averages come from inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.',\n '- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. sold_usd and bought_usd sum the routes on the edge at the day price: a route side with no price adds 0, so 0 can mean no price. Do not read 0 as worth nothing. The USE facts SWAP row says which side had no price (sold_price_missing, bought_price_missing). strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool code is not proven). Today no route is swap: the swap reader reads transaction receipts only, with no execution trace, so every served route is swap_like, with reason unknown_pool_code and families unknown. A Uniswap V2 or V3 swap reads this way, and unknown does not mean the protocol is unsupported. A filter on strength = \"swap\" or on a known family matches nothing. A route that could not be paired (swap_unsplit) is never served: it has no payer, recipient or pool, makes no SWAPPED edge and no SWAP row, and exists in the warehouse only. Every Uniswap v4 swap is swap_unsplit today, by design, so v4 swaps are hidden. A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened. Swap attribution is read from SWAPPED, the aggregate (strength, pools, families), or from the USE facts SWAP row, one route. FLOWS_TO carries value only.',\n \"- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.\",\n '- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.',\n '- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.',\n '- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event. SWAP, LIQUIDITY_* and BRIDGE_CROSSING take the same rule as TRANSFER: an address pair with one day. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.',\n '- A USE facts SWAP row carries no route: it has no pools and no families column. A read that names one of them, to return it, to filter on it or to order by it, is refused. The route of a swap stays a topology question. For the pools of the swaps of an address, read SWAPPED.pools and SWAPPED.families on USE topology, anchored on the payer or the recipient. SWAPPED has one link per payer, recipient, sold asset and bought asset, so its pools cover every route on the link, not one route.',\n '- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. is_exchange is absent unless true, so a labelled node with no is_exchange is walked through, and is_scam, is_victim and is_sanctioned do not end a walk. At a Pool, follow the pool trace rule above.',\n '- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route LIMIT 5. ANY SHORTEST takes the same pattern. A route search asks for one path. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.',\n '- Call meta_schema {network} first when you need field names; it is cached for 24 hours. The two discovery reads below are the fallback.',\n '- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) RETURN n.address AS address, n.network AS network, n.labels AS labels, n.last_activity_timestamp AS last_activity_timestamp 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 \"- Anchor every topology read that filters on a link property: put an address in its pattern. Without one, a read starts from every link of the type it names, and you should not count on the filter to narrow that: WHERE x.strength = 'swap' on SWAPPED checks every SWAPPED link, LIMIT stops the read only after enough rows match, and a filter that matches few or none can run to the 60 s topology limit and fail with query_timeout. Example: MATCH (a:Address {address: $addr})-[x:SWAPPED]->(b:Address) WHERE x.swap_count >= 2 RETURN b.address, x.swap_count LIMIT 25. Pick an address with few links: degree_out and degree_in are a rough guide, because they count neighbours, not links, and an address with hundreds of thousands of neighbours can fail the same way. Discovery probes with LIMIT and no filter stay valid. The queries of one batch share a 100 s budget; USE facts queries stop at 30 s.\",\n '- graph_query uses the active Chain Insights graph endpoint. Select the graph with USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for one keyed lookup on the chain node: a transaction by tx_id, a block by block_height or block_hash, an address at one block, or the head. On topology, 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\n// The routing paragraph comes first: a host that keeps only the start of the\n// instructions must still show where each kind of question goes.\nconst ROUTING_HEAD = routingHead()\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is an AML and graph-analysis MCP server for AI agents.',\n RULES_FIRST,\n ANSWER_RULES,\n CHAIN_INSIGHTS_WORKFLOW,\n ROUTING_HEAD,\n PICTURE_RULES,\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 RULES_FIRST,\n ANSWER_RULES,\n 'Call graph_query or graph_query_batch with network=robinhood. meta_network_capabilities takes no arguments (send {}); call it only to check which tools and layers are live.',\n ROUTING_HEAD,\n PICTURE_RULES,\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 'graph_query':\n return {\n query: z.string().min(1).describe(`Read-only GQL/Cypher query. ${GRAPH_LAYERS_TEXT}`),\n network: NETWORK_SCHEMA,\n }\n case 'graph_query_batch':\n return {\n network: NETWORK_SCHEMA,\n queries: z\n .array(\n z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })\n )\n .min(1)\n .max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction fallbackGraphPrimitiveTools(): McpTool[] {\n return FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES.map((name) => ({\n name,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[name],\n }))\n}\n\n/**\n * Local payment wallet address for tools that address the caller on the\n * server (subscription_status). Returns null — never throws — when no wallet\n * is configured; callers degrade to an unavailable-shape result.\n */\n/**\n * Local payment wallet address for the top-up view: null when no wallet file\n * exists, and an error when the file exists but cannot be read. Reads the\n * address only; nothing is signed or sent.\n */\nasync function localTopupWalletAddress(): Promise<string | null> {\n const { isWalletConfigured } = await import('../wallet/index.js')\n if (!(await isWalletConfigured())) return null\n const { getWalletAccount } = await import('../wallet/tools.js')\n return (await getWalletAccount()).address\n}\n\nasync function localSubscriptionWalletAddress(): Promise<string | null> {\n try {\n const { getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return account.address\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n })\n )\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [\n {\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n },\n ],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) =>\n isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }\n ),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath =\n process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() ||\n path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(\n level: 'info' | 'error',\n event: string,\n fields: Record<string, unknown> = {}\n ): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(\n filePath,\n JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n',\n { mode: 0o600 }\n )\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((\n name: string,\n config: ToolRegistrationConfig,\n handler: ToolHandler\n ) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(\n remoteClient: RemoteToolCaller,\n logger: ReturnType<typeof createMcpLogger>\n): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const toolArgs = input.arguments ?? {}\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in PUBLIC_MCP_TOOL_REQUIRED_ARGS)) return normalized\n\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[toolName]\n if (!allowedArgs) return normalized\n return Object.fromEntries(Object.entries(normalized).filter(([key]) => allowedArgs.includes(key)))\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>\n): string | null {\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\nfunction knownPublicToolAnnotations(toolName: string): Record<string, boolean> | undefined {\n if (\n toolName === 'graph_query' ||\n toolName === 'graph_query_batch' ||\n toolName.startsWith('aml_')\n ) {\n return {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n }\n }\n return undefined\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\n/**\n * The text a call to the graph endpoint answers with when it throws: payment\n * guidance for a payment failure, the error otherwise. Shared by every proxied\n * tool and by the money-flow reads.\n */\nfunction remoteCallFailureText(toolName: string, err: unknown): string {\n if (err instanceof PaymentRequiredError) return err.message\n const msg = (err as Error).message ?? String(err)\n if (/\\b402\\b/.test(msg) || msg.toLowerCase().includes('payment')) {\n return (\n `Payment required for ${toolName}. 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 return `MCP call failed: ${msg}`\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 '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-schema',\n {\n title: 'Graph Schema',\n description: 'Read the live graph schema of one network before writing a query.',\n argsSchema: { network: NETWORK_SCHEMA },\n },\n async ({ network }) =>\n promptResult(\n `Use Chain Insights meta_schema with network ${network}. Report the labels, link types and fields exactly as returned; do not invent fields.`,\n 'Graph schema'\n )\n )\n\n server.registerPrompt(\n 'meta-usage-status',\n {\n title: 'Usage Status',\n description: \"Check the caller's public free graph_query quota.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_usage_status. Report the quota fields exactly as returned.',\n 'Usage status'\n )\n )\n\n server.registerPrompt(\n 'meta-subscription-status',\n {\n title: 'Subscription Status',\n description: \"Check the caller's CIA subscription window, daily allowance, and tier.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_subscription_status. Report the subscription facts exactly as returned.',\n 'Subscription status'\n )\n )\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Graph Query',\n description: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n query: z.string().describe('Read-only GQL/Cypher query'),\n },\n },\n async ({ network, query }) =>\n promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ].join('\\n'),\n 'Graph query'\n )\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Graph Query Batch',\n description:\n 'Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n queries: z\n .string()\n .describe('JSON array of query objects with optional id and required query fields'),\n per_query_timeout_seconds: z\n .string()\n .optional()\n .describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ network, queries, per_query_timeout_seconds }) =>\n promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds\n ? `per_query_timeout_seconds: ${per_query_timeout_seconds}`\n : '',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ]\n .filter(Boolean)\n .join('\\n'),\n 'Graph query batch'\n )\n )\n\n server.registerPrompt(\n 'wallet-balance',\n {\n title: 'Wallet Balance',\n description:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights wallet_balance. Show the wallet address, payment network, token, and amount exactly as returned.',\n 'Wallet balance'\n )\n )\n\n server.registerPrompt(\n 'meta-help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and workflow guidance.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_help. Summarize the available tools and workflow guidance without inventing capabilities.',\n 'Chain Insights help'\n )\n )\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (\n GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) &&\n Array.isArray(childValue)\n ) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\n// The remote result _meta passes through, MCP Apps keys (ui, ui/resourceUri)\n// included. Only the old graph-report envelope (chainInsights.graph) is dropped.\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\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\n// Raw graph arrays and app_data are stripped from the aml_* answers only, the\n// graph-report tools of the old viewer. Every other answer (graph_query,\n// graph_query_batch) keeps its structuredContent exactly as the hosted server\n// returned it: the views read it.\nfunction stripsGraphPayload(toolName: string): boolean {\n return toolName.startsWith('aml_')\n}\n\nfunction normalizeRemoteToolResult(toolName: string, result: RemoteToolResult) {\n const normalized = {\n content: result.content ?? [],\n structuredContent: stripsGraphPayload(toolName)\n ? sanitizeStructuredContentForGraphPayload(result.structuredContent)\n : result.structuredContent,\n _meta: sanitizeRemoteMeta(result._meta),\n isError: result.isError,\n }\n // Every timestamp column gets its UTC text twin (src/mcp/timestamp-text.ts).\n return toolName === 'graph_query' || toolName === 'graph_query_batch'\n ? withTimestampTextResult(normalized)\n : normalized\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 // Inside Claude Desktop the Claude Code copy cannot draw the views. When\n // Claude Desktop's own drawing copy is configured, this copy lists no tools\n // so that each tool appears once (src/mcp/claude-desktop.ts).\n const { defersToClaudeDesktop, DEFERRED_SERVER_INSTRUCTIONS } =\n await import('./claude-desktop.js')\n if (defersToClaudeDesktop()) {\n await logger.info('proxy.deferred_to_claude_desktop', { version: PACKAGE_VERSION })\n const deferred = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n { instructions: DEFERRED_SERVER_INSTRUCTIONS }\n )\n await deferred.connect(new StdioServerTransport())\n return\n }\n\n // npm runs no setup step on upgrade: bring the skill copies setup installed\n // up to this release. A failure here never stops the proxy.\n // CHAIN_INSIGHTS_SKILL_REFRESH=0 turns it off for people who manage the skill themselves.\n if (process.env['CHAIN_INSIGHTS_SKILL_REFRESH'] !== '0') {\n try {\n const { refreshInstalledSkills } = await import('../skill-refresh.js')\n const refresh = refreshInstalledSkills(PACKAGE_VERSION)\n if (refresh.refreshed.length > 0 || refresh.failed.length > 0) {\n await logger.info('skills.refresh', { version: PACKAGE_VERSION, ...refresh })\n }\n } catch (error) {\n await logger.info('skills.refresh', {\n version: PACKAGE_VERSION,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\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 let remoteRetryable = false\n // Connect to the graph endpoint: a busy endpoint (429) is waited for, a\n // non-streamable one gets the SSE fallback, and a busy failure leaves the\n // proxy able to connect again on the next graph call (src/mcp/remote-connect.ts).\n const connectRemoteOnce = async (): Promise<void> => {\n if (!mcpFetch) return\n const fetchForRemote = mcpFetch\n const result = await connectRemote({\n endpoint: graphMcpEndpoint,\n connectStreamable: () =>\n connectFresh(remoteClient, new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: fetchForRemote })),\n connectSse: async () => {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await connectFresh(remoteClient, new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: fetchForRemote }))\n },\n log: (event, fields) => (event === 'remote.connect' ? logger.info(event, fields) : logger.error(event, fields)),\n })\n remoteConnected = result.connected\n remoteRetryable = result.retryable\n if (result.connected) {\n remoteUnavailableMessage = undefined\n installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n } else {\n remoteUnavailableMessage = result.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 // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n // With the tool list cached, the proxy answers the host's initialize and\n // tools/list at once and connects to the endpoint in the background. A busy\n // endpoint (429) is retried for up to 15 s; a host that lists tools inside\n // that window (Codex starts a fresh proxy on every resumed turn and lists in\n // about 10 s) must still see graph_query, or it reports the connector\n // unavailable and reaches for a shell. The first graph call waits for the\n // connect in flight.\n let connecting: Promise<void> | null = null\n const startConnect = (): Promise<void> => {\n if (!connecting) {\n connecting = connectRemoteOnce().finally(() => {\n connecting = null\n })\n }\n return connecting\n }\n if (tools) {\n void startConnect()\n } else {\n await startConnect()\n }\n /** A graph call waits for the connect in flight; one that lost the endpoint to a busy signal tries again, once. */\n const ensureRemote = async (): Promise<void> => {\n if (remoteConnected) return\n if (connecting) {\n await connecting\n return\n }\n if (!remoteRetryable) return\n await startConnect()\n }\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 await ensureRemote()\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 // meta_schema: the live graph schema of one network, built from the catalog\n // statements of the graph endpoint and kept on disk for 24 hours. A build reads\n // through the proxy's one remote session; a cache hit reads nothing.\n const schemaDependencies: GraphSchemaDependencies = {\n endpoint: graphMcpEndpoint,\n withClient: async (fn) => {\n await ensureRemote()\n if (!remoteConnected) {\n throw new GraphSchemaError(\n `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`\n )\n }\n return fn(remoteClient)\n },\n describeFailure: (err) => remoteCallFailureText('meta_schema', err),\n }\n server.registerTool(\n 'meta_schema',\n {\n title: META_SCHEMA_TITLE,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_schema,\n inputSchema: {\n network: NETWORK_SCHEMA,\n refresh: z\n .boolean()\n .optional()\n .describe('Rebuild the schema now instead of reading the 24 hour cache.'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async (args) => handleMetaSchema(args, schemaDependencies)\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 _meta: VIEW_TOOL_META,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n await ensureRemote()\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n if (!remoteToolNames.has('usage_status')) {\n return jsonTextResult(primitiveBackendUsageStatus(graphMcpEndpoint))\n }\n const result = (await remoteClient.callTool({\n name: 'usage_status',\n arguments: {},\n })) as RemoteToolResult\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_usage_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return {\n content: [\n { type: 'text' as const, text: `Usage status failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n\n server.registerTool(\n 'meta_subscription_status',\n {\n title: 'Subscription Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_subscription_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n _meta: VIEW_TOOL_META,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n // Local proxy shape discipline: the tool is always present and never\n // throws. When the server tool is absent or errors, the facts explain\n // the unavailability.\n try {\n await ensureRemote()\n if (!remoteConnected) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n remoteUnavailableMessage ??\n `Chain Insights Graph is not connected at ${graphMcpEndpoint}`\n )\n )\n }\n if (!remoteToolNames.has('subscription_status')) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'The graph backend exposes primitive graph tools but no subscription_status tool.'\n )\n )\n }\n const walletAddress = await localSubscriptionWalletAddress()\n if (!walletAddress) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'No local payment wallet is configured; run `cia wallet create` or `cia wallet import` first.'\n )\n )\n }\n const result = (await remoteClient.callTool({\n name: 'subscription_status',\n arguments: { wallet: walletAddress },\n })) as RemoteToolResult\n if (result.isError === true) {\n const firstText = Array.isArray(result.content)\n ? result.content.find(\n (block): block is Extract<ContentBlock, { type: 'text' }> =>\n block.type === 'text' && typeof block.text === 'string'\n )\n : undefined\n const reason = firstText?.text\n ? `subscription_status failed: ${firstText.text}`\n : 'subscription_status failed: the server returned an error'\n return jsonTextResult(unavailableSubscriptionStatus(graphMcpEndpoint, reason))\n }\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_subscription_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n `subscription_status failed: ${(err as Error).message}`\n )\n )\n }\n }\n )\n\n server.registerTool(\n 'wallet_balance',\n {\n title: 'Wallet Balance',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.wallet_balance,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n const { formatWalletBalanceResult, getWalletAccount, getWalletBalanceResult } =\n await import('../wallet/tools.js')\n const account = await getWalletAccount()\n const structuredContent = await getWalletBalanceResult(account)\n return {\n content: [{ type: 'text' as const, text: formatWalletBalanceResult(structuredContent) }],\n structuredContent: structuredContent as unknown as Record<string, unknown>,\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n // graph_expand, the one dedicated tool, for the view only (visibility\n // [\"app\"]): a node click is at most five anchored topology graph_query reads\n // (the node, then two chunks a side), a link click one facts graph_query\n // read, all sent through the remote client, the\n // path and payment wrapping a graph_query call takes, so each read is billed\n // as a graph query.\n const flowsDependencies: FlowsDependencies = {\n graphQuery: async (args) => {\n const options = remoteToolRequestOptions('graph_query')\n return (await withRateLimitRetry(() =>\n remoteClient.callTool({ name: 'graph_query', arguments: args }, undefined, options)\n )) as GraphQueryAnswer\n },\n describeFailure: (err) => remoteCallFailureText('graph_query', err),\n unavailable: () =>\n remoteConnected\n ? undefined\n : `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }\n // One function answers a click, from the host's view and from the browser\n // page alike.\n const runGraphExpand = (args: unknown) => handleGraphExpand(args, flowsDependencies)\n\n // The browser window of a drawn answer (src/mcp/view-server.ts). Started on\n // the first drawn answer, once per process. The deferred copy above returns\n // before this point, so it never starts one.\n let viewServerStart: Promise<ViewServer | null> | undefined\n const viewServer = (): Promise<ViewServer | null> => {\n viewServerStart ??= startViewServer({\n html: readClaudeViewHtml,\n runTool: async (name, args) => {\n if (name === GRAPH_EXPAND_TOOL) return runGraphExpand(args)\n throw new Error(`The browser page cannot call ${name}`)\n },\n log: (event, fields) => {\n void logger.info(event, fields)\n if (event === 'view.server.started') {\n process.stderr.write(`[chain-insights] view.server.started port=${String(fields.port)}\\n`)\n }\n },\n }).catch(async (err: unknown) => {\n await logger.error('view.server.failed', { error: errorForLog(err) })\n process.stderr.write(\n `[chain-insights] view.server.failed: ${(err as Error).message ?? String(err)}\\n`\n )\n return null\n })\n return viewServerStart\n }\n\n // A drawn answer gets its browser link: view_url in structuredContent and one\n // line of text. The stored answer is the one the host receives, view_url\n // included. A refused answer, or one with nothing to draw, stays as it is.\n async function withViewUrl<T>(toolName: string, args: unknown, result: T): Promise<T> {\n if (!isRecord(result) || result.isError === true) return result\n const structuredContent = result.structuredContent\n if (!isRecord(structuredContent)) return result\n const browser = await viewServer()\n if (!browser) return result\n const id = newViewId()\n const url = browser.viewUrl(id)\n const content = Array.isArray(result.content) ? result.content : []\n const linked = {\n ...result,\n content: [...content, { type: 'text' as const, text: `${BROWSER_VIEW_LINE} ${url}` }],\n structuredContent: { ...structuredContent, view_url: url },\n }\n browser.put({ toolName, arguments: args, result: linked }, id)\n return linked as T\n }\n\n server.registerTool(\n GRAPH_EXPAND_TOOL,\n {\n title: GRAPH_EXPAND_TITLE,\n description: GRAPH_EXPAND_DESCRIPTION,\n inputSchema: {\n network: z.string(),\n address: z.string().optional(),\n in_offset: z.number().int().min(0).max(FLOWS_MAX_OFFSET).optional(),\n out_offset: z.number().int().min(0).max(FLOWS_MAX_OFFSET).optional(),\n from: z.string().optional(),\n to: z.string().optional(),\n day: z.string().optional(),\n },\n annotations: VIEW_TOOL_ANNOTATIONS,\n _meta: APP_ONLY_TOOL_META,\n },\n async (args) => withViewUrl(GRAPH_EXPAND_TOOL, args, await runGraphExpand(args))\n )\n\n server.registerTool(\n 'meta_rules',\n {\n title: 'Chain Insights Rules',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_rules,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => {\n const { readPackagedSkillRules } = await import('../skill-refresh.js')\n return {\n content: [{ type: 'text' as const, text: readPackagedSkillRules() }],\n isError: false,\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: supported networks and available tools. Takes no arguments.',\n '- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- 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 PICTURE_HELP_LINE,\n '',\n 'Wallet tools:',\n '- wallet_balance: show the local payment wallet address, payment network, token, and amount.',\n '- meta_rules: the full query, picture and answer rules. Call once per chat when the chain-insights-cypher skill is not loaded.',\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: supported networks and available tools. Takes no arguments.',\n '- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- 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 '- meta_rules: the full query, picture and answer rules. Call once per chat when the chain-insights-cypher skill is not loaded.',\n '',\n PICTURE_HELP_LINE,\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 // A tool only a view of the graph endpoint may call has no view here.\n if (isAppOnlyTool(tool)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n await ensureRemote()\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 = withoutUtcProperties(tool.name, 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 callRemote = (requestArgs: Record<string, unknown>) =>\n withRateLimitRetry(() =>\n requestOptions\n ? remoteClient.callTool({ name: tool.name, arguments: requestArgs }, undefined, requestOptions)\n : remoteClient.callTool({ name: tool.name, arguments: requestArgs })\n )\n // A batch's chain_busy members are resent by the proxy (src/mcp/batch-busy-retry.ts).\n const result =\n tool.name === 'graph_query_batch'\n ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a) as Promise<BatchResult>)\n : await callRemote(request.arguments)\n const answer = normalizeRemoteToolResult(tool.name, result as RemoteToolResult)\n return BROWSER_VIEW_TOOL_NAMES.has(tool.name)\n ? await withViewUrl(tool.name, normalizedArgs, answer)\n : answer\n } catch (err) {\n const limited = rateLimitedResult(tool.name, err)\n if (limited) return limited\n return {\n content: [{ type: 'text' as const, text: remoteCallFailureText(tool.name, err) }],\n isError: true,\n }\n }\n }\n // The graph endpoint's own MCP Apps metadata (_meta.ui, ui/resourceUri)\n // is never forwarded: the proxy is the only source of views. graph_query\n // gets the view the proxy serves.\n const annotations = knownPublicToolAnnotations(tool.name)\n const drawsView = tool.name === 'graph_query'\n const toolConfig = {\n title: tool.title ?? KNOWN_PUBLIC_TOOL_TITLES[tool.name],\n description: claudeFacingToolDescription(tool),\n inputSchema,\n ...(annotations ? { annotations } : {}),\n ...(drawsView ? { _meta: VIEW_TOOL_META } : {}),\n }\n\n server.registerTool(tool.name, toolConfig, handler)\n }\n\n // The local top-up view (wallet address and QR code). It stays in the local\n // proxy: the wallet it funds is a local file, and the hosted connector never\n // offers it (ruling 2026-10-05).\n const { registerTopupView, TOPUP_VIEW_URI } = await import('../wallet/mcp-proxy/topup-server.js')\n await registerTopupView(server, { walletAddress: localTopupWalletAddress })\n\n // ui://chain-insights/view, read from the package. Nothing is fetched.\n server.registerResource(\n 'Chain Insights view',\n CLAUDE_VIEW_URI,\n {\n title: 'Chain Insights view',\n description:\n 'The Chain Insights view for hosts that draw MCP apps: money flows, query results and balance, in light and dark.',\n mimeType: MCP_APP_MIME_TYPE,\n _meta: VIEW_RESOURCE_META,\n },\n async () => ({\n contents: [\n {\n uri: CLAUDE_VIEW_URI,\n mimeType: MCP_APP_MIME_TYPE,\n text: readClaudeViewHtml(),\n _meta: VIEW_RESOURCE_META,\n },\n ],\n })\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].filter((name) => !APP_ONLY_LOCAL_TOOL_NAMES.has(name)),\n ...(tools ?? [])\n .filter((tool) => !isAppOnlyTool(tool))\n .map((tool) => tool.name)\n .filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n ui_resources: [CLAUDE_VIEW_URI, TOPUP_VIEW_URI],\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":["defaultSleep","isRecord","isRecord","isRecord"],"sources":["../src/mcp/rate-limit.ts","../src/mcp/batch-busy-retry.ts","../src/mcp/layer-routing.ts","../src/mcp/view-server.ts","../src/mcp/graph-row-columns.ts","../src/mcp/remote-connect.ts","../src/mcp/timestamp-text.ts","../src/mcp/proxy.ts"],"sourcesContent":["/**\n * The graph endpoint limits requests per address. Its refusal arrives as a\n * thrown HTTP error, \"too many requests from this address; retry after 3\n * seconds\", not as a typed tool reply. Every proxied tool reads only, and a\n * refused request is not billed, so one wait and one resend are safe.\n *\n * When the resend is refused too, or the endpoint asks for a longer wait than\n * the proxy holds a call for, the caller gets the refusal envelope the graph\n * server uses for every other limit: class `capacity`. The skills tell an\n * agent what to do with that class.\n */\n\n/** The longest wait the proxy holds a call for before it sends it once more. */\nexport const MAX_RETRY_WAIT_MS = 10_000\n/** The wait when the endpoint names none. The skills say at least 5 seconds. */\nexport const DEFAULT_RETRY_WAIT_MS = 5_000\nconst MIN_RETRY_WAIT_MS = 1_000\n\nconst RATE_LIMITED = /\\b429\\b|too many requests|rate.?limit/i\nconst RETRY_AFTER = /retry after (\\d+) seconds?/i\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\n/**\n * The wait the endpoint asks for, in milliseconds, when err is its rate\n * limit, and null for any other failure.\n */\nexport function rateLimitWaitMs(err: unknown): number | null {\n const code = err && typeof err === 'object' ? (err as { code?: unknown }).code : undefined\n const message = messageOf(err)\n if (code !== 429 && !RATE_LIMITED.test(message)) return null\n const seconds = RETRY_AFTER.exec(message)?.[1]\n return seconds ? Math.max(Number(seconds) * 1000, MIN_RETRY_WAIT_MS) : DEFAULT_RETRY_WAIT_MS\n}\n\nexport type Sleep = (ms: number) => Promise<void>\n\nconst defaultSleep: Sleep = (ms) => new Promise((done) => setTimeout(done, ms))\n\n/**\n * Runs call. When the endpoint refuses it for the rate limit and asks for a\n * wait of at most MAX_RETRY_WAIT_MS, waits that long and runs it once more.\n * Any other failure, and the second refusal, is thrown to the caller.\n */\nexport async function withRateLimitRetry<T>(\n call: () => Promise<T>,\n sleep: Sleep = defaultSleep\n): Promise<T> {\n try {\n return await call()\n } catch (err) {\n const wait = rateLimitWaitMs(err)\n if (wait === null || wait > MAX_RETRY_WAIT_MS) throw err\n await sleep(wait)\n return await call()\n }\n}\n\n/**\n * The reply for a call the endpoint refused for the rate limit: the text the\n * agent reads and the `capacity` envelope the skills act on. Null when err is\n * not the rate limit.\n */\nexport function rateLimitedResult(toolName: string, err: unknown) {\n const wait = rateLimitWaitMs(err)\n if (wait === null) return null\n const seconds = Math.ceil(wait / 1000)\n const fix = `Wait ${seconds} seconds, then send the same request once.`\n return {\n content: [\n {\n type: 'text' as const,\n text: `${toolName} was not run: Chain Insights Graph is limiting requests from this address. ${fix}`,\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: toolName,\n hint: null,\n error_detail: {\n code: 'rate_limited',\n rule: 'requests_per_address',\n class: 'capacity',\n fix,\n example: '',\n },\n },\n isError: true,\n }\n}\n","/**\n * A graph_query_batch member that reads the chain node costs several node\n * calls (an Address read costs four), and the graph server caps node calls\n * per caller per second. Three chain reads in one batch come back as one\n * answer and two `chain_busy` refusals: \"this caller used its share of node\n * calls this second. Retry after a short wait.\" A refused member is not\n * billed and every read is safe to repeat, so the proxy waits and resends\n * only the busy members, up to MAX_ROUNDS times, and merges the answers back\n * into the batch under their ids. The model never sees a `chain_busy` unless\n * the server stays busy for every round.\n */\n\nconst BUSY_CODE = 'chain_busy'\nexport const BUSY_WAIT_MS = 1_100\nexport const MAX_ROUNDS = 3\n\ntype Member = Record<string, unknown>\ntype Query = { id?: string; query: string }\n\nexport type BatchResult = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n _meta?: unknown\n}\n\ntype CallBatch = (args: Record<string, unknown>) => Promise<BatchResult>\ntype Sleep = (ms: number) => Promise<void>\n\nconst defaultSleep: Sleep = (ms) => new Promise((done) => setTimeout(done, ms))\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction membersOf(result: BatchResult): Member[] | null {\n const facts = isRecord(result.structuredContent) ? result.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n return Array.isArray(queries) && queries.every(isRecord) ? (queries as Member[]) : null\n}\n\nexport function isBusyMember(member: Member): boolean {\n const detail = member.error_detail\n return isRecord(detail) && detail.code === BUSY_CODE\n}\n\n/** The ids of the members the server refused as chain_busy. */\nexport function busyMemberIds(result: BatchResult): string[] {\n const members = membersOf(result)\n if (!members) return []\n return members.filter(isBusyMember).map((m) => String(m.id))\n}\n\nfunction recount(batch: Record<string, unknown>, members: Member[]): Record<string, unknown> {\n const completed = members.filter((m) => m.ok === true).length\n const billable = members.reduce(\n (sum, m) => sum + (typeof m.billable_units === 'number' ? m.billable_units : 0),\n 0\n )\n return { ...batch, completed, failed: members.length - completed, billable_units: billable }\n}\n\n/**\n * Replaces the busy members of `first` with the answers `again` carries for\n * the same ids, and recounts the batch envelope. The text block is rebuilt\n * from the structured content so the two never disagree.\n */\nexport function mergeBatchAnswers(first: BatchResult, again: BatchResult): BatchResult {\n const members = membersOf(first)\n const fresh = membersOf(again)\n if (!members || !fresh) return first\n const byId = new Map(fresh.map((m) => [String(m.id), m]))\n const merged = members.map((m) => (isBusyMember(m) && byId.has(String(m.id)) ? byId.get(String(m.id))! : m))\n const structured = first.structuredContent as Record<string, unknown>\n const facts = structured.facts as Record<string, unknown>\n const batch = isRecord(facts.batch) ? recount(facts.batch, merged) : facts.batch\n const nextStructured = { ...structured, facts: { ...facts, queries: merged, batch } }\n return {\n ...first,\n structuredContent: nextStructured,\n content: [{ type: 'text', text: JSON.stringify(nextStructured) }],\n isError: merged.some((m) => m.ok !== true) ? first.isError : false,\n }\n}\n\n/**\n * Runs the batch, then resends only its chain_busy members after a short wait,\n * up to MAX_ROUNDS times, merging each answer back under its id.\n */\nexport async function withBusyMemberRetry(\n args: Record<string, unknown>,\n call: CallBatch,\n sleep: Sleep = defaultSleep\n): Promise<BatchResult> {\n let result = await call(args)\n const queries = Array.isArray(args.queries) ? (args.queries as Query[]) : []\n for (let round = 0; round < MAX_ROUNDS; round += 1) {\n const busy = new Set(busyMemberIds(result))\n if (busy.size === 0) break\n const resend = queries.filter((q, index) => busy.has(String(q.id ?? `q${index + 1}`)))\n if (resend.length === 0) break\n await sleep(BUSY_WAIT_MS)\n const again = await call({ ...args, queries: resend })\n result = mergeBatchAnswers(result, again)\n }\n return result\n}\n\n/** A single graph_query refused as chain_busy: an error result whose text starts with the code. */\nexport function isBusyResult(result: BatchResult): boolean {\n if (!result.isError) return false\n const first = Array.isArray(result.content) ? result.content[0] : undefined\n const text = isRecord(first) && typeof first.text === 'string' ? first.text : ''\n return /^(Query execution failed: )?chain_busy\\b/.test(text.trim())\n}\n\n/**\n * Runs a single graph_query and, when the server refuses it as chain_busy,\n * waits and sends it again, up to MAX_ROUNDS times. Seven of 100 loop rounds\n * on prompt set v2 showed a model giving up on the first such refusal.\n */\nexport async function withBusyRetry(\n args: Record<string, unknown>,\n call: CallBatch,\n sleep: Sleep = defaultSleep\n): Promise<BatchResult> {\n let result = await call(args)\n for (let round = 0; round < MAX_ROUNDS && isBusyResult(result); round += 1) {\n await sleep(BUSY_WAIT_MS)\n result = await call(args)\n }\n return result\n}\n","// The one source of the routing rule of the three read layers, and of the move\n// that each class of refusal asks of an agent.\n//\n// Three places teach them: the graph hints that the MCP proxy serves\n// (GRAPH_SCHEMA_HINTS in proxy.ts builds its two routing hints from this file),\n// the `chain-insights-cypher` skill and docs/graph-tools.md. The skill and the\n// guide are Markdown and hold the lines word for word. tests/layer-routing.test.ts\n// fails when one of the three differs from this file by one word.\n//\n// The rule follows the graph server's own: topology searches outward from one\n// known address, while facts and chain look up one known thing. The wording asks\n// the agent to say first what the question names (an address, a pair with a day,\n// a transaction, a block), because a question that names none of them is served\n// on no layer today. Nothing here is a limit. A limit that the server publishes\n// is read from `meta_network_capabilities`, and the text names the field, never\n// the number.\n//\n// `USE chain` serves the four labels of the server's chain catalogue and no\n// other: a transaction by its `tx_id`, a block by its `block_height` or its\n// `block_hash`, an address at one block (`Address`, with an optional `at_block`),\n// and the head, which takes no key. A kind (`:Account`, `:Contract`) is never a\n// lookup label: the `Address` lookup reads it as `is_contract` and `nonce`.\n// tests/fixtures/chain-catalogue.json pins the labels, and\n// tests/chain-catalogue-pin.test.ts holds every chain recipe and the chain\n// routing line to them. The depth below which a past block is served is a limit\n// that the server publishes, so the text names its field and never its number.\n\n/** The routing rule, in one sentence. The server states it in the same words. */\nexport const ROUTING_RULE =\n 'Route by what the question names: an address, a pair with a day, a transaction or a block. Topology searches outward from an address, while facts and chain look up one known thing.'\n\n/** The rule in three lines, one for each layer. */\nexport const ROUTING_LINES = [\n 'I know an address and want its links, senders, receivers, hops or a route: `USE topology`, anchored on that address.',\n 'I know both addresses of a pair and one day, and want the rows of that day: `USE facts`.',\n \"I want the chain's own record of a transaction by its `tx_id`, a block by its `block_height` or `block_hash`, an address at one block (`Address`), or the head: `USE chain`.\",\n] as const\n\n/** The chain layer reads one address at one block. A search for counterparties stays on topology. */\nexport const ROUTING_ADDRESS_ON_CHAIN =\n '`USE chain` with `Address` reads the balance, the nonce or the kind of one address, now or at a past block with `at_block`. A past block must be at least `chain_admission.at_block_min_depth` blocks below the tip. To find the counterparties of an address, search `USE topology`, then read the pair and one day on `USE facts`.'\n\n/** What to do when two layers fit. */\nexport const ROUTING_TWO_FIT =\n 'A transaction is a chain question: read it on `USE chain` by its `tx_id`. Chain gives its `block_date`; the transfers between a pair on that day are a `USE facts` read.'\n\n/** The key that each layer hands to the next. Each key keeps its name. */\nexport const ROUTING_HANDOFF =\n 'The layers hand each other keys, and each key keeps its name. Topology gives the pair and the first and last seen time. Facts gives the `tx_id` and the `block_height`. Chain takes an `address`, a `tx_id`, a `block_height` or a `block_hash`, and gives the `block_date` of a transaction or a block.'\n\n/** A whole-chain question is a time-bounded topology search; a server without search mode refuses it once. */\nexport const ROUTING_NOT_SERVED =\n 'A question about the whole chain (recent activity, the biggest senders, a top list) is a `USE topology` search: bound it by time with `f.last_seen_timestamp >= <epoch ms>` on the `FLOWS_TO` link, then sort and `LIMIT`. A search has 10 s. Read `Head` first (`USE chain MATCH (h:Head) RETURN h.graph_blocks_behind`, under a second): when `graph_blocks_behind` is above 100000 (a few hours of blocks: a robinhood block is about a tenth of a second apart from the next), the search cannot finish, so do not send it; say the graph is that many blocks behind the head and ask for an address. Never turn the block count into days or hours by head. To say how fresh the data is, give the newest `_utc` time you read on the layer you used: `graph_blocks_behind` counts the slowest graph layer, and the links of an address and the facts rows are often newer than it. If the server refuses the search with `anchor_missing`, or stops it with `query_timeout`, it cannot search the whole chain now: say so in one line and ask for an address. A narrower window times out the same way, so do not send it again.'\n\n/** What a refusal carries, and which part of it decides the move. */\nexport const MOVE_INTRO =\n 'A refused, killed, busy or failed query comes back with `error_detail`: `code`, `rule`, `class`, `fix` and `example`. The `class` decides your next move.'\n\n/** The move for each class of the refusal envelope. */\nexport const MOVE_BY_CLASS = {\n refused: 'Class `refused`: read `fix`, rewrite the query from `example`, and send it once.',\n killed: 'Class `killed`: narrow the query (fewer properties, rows or hops) and send it once.',\n capacity: 'Class `capacity`: wait at least 5 seconds, then send the same query once.',\n failed: 'Class `failed`: tell the user what is down. Do not retry.',\n} as const\n\n/** A `fix` that names another layer. */\nexport const MOVE_OTHER_LAYER = 'A `fix` that names another layer means move to that layer.'\n\n/** One try, then the user. Never a loop. */\nexport const MOVE_ONCE =\n 'One rewrite or one retry for each query. When it is refused or busy again, stop and tell the user. Quote the `fix` text and send nothing more for that question.'\n\n/** After a refusal or a kill the server has already said no. */\nexport const MOVE_NEVER_SAME = 'Never send the same text again after `refused` or `killed`.'\n\n/** A batch has good members that already ran. */\nexport const MOVE_BATCH = 'In a batch, send again only the members that came back `capacity`.'\n\n/** The routing sentences, in the order a reader meets them. */\nexport function routingLines(): string[] {\n return [\n ROUTING_RULE,\n ...ROUTING_LINES,\n ROUTING_ADDRESS_ON_CHAIN,\n ROUTING_TWO_FIT,\n ROUTING_HANDOFF,\n ROUTING_NOT_SERVED,\n ]\n}\n\n/** The move-by-class sentences, in the order a reader meets them. */\nexport function moveLines(): string[] {\n return [\n MOVE_INTRO,\n MOVE_BY_CLASS.refused,\n MOVE_BY_CLASS.killed,\n MOVE_BY_CLASS.capacity,\n MOVE_BY_CLASS.failed,\n MOVE_OTHER_LAYER,\n MOVE_ONCE,\n MOVE_NEVER_SAME,\n MOVE_BATCH,\n ]\n}\n\n/**\n * The routing rule and its three lines, with the question that no layer serves,\n * as one paragraph. The proxy serves it first among its instructions: a host\n * that keeps only the start of the instructions (Claude Code keeps 2 KB) still\n * shows the agent where each kind of question goes, and which kind goes nowhere.\n */\nexport function routingHead(): string {\n return `Pick the layer first. ${ROUTING_RULE} 1. ${ROUTING_LINES[0]} 2. ${ROUTING_LINES[1]} 3. ${ROUTING_LINES[2]} ${ROUTING_NOT_SERVED}`\n}\n\n/**\n * The two hints that the MCP proxy serves for routing and for refusals. Each is\n * one line of the served instructions.\n */\nexport function routingHintLines(): string[] {\n return [\n `- Pick the layer first. ${ROUTING_RULE} 1. ${ROUTING_LINES[0]} 2. ${ROUTING_LINES[1]} 3. ${ROUTING_LINES[2]} ${ROUTING_NOT_SERVED} ${ROUTING_ADDRESS_ON_CHAIN} ${ROUTING_TWO_FIT} ${ROUTING_HANDOFF} The lookups that USE chain serves are listed in chain_admission.lookups of meta_network_capabilities. Read every limit that the server publishes from meta_network_capabilities (chain_admission and, when the server sends them, topology_admission and facts_admission). Never write a limit down.`,\n `- Act on a refusal by its class. ${moveLines().join(' ')}`,\n ]\n}\n","import { randomBytes } from 'node:crypto'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\n\n/**\n * The Chain Insights view as a browser page on the user's own PC.\n *\n * Claude Desktop draws the view in a small frame beside the answer and offers\n * no full screen. The proxy therefore serves the same view file over HTTP on\n * 127.0.0.1, and every drawn answer carries a link to it (view_url). The page\n * reads the answer back from this server by its id and calls graph_expand\n * through it, so a click in the browser costs what a click in the host costs.\n *\n * Routes, all same-origin, no CORS:\n * GET /view?view=<id> the view file; when the id is held, the answer\n * is written into it as\n * <script type=\"application/json\" data-chain-insights-view=\"<id>\">\n * so a window that may load the page but not call\n * back to it (Claude Desktop's browser pane blocks\n * requests to local hosts) still draws\n * GET /api/view/<id> {\"arguments\": ..., \"result\": ...}, or 404\n * POST /api/tools/graph_expand {\"arguments\": {...}} -> the CallToolResult\n *\n * The server binds to 127.0.0.1 only, refuses a Host header that does not\n * name it (DNS rebinding), and keeps at most 50 answers in memory.\n */\n\nexport const VIEW_STORE_LIMIT = 50\nexport const VIEW_BODY_LIMIT_BYTES = 64 * 1024\n// The one tool the page may call: the view's own click tool.\nexport const VIEW_PAGE_TOOLS = new Set(['graph_expand'])\n\nexport interface ViewEntry {\n toolName: string\n arguments: unknown\n result: unknown\n}\n\n/** A fresh answer id: 12 random bytes, URL-safe base64 (16 characters). */\nexport function newViewId(): string {\n return randomBytes(12).toString('base64url')\n}\n\n/** At most `limit` answers, the oldest evicted first. */\nexport class ViewStore {\n private readonly entries = new Map<string, ViewEntry>()\n\n constructor(private readonly limit: number = VIEW_STORE_LIMIT) {}\n\n put(entry: ViewEntry, id: string = newViewId()): string {\n this.entries.delete(id)\n this.entries.set(id, entry)\n while (this.entries.size > this.limit) {\n const oldest = this.entries.keys().next().value\n if (oldest === undefined) break\n this.entries.delete(oldest)\n }\n return id\n }\n\n get(id: string): ViewEntry | undefined {\n return this.entries.get(id)\n }\n\n get size(): number {\n return this.entries.size\n }\n}\n\n/** The attribute the page reads the written-in answer from; the view (chain-insights-ui browser.ts) names the same one. */\nexport const EMBEDDED_VIEW_ATTR = 'data-chain-insights-view'\n\n/** JSON safe inside a script element: no sequence that could close it, no line separators. */\nfunction scriptJson(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n}\n\n/**\n * The view file with the answer written in: one JSON script element before\n * </head> (else at the top). The id is URL-safe base64, attribute-safe as is.\n */\nexport function pageWithEntry(html: string, id: string, entry: ViewEntry): string {\n const script = `<script type=\"application/json\" ${EMBEDDED_VIEW_ATTR}=\"${id}\">${scriptJson({ arguments: entry.arguments, result: entry.result })}</script>`\n const head = html.indexOf('</head>')\n if (head >= 0) return `${html.slice(0, head)}${script}\\n${html.slice(head)}`\n return `${script}\\n${html}`\n}\n\nexport interface ViewServerOptions {\n /** The view file; the held answer of ?view=<id> is written into it. */\n html: () => string\n /** Runs a page tool (graph_expand only) and returns its CallToolResult. */\n runTool: (name: string, args: Record<string, unknown>) => Promise<unknown>\n /** One structured log line per event; never throws. */\n log?: (event: string, fields: Record<string, unknown>) => void\n limit?: number\n}\n\nexport interface ViewServer {\n readonly port: number\n /** Stores an answer and returns its id. */\n put(entry: ViewEntry, id?: string): string\n /** The browser link of one answer. */\n viewUrl(id: string): string\n close(): Promise<void>\n}\n\nconst COMMON_HEADERS = {\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n}\n\nfunction send(\n res: ServerResponse,\n status: number,\n contentType: string,\n body: string | Buffer\n): void {\n res.writeHead(status, { ...COMMON_HEADERS, 'Content-Type': contentType })\n res.end(body)\n}\n\nfunction sendJson(res: ServerResponse, status: number, value: unknown): void {\n send(res, status, 'application/json; charset=utf-8', JSON.stringify(value))\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nclass BodyTooLarge extends Error {}\n\nfunction readBody(req: IncomingMessage, limit: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n let failed = false\n req.on('data', (chunk: Buffer) => {\n if (failed) return\n size += chunk.length\n if (size > limit) {\n failed = true\n reject(new BodyTooLarge())\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (!failed) resolve(Buffer.concat(chunks).toString('utf8'))\n })\n req.on('error', (err) => {\n if (!failed) {\n failed = true\n reject(err)\n }\n })\n })\n}\n\n/**\n * Starts the server on 127.0.0.1 and a port the system picks. The server does\n * not keep the process alive: the proxy ends when its host closes stdio.\n */\nexport async function startViewServer(options: ViewServerOptions): Promise<ViewServer> {\n const store = new ViewStore(options.limit ?? VIEW_STORE_LIMIT)\n let port = 0\n const allowedHosts = () => new Set([`127.0.0.1:${port}`, `localhost:${port}`])\n const allowedOrigins = () => new Set([`http://127.0.0.1:${port}`, `http://localhost:${port}`])\n\n async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const host = (req.headers.host ?? '').toLowerCase()\n if (!allowedHosts().has(host)) {\n sendJson(res, 403, { error: 'forbidden host' })\n return\n }\n const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`)\n const method = req.method ?? 'GET'\n const pathname = url.pathname\n\n if (method === 'GET' && pathname === '/view') {\n const id = url.searchParams.get('view') ?? ''\n const entry = /^[A-Za-z0-9_-]+$/.test(id) ? store.get(id) : undefined\n send(res, 200, 'text/html; charset=utf-8', entry ? pageWithEntry(options.html(), id, entry) : options.html())\n return\n }\n\n const viewMatch = /^\\/api\\/view\\/([A-Za-z0-9_-]+)$/.exec(pathname)\n if (method === 'GET' && viewMatch) {\n const entry = store.get(viewMatch[1])\n if (!entry) {\n sendJson(res, 404, { error: 'unknown view' })\n return\n }\n sendJson(res, 200, { arguments: entry.arguments, result: entry.result })\n return\n }\n\n const toolMatch = /^\\/api\\/tools\\/([A-Za-z0-9_-]+)$/.exec(pathname)\n if (method === 'POST' && toolMatch && VIEW_PAGE_TOOLS.has(toolMatch[1])) {\n // A page on another site can post to 127.0.0.1 with the right Host\n // header. Its browser sends its own Origin, and a JSON body needs a\n // preflight this server never answers, so both are required here.\n const origin = req.headers.origin\n if (origin !== undefined && !allowedOrigins().has(origin.toLowerCase())) {\n sendJson(res, 403, { error: 'forbidden origin' })\n return\n }\n const contentType = (req.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase()\n if (contentType !== 'application/json') {\n sendJson(res, 415, { error: 'send application/json' })\n return\n }\n let body: unknown\n try {\n const text = await readBody(req, VIEW_BODY_LIMIT_BYTES)\n body = text.trim() === '' ? {} : JSON.parse(text)\n } catch (err) {\n if (err instanceof BodyTooLarge) {\n sendJson(res, 413, { error: 'body too large' })\n req.resume()\n return\n }\n sendJson(res, 400, { error: 'invalid JSON' })\n return\n }\n const args = isRecord(body) ? (body.arguments ?? {}) : undefined\n if (!isRecord(args)) {\n sendJson(res, 400, { error: 'arguments must be an object' })\n return\n }\n const result = await options.runTool(toolMatch[1], args)\n sendJson(res, 200, result)\n return\n }\n\n sendJson(res, 404, { error: 'not found' })\n }\n\n const server: Server = createServer((req, res) => {\n handle(req, res).catch((err: unknown) => {\n options.log?.('view.server.request_failed', {\n error: err instanceof Error ? err.message : String(err),\n })\n if (!res.headersSent) sendJson(res, 500, { error: 'internal error' })\n else res.end()\n })\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(0, '127.0.0.1', () => {\n server.off('error', reject)\n resolve()\n })\n })\n const address = server.address()\n if (!address || typeof address !== 'object') {\n server.close()\n throw new Error('The view server has no port')\n }\n port = address.port\n server.unref()\n options.log?.('view.server.started', { port })\n\n return {\n port,\n put: (entry, id) => store.put(entry, id),\n viewUrl: (id) => `http://127.0.0.1:${port}/view?view=${encodeURIComponent(id)}`,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n server.closeAllConnections?.()\n }),\n }\n}\n","/**\n * The columns the Chain Insights view reads from a graph_query row to draw a\n * graph. The view (Chain Insights UI, apps/claude-view/pick.ts, graphFromRows)\n * reads them by name; the proxy's picture hint names them to the model, and\n * every FLOWS_TO recipe this package teaches must return all of them, or the\n * drawn link panel shows \"Unavailable\" for the column the recipe forgot\n * (0.54.2: first_seen_timestamp). tests/graph-row-columns.test.ts holds the\n * three sides together: the bundled view, the hint and the recipes.\n */\n\n/** A row draws a link only with both of these. */\nexport const GRAPH_REQUIRED_COLUMNS = Object.freeze(['from_address', 'to_address'] as const)\n\n/** What a FLOWS_TO link carries: the link panel of the view shows each one. */\nexport const GRAPH_LINK_COLUMNS = Object.freeze([\n 'amount_usd_sum',\n 'tx_count',\n 'first_seen_timestamp',\n 'last_seen_timestamp',\n] as const)\n\n/** What the two addresses of a link carry: labels and the exchange mark on the node. */\nexport const GRAPH_NODE_COLUMNS = Object.freeze([\n 'from_labels',\n 'to_labels',\n 'from_is_exchange',\n 'to_is_exchange',\n] as const)\n\n/** The kind of a link, when a query joins several link types. */\nexport const GRAPH_KIND_COLUMNS = Object.freeze(['link_kind'] as const)\n\n/** Every optional column, in the order the hint names them. */\nexport const GRAPH_OPTIONAL_COLUMNS = Object.freeze([\n ...GRAPH_LINK_COLUMNS,\n ...GRAPH_KIND_COLUMNS,\n ...GRAPH_NODE_COLUMNS,\n])\n\n/** The columns a FLOWS_TO recipe must return beside from_address and to_address. */\nexport const FLOWS_RECIPE_COLUMNS = Object.freeze([...GRAPH_LINK_COLUMNS, ...GRAPH_NODE_COLUMNS])\n\n/** The `RETURN` tail of a FLOWS_TO recipe over link `f` between `from` and `to`. */\nexport function flowsRecipeReturn(from: string, to: string, link = 'f'): string {\n return (\n `${from}.address AS from_address, ${to}.address AS to_address, ` +\n `${link}.amount_usd_sum AS amount_usd_sum, ${link}.tx_count AS tx_count, ` +\n `${link}.first_seen_timestamp AS first_seen_timestamp, ${link}.last_seen_timestamp AS last_seen_timestamp, ` +\n `${from}.labels AS from_labels, ${to}.labels AS to_labels, ` +\n `${from}.is_exchange AS from_is_exchange, ${to}.is_exchange AS to_is_exchange`\n )\n}\n\n/** The `AS name` columns a query returns. */\nexport function returnedColumns(query: string): string[] {\n return [...query.matchAll(/\\bAS\\s+([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1])\n}\n\n/** A query that draws a graph of FLOWS_TO links: it matches the link and returns both addresses. */\nexport function isFlowsGraphQuery(query: string): boolean {\n const columns = new Set(returnedColumns(query))\n return /\\[\\w+:FLOWS_TO\\]/.test(query) && GRAPH_REQUIRED_COLUMNS.every((c) => columns.has(c))\n}\n","/**\n * Connecting the proxy to the Chain Insights Graph endpoint.\n *\n * The hosted endpoint sheds load with HTTP 429 (an in-flight cap per IP, 8\n * anonymous, with Retry-After: 1). Before this module, a 429 on the first\n * POST made the proxy fall back to the SSE transport, which the endpoint\n * answers with 405, and the proxy then reported \"Graph unreachable ... SSE\n * error: Non-200 status code (405)\" for every graph query of its lifetime,\n * even once the endpoint had room again. Seen 2026-10-09 with nine proxies\n * on one workstation: two of ten test conversations lost every topology and\n * facts query while chain lookups kept working.\n *\n * Rules:\n * - 429, 502, 503 and 504 are \"busy\": wait (Retry-After, else a doubling\n * backoff from one second) and try the same transport again, up to\n * `attempts` times. Never fall back to SSE for a busy endpoint.\n * - 404 and 405 mean the endpoint is not streamable: try SSE once.\n * - Anything else (a refused connection, DNS, TLS) tries SSE once as before.\n * - A failure that was busy at the end is `retryable`: the proxy stays up and\n * connects again on the next graph call instead of answering dead forever.\n */\n\nexport type RemoteTransport = 'streamable_http' | 'sse'\n\n/** The least of an MCP client this module needs: connect, and close what a failed connect left attached. */\nexport interface ConnectableClient {\n connect(transport: unknown): Promise<void>\n close?: () => Promise<void>\n}\n\n/**\n * Connects a client to a fresh transport. The SDK leaves a transport attached\n * when `connect` fails after `start` (an initialize answered 429), and the\n * next `connect` then throws \"Already connected to a transport\", so every try\n * closes first. Seen in round 83 of the 2026-10-10 test loop.\n */\nexport async function connectFresh(client: ConnectableClient, transport: unknown): Promise<void> {\n if (typeof client.close === 'function') await client.close().catch(() => undefined)\n await client.connect(transport)\n}\n\nexport interface RemoteConnectResult {\n connected: boolean\n transport: RemoteTransport | null\n /** The user-facing reason when not connected. */\n message?: string\n /** True when a later attempt may succeed (the endpoint was busy). */\n retryable: boolean\n attempts: number\n}\n\nexport interface RemoteConnectOptions {\n endpoint: string\n /** Opens a fresh streamable-HTTP transport and connects. Throws on failure. */\n connectStreamable: () => Promise<void>\n /** Opens a fresh SSE transport and connects. Throws on failure. */\n connectSse: () => Promise<void>\n /** How many streamable tries while the endpoint is busy. */\n attempts?: number\n sleep?: (ms: number) => Promise<void>\n log?: (event: string, fields: Record<string, unknown>) => Promise<void> | void\n}\n\nconst BUSY = new Set([429, 502, 503, 504])\nconst NOT_STREAMABLE = new Set([404, 405])\n\n/** The HTTP status an SDK transport error carries, from its `code` or its text. */\nexport function httpStatusOf(err: unknown): number | null {\n if (err && typeof err === 'object') {\n const code = (err as { code?: unknown }).code\n if (typeof code === 'number' && code >= 100 && code < 600) return code\n const message = (err as { message?: unknown }).message\n if (typeof message === 'string') {\n const m = /\\b(4\\d\\d|5\\d\\d)\\b/.exec(message)\n if (m) return Number(m[1])\n }\n }\n return null\n}\n\n/** Milliseconds to wait before the next try: Retry-After when the error names one, else 1 s doubling. */\nexport function busyDelayMs(err: unknown, attempt: number): number {\n const retryAfter = (err as { retryAfter?: unknown } | null)?.retryAfter\n if (typeof retryAfter === 'number' && retryAfter > 0) return Math.min(retryAfter * 1000, 30_000)\n return Math.min(1000 * 2 ** (attempt - 1), 8000)\n}\n\nexport function describeRemoteFailure(endpoint: string, err: unknown, status: number | null): string {\n if (status !== null && BUSY.has(status)) {\n return (\n `Chain Insights Graph is at capacity at ${endpoint} (HTTP ${status}, class capacity): ` +\n 'wait at least 5 seconds, then send the same query once.'\n )\n }\n const text = err instanceof Error ? err.message : String(err)\n return `Chain Insights Graph unreachable at ${endpoint}: ${text}`\n}\n\nexport async function connectRemote(options: RemoteConnectOptions): Promise<RemoteConnectResult> {\n const attempts = Math.max(1, options.attempts ?? 5)\n const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))\n const log = options.log ?? (() => undefined)\n let lastErr: unknown = null\n let lastStatus: number | null = null\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await options.connectStreamable()\n await log('remote.connect', { transport: 'streamable_http', endpoint: options.endpoint, attempt })\n return { connected: true, transport: 'streamable_http', retryable: false, attempts: attempt }\n } catch (err) {\n lastErr = err\n lastStatus = httpStatusOf(err)\n await log('remote.connect_failed', {\n transport: 'streamable_http',\n endpoint: options.endpoint,\n attempt,\n status: lastStatus,\n })\n if (lastStatus !== null && BUSY.has(lastStatus) && attempt < attempts) {\n await sleep(busyDelayMs(err, attempt))\n continue\n }\n break\n }\n }\n const busy = lastStatus !== null && BUSY.has(lastStatus)\n if (!busy) {\n // Not streamable, or not reachable at all: one SSE try, as before.\n try {\n await options.connectSse()\n await log('remote.connect', { transport: 'sse', endpoint: options.endpoint })\n return { connected: true, transport: 'sse', retryable: false, attempts }\n } catch (err2) {\n await log('remote.connect_failed', { transport: 'sse', endpoint: options.endpoint, status: httpStatusOf(err2) })\n // The SSE 405 says nothing the user can act on; name the first failure\n // unless the streamable failure was only \"not streamable\".\n const named = lastStatus !== null && NOT_STREAMABLE.has(lastStatus) ? err2 : lastErr\n return {\n connected: false,\n transport: null,\n retryable: false,\n attempts,\n message: describeRemoteFailure(options.endpoint, named, null),\n }\n }\n }\n return {\n connected: false,\n transport: null,\n retryable: true,\n attempts,\n message: describeRemoteFailure(options.endpoint, lastErr, lastStatus),\n }\n}\n","/**\n * A UTC text twin beside every timestamp column of a graph answer.\n *\n * The graph serves integer milliseconds. A model that converts them by head\n * gets the time of day wrong about one time in three (loop 3, 2026-10-10:\n * four of twelve answers about one block named 00:15, 09:35, 06:35 or 09:15\n * for 23:55:43 UTC). Hosts without a shell (Claude Desktop) have no other\n * way to convert, so the proxy writes `<column>_utc` next to each one.\n */\n\nconst TIMESTAMP_KEY = /(_timestamp|_time|_at|_seen|_active)$/\nconst MIN_MS = 1_000_000_000_000 // 2001-09-09\nconst MAX_MS = 4_000_000_000_000 // 2096-10-02\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** `2026-08-05 23:55:43 UTC` for epoch milliseconds. */\nexport function utcText(ms: number): string {\n return `${new Date(ms).toISOString().slice(0, 19).replace('T', ' ')} UTC`\n}\n\nfunction isEpochMs(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= MIN_MS && value <= MAX_MS\n}\n\n/**\n * One row with a `<key>_utc` string after every epoch-millisecond timestamp\n * column. A column the model itself named `*_utc` (its query asked for the\n * twin as a property, see stripUtcProperties) arrives as milliseconds and is\n * rendered as UTC text in place: the model gets the column it asked for.\n */\nexport function addTimestampText(row: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(row)) {\n out[key] = key.endsWith('_utc') && isEpochMs(value) ? utcText(value) : value\n if (TIMESTAMP_KEY.test(key) && isEpochMs(value) && !(`${key}_utc` in row)) out[`${key}_utc`] = utcText(value)\n }\n return out\n}\n\n/**\n * The `_utc` twin is added to a reply by the proxy; it is no graph property.\n * Thirteen of 48 loop-5 rounds wrote `f.first_seen_timestamp_utc` into a\n * query anyway: topology answers null, a SWAP read is refused. The property\n * reference is rewritten to its millisecond column, `x.<column>_utc` to\n * `x.<column>`; an alias after AS is not a property and stays as written, so\n * `f.first_seen_timestamp_utc AS first_seen_utc` yields a `first_seen_utc`\n * column that addTimestampText renders as text.\n */\nexport function stripUtcProperties(query: string): string {\n return query.replace(/\\.([A-Za-z_][A-Za-z0-9_]*?)_utc\\b/g, '.$1')\n}\n\n/** graph_query and graph_query_batch arguments with stripUtcProperties applied to every query. */\nexport function withoutUtcProperties(toolName: string, args: Record<string, unknown>): Record<string, unknown> {\n if (toolName === 'graph_query' && typeof args.query === 'string') return { ...args, query: stripUtcProperties(args.query) }\n if (toolName === 'graph_query_batch' && Array.isArray(args.queries)) {\n return {\n ...args,\n queries: args.queries.map((entry) =>\n isRecord(entry) && typeof entry.query === 'string' ? { ...entry, query: stripUtcProperties(entry.query) } : entry\n ),\n }\n }\n return args\n}\n\nfunction mapResults(holder: unknown): unknown {\n if (!isRecord(holder) || !Array.isArray(holder.results)) return holder\n return { ...holder, results: holder.results.map((row) => (isRecord(row) ? addTimestampText(row) : row)) }\n}\n\n/**\n * The structured content of graph_query (facts.query.results) or\n * graph_query_batch (facts.queries[].results) with the UTC twins added.\n * Anything else comes back as it was.\n */\nexport function withTimestampText(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n const facts = structuredContent.facts\n if (!isRecord(facts.query) && !Array.isArray(facts.queries)) return structuredContent\n const next: Record<string, unknown> = { ...facts }\n if (isRecord(facts.query)) next.query = mapResults(facts.query)\n if (Array.isArray(facts.queries)) next.queries = facts.queries.map(mapResults)\n return { ...structuredContent, facts: next }\n}\n\ntype TextBlock = { type: string; text?: string }\n\n/**\n * A tool result whose structured content and single JSON text block both carry\n * the UTC twins. A text block that is not the JSON of the structured content\n * is left alone.\n */\nexport function withTimestampTextResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withTimestampText(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured =\n parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return {\n ...result,\n structuredContent: structured,\n content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content,\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { appendFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\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 { rateLimitedResult, withRateLimitRetry } from './rate-limit.js'\nimport { withBusyMemberRetry, withBusyRetry, type BatchResult } from './batch-busy-retry.js'\nimport {\n HIDDEN_REMOTE_TOOL_NAMES,\n PUBLIC_MCP_TOOL_ALLOWED_ARGS,\n PUBLIC_MCP_TOOL_REQUIRED_ARGS,\n isAppOnlyTool,\n} from './tool-visibility.js'\nimport { PaymentRequiredError } from './client.js'\nimport { primitiveBackendUsageStatus } from './usage-status.js'\nimport { unavailableSubscriptionStatus } from './subscription-status.js'\nimport { mirrorGraphNetworkCapabilities } from './capabilities.js'\nimport { ROUTING_NOT_SERVED, routingHead, routingHintLines } from './layer-routing.js'\nimport {\n GraphSchemaError,\n META_SCHEMA_DESCRIPTION,\n META_SCHEMA_TITLE,\n handleMetaSchema,\n type GraphSchemaDependencies,\n} from './graph-schema.js'\nimport { actionLogSignalsFromResult, appendActionLog } from './action-log.js'\nimport {\n FLOWS_MAX_OFFSET,\n GRAPH_EXPAND_DESCRIPTION,\n GRAPH_EXPAND_TITLE,\n GRAPH_EXPAND_TOOL,\n handleGraphExpand,\n type FlowsDependencies,\n type GraphQueryAnswer,\n} from './flows.js'\nimport { newViewId, startViewServer, type ViewServer } from './view-server.js'\nimport { GRAPH_OPTIONAL_COLUMNS, flowsRecipeReturn } from './graph-row-columns.js'\nimport { connectFresh, connectRemote } from './remote-connect.js'\nimport { withTimestampTextResult, withoutUtcProperties } from './timestamp-text.js'\n\nconst LOCAL_TOOL_NAMES = new Set([\n 'meta_network_capabilities',\n 'meta_schema',\n 'meta_usage_status',\n 'meta_subscription_status',\n 'meta_help',\n 'meta_rules',\n 'wallet_balance',\n 'wallet_topup',\n GRAPH_EXPAND_TOOL,\n])\n// Local tools only the view calls (visibility [\"app\"]): never named to the model.\nconst APP_ONLY_LOCAL_TOOL_NAMES = new Set([GRAPH_EXPAND_TOOL])\nconst GRAPH_ARRAY_KEYS = ['nodes', 'edges', 'flows', 'edge_anchors'] as const\n\n// The Chain Insights view: one HTML file, served by this proxy and by nothing\n// else. The graph endpoint serves no view; a view, a ui:// resource or a\n// _meta.ui it advertises is never forwarded. The tool result says which view\n// the file draws: a graph, a chart or a table from the columns of a graph_query\n// answer, the balance (meta_usage_status, meta_subscription_status), or the\n// answer of a click (graph_expand). The metadata below is the wire contract of\n// the Claude views, character for character.\nexport const CLAUDE_VIEW_URI = 'ui://chain-insights/view'\nexport const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app'\n// A tool whose answer the view draws: the resource under the current key and\n// the legacy flat key hosts still read.\nconst VIEW_TOOL_META = {\n ui: { resourceUri: CLAUDE_VIEW_URI },\n 'ui/resourceUri': CLAUDE_VIEW_URI,\n}\n// A tool only the view calls: the host keeps it away from the model.\nconst APP_ONLY_TOOL_META = { ui: { resourceUri: CLAUDE_VIEW_URI, visibility: ['app'] } }\nconst VIEW_RESOURCE_META = {\n ui: { prefersBorder: false, csp: { resourceDomains: ['https://assets.claude.ai'] } },\n}\nconst VIEW_TOOL_ANNOTATIONS = { readOnlyHint: true }\n// The answers that also open as a browser window on the user's PC\n// (src/mcp/view-server.ts): each carries view_url.\nconst BROWSER_VIEW_TOOL_NAMES = new Set(['graph_query', 'graph_query_batch', GRAPH_EXPAND_TOOL])\nconst BROWSER_VIEW_LINE = 'Open the graph in a browser window:'\nconst CLAUDE_VIEW_FILE = 'claude-view.html'\n\nlet claudeViewHtml: string | undefined\n\n/**\n * The view file shipped in the package: dist/apps/claude-view.html next to the\n * built proxy, or src/mcp/apps/claude-view.html when run from source. Read once.\n */\nexport function readClaudeViewHtml(): string {\n if (claudeViewHtml !== undefined) return claudeViewHtml\n const here = path.dirname(fileURLToPath(import.meta.url))\n const candidates = [\n path.join(here, 'apps', CLAUDE_VIEW_FILE),\n path.join(here, '..', 'src', 'mcp', 'apps', CLAUDE_VIEW_FILE),\n ]\n for (const candidate of candidates) {\n try {\n claudeViewHtml = readFileSync(candidate, 'utf8')\n return claudeViewHtml\n } catch {\n /* try the next place */\n }\n }\n throw new Error(`The Chain Insights view file ${CLAUDE_VIEW_FILE} is missing from the package`)\n}\n\nexport type McpProxyMode = 'workspace' | 'stateless'\n\nexport function resolveMcpProxyMode(env: NodeJS.ProcessEnv = process.env): McpProxyMode {\n const raw = env['CHAIN_INSIGHTS_MCP_PROXY_MODE']?.trim().toLowerCase()\n if (!raw || raw === 'stateless') return 'stateless'\n if (raw === 'workspace') return 'workspace'\n if (raw === 'no-workspace' || raw === 'workspace-less') return 'stateless'\n throw new Error(`CHAIN_INSIGHTS_MCP_PROXY_MODE must be workspace or stateless; got \"${raw}\"`)\n}\n\nconst GRAPH_LAYERS_TEXT =\n \"Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for the chain node's own record of one known transaction, block, address or the head.\"\n\n// The rules where every host reads them, the tool description: a host that\n// loads no skill and keeps no server instructions still sees this first.\nconst TOOL_RULES_FIRST =\n 'Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once before this tool. For a graph, alias the columns from_address and to_address; the host draws the picture from the answer itself. Never draw it again with another tool, such as a visualize or widget tool.'\n\nconst KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string> = {\n meta_network_capabilities:\n 'Return the current Chain Insights network and tool support matrix. Takes no arguments: send {}.',\n meta_schema: META_SCHEMA_DESCRIPTION,\n meta_usage_status: \"Return the caller's public free graph_query quota for the current UTC day.\",\n meta_subscription_status:\n \"Return the caller's CIA subscription window end, daily allowance, consumption, and tier.\",\n meta_help: 'Show a short guide to Chain Insights tools and workflow.',\n meta_rules:\n 'Return the Chain Insights rules: how to write graph_query and graph_query_batch reads that are served, how to name columns so the answer is drawn as a graph, a time series or a table, and how to write the answer. Call it once per chat, before the first graph query, when the chain-insights-cypher skill is not loaded. Takes no arguments: send {}.',\n wallet_balance:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,\n graph_query_batch: `${TOOL_RULES_FIRST} Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads. For a time series across days, send one USE facts total per day with the query id set to the day, \"YYYY-MM-DD\": the view joins them into one series. Write every address in full in your answer, all 42 characters.`,\n}\n// Titles for proxied tools whose graph endpoint definition carries none.\nconst KNOWN_PUBLIC_TOOL_TITLES: Record<string, string> = {\n graph_query: 'Graph Query',\n graph_query_batch: 'Graph Query Batch',\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: robinhood, the one public network. CIA does not pick a default network, so always pass it. meta_network_capabilities (no arguments) lists the live networks.'\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\n// What the Chain Insights view draws from a graph_query answer, told by column\n// name. The same rules are in skills/chain-insights-cypher/SKILL.md.\nexport const PICTURE_RULES = [\n 'Pictures: hosts that draw views, such as Claude Desktop and the Codex app, draw a graph_query answer from its column names. No other tool is needed. Choose the picture by the question: who sent to whom, flows and routes are a graph; how something changed over time is a time series; a list, a lookup or the details of one thing is a table.',\n `- Rows with from_address and to_address columns draw a graph of at most 5,000 addresses. Optional columns: ${GRAPH_OPTIONAL_COLUMNS.join(', ')}. A recipe that draws a graph returns all of them; a column it leaves out shows as Unavailable in the drawn link. Example: USE topology MATCH (a:Address {address: $addr})-[f:FLOWS_TO]->(b:Address) WHERE NOT a:Pool RETURN ${flowsRecipeReturn('a', 'b')} ORDER BY f.last_seen_timestamp DESC LIMIT 25`,\n '- Rows with a day, date, hour, week, month or *_timestamp column, number columns, no address in any value and at least 3 distinct times draw a time series. Within one day, the transfers of a known pair: USE facts MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN t.block_timestamp AS block_timestamp, t.amount_usd AS amount_usd LIMIT 200',\n '- Across days: facts reads one day at a time, so send graph_query_batch with one query per day (3 to 20 days), each query id the day as \"YYYY-MM-DD\", each returning one total row: USE facts MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN count(t) AS tx_count, sum(t.amount_usd) AS amount_usd_sum. The view joins the days into one time series. Do not group by day or name several days in one read: both are refused.',\n '- Any other rows draw a table.',\n '- The host draws the picture from the answer itself. Do not draw it again with another tool, such as a visualize or widget tool: the user would see the same answer drawn twice.',\n \"- Every drawn answer carries view_url, a page on the user's own PC. When the user asks to see the graph bigger, in a window, or full screen, or says the Open in browser button did nothing: if this host has a browser tool (Claude Desktop: the Claude_Browser navigate tool), open view_url there; otherwise give the user the view_url link as a plain link. Claude Desktop opens no local link from the button itself, only from its browser tool.\",\n].join('\\n')\n\n// The same rule in one sentence, for meta_help.\nconst PICTURE_HELP_LINE =\n 'In hosts that draw views, a graph_query answer with from_address and to_address columns draws a graph, a day or time column with numbers draws a time series (across days: a graph_query_batch of one-day totals, each query id the day), and any other rows draw a table.'\n\n// Hosts that read skills from local files (Claude Code, Codex) load the\n// chain-insights-cypher skill that setup copies and the proxy keeps current.\n// Claude Desktop chat reads skills only from the user's claude.ai account, so\n// the proxy serves the same text through meta_rules: no upload, and always the\n// version of the installed package.\nconst RULES_FIRST =\n 'Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once (send {}) before the first graph_query or graph_query_batch, and follow what it returns. When the skill is loaded, do not call meta_rules.'\n\n// What every reply to the user follows. First in the instructions, so a host\n// that keeps only their start still has them; the skill says the same.\nconst ANSWER_RULES = [\n 'Answer rules, for every reply to the user:',\n '1. Write every address in full: 0x plus 40 hex characters, 42 in all, in sentences, lists, tables and summaries. Never shorten one with ... or …, not even a repeat mention of an address named earlier. The drawn picture may shorten its labels; your text never does. A table column that needs a short label names the thing (Pair A, Wallet Y) and the full address is listed above the table.',\n '2. Do not print the query, its USE line, its LIMIT, the epoch numbers you computed, or a Ref: line, unless the user asks how a number was found. Write a time from the <column>_utc twin the answer carries, never by converting milliseconds by head; the twin is in the reply only, so ask the query for the _timestamp column. Say what a value means in plain words and never print a column name such as is_contract, delegated_to or nonce in the answer.',\n '3. Tokens are not dollars: a transfer moves tokens worth an amount at the day price. Say \"tokens worth about 183 USD on 2026-10-06\", never \"sent 183 USD\". Write the unit as USD after the number, never a $ sign; head a table column \"Tokens worth (USD)\".',\n '4. Never draw a graph or a chart in text, ASCII or Mermaid: the host draws the picture from the columns. Write a short answer beside it, and never promise a picture: in a host that draws nothing, such as Claude Code or a terminal, the text answer carries everything.',\n].join('\\n')\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 graph_query(_batch) for every question about addresses, money flow and transactions.',\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 '- Pass network=robinhood, the one public network, on every graph tool call. CIA does not pick a default network. meta_network_capabilities takes no arguments (send {}); call it only to check which tools and layers are live.',\n ...routingHintLines(),\n '- Coverage is per kind of data. coverage.complete_through_block is the floor of USE facts: below it every raw relationship is indexed. graph_progress.complete_through_block is the floor of USE topology, and graph_progress.layers gives each link kind its own height. An empty answer above the floor may mean not indexed yet, not absent on chain. An absent chain_tip_block means the indexers are catching up: read freshness.max_data_age_seconds for the lag.',\n \"- The graph is address-grain. The only topology money node label is Address, keyed by the raw chain-native H160 address on EVM networks, for example 0x1874a43d7c6d888f9eda3d22a3a49704e3cadb24. There is no separate identity key. network is the query's network on every node and relationship, never stored, except on :Chain, where it names the remote chain.\",\n '- An Address carries a kind from chain facts. topology carries :Account or :Contract as a second label on an Address (:Contract:SmartAccount for a contract wallet), with is_contract kept beside :Contract, and an address that only received has none. facts serves no kind, and a facts read that names :Account or :Contract is refused: ask USE topology or USE chain. chain Address returns is_contract, nonce and delegated_to at the newest block, or at the block named by at_block. block_timestamp is epoch milliseconds on every layer.',\n '- Address nodes carry address, network, labels, and the role flags is_exchange, is_scam, is_victim and is_sanctioned. (:Address)-[:LINKED]-(:Address) is an undirected ownership-overlay edge (basis derived/associated, plus confidence, source_event, declared_owner, owner_state) 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 LIMIT 25.',\n '- Labels hold role words, never detector names: Exchange (plus the exchange name), Scam, Victim and Sanctioned (plus the entity name). Each role is also a node label and a flag on the Address: :Exchange and is_exchange follow the exchange label, :Scam and is_scam the risk label, :Victim and is_victim the protection label, :Sanctioned and is_sanctioned the sanctioned label. Each flag is present only when true and absent otherwise, never false: test IS NOT NULL or IS NULL, not = false. A withdrawn label removes its node label and its flag.',\n '- Address nodes carry base activity rollups: degree_in/degree_out/degree_total (distinct counterparty addresses), tx_in_count/tx_out_count/tx_total_count, total_in_usd/total_out_usd/total_volume_usd, net_flow_usd (in minus out; positive = net receiver) — all computed from external flows only — and first_activity_timestamp/last_activity_timestamp/activity_span_days, which include all flows (self-loops included). FLOWS_TO edges carry exactly tx_count, amount_usd_sum (total money flow, token and native value merged), first_seen_timestamp, last_seen_timestamp. Lifetime aggregates are the only serving window. Averages are computed inline (amount_usd_sum / toFloat(tx_count)). tx_count counts token and native transfers plus internal native transfers (a contract sending ETH during a call), and amount_usd_sum prices them all. USE facts TRANSFER lists all three, so the rows of a pair are the transfers its link counts, up to the height the link was built to. A link covers all time and a USE facts read covers one day: read the transfers of a pair one day at a time. A transaction anchor of a pair resolves through USE facts, on the UTC day of the first_seen_timestamp or last_seen_timestamp of the link: MATCH (a:Address {address: $from})-[t:TRANSFER]->(b:Address {address: $to}) WHERE t.block_date = \"YYYY-MM-DD\" RETURN t.tx_id, t.block_timestamp LIMIT 1.',\n '- For actor-level exposure (AC11), UNION FLOWS_TO and SWAPPED reachability over one visible LINKED hop instead of expanding through the LINKED edge itself: MATCH (a:Address {address: $addr})-[:LINKED]-(owned:Address)-[r:FLOWS_TO|SWAPPED]-(b:Address) WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address RETURN owned.address, b.address, type(r), coalesce(r.amount_usd_sum, r.bought_usd) LIMIT 50.',\n '- Labels and per-label risk live on the address node: the labels array plus three parallel lists, label_risk_labels, label_risk_levels and label_risk_updated_timestamps, where entry i of each list is one label row. No overall risk score is served. USE facts serves bounded single-event rows (TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING edges) only; lifetime address metrics (degrees, totals, activity window) are node properties on USE topology.',\n '- (from:Address)-[t:TRANSFER]->(to:Address) on USE facts returns individual transfer rows, not aggregates, with properties amount, amount_usd, asset_symbol, asset_contract, tx_id, block_height, block_timestamp, event_index, edge_index, kind, price_usd, and price_missing. Every TRANSFER row has a kind: token, native or internal. An internal row is ETH a contract sends while it runs a call, such as the ETH leg of a wrap. Add t.kind = \"internal\" to the pair and the day to read only the internal rows. A facts read names an address pair with one day, whether it is a TRANSFER row-select or a count()/sum() aggregate: both endpoint addresses as {address: \"...\"} on from and to, with WHERE t.block_date = \"YYYY-MM-DD\". A transaction is read on USE chain by its tx_id, not on facts. t.block_timestamp bounds in epoch milliseconds may narrow the day to a time window. One address, a day alone, a window of days, a block range or a bare LIMIT is not enough. A facts read holds at most 200 rows in a reply, has one relationship and no hop, and takes no ORDER BY: sort the page yourself.',\n '- Facts graph labels include Address; the TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING relationships each connect two Address nodes. Facts address keys match topology address values exactly.',\n '- Topology relationships include FLOWS_TO, SWAPPED, OPERATED_BY, LINKED, and RISK_PROXIMITY between Address nodes, ADDED_LIQUIDITY and REMOVED_LIQUIDITY to and from Pool nodes, and BRIDGED to and from Chain nodes.',\n \"- (:Address)-[:OPERATED_BY]->(:Address) is the directed owner-to-operator edge: the transaction sender that moved the owner's tokens (ERC-20/721), or the event operator (ERC-1155); not the approved spender. Aggregate properties: tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp, and optional token_standard (ERC20/ERC721/ERC1155; absent when the pair is mixed-standard). Valuation fields: valuation_tracked_count, valued_count and the reason counters missing_valuation_price_count, unknown_quantity_count, unrepresentable_quantity_count and usd_range_count. valuation_complete is true when valued_count equals valuation_tracked_count, valuation_coverage_ratio is valued_count divided by valuation_tracked_count, and amount_usd_sum counts a transfer with no USD value as 0, so it is a floor unless valuation_complete is true. One edge per owner/operator pair; direct transfers with no operator create none; an owner never operates for itself. Topology-only, and a topology fact — not a risk label. Probe: MATCH (owner:Address)-[operation:OPERATED_BY]->(operator:Address {address: $addr}) RETURN owner.address AS owner, operation.tx_count AS tx_count ORDER BY operation.tx_count DESC LIMIT 10.\",\n '- FLOWS_TO properties are tx_count, amount_usd_sum, first_seen_timestamp, last_seen_timestamp. pair_key and synced_through_height are internal sync bookkeeping, not to be queried. synced_through_height is also on the other summed links (SWAPPED, OPERATED_BY, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED, APPROVED, SPONSORED, BUNDLED, SIGNED_FOR) and pair_key on SWAPPED: bookkeeping there too. Tx ids of every transfer, internal ones included, come from USE facts TRANSFER. Averages come from inline arithmetic. FLOWS_TO into and out of pools is kept; the pool trace rule below governs a trace that reaches one.',\n '- Swaps, liquidity and bridges on USE topology are lifetime totals per pair; single events are USE facts rows. (:Address)-[:SWAPPED]->(:Address) joins a swap payer to a different recipient, one edge per payer, recipient, sold_asset and bought_asset, with swap_count, sold_amount_raw, bought_amount_raw, sold_usd, bought_usd, pools, families, strength, first_seen_height and last_seen_height. sold_usd and bought_usd sum the routes on the edge at the day price: a route side with no price adds 0, so 0 can mean no price. Do not read 0 as worth nothing. The USE facts SWAP row says which side had no price (sold_price_missing, bought_price_missing). strength is swap (the whole route is proven: payer, recipient, assets, exact raw amounts and conservation) or swap_like (the shape is a swap but the pool code is not proven). Today no route is swap: the swap reader reads transaction receipts only, with no execution trace, so every served route is swap_like, with reason unknown_pool_code and families unknown. A Uniswap V2 or V3 swap reads this way, and unknown does not mean the protocol is unsupported. A filter on strength = \"swap\" or on a known family matches nothing. A route that could not be paired (swap_unsplit) is never served: it has no payer, recipient or pool, makes no SWAPPED edge and no SWAP row, and exists in the warehouse only. Every Uniswap v4 swap is swap_unsplit today, by design, so v4 swaps are hidden. A self swap makes no edge, and a missing SWAPPED edge is not proof that no swap happened. Swap attribution is read from SWAPPED, the aggregate (strength, pools, families), or from the USE facts SWAP row, one route. FLOWS_TO carries value only.',\n \"- Pool is a second label on an Address: the pool of a swap route or liquidity event, with liquidity_added_usd, liquidity_removed_usd, liquidity_net_usd, provider_count and receiver_count. (:Address)-[:ADDED_LIQUIDITY]->(:Pool) and (:Pool)-[:REMOVED_LIQUIDITY]->(:Address) join named providers and receivers to the pool, with event_count, totals_raw, usd, unpriced_count, first_seen_height and last_seen_height; REMOVED_LIQUIDITY adds fees_raw, receiver_added_usd (USD the receiver itself added to the same pool) and receiver_provided. A receiver's profit from a pool is usd minus receiver_added_usd: receiver_provided false is a drain by a stranger, true with a large profit is the creator's rug pull.\",\n '- (:Address)-[:BRIDGED]->(:Chain) is outbound bridge use and (:Chain)-[:BRIDGED]->(:Address) inbound, with kinds, events, totals_raw, first_height, last_height and last_bridge_event_id. A Chain node (network, address) is the remote bridge endpoint, never an Address, so no FLOWS_TO walk passes through it.',\n '- Pool trace rule, for every trace: 1. Enter a `:Pool` on any edge. 2. Leave a `:Pool` only on `REMOVED_LIQUIDITY`, to the address the liquidity was paid to. 3. Never leave a `:Pool` on `FLOWS_TO`. 4. Across a swap, follow `SWAPPED` from payer to recipient, between two different addresses. Do not walk through the pool. Rug-pull probe: MATCH (victim:Address {address: $addr})-[paid:FLOWS_TO]->(pool:Pool)-[removal:REMOVED_LIQUIDITY]->(receiver:Address) WHERE NOT victim:Pool AND receiver.address <> victim.address RETURN pool.address AS pool_address, receiver.address AS receiver_address, removal.usd AS removed_usd, removal.receiver_added_usd AS receiver_added_usd, removal.receiver_provided AS receiver_provided LIMIT 25.',\n '- USE facts serves the single events. (payer:Address)-[s:SWAP]->(recipient:Address) is one row per swap route, self swaps included, with strength, reason, route_id and the sold_ and bought_ asset, amount and usd columns. (provider:Address)-[:LIQUIDITY_ADD]->(pool:Address) and (pool:Address)-[:LIQUIDITY_REMOVE]->(receiver:Address) are one row per liquidity event, with party_state and evidence_state. (sender:Address)-[c:BRIDGE_CROSSING]->(recipient:Address) is one row per bridge event. SWAP, LIQUIDITY_* and BRIDGE_CROSSING take the same rule as TRANSFER: an address pair with one day. USD comes from the daily price services, never from a swap: with no price, USD is empty and the matching price_missing column is true (price_missing on TRANSFER, sold_price_missing and bought_price_missing on SWAP, amount0_price_missing and amount1_price_missing on LIQUIDITY_*). block_timestamp on TRANSFER, SWAP and LIQUIDITY_* rows is epoch milliseconds, in filters and in results; BRIDGE_CROSSING has none.',\n '- A USE facts SWAP row carries no route: it has no pools and no families column. A read that names one of them, to return it, to filter on it or to order by it, is refused. The route of a swap stays a topology question. For the pools of the swaps of an address, read SWAPPED.pools and SWAPPED.families on USE topology, anchored on the payer or the recipient. SWAPPED has one link per payer, recipient, sold asset and bought asset, so its pools cover every route on the link, not one route.',\n '- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL. is_exchange is absent unless true, so a labelled node with no is_exchange is walked through, and is_scam, is_victim and is_sanctioned do not end a walk. At a Pool, follow the pool trace rule above.',\n '- Pool guard: a trace walks FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient without passing through the pool. A walk may end at a Pool, but never starts at one or passes through one: its start and every address in its middle stay off a Pool. A fixed-hop walk adds WHERE NOT src:Pool AND NOT mid:Pool, each its own AND term, never inside an OR. A quantified or shortest-path walk puts the guards inside the path pattern, on the start and on up to 4 guarded hops before one last hop: MATCH p = SHORTEST 1 (a:Address {address: $from} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: $to}) RETURN [n IN nodes(p) | n.address] AS route LIMIT 5. ANY SHORTEST takes the same pattern. A route search asks for one path. An open target from one address: MATCH SHORTEST 1 (a:Address {address: $addr} WHERE NOT a:Pool) (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,4} ()-[:FLOWS_TO|SWAPPED]-(b:Address) RETURN b.address LIMIT 50. Use these shapes as written, changing only the addresses and the RETURN. A WHERE placed after a SHORTEST pattern runs after the shortest route is chosen, so it drops a route that crosses a pool instead of finding the route that avoids it.',\n '- Call meta_schema {network} first when you need field names; it is cached for 24 hours. The two discovery reads below are the fallback.',\n '- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) RETURN n.address AS address, n.network AS network, n.labels AS labels, n.last_activity_timestamp AS last_activity_timestamp 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 \"- Anchor every topology read that filters on a link property: put an address in its pattern. Without one, a read starts from every link of the type it names, and you should not count on the filter to narrow that: WHERE x.strength = 'swap' on SWAPPED checks every SWAPPED link, LIMIT stops the read only after enough rows match, and a filter that matches few or none can run to the 60 s topology limit and fail with query_timeout. Example: MATCH (a:Address {address: $addr})-[x:SWAPPED]->(b:Address) WHERE x.swap_count >= 2 RETURN b.address, x.swap_count LIMIT 25. Pick an address with few links: degree_out and degree_in are a rough guide, because they count neighbours, not links, and an address with hundreds of thousands of neighbours can fail the same way. Discovery probes with LIMIT and no filter stay valid. The queries of one batch share a 100 s budget; USE facts queries stop at 30 s.\",\n '- graph_query uses the active Chain Insights graph endpoint. Select the graph with USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool label, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for one keyed lookup on the chain node: a transaction by tx_id, a block by block_height or block_hash, an address at one block, or the head. On topology, 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\n// The routing paragraph comes first: a host that keeps only the start of the\n// instructions must still show where each kind of question goes.\nconst ROUTING_HEAD = routingHead()\n\nconst SERVER_INSTRUCTIONS = [\n 'Chain Insights is an AML and graph-analysis MCP server for AI agents.',\n RULES_FIRST,\n ANSWER_RULES,\n CHAIN_INSIGHTS_WORKFLOW,\n ROUTING_HEAD,\n PICTURE_RULES,\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 RULES_FIRST,\n ANSWER_RULES,\n 'Call graph_query or graph_query_batch with network=robinhood. meta_network_capabilities takes no arguments (send {}); call it only to check which tools and layers are live.',\n ROUTING_HEAD,\n PICTURE_RULES,\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 'graph_query':\n return {\n query: z.string().min(1).describe(`Read-only GQL/Cypher query. ${GRAPH_LAYERS_TEXT}`),\n network: NETWORK_SCHEMA,\n }\n case 'graph_query_batch':\n return {\n network: NETWORK_SCHEMA,\n queries: z\n .array(\n z.object({\n id: z.string().optional(),\n query: z.string().min(1).describe('Read-only GQL/Cypher query'),\n })\n )\n .min(1)\n .max(20),\n per_query_timeout_seconds: z.number().int().min(1).max(600).optional(),\n }\n default:\n return null\n }\n}\n\nfunction fallbackGraphPrimitiveTools(): McpTool[] {\n return FALLBACK_GRAPH_PRIMITIVE_TOOL_NAMES.map((name) => ({\n name,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[name],\n }))\n}\n\n/**\n * Local payment wallet address for tools that address the caller on the\n * server (subscription_status). Returns null — never throws — when no wallet\n * is configured; callers degrade to an unavailable-shape result.\n */\n/**\n * Local payment wallet address for the top-up view: null when no wallet file\n * exists, and an error when the file exists but cannot be read. Reads the\n * address only; nothing is signed or sent.\n */\nasync function localTopupWalletAddress(): Promise<string | null> {\n const { isWalletConfigured } = await import('../wallet/index.js')\n if (!(await isWalletConfigured())) return null\n const { getWalletAccount } = await import('../wallet/tools.js')\n return (await getWalletAccount()).address\n}\n\nasync function localSubscriptionWalletAddress(): Promise<string | null> {\n try {\n const { getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n return account.address\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction redactLogValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(redactLogValue)\n if (!isRecord(value)) return value\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => {\n if (/token|secret|password|private.?key|authorization/i.test(key)) return [key, '[redacted]']\n return [key, redactLogValue(entry)]\n })\n )\n}\n\nfunction errorForLog(err: unknown): Record<string, unknown> {\n const error = err as Error\n return {\n name: error.name ?? 'Error',\n message: error.message ?? String(err),\n }\n}\n\nfunction sanitizeCypher(query: string): string {\n return query.replace(/\\s+/g, ' ').trim()\n}\n\nfunction cypherLogPayload(tool: string, args: unknown): Record<string, unknown> | null {\n if (!isRecord(args)) return null\n if (tool === 'graph_query') {\n return {\n network: args.network,\n queries: [\n {\n id: tool,\n query: typeof args.query === 'string' ? sanitizeCypher(args.query) : args.query,\n },\n ],\n }\n }\n if (tool === 'graph_query_batch') {\n const queries = Array.isArray(args.queries) ? args.queries : []\n return {\n network: args.network,\n per_query_timeout_seconds: args.per_query_timeout_seconds,\n query_count: queries.length,\n queries: queries.map((entry, index) =>\n isRecord(entry)\n ? {\n id: typeof entry.id === 'string' ? entry.id : `q${index + 1}`,\n query: typeof entry.query === 'string' ? sanitizeCypher(entry.query) : entry.query,\n }\n : { id: `q${index + 1}`, query: entry }\n ),\n }\n }\n return null\n}\n\nfunction createMcpLogger(config: Pick<InvestigatorConfig, 'dataDir'>) {\n const disabled = process.env.CHAIN_INSIGHTS_MCP_LOG === '0'\n const filePath =\n process.env.CHAIN_INSIGHTS_MCP_LOG_PATH?.trim() ||\n path.join(config.dataDir, '.chain-insights', 'runtime', 'logs', 'mcp-proxy.jsonl')\n\n async function write(\n level: 'info' | 'error',\n event: string,\n fields: Record<string, unknown> = {}\n ): Promise<void> {\n if (disabled) return\n try {\n await mkdir(path.dirname(filePath), { recursive: true })\n await appendFile(\n filePath,\n JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...fields,\n }) + '\\n',\n { mode: 0o600 }\n )\n } catch {\n // Logging must never break the stdio MCP server.\n }\n }\n\n return {\n filePath,\n info: (event: string, fields?: Record<string, unknown>) => write('info', event, fields),\n error: (event: string, fields?: Record<string, unknown>) => write('error', event, fields),\n }\n}\n\nfunction installToolLogging(server: McpServer, logger: ReturnType<typeof createMcpLogger>): void {\n const existingRegisterTool = server.registerTool\n const originalRegisterTool = existingRegisterTool.bind(server)\n const wrappedRegisterTool = ((\n name: string,\n config: ToolRegistrationConfig,\n handler: ToolHandler\n ) => {\n const wrapped: ToolHandler = async (args, extra) => {\n const startedAt = Date.now()\n await logger.info('tool.start', {\n tool: name,\n args: redactLogValue(args),\n })\n try {\n const result = await handler(args, extra)\n const isError = isRecord(result) && result.isError === true\n await logger.info('tool.end', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n is_error: isError,\n })\n return result\n } catch (err) {\n await logger.error('tool.throw', {\n tool: name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n throw err\n }\n }\n return originalRegisterTool(name, config, wrapped as never)\n }) as typeof server.registerTool\n Object.assign(wrappedRegisterTool, existingRegisterTool)\n server.registerTool = wrappedRegisterTool\n}\n\nfunction installRemoteCypherLogging(\n remoteClient: RemoteToolCaller,\n logger: ReturnType<typeof createMcpLogger>\n): void {\n const existingCallTool = remoteClient.callTool\n const originalCallTool = existingCallTool.bind(remoteClient)\n const wrappedCallTool = (async (...args: Parameters<Client['callTool']>) => {\n const input = args[0] as ToolCallInput\n const queryPayload = cypherLogPayload(input.name, input.arguments)\n const toolArgs = input.arguments ?? {}\n const startedAt = Date.now()\n if (queryPayload) {\n await logger.info('topology.start', {\n tool: input.name,\n ...queryPayload,\n })\n }\n try {\n const result = await originalCallTool(...args)\n if (queryPayload) {\n await logger.info('topology.end', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n is_error: isRecord(result) && result.isError === true,\n })\n }\n const { warnings, search_limits } = actionLogSignalsFromResult(result)\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'ok',\n duration_ms: Date.now() - startedAt,\n warnings,\n search_limits,\n })\n return result\n } catch (err) {\n if (queryPayload) {\n await logger.error('cypher.throw', {\n tool: input.name,\n duration_ms: Date.now() - startedAt,\n error: errorForLog(err),\n })\n }\n await appendActionLog({\n timestamp: startedAt,\n tool: input.name,\n args: toolArgs,\n outcome: 'error',\n duration_ms: Date.now() - startedAt,\n error: (err as Error).message,\n })\n throw err\n }\n }) as typeof remoteClient.callTool\n Object.assign(wrappedCallTool, existingCallTool)\n remoteClient.callTool = wrappedCallTool\n}\n\nfunction remoteToolRequestOptions(toolName: string): Parameters<Client['callTool']>[2] | undefined {\n if (toolName === 'graph_query' || toolName === 'graph_query_batch') {\n return {\n timeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: REMOTE_GRAPH_TOOL_REQUEST_TIMEOUT_MS,\n }\n }\n return undefined\n}\n\nfunction isBlankArgument(value: unknown): boolean {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0 || value.every(isBlankArgument)\n return false\n}\n\nfunction normalizeRemoteToolArguments(toolName: string, args: unknown): Record<string, unknown> {\n const normalized = isRecord(args) ? { ...args } : {}\n if (!(toolName in PUBLIC_MCP_TOOL_REQUIRED_ARGS)) return normalized\n\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[toolName]\n if (!allowedArgs) return normalized\n return Object.fromEntries(Object.entries(normalized).filter(([key]) => allowedArgs.includes(key)))\n}\n\nfunction validateKnownPublicToolArguments(\n toolName: string,\n args: Record<string, unknown>\n): string | null {\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[toolName]\n if (!requiredArgs) return null\n\n for (const argName of requiredArgs) {\n if (isBlankArgument(args[argName])) {\n return `Missing required argument: ${argName}`\n }\n }\n\n return null\n}\n\nfunction claudeFacingToolDescription(tool: McpTool): string {\n const baseDescription = KNOWN_PUBLIC_TOOL_DESCRIPTIONS[tool.name] ?? tool.description ?? tool.name\n const requiredArgs = PUBLIC_MCP_TOOL_REQUIRED_ARGS[tool.name]\n if (!requiredArgs) return baseDescription\n return [\n baseDescription,\n '',\n `Required arguments: ${requiredArgs.join(', ')}.`,\n 'If the user did not provide the network, ask for it before calling this tool. Do not guess a default network.',\n ].join('\\n')\n}\n\nfunction knownPublicToolAnnotations(toolName: string): Record<string, boolean> | undefined {\n if (\n toolName === 'graph_query' ||\n toolName === 'graph_query_batch' ||\n toolName.startsWith('aml_')\n ) {\n return {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n }\n }\n return undefined\n}\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n structuredContent?: Record<string, unknown>\n _meta?: Record<string, unknown>\n isError?: boolean\n}\n\n/**\n * The text a call to the graph endpoint answers with when it throws: payment\n * guidance for a payment failure, the error otherwise. Shared by every proxied\n * tool and by the money-flow reads.\n */\nfunction remoteCallFailureText(toolName: string, err: unknown): string {\n if (err instanceof PaymentRequiredError) return err.message\n const msg = (err as Error).message ?? String(err)\n if (/\\b402\\b/.test(msg) || msg.toLowerCase().includes('payment')) {\n return (\n `Payment required for ${toolName}. 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 return `MCP call failed: ${msg}`\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 '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-schema',\n {\n title: 'Graph Schema',\n description: 'Read the live graph schema of one network before writing a query.',\n argsSchema: { network: NETWORK_SCHEMA },\n },\n async ({ network }) =>\n promptResult(\n `Use Chain Insights meta_schema with network ${network}. Report the labels, link types and fields exactly as returned; do not invent fields.`,\n 'Graph schema'\n )\n )\n\n server.registerPrompt(\n 'meta-usage-status',\n {\n title: 'Usage Status',\n description: \"Check the caller's public free graph_query quota.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_usage_status. Report the quota fields exactly as returned.',\n 'Usage status'\n )\n )\n\n server.registerPrompt(\n 'meta-subscription-status',\n {\n title: 'Subscription Status',\n description: \"Check the caller's CIA subscription window, daily allowance, and tier.\",\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_subscription_status. Report the subscription facts exactly as returned.',\n 'Subscription status'\n )\n )\n\n server.registerPrompt(\n 'graph-query',\n {\n title: 'Graph Query',\n description: 'Run a read-only GQL/Cypher query through the Chain Insights graph endpoint.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n query: z.string().describe('Read-only GQL/Cypher query'),\n },\n },\n async ({ network, query }) =>\n promptResult(\n [\n `Use Chain Insights graph_query on ${network} with this read-only GQL/Cypher query:`,\n '',\n '```gql',\n query,\n '```',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ].join('\\n'),\n 'Graph query'\n )\n )\n\n server.registerPrompt(\n 'graph-query-batch',\n {\n title: 'Graph Query Batch',\n description:\n 'Run related read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch.',\n argsSchema: {\n network: NETWORK_SCHEMA,\n queries: z\n .string()\n .describe('JSON array of query objects with optional id and required query fields'),\n per_query_timeout_seconds: z\n .string()\n .optional()\n .describe('Optional integer timeout per query, 1-600 seconds'),\n },\n },\n async ({ network, queries, per_query_timeout_seconds }) =>\n promptResult(\n [\n `Use Chain Insights graph_query_batch on ${network} with these read-only GQL/Cypher queries:`,\n '',\n '```json',\n queries,\n '```',\n per_query_timeout_seconds\n ? `per_query_timeout_seconds: ${per_query_timeout_seconds}`\n : '',\n '',\n `${GRAPH_LAYERS_TEXT} If you need schema context, first run small discovery queries such as MATCH (a:Address) RETURN a.address AS address, keys(a) AS address_properties LIMIT 5 and MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN keys(r) AS flow_properties LIMIT 5. Return the full address when available; never shorten addresses with ellipses.`,\n ]\n .filter(Boolean)\n .join('\\n'),\n 'Graph query batch'\n )\n )\n\n server.registerPrompt(\n 'wallet-balance',\n {\n title: 'Wallet Balance',\n description:\n 'Show the local Chain Insights payment wallet address, payment network, token, and amount.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights wallet_balance. Show the wallet address, payment network, token, and amount exactly as returned.',\n 'Wallet balance'\n )\n )\n\n server.registerPrompt(\n 'meta-help',\n {\n title: 'Chain Insights Help',\n description: 'Show available Chain Insights tools and workflow guidance.',\n argsSchema: {},\n },\n async () =>\n promptResult(\n 'Use Chain Insights meta_help. Summarize the available tools and workflow guidance without inventing capabilities.',\n 'Chain Insights help'\n )\n )\n}\n\nfunction sanitizeStructuredContentForGraphPayload(\n structuredContent: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!structuredContent) return undefined\n return sanitizeStructuredValue(structuredContent) as Record<string, unknown>\n}\n\nfunction sanitizeStructuredValue(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value\n\n const sanitized: Record<string, unknown> = {}\n for (const [key, childValue] of Object.entries(value)) {\n if (key === 'app_data') continue\n if (\n GRAPH_ARRAY_KEYS.includes(key as (typeof GRAPH_ARRAY_KEYS)[number]) &&\n Array.isArray(childValue)\n ) {\n continue\n }\n sanitized[key] = sanitizeStructuredValue(childValue)\n }\n\n return sanitized\n}\n\n// The remote result _meta passes through, MCP Apps keys (ui, ui/resourceUri)\n// included. Only the old graph-report envelope (chainInsights.graph) is dropped.\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\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\n// Raw graph arrays and app_data are stripped from the aml_* answers only, the\n// graph-report tools of the old viewer. Every other answer (graph_query,\n// graph_query_batch) keeps its structuredContent exactly as the hosted server\n// returned it: the views read it.\nfunction stripsGraphPayload(toolName: string): boolean {\n return toolName.startsWith('aml_')\n}\n\nfunction normalizeRemoteToolResult(toolName: string, result: RemoteToolResult) {\n const normalized = {\n content: result.content ?? [],\n structuredContent: stripsGraphPayload(toolName)\n ? sanitizeStructuredContentForGraphPayload(result.structuredContent)\n : result.structuredContent,\n _meta: sanitizeRemoteMeta(result._meta),\n isError: result.isError,\n }\n // Every timestamp column gets its UTC text twin (src/mcp/timestamp-text.ts).\n return toolName === 'graph_query' || toolName === 'graph_query_batch'\n ? withTimestampTextResult(normalized)\n : normalized\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 // Inside Claude Desktop the Claude Code copy cannot draw the views. When\n // Claude Desktop's own drawing copy is configured, this copy lists no tools\n // so that each tool appears once (src/mcp/claude-desktop.ts).\n const { defersToClaudeDesktop, DEFERRED_SERVER_INSTRUCTIONS } =\n await import('./claude-desktop.js')\n if (defersToClaudeDesktop()) {\n await logger.info('proxy.deferred_to_claude_desktop', { version: PACKAGE_VERSION })\n const deferred = new McpServer(\n { name: 'chain-insights', version: PACKAGE_VERSION },\n { instructions: DEFERRED_SERVER_INSTRUCTIONS }\n )\n await deferred.connect(new StdioServerTransport())\n return\n }\n\n // npm runs no setup step on upgrade: bring the skill copies setup installed\n // up to this release. A failure here never stops the proxy.\n // CHAIN_INSIGHTS_SKILL_REFRESH=0 turns it off for people who manage the skill themselves.\n if (process.env['CHAIN_INSIGHTS_SKILL_REFRESH'] !== '0') {\n try {\n const { refreshInstalledSkills } = await import('../skill-refresh.js')\n const refresh = refreshInstalledSkills(PACKAGE_VERSION)\n if (refresh.refreshed.length > 0 || refresh.failed.length > 0) {\n await logger.info('skills.refresh', { version: PACKAGE_VERSION, ...refresh })\n }\n } catch (error) {\n await logger.info('skills.refresh', {\n version: PACKAGE_VERSION,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\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 let remoteRetryable = false\n // Connect to the graph endpoint: a busy endpoint (429) is waited for, a\n // non-streamable one gets the SSE fallback, and a busy failure leaves the\n // proxy able to connect again on the next graph call (src/mcp/remote-connect.ts).\n const connectRemoteOnce = async (): Promise<void> => {\n if (!mcpFetch) return\n const fetchForRemote = mcpFetch\n const result = await connectRemote({\n endpoint: graphMcpEndpoint,\n connectStreamable: () =>\n connectFresh(remoteClient, new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: fetchForRemote })),\n connectSse: async () => {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n await connectFresh(remoteClient, new SSEClientTransport(new URL(graphMcpEndpoint), { fetch: fetchForRemote }))\n },\n log: (event, fields) => (event === 'remote.connect' ? logger.info(event, fields) : logger.error(event, fields)),\n })\n remoteConnected = result.connected\n remoteRetryable = result.retryable\n if (result.connected) {\n remoteUnavailableMessage = undefined\n installRemoteCypherLogging(remoteClient as unknown as RemoteToolCaller, logger)\n } else {\n remoteUnavailableMessage = result.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 // Schema cache check — skip remote listTools call on cache hit\n let tools: McpTool[] | null = await loadSchema(graphMcpEndpoint)\n\n // With the tool list cached, the proxy answers the host's initialize and\n // tools/list at once and connects to the endpoint in the background. A busy\n // endpoint (429) is retried for up to 15 s; a host that lists tools inside\n // that window (Codex starts a fresh proxy on every resumed turn and lists in\n // about 10 s) must still see graph_query, or it reports the connector\n // unavailable and reaches for a shell. The first graph call waits for the\n // connect in flight.\n let connecting: Promise<void> | null = null\n const startConnect = (): Promise<void> => {\n if (!connecting) {\n connecting = connectRemoteOnce().finally(() => {\n connecting = null\n })\n }\n return connecting\n }\n if (tools) {\n void startConnect()\n } else {\n await startConnect()\n }\n /** A graph call waits for the connect in flight; one that lost the endpoint to a busy signal tries again, once. */\n const ensureRemote = async (): Promise<void> => {\n if (remoteConnected) return\n if (connecting) {\n await connecting\n return\n }\n if (!remoteRetryable) return\n await startConnect()\n }\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 await ensureRemote()\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 // meta_schema: the live graph schema of one network, built from the catalog\n // statements of the graph endpoint and kept on disk for 24 hours. A build reads\n // through the proxy's one remote session; a cache hit reads nothing.\n const schemaDependencies: GraphSchemaDependencies = {\n endpoint: graphMcpEndpoint,\n withClient: async (fn) => {\n await ensureRemote()\n if (!remoteConnected) {\n throw new GraphSchemaError(\n `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`\n )\n }\n return fn(remoteClient)\n },\n describeFailure: (err) => remoteCallFailureText('meta_schema', err),\n }\n server.registerTool(\n 'meta_schema',\n {\n title: META_SCHEMA_TITLE,\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_schema,\n inputSchema: {\n network: NETWORK_SCHEMA,\n refresh: z\n .boolean()\n .optional()\n .describe('Rebuild the schema now instead of reading the 24 hour cache.'),\n },\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async (args) => handleMetaSchema(args, schemaDependencies)\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 _meta: VIEW_TOOL_META,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n await ensureRemote()\n if (!remoteConnected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n },\n ],\n isError: true,\n }\n }\n if (!remoteToolNames.has('usage_status')) {\n return jsonTextResult(primitiveBackendUsageStatus(graphMcpEndpoint))\n }\n const result = (await remoteClient.callTool({\n name: 'usage_status',\n arguments: {},\n })) as RemoteToolResult\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_usage_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return {\n content: [\n { type: 'text' as const, text: `Usage status failed: ${(err as Error).message}` },\n ],\n isError: true,\n }\n }\n }\n )\n\n server.registerTool(\n 'meta_subscription_status',\n {\n title: 'Subscription Status',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_subscription_status,\n inputSchema: EMPTY_INPUT_SCHEMA,\n _meta: VIEW_TOOL_META,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n // Local proxy shape discipline: the tool is always present and never\n // throws. When the server tool is absent or errors, the facts explain\n // the unavailability.\n try {\n await ensureRemote()\n if (!remoteConnected) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n remoteUnavailableMessage ??\n `Chain Insights Graph is not connected at ${graphMcpEndpoint}`\n )\n )\n }\n if (!remoteToolNames.has('subscription_status')) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'The graph backend exposes primitive graph tools but no subscription_status tool.'\n )\n )\n }\n const walletAddress = await localSubscriptionWalletAddress()\n if (!walletAddress) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n 'No local payment wallet is configured; run `cia wallet create` or `cia wallet import` first.'\n )\n )\n }\n const result = (await remoteClient.callTool({\n name: 'subscription_status',\n arguments: { wallet: walletAddress },\n })) as RemoteToolResult\n if (result.isError === true) {\n const firstText = Array.isArray(result.content)\n ? result.content.find(\n (block): block is Extract<ContentBlock, { type: 'text' }> =>\n block.type === 'text' && typeof block.text === 'string'\n )\n : undefined\n const reason = firstText?.text\n ? `subscription_status failed: ${firstText.text}`\n : 'subscription_status failed: the server returned an error'\n return jsonTextResult(unavailableSubscriptionStatus(graphMcpEndpoint, reason))\n }\n const structuredContent = isRecord(result.structuredContent)\n ? { ...result.structuredContent, tool: 'meta_subscription_status' }\n : undefined\n return {\n content: structuredContent\n ? [{ type: 'text' as const, text: JSON.stringify(structuredContent, null, 2) }]\n : (result.content ?? []),\n structuredContent,\n _meta: result._meta,\n isError: result.isError,\n }\n } catch (err) {\n return jsonTextResult(\n unavailableSubscriptionStatus(\n graphMcpEndpoint,\n `subscription_status failed: ${(err as Error).message}`\n )\n )\n }\n }\n )\n\n server.registerTool(\n 'wallet_balance',\n {\n title: 'Wallet Balance',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.wallet_balance,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async () => {\n try {\n const { formatWalletBalanceResult, getWalletAccount, getWalletBalanceResult } =\n await import('../wallet/tools.js')\n const account = await getWalletAccount()\n const structuredContent = await getWalletBalanceResult(account)\n return {\n content: [{ type: 'text' as const, text: formatWalletBalanceResult(structuredContent) }],\n structuredContent: structuredContent as unknown as Record<string, unknown>,\n isError: false,\n }\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Balance failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n // graph_expand, the one dedicated tool, for the view only (visibility\n // [\"app\"]): a node click is at most five anchored topology graph_query reads\n // (the node, then two chunks a side), a link click one facts graph_query\n // read, all sent through the remote client, the\n // path and payment wrapping a graph_query call takes, so each read is billed\n // as a graph query.\n const flowsDependencies: FlowsDependencies = {\n graphQuery: async (args) => {\n const options = remoteToolRequestOptions('graph_query')\n return (await withRateLimitRetry(() =>\n remoteClient.callTool({ name: 'graph_query', arguments: args }, undefined, options)\n )) as GraphQueryAnswer\n },\n describeFailure: (err) => remoteCallFailureText('graph_query', err),\n unavailable: () =>\n remoteConnected\n ? undefined\n : `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`,\n }\n // One function answers a click, from the host's view and from the browser\n // page alike.\n const runGraphExpand = (args: unknown) => handleGraphExpand(args, flowsDependencies)\n\n // The browser window of a drawn answer (src/mcp/view-server.ts). Started on\n // the first drawn answer, once per process. The deferred copy above returns\n // before this point, so it never starts one.\n let viewServerStart: Promise<ViewServer | null> | undefined\n const viewServer = (): Promise<ViewServer | null> => {\n viewServerStart ??= startViewServer({\n html: readClaudeViewHtml,\n runTool: async (name, args) => {\n if (name === GRAPH_EXPAND_TOOL) return runGraphExpand(args)\n throw new Error(`The browser page cannot call ${name}`)\n },\n log: (event, fields) => {\n void logger.info(event, fields)\n if (event === 'view.server.started') {\n process.stderr.write(`[chain-insights] view.server.started port=${String(fields.port)}\\n`)\n }\n },\n }).catch(async (err: unknown) => {\n await logger.error('view.server.failed', { error: errorForLog(err) })\n process.stderr.write(\n `[chain-insights] view.server.failed: ${(err as Error).message ?? String(err)}\\n`\n )\n return null\n })\n return viewServerStart\n }\n\n // A drawn answer gets its browser link: view_url in structuredContent and one\n // line of text. The stored answer is the one the host receives, view_url\n // included. A refused answer, or one with nothing to draw, stays as it is.\n async function withViewUrl<T>(toolName: string, args: unknown, result: T): Promise<T> {\n if (!isRecord(result) || result.isError === true) return result\n const structuredContent = result.structuredContent\n if (!isRecord(structuredContent)) return result\n const browser = await viewServer()\n if (!browser) return result\n const id = newViewId()\n const url = browser.viewUrl(id)\n const content = Array.isArray(result.content) ? result.content : []\n const linked = {\n ...result,\n content: [...content, { type: 'text' as const, text: `${BROWSER_VIEW_LINE} ${url}` }],\n structuredContent: { ...structuredContent, view_url: url },\n }\n browser.put({ toolName, arguments: args, result: linked }, id)\n return linked as T\n }\n\n server.registerTool(\n GRAPH_EXPAND_TOOL,\n {\n title: GRAPH_EXPAND_TITLE,\n description: GRAPH_EXPAND_DESCRIPTION,\n inputSchema: {\n network: z.string(),\n address: z.string().optional(),\n in_offset: z.number().int().min(0).max(FLOWS_MAX_OFFSET).optional(),\n out_offset: z.number().int().min(0).max(FLOWS_MAX_OFFSET).optional(),\n from: z.string().optional(),\n to: z.string().optional(),\n day: z.string().optional(),\n },\n annotations: VIEW_TOOL_ANNOTATIONS,\n _meta: APP_ONLY_TOOL_META,\n },\n async (args) => withViewUrl(GRAPH_EXPAND_TOOL, args, await runGraphExpand(args))\n )\n\n server.registerTool(\n 'meta_rules',\n {\n title: 'Chain Insights Rules',\n description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_rules,\n inputSchema: EMPTY_INPUT_SCHEMA,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => {\n const { readPackagedSkillRules } = await import('../skill-refresh.js')\n return {\n content: [{ type: 'text' as const, text: readPackagedSkillRules() }],\n isError: false,\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: supported networks and available tools. Takes no arguments.',\n '- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- 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 PICTURE_HELP_LINE,\n '',\n 'Wallet tools:',\n '- wallet_balance: show the local payment wallet address, payment network, token, and amount.',\n '- meta_rules: the full query, picture and answer rules. Call once per chat when the chain-insights-cypher skill is not loaded.',\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: supported networks and available tools. Takes no arguments.',\n '- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.',\n '- meta_usage_status: check the caller public free graph_query quota.',\n '- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.',\n '- 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 '- meta_rules: the full query, picture and answer rules. Call once per chat when the chain-insights-cypher skill is not loaded.',\n '',\n PICTURE_HELP_LINE,\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 // A tool only a view of the graph endpoint may call has no view here.\n if (isAppOnlyTool(tool)) continue\n const inputSchema = knownPublicToolInputSchema(tool.name) ?? z.object({}).passthrough()\n const handler = async (args: unknown) => {\n try {\n await ensureRemote()\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 = withoutUtcProperties(tool.name, 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 callRemote = (requestArgs: Record<string, unknown>) =>\n withRateLimitRetry(() =>\n requestOptions\n ? remoteClient.callTool({ name: tool.name, arguments: requestArgs }, undefined, requestOptions)\n : remoteClient.callTool({ name: tool.name, arguments: requestArgs })\n )\n // A batch's chain_busy members are resent by the proxy (src/mcp/batch-busy-retry.ts).\n const result =\n tool.name === 'graph_query_batch'\n ? await withBusyMemberRetry(normalizedArgs, (a) => callRemote(a) as Promise<BatchResult>)\n : tool.name === 'graph_query'\n ? await withBusyRetry(normalizedArgs, (a) => callRemote(a) as Promise<BatchResult>)\n : await callRemote(request.arguments)\n const answer = normalizeRemoteToolResult(tool.name, result as RemoteToolResult)\n return BROWSER_VIEW_TOOL_NAMES.has(tool.name)\n ? await withViewUrl(tool.name, normalizedArgs, answer)\n : answer\n } catch (err) {\n const limited = rateLimitedResult(tool.name, err)\n if (limited) return limited\n return {\n content: [{ type: 'text' as const, text: remoteCallFailureText(tool.name, err) }],\n isError: true,\n }\n }\n }\n // The graph endpoint's own MCP Apps metadata (_meta.ui, ui/resourceUri)\n // is never forwarded: the proxy is the only source of views. graph_query\n // gets the view the proxy serves.\n const annotations = knownPublicToolAnnotations(tool.name)\n const drawsView = tool.name === 'graph_query'\n const toolConfig = {\n title: tool.title ?? KNOWN_PUBLIC_TOOL_TITLES[tool.name],\n description: claudeFacingToolDescription(tool),\n inputSchema,\n ...(annotations ? { annotations } : {}),\n ...(drawsView ? { _meta: VIEW_TOOL_META } : {}),\n }\n\n server.registerTool(tool.name, toolConfig, handler)\n }\n\n // The local top-up view (wallet address and QR code). It stays in the local\n // proxy: the wallet it funds is a local file, and the hosted connector never\n // offers it (ruling 2026-10-05).\n const { registerTopupView, TOPUP_VIEW_URI } = await import('../wallet/mcp-proxy/topup-server.js')\n await registerTopupView(server, { walletAddress: localTopupWalletAddress })\n\n // ui://chain-insights/view, read from the package. Nothing is fetched.\n server.registerResource(\n 'Chain Insights view',\n CLAUDE_VIEW_URI,\n {\n title: 'Chain Insights view',\n description:\n 'The Chain Insights view for hosts that draw MCP apps: money flows, query results and balance, in light and dark.',\n mimeType: MCP_APP_MIME_TYPE,\n _meta: VIEW_RESOURCE_META,\n },\n async () => ({\n contents: [\n {\n uri: CLAUDE_VIEW_URI,\n mimeType: MCP_APP_MIME_TYPE,\n text: readClaudeViewHtml(),\n _meta: VIEW_RESOURCE_META,\n },\n ],\n })\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].filter((name) => !APP_ONLY_LOCAL_TOOL_NAMES.has(name)),\n ...(tools ?? [])\n .filter((tool) => !isAppOnlyTool(tool))\n .map((tool) => tool.name)\n .filter((name) => !HIDDEN_REMOTE_TOOL_NAMES.has(name) && !LOCAL_TOOL_NAMES.has(name)),\n ].length,\n ui_resources: [CLAUDE_VIEW_URI, TOPUP_VIEW_URI],\n })\n\n // Signal handling — clean shutdown\n const shutdown = async () => {\n await logger.info('proxy.shutdown')\n transport.close()\n process.exit(0)\n }\n process.on('SIGINT', () => {\n void shutdown()\n })\n process.on('SIGTERM', () => {\n void shutdown()\n })\n}\n\n// Entry point — only execute when run as the main module (not when imported by tests)\n// Using process.argv check to detect direct execution vs import\nif (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\\\/g, '/'))) {\n createProxy().catch((err) => {\n process.stderr.write(`Chain Insights MCP proxy startup failed: ${(err as Error).message}\\n`)\n process.exit(1)\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.56.16",
3
+ "version": "0.56.17",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk screening and Chain Insights Graph access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {