chain-insights 0.2.28 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,7 @@ MCP endpoint for development; hosted endpoints are set explicitly with
18
18
  | `stake_insights` | Explain Bittensor staking relationships, net stake movement, and counterparties |
19
19
  | `track_funds` | Trace victim/source funds through intermediaries to exchange deposit candidates |
20
20
  | `scam_topology` | Expand a known victim incident into reviewable scam infrastructure and label candidates |
21
+ | `usage_status` | Check the caller's public free graph query quota for today |
21
22
  | `graph_query` | Run one read-only GQL/Cypher query against a GraphRAG MCP graph layer |
22
23
  | `graph_query_batch` | Run related read-only graph queries as one MCP call |
23
24
 
@@ -101,6 +102,7 @@ Check the configured endpoint and current GraphRAG MCP capabilities:
101
102
  ```bash
102
103
  cia config get graphMcpEndpoint
103
104
  cia mcp networks
105
+ cia mcp call usage_status
104
106
  cia mcp tools --refresh
105
107
  ```
106
108
 
@@ -108,6 +110,12 @@ If network or tool discovery fails, check the endpoint and access mode first.
108
110
  The CLI can still initialize workspaces and manage cases without a reachable
109
111
  GraphRAG MCP endpoint.
110
112
 
113
+ Hosted GraphRAG MCP lets new users try `graph_query` with a small public free
114
+ quota before setting up paid access. The default public free graph_query quota
115
+ is 10 execution seconds per IP per UTC day. Use `usage_status` to see the
116
+ current caller quota. When the free quota is exhausted, prepare a wallet or use
117
+ an invited tester access key and retry.
118
+
111
119
  Open a case and run a small investigation:
112
120
 
113
121
  ```bash
@@ -182,8 +190,9 @@ Graph queries must choose the right read layer explicitly:
182
190
  | `facts` | Labels, features, risk scores, assets, and enrichment |
183
191
 
184
192
  Use `graph_query_batch` when related reads should share one call and one
185
- result envelope. Endpoint access and authentication are configured separately;
186
- see [MCP proxy](docs/mcp-proxy.md).
193
+ result envelope. Use explicit `LIMIT` and pagination in your query when you
194
+ want bounded result sets. Endpoint access and authentication are configured
195
+ separately; see [MCP proxy](docs/mcp-proxy.md).
187
196
 
188
197
  ## AML Tools
189
198
 
@@ -1,4 +1,4 @@
1
- import { a as resolveGraphMcpEndpoint } from "./client-7vkBVEIj.mjs";
1
+ import { a as resolveGraphMcpEndpoint } from "./client-D4JE7fFF.mjs";
2
2
  //#region src/mcp/capabilities.ts
3
3
  function metadataNetworksUrl(endpoint) {
4
4
  const url = new URL(endpoint);
@@ -81,4 +81,4 @@ function formatNetworkCapabilities(document) {
81
81
  //#endregion
82
82
  export { fetchNetworkCapabilities, formatNetworkCapabilities };
83
83
 
84
- //# sourceMappingURL=capabilities-CYv7k4wI.mjs.map
84
+ //# sourceMappingURL=capabilities-BC3Y5EOi.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities-CYv7k4wI.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkRetention {\n mode: 'full_history' | 'rolling_window' | 'expanding_then_rolling' | 'bounded_range' | 'unknown' | string\n window_days?: number\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n started_at?: string\n rolls_after_at?: string\n current_window_seconds?: number\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n retention?: NetworkRetention | null\n}\n\nexport interface NetworkCapability {\n network: string\n display_name?: string\n status: string\n default?: boolean\n layers: Record<string, NetworkLayerCapability>\n tools: Record<string, string>\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n freshness?: {\n last_processed_at?: string\n last_successful_sync_at?: string\n max_data_age_seconds?: number\n last_processing_duration_seconds?: number\n }\n}\n\nexport interface NetworkCapabilitiesDocument {\n schema: 'chain-insights.network-capabilities.v1'\n networks: NetworkCapability[]\n}\n\nfunction metadataNetworksUrl(endpoint: string): URL {\n const url = new URL(endpoint)\n url.pathname = '/metadata/networks'\n url.search = ''\n url.hash = ''\n return url\n}\n\nexport async function fetchNetworkCapabilities(\n config: Pick<InvestigatorConfig, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint' | 'mcpEndpoint'>,\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.trim()\n if (token) {\n headers.set('X-MCP-Debug-Token', token)\n headers.set('Authorization', `Bearer ${token}`)\n }\n let response: Response\n try {\n response = await fetch(request, { headers })\n } catch (err) {\n throw new Error(`network capabilities unavailable at ${request}: ${(err as Error).message}`)\n }\n if (!response.ok) {\n throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`)\n }\n const parsed = await response.json() as NetworkCapabilitiesDocument\n if (parsed.schema !== 'chain-insights.network-capabilities.v1' || !Array.isArray(parsed.networks)) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return parsed\n}\n\nfunction layerValue(network: NetworkCapability, layer: string): string {\n const capability = network.layers[layer]\n if (!capability?.enabled) return 'no'\n return 'yes'\n}\n\nfunction availableToolsLabel(network: NetworkCapability): string {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.length > 0 ? tools.join(', ') : 'none'\n}\n\nfunction shortDate(value?: string): string {\n if (!value) return ''\n return value.slice(0, 10)\n}\n\nfunction datasetLabel(network: NetworkCapability): string {\n const coverage = network.coverage\n if (!coverage) return 'unknown'\n const blockRange = coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange = coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : 'dates unknown'\n if (blockRange === 'blocks unknown' && dateRange === 'dates unknown') return 'unknown'\n return `${blockRange} / ${dateRange}`\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const headers = ['Network', 'Topology', 'Facts', 'Risk', 'Dataset', 'Available tools']\n const widths = [14, 10, 8, 8, 38, 64]\n const row = (values: string[]) => values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [\n row(headers),\n widths.map((width) => '-'.repeat(width)).join(' '),\n ...document.networks.map((network) => row([\n network.display_name || network.network,\n layerValue(network, 'topology'),\n layerValue(network, 'facts'),\n layerValue(network, 'risk'),\n datasetLabel(network),\n availableToolsLabel(network),\n ])),\n ].join('\\n')\n}\n"],"mappings":";;AAgDA,SAAS,oBAAoB,UAAuB;CAClD,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO;AACT;AAEA,eAAsB,yBACpB,QACsC;CAEtC,MAAM,UAAU,oBADC,wBAAwB,MACE,CAAC;CAC5C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,QAAQ,OAAO,mBAAmB,KAAK,KAAK,OAAO,cAAc,KAAK;CAC5E,IAAI,OAAO;EACT,QAAQ,IAAI,qBAAqB,KAAK;EACtC,QAAQ,IAAI,iBAAiB,UAAU,OAAO;CAChD;CACA,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;CAC7C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,IAAK,IAAc,SAAS;CAC7F;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,SAAS,SAAS,QAAQ;CAE3F,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,WAAW,4CAA4C,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAC9F,MAAM,IAAI,MAAM,sDAAsD;CAExE,OAAO;AACT;AAEA,SAAS,WAAW,SAA4B,OAAuB;CAErE,IAAI,CADe,QAAQ,OAAO,QACjB,SAAS,OAAO;CACjC,OAAO;AACT;AAEA,SAAS,oBAAoB,SAAoC;CAC/D,MAAM,QAAQ,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,EAC7C,QAAQ,GAAG,YAAY,WAAW,WAAW,EAC7C,KAAK,CAAC,UAAU,IAAI;CACvB,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,MAAM,GAAG,EAAE;AAC1B;AAEA,SAAS,aAAa,SAAoC;CACxD,MAAM,WAAW,QAAQ;CACzB,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,aAAa,SAAS,eAAe,KAAA,KAAa,SAAS,aAAa,KAAA,IAC1E,GAAG,SAAS,WAAW,IAAI,SAAS,aACpC;CACJ,MAAM,YAAY,SAAS,kBAAkB,SAAS,eAClD,GAAG,UAAU,SAAS,cAAc,EAAE,IAAI,UAAU,SAAS,YAAY,MACzE;CACJ,IAAI,eAAe,oBAAoB,cAAc,iBAAiB,OAAO;CAC7E,OAAO,GAAG,WAAW,KAAK;AAC5B;AAEA,SAAgB,0BAA0B,UAA+C;CACvF,IAAI,SAAS,SAAS,WAAW,GAAG,OAAO;CAC3C,MAAM,UAAU;EAAC;EAAW;EAAY;EAAS;EAAQ;EAAW;CAAiB;CACrF,MAAM,SAAS;EAAC;EAAI;EAAI;EAAG;EAAG;EAAI;CAAE;CACpC,MAAM,OAAO,WAAqB,OAAO,KAAK,OAAO,UAAU,MAAM,OAAO,OAAO,MAAO,CAAC,EAAE,KAAK,IAAI;CACtG,OAAO;EACL,IAAI,OAAO;EACX,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI;EAClD,GAAG,SAAS,SAAS,KAAK,YAAY,IAAI;GACxC,QAAQ,gBAAgB,QAAQ;GAChC,WAAW,SAAS,UAAU;GAC9B,WAAW,SAAS,OAAO;GAC3B,WAAW,SAAS,MAAM;GAC1B,aAAa,OAAO;GACpB,oBAAoB,OAAO;EAC7B,CAAC,CAAC;CACJ,EAAE,KAAK,IAAI;AACb"}
1
+ {"version":3,"file":"capabilities-BC3Y5EOi.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkRetention {\n mode: 'full_history' | 'rolling_window' | 'expanding_then_rolling' | 'bounded_range' | 'unknown' | string\n window_days?: number\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n started_at?: string\n rolls_after_at?: string\n current_window_seconds?: number\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n retention?: NetworkRetention | null\n}\n\nexport interface NetworkCapability {\n network: string\n display_name?: string\n status: string\n default?: boolean\n layers: Record<string, NetworkLayerCapability>\n tools: Record<string, string>\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n freshness?: {\n last_processed_at?: string\n last_successful_sync_at?: string\n max_data_age_seconds?: number\n last_processing_duration_seconds?: number\n }\n}\n\nexport interface NetworkCapabilitiesDocument {\n schema: 'chain-insights.network-capabilities.v1'\n networks: NetworkCapability[]\n}\n\nfunction metadataNetworksUrl(endpoint: string): URL {\n const url = new URL(endpoint)\n url.pathname = '/metadata/networks'\n url.search = ''\n url.hash = ''\n return url\n}\n\nexport async function fetchNetworkCapabilities(\n config: Pick<InvestigatorConfig, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint' | 'mcpEndpoint'>,\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.trim()\n if (token) {\n headers.set('X-MCP-Debug-Token', token)\n headers.set('Authorization', `Bearer ${token}`)\n }\n let response: Response\n try {\n response = await fetch(request, { headers })\n } catch (err) {\n throw new Error(`network capabilities unavailable at ${request}: ${(err as Error).message}`)\n }\n if (!response.ok) {\n throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`)\n }\n const parsed = await response.json() as NetworkCapabilitiesDocument\n if (parsed.schema !== 'chain-insights.network-capabilities.v1' || !Array.isArray(parsed.networks)) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return parsed\n}\n\nfunction layerValue(network: NetworkCapability, layer: string): string {\n const capability = network.layers[layer]\n if (!capability?.enabled) return 'no'\n return 'yes'\n}\n\nfunction availableToolsLabel(network: NetworkCapability): string {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.length > 0 ? tools.join(', ') : 'none'\n}\n\nfunction shortDate(value?: string): string {\n if (!value) return ''\n return value.slice(0, 10)\n}\n\nfunction datasetLabel(network: NetworkCapability): string {\n const coverage = network.coverage\n if (!coverage) return 'unknown'\n const blockRange = coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange = coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : 'dates unknown'\n if (blockRange === 'blocks unknown' && dateRange === 'dates unknown') return 'unknown'\n return `${blockRange} / ${dateRange}`\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const headers = ['Network', 'Topology', 'Facts', 'Risk', 'Dataset', 'Available tools']\n const widths = [14, 10, 8, 8, 38, 64]\n const row = (values: string[]) => values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [\n row(headers),\n widths.map((width) => '-'.repeat(width)).join(' '),\n ...document.networks.map((network) => row([\n network.display_name || network.network,\n layerValue(network, 'topology'),\n layerValue(network, 'facts'),\n layerValue(network, 'risk'),\n datasetLabel(network),\n availableToolsLabel(network),\n ])),\n ].join('\\n')\n}\n"],"mappings":";;AAgDA,SAAS,oBAAoB,UAAuB;CAClD,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO;AACT;AAEA,eAAsB,yBACpB,QACsC;CAEtC,MAAM,UAAU,oBADC,wBAAwB,MACE,CAAC;CAC5C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,QAAQ,OAAO,mBAAmB,KAAK,KAAK,OAAO,cAAc,KAAK;CAC5E,IAAI,OAAO;EACT,QAAQ,IAAI,qBAAqB,KAAK;EACtC,QAAQ,IAAI,iBAAiB,UAAU,OAAO;CAChD;CACA,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;CAC7C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,IAAK,IAAc,SAAS;CAC7F;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,SAAS,SAAS,QAAQ;CAE3F,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,WAAW,4CAA4C,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAC9F,MAAM,IAAI,MAAM,sDAAsD;CAExE,OAAO;AACT;AAEA,SAAS,WAAW,SAA4B,OAAuB;CAErE,IAAI,CADe,QAAQ,OAAO,QACjB,SAAS,OAAO;CACjC,OAAO;AACT;AAEA,SAAS,oBAAoB,SAAoC;CAC/D,MAAM,QAAQ,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,EAC7C,QAAQ,GAAG,YAAY,WAAW,WAAW,EAC7C,KAAK,CAAC,UAAU,IAAI;CACvB,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,MAAM,GAAG,EAAE;AAC1B;AAEA,SAAS,aAAa,SAAoC;CACxD,MAAM,WAAW,QAAQ;CACzB,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,aAAa,SAAS,eAAe,KAAA,KAAa,SAAS,aAAa,KAAA,IAC1E,GAAG,SAAS,WAAW,IAAI,SAAS,aACpC;CACJ,MAAM,YAAY,SAAS,kBAAkB,SAAS,eAClD,GAAG,UAAU,SAAS,cAAc,EAAE,IAAI,UAAU,SAAS,YAAY,MACzE;CACJ,IAAI,eAAe,oBAAoB,cAAc,iBAAiB,OAAO;CAC7E,OAAO,GAAG,WAAW,KAAK;AAC5B;AAEA,SAAgB,0BAA0B,UAA+C;CACvF,IAAI,SAAS,SAAS,WAAW,GAAG,OAAO;CAC3C,MAAM,UAAU;EAAC;EAAW;EAAY;EAAS;EAAQ;EAAW;CAAiB;CACrF,MAAM,SAAS;EAAC;EAAI;EAAI;EAAG;EAAG;EAAI;CAAE;CACpC,MAAM,OAAO,WAAqB,OAAO,KAAK,OAAO,UAAU,MAAM,OAAO,OAAO,MAAO,CAAC,EAAE,KAAK,IAAI;CACtG,OAAO;EACL,IAAI,OAAO;EACX,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI;EAClD,GAAG,SAAS,SAAS,KAAK,YAAY,IAAI;GACxC,QAAQ,gBAAgB,QAAQ;GAChC,WAAW,SAAS,UAAU;GAC9B,WAAW,SAAS,OAAO;GAC3B,WAAW,SAAS,MAAM;GAC1B,aAAa,OAAO;GACpB,oBAAoB,OAAO;EAC7B,CAAC,CAAC;CACJ,EAAE,KAAK,IAAI;AACb"}
@@ -1,4 +1,4 @@
1
- const require_client = require("./client-ByXzwNum.cjs");
1
+ const require_client = require("./client-Db6IV1tv.cjs");
2
2
  //#region src/mcp/capabilities.ts
3
3
  function metadataNetworksUrl(endpoint) {
4
4
  const url = new URL(endpoint);
package/dist/cli.cjs CHANGED
@@ -73,7 +73,7 @@ function optionalScamTopologyActivityPolicy(value) {
73
73
  async function withGraphMcpClient(name, fn) {
74
74
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
75
75
  const config = await loadConfig();
76
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-ByXzwNum.cjs")).then((n) => n.client_exports);
76
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Db6IV1tv.cjs")).then((n) => n.client_exports);
77
77
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
78
78
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
79
79
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
@@ -93,7 +93,7 @@ function printMcpTextContent(result) {
93
93
  }
94
94
  async function printNetworkCapabilities(opts) {
95
95
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
96
- const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-BAZ16MFI.cjs"));
96
+ const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-D5PSx9Hj.cjs"));
97
97
  const document = await fetchNetworkCapabilities(await loadConfig());
98
98
  if (opts.json) console.log(JSON.stringify(document, null, 2));
99
99
  else console.log(formatNetworkCapabilities(document));
@@ -365,7 +365,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
365
365
  const { formatToolsTable } = await Promise.resolve().then(() => require("./format-9NLBykEL.cjs"));
366
366
  const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-iAVQV3t0.cjs")).then((n) => n.tool_visibility_exports);
367
367
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
368
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-ByXzwNum.cjs")).then((n) => n.client_exports);
368
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Db6IV1tv.cjs")).then((n) => n.client_exports);
369
369
  const config = await loadConfig();
370
370
  const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
371
371
  let tools = opts.refresh ? null : await loadSchema(graphMcpEndpoint);
@@ -748,7 +748,7 @@ program.command("playbook").description("Run and manage investigation playbooks"
748
748
  console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
749
749
  process.exit(1);
750
750
  }
751
- const { PlaybookRunner } = await Promise.resolve().then(() => require("./runner-BKHImisp.cjs"));
751
+ const { PlaybookRunner } = await Promise.resolve().then(() => require("./runner-BCDeBYsR.cjs"));
752
752
  await PlaybookRunner.run(definition, {
753
753
  caseId: opts.case,
754
754
  from: fromN,
package/dist/cli.mjs CHANGED
@@ -71,7 +71,7 @@ function optionalScamTopologyActivityPolicy(value) {
71
71
  async function withGraphMcpClient(name, fn) {
72
72
  const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
73
73
  const config = await loadConfig();
74
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7vkBVEIj.mjs").then((n) => n.n);
74
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-D4JE7fFF.mjs").then((n) => n.n);
75
75
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
76
76
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
77
77
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
@@ -91,7 +91,7 @@ function printMcpTextContent(result) {
91
91
  }
92
92
  async function printNetworkCapabilities(opts) {
93
93
  const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
94
- const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-CYv7k4wI.mjs");
94
+ const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-BC3Y5EOi.mjs");
95
95
  const document = await fetchNetworkCapabilities(await loadConfig());
96
96
  if (opts.json) console.log(JSON.stringify(document, null, 2));
97
97
  else console.log(formatNetworkCapabilities(document));
@@ -363,7 +363,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
363
363
  const { formatToolsTable } = await import("./format-Bq94jSyw.mjs");
364
364
  const { visibleRemoteTools } = await import("./tool-visibility-BHRFLXuU.mjs").then((n) => n.n);
365
365
  const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
366
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7vkBVEIj.mjs").then((n) => n.n);
366
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-D4JE7fFF.mjs").then((n) => n.n);
367
367
  const config = await loadConfig();
368
368
  const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
369
369
  let tools = opts.refresh ? null : await loadSchema(graphMcpEndpoint);
@@ -746,7 +746,7 @@ program.command("playbook").description("Run and manage investigation playbooks"
746
746
  console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
747
747
  process.exit(1);
748
748
  }
749
- const { PlaybookRunner } = await import("./runner-B-9g4X1q.mjs");
749
+ const { PlaybookRunner } = await import("./runner-CTFK0Qcg.mjs");
750
750
  await PlaybookRunner.run(definition, {
751
751
  caseId: opts.case,
752
752
  from: fromN,
@@ -143,6 +143,11 @@ async function createConfiguredFetchWithToken(authToken, missingTokenName) {
143
143
  if (!await isWalletConfigured()) throw new Error("Hosted access is not configured. Run `chain-insights access-key set <key>` for invited test access. For wallet-paid access, run `chain-insights wallet import <private-key>` once, then run `chain-insights wallet ready`; run `chain-insights wallet topup` if it says the wallet needs funds.");
144
144
  return createMcpFetchClient(await decryptKey());
145
145
  }
146
+ async function createConfiguredGraphPaidOrFreeFetch() {
147
+ const { isWalletConfigured, decryptKey } = await import("./wallet-BL0fJC29.mjs").then((n) => n.s);
148
+ if (!await isWalletConfigured()) return createPaymentFailureReportingFetch(fetch);
149
+ return createMcpFetchClient(await decryptKey());
150
+ }
146
151
  async function createConfiguredMcpFetch(config) {
147
152
  return createConfiguredFetchWithToken(config.mcpAuthToken, "mcpAuthToken");
148
153
  }
@@ -152,9 +157,9 @@ async function createConfiguredGraphMcpFetch(config) {
152
157
  if (!authToken) throw new Error("Graph MCP debug mode requires graphMcpAuthToken. Run `cia access-key set <key>` or `cia debug on --token <token>`.");
153
158
  return createMcpAuthFetchClient(authToken);
154
159
  }
155
- return createConfiguredFetchWithToken(void 0, "walletPrivateKey");
160
+ return createConfiguredGraphPaidOrFreeFetch();
156
161
  }
157
162
  //#endregion
158
163
  export { resolveGraphMcpEndpoint as a, createMcpFetchClient as i, client_exports as n, createConfiguredMcpFetch as r, PaymentRequiredError as t };
159
164
 
160
- //# sourceMappingURL=client-7vkBVEIj.mjs.map
165
+ //# sourceMappingURL=client-D4JE7fFF.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-7vkBVEIj.mjs","names":[],"sources":["../src/mcp/client.ts"],"sourcesContent":["import { wrapFetchWithPaymentFromConfig } from '@x402/fetch'\nimport { ExactEvmScheme } from '@x402/evm'\nimport { UptoEvmScheme } from '@x402/evm/upto/client'\nimport { privateKeyToAccount } from 'viem/accounts'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { prepareWalletForPaidCalls } from '../wallet/tools.js'\n\ntype FetchLike = typeof fetch\ntype FetchInput = Parameters<FetchLike>[0]\ntype FetchInit = Parameters<FetchLike>[1]\n\nexport class PaymentRequiredError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'PaymentRequiredError'\n }\n}\n\nfunction createHeaderFetch(authToken: string, baseFetch: FetchLike): FetchLike {\n return (async (input: FetchInput, init?: FetchInit) => {\n const requestHeaders = input instanceof Request ? input.headers : undefined\n const headers = new Headers(init?.headers ?? requestHeaders)\n headers.set('X-MCP-Debug-Token', authToken)\n headers.set('Authorization', `Bearer ${authToken}`)\n\n return baseFetch(input, {\n ...init,\n headers,\n })\n }) as FetchLike\n}\n\nexport const PAYMENT_NEXT_STEPS =\n 'Next steps: run `chain-insights wallet ready` to check funding and finish one-time payment setup, ' +\n 'run `chain-insights wallet topup` if it says the wallet needs USDC, ' +\n 'or `chain-insights access-key set <key>` if you have been given test access.'\n\ninterface PaymentRequirementDetails {\n reason: string\n scheme?: string\n network?: string\n amount?: string\n amountUnits?: bigint\n payTo?: string\n}\n\nfunction paymentRequirementFromResponse(response: Response): PaymentRequirementDetails | null {\n const encoded = response.headers.get('payment-required')\n if (!encoded) return null\n\n try {\n const decoded = Buffer.from(encoded, 'base64').toString('utf8')\n const parsed = JSON.parse(decoded) as {\n error?: unknown\n accepts?: Array<{ scheme?: unknown; network?: unknown; amount?: unknown; payTo?: unknown }>\n }\n const reason = typeof parsed.error === 'string' && parsed.error.trim() ? parsed.error.trim() : 'payment_required'\n const firstRequirement = Array.isArray(parsed.accepts) ? parsed.accepts[0] : undefined\n const amount = typeof firstRequirement?.amount === 'string' ? firstRequirement.amount.trim() : undefined\n return {\n reason,\n scheme: typeof firstRequirement?.scheme === 'string' ? firstRequirement.scheme : undefined,\n network: typeof firstRequirement?.network === 'string' ? firstRequirement.network : undefined,\n amount,\n amountUnits: amount && /^\\d+$/.test(amount) ? BigInt(amount) : undefined,\n payTo: typeof firstRequirement?.payTo === 'string' ? firstRequirement.payTo.trim() : undefined,\n }\n } catch {\n return null\n }\n}\n\nfunction describePaymentRequiredResponse(response: Response, payerAddress?: string): string {\n const requirement = paymentRequirementFromResponse(response)\n if (!requirement) return `Payment required — this tool costs USDC on Base via x402 micropayments. ${PAYMENT_NEXT_STEPS}`\n\n try {\n const { reason, payTo } = requirement\n if (payerAddress && payTo && payerAddress.toLowerCase() === payTo.toLowerCase()) {\n return 'Local payment wallet matches the MCP payTo address. Configure a separate payer wallet with USDC on Base; do not use the service recipient wallet as the client payment wallet.'\n }\n const details = [\n requirement.scheme ? `scheme=${requirement.scheme}` : undefined,\n requirement.network ? `network=${requirement.network}` : undefined,\n requirement.amount ? `amount=${requirement.amount}` : undefined,\n ].filter(Boolean).join(' ')\n const message = details ? `x402 payment failed: ${reason} (${details})` : `x402 payment failed: ${reason}`\n if (reason.includes('allowance_required')) {\n return `${message}. The payment wallet needs one-time setup before paid MCP calls can settle. Run \\`chain-insights wallet ready\\`; Base ETH is used for the setup gas.`\n }\n if (reason === 'payment_required') {\n return `${message}. ${PAYMENT_NEXT_STEPS}`\n }\n return `${message}. ${PAYMENT_NEXT_STEPS}`\n } catch {\n return `Payment required — this tool costs USDC on Base via x402 micropayments. ${PAYMENT_NEXT_STEPS}`\n }\n}\n\nfunction createPaymentFailureReportingFetch(\n baseFetch: FetchLike,\n payerAddress?: string,\n paymentWallet?: { address: `0x${string}`; privateKey: `0x${string}` },\n): FetchLike {\n const reportingFetch = (async (input: FetchInput, init?: FetchInit) => {\n const response = await baseFetch(input, init)\n if (response.status !== 402) return response\n const requirement = paymentRequirementFromResponse(response)\n if (paymentWallet && requirement?.reason.includes('allowance_required')) {\n try {\n await prepareWalletForPaidCalls({\n account: paymentWallet,\n ...(requirement.amountUnits === undefined ? {} : { minimumApprovalUnits: requirement.amountUnits }),\n })\n } catch (err) {\n throw new PaymentRequiredError(\n 'Payment setup is not ready yet. Run `chain-insights wallet ready` and try again. ' +\n `${(err as Error).message}`,\n )\n }\n const retryResponse = await baseFetch(input, init)\n if (retryResponse.status !== 402) return retryResponse\n throw new PaymentRequiredError(describePaymentRequiredResponse(retryResponse, payerAddress))\n }\n throw new PaymentRequiredError(describePaymentRequiredResponse(response, payerAddress))\n }) as FetchLike\n return Object.assign(reportingFetch, baseFetch)\n}\n\n/**\n * Creates an x402-payment-wrapped fetch function for the Chain Insights MCP.\n * Payments are made in USDC on Base Mainnet (eip155:8453).\n *\n * The factory is pure — no side effects, no state, no caching.\n * If called with an invalid private key format, viem throws — the error propagates.\n *\n * @param privateKey - 0x-prefixed EVM private key (decrypted from wallet.json)\n * @returns A fetch-compatible function that auto-handles HTTP 402 payment challenges\n */\nexport function createMcpFetchClient(privateKey: `0x${string}`, authToken?: string) {\n const account = privateKeyToAccount(privateKey)\n const paymentFetch = wrapFetchWithPaymentFromConfig(fetch, {\n schemes: [\n {\n network: 'eip155:8453', // Base Mainnet — dynamic MCP pricing uses the x402 upto scheme\n client: new UptoEvmScheme(account),\n },\n {\n network: 'eip155:8453', // Base Mainnet — only supported chain in v1\n client: new ExactEvmScheme(account),\n },\n ],\n })\n const reportingFetch = createPaymentFailureReportingFetch(\n paymentFetch,\n account.address,\n { address: account.address, privateKey },\n )\n return authToken ? createHeaderFetch(authToken, reportingFetch) : reportingFetch\n}\n\n/**\n * Creates a bearer/debug-token fetch for local Graph MCP testing.\n *\n * The public x402 debug bypass expects X-MCP-Debug-Token.\n * Private endpoints commonly expect Authorization: Bearer <token>.\n * Sending both lets one config value work for public debug and private M2M endpoints.\n *\n * Wraps with 402 interception so that if the server still requires payment\n * (e.g. token not accepted for paid tools), the user sees actionable guidance\n * instead of a generic transport error.\n */\nexport function createMcpAuthFetchClient(authToken: string, baseFetch: FetchLike = fetch): FetchLike {\n const headerFetch = createHeaderFetch(authToken, baseFetch)\n return createPaymentFailureReportingFetch(headerFetch)\n}\n\nexport function resolveGraphMcpEndpoint(config: Pick<InvestigatorConfig, 'graphMcpEndpoint' | 'mcpEndpoint'>): string {\n const graphEndpoint = config.graphMcpEndpoint?.trim()\n return graphEndpoint || config.mcpEndpoint\n}\n\nasync function createConfiguredFetchWithToken(\n authToken: string | undefined,\n missingTokenName: string,\n): Promise<FetchLike> {\n const normalizedAuthToken = authToken?.trim()\n if (normalizedAuthToken) return createMcpAuthFetchClient(normalizedAuthToken)\n\n const { isWalletConfigured, decryptKey } = await import('../wallet/index.js')\n if (!(await isWalletConfigured())) {\n throw new Error(\n 'Hosted access is not configured. ' +\n 'Run `chain-insights access-key set <key>` for invited test access. ' +\n 'For wallet-paid access, run `chain-insights wallet import <private-key>` once, then run `chain-insights wallet ready`; ' +\n 'run `chain-insights wallet topup` if it says the wallet needs funds.',\n )\n }\n\n const privateKey = await decryptKey()\n return createMcpFetchClient(privateKey as `0x${string}`)\n}\n\nexport async function createConfiguredMcpFetch(config: Pick<InvestigatorConfig, 'mcpAuthToken'>): Promise<FetchLike> {\n return createConfiguredFetchWithToken(config.mcpAuthToken, 'mcpAuthToken')\n}\n\nexport async function createConfiguredGraphMcpFetch(\n config: Pick<InvestigatorConfig, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode'>,\n): Promise<FetchLike> {\n if (config.graphMcpMode === 'debug') {\n const authToken = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.trim()\n if (!authToken) {\n throw new Error('Graph MCP debug mode requires graphMcpAuthToken. Run `cia access-key set <key>` or `cia debug on --token <token>`.')\n }\n return createMcpAuthFetchClient(authToken)\n }\n\n return createConfiguredFetchWithToken(undefined, 'walletPrivateKey')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-D4JE7fFF.mjs","names":[],"sources":["../src/mcp/client.ts"],"sourcesContent":["import { wrapFetchWithPaymentFromConfig } from '@x402/fetch'\nimport { ExactEvmScheme } from '@x402/evm'\nimport { UptoEvmScheme } from '@x402/evm/upto/client'\nimport { privateKeyToAccount } from 'viem/accounts'\nimport type { InvestigatorConfig } from '../config/schema.js'\nimport { prepareWalletForPaidCalls } from '../wallet/tools.js'\n\ntype FetchLike = typeof fetch\ntype FetchInput = Parameters<FetchLike>[0]\ntype FetchInit = Parameters<FetchLike>[1]\n\nexport class PaymentRequiredError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'PaymentRequiredError'\n }\n}\n\nfunction createHeaderFetch(authToken: string, baseFetch: FetchLike): FetchLike {\n return (async (input: FetchInput, init?: FetchInit) => {\n const requestHeaders = input instanceof Request ? input.headers : undefined\n const headers = new Headers(init?.headers ?? requestHeaders)\n headers.set('X-MCP-Debug-Token', authToken)\n headers.set('Authorization', `Bearer ${authToken}`)\n\n return baseFetch(input, {\n ...init,\n headers,\n })\n }) as FetchLike\n}\n\nexport const PAYMENT_NEXT_STEPS =\n 'Next steps: run `chain-insights wallet ready` to check funding and finish one-time payment setup, ' +\n 'run `chain-insights wallet topup` if it says the wallet needs USDC, ' +\n 'or `chain-insights access-key set <key>` if you have been given test access.'\n\ninterface PaymentRequirementDetails {\n reason: string\n scheme?: string\n network?: string\n amount?: string\n amountUnits?: bigint\n payTo?: string\n}\n\nfunction paymentRequirementFromResponse(response: Response): PaymentRequirementDetails | null {\n const encoded = response.headers.get('payment-required')\n if (!encoded) return null\n\n try {\n const decoded = Buffer.from(encoded, 'base64').toString('utf8')\n const parsed = JSON.parse(decoded) as {\n error?: unknown\n accepts?: Array<{ scheme?: unknown; network?: unknown; amount?: unknown; payTo?: unknown }>\n }\n const reason = typeof parsed.error === 'string' && parsed.error.trim() ? parsed.error.trim() : 'payment_required'\n const firstRequirement = Array.isArray(parsed.accepts) ? parsed.accepts[0] : undefined\n const amount = typeof firstRequirement?.amount === 'string' ? firstRequirement.amount.trim() : undefined\n return {\n reason,\n scheme: typeof firstRequirement?.scheme === 'string' ? firstRequirement.scheme : undefined,\n network: typeof firstRequirement?.network === 'string' ? firstRequirement.network : undefined,\n amount,\n amountUnits: amount && /^\\d+$/.test(amount) ? BigInt(amount) : undefined,\n payTo: typeof firstRequirement?.payTo === 'string' ? firstRequirement.payTo.trim() : undefined,\n }\n } catch {\n return null\n }\n}\n\nfunction describePaymentRequiredResponse(response: Response, payerAddress?: string): string {\n const requirement = paymentRequirementFromResponse(response)\n if (!requirement) return `Payment required — this tool costs USDC on Base via x402 micropayments. ${PAYMENT_NEXT_STEPS}`\n\n try {\n const { reason, payTo } = requirement\n if (payerAddress && payTo && payerAddress.toLowerCase() === payTo.toLowerCase()) {\n return 'Local payment wallet matches the MCP payTo address. Configure a separate payer wallet with USDC on Base; do not use the service recipient wallet as the client payment wallet.'\n }\n const details = [\n requirement.scheme ? `scheme=${requirement.scheme}` : undefined,\n requirement.network ? `network=${requirement.network}` : undefined,\n requirement.amount ? `amount=${requirement.amount}` : undefined,\n ].filter(Boolean).join(' ')\n const message = details ? `x402 payment failed: ${reason} (${details})` : `x402 payment failed: ${reason}`\n if (reason.includes('allowance_required')) {\n return `${message}. The payment wallet needs one-time setup before paid MCP calls can settle. Run \\`chain-insights wallet ready\\`; Base ETH is used for the setup gas.`\n }\n if (reason === 'payment_required') {\n return `${message}. ${PAYMENT_NEXT_STEPS}`\n }\n return `${message}. ${PAYMENT_NEXT_STEPS}`\n } catch {\n return `Payment required — this tool costs USDC on Base via x402 micropayments. ${PAYMENT_NEXT_STEPS}`\n }\n}\n\nfunction createPaymentFailureReportingFetch(\n baseFetch: FetchLike,\n payerAddress?: string,\n paymentWallet?: { address: `0x${string}`; privateKey: `0x${string}` },\n): FetchLike {\n const reportingFetch = (async (input: FetchInput, init?: FetchInit) => {\n const response = await baseFetch(input, init)\n if (response.status !== 402) return response\n const requirement = paymentRequirementFromResponse(response)\n if (paymentWallet && requirement?.reason.includes('allowance_required')) {\n try {\n await prepareWalletForPaidCalls({\n account: paymentWallet,\n ...(requirement.amountUnits === undefined ? {} : { minimumApprovalUnits: requirement.amountUnits }),\n })\n } catch (err) {\n throw new PaymentRequiredError(\n 'Payment setup is not ready yet. Run `chain-insights wallet ready` and try again. ' +\n `${(err as Error).message}`,\n )\n }\n const retryResponse = await baseFetch(input, init)\n if (retryResponse.status !== 402) return retryResponse\n throw new PaymentRequiredError(describePaymentRequiredResponse(retryResponse, payerAddress))\n }\n throw new PaymentRequiredError(describePaymentRequiredResponse(response, payerAddress))\n }) as FetchLike\n return Object.assign(reportingFetch, baseFetch)\n}\n\n/**\n * Creates an x402-payment-wrapped fetch function for the Chain Insights MCP.\n * Payments are made in USDC on Base Mainnet (eip155:8453).\n *\n * The factory is pure — no side effects, no state, no caching.\n * If called with an invalid private key format, viem throws — the error propagates.\n *\n * @param privateKey - 0x-prefixed EVM private key (decrypted from wallet.json)\n * @returns A fetch-compatible function that auto-handles HTTP 402 payment challenges\n */\nexport function createMcpFetchClient(privateKey: `0x${string}`, authToken?: string) {\n const account = privateKeyToAccount(privateKey)\n const paymentFetch = wrapFetchWithPaymentFromConfig(fetch, {\n schemes: [\n {\n network: 'eip155:8453', // Base Mainnet — dynamic MCP pricing uses the x402 upto scheme\n client: new UptoEvmScheme(account),\n },\n {\n network: 'eip155:8453', // Base Mainnet — only supported chain in v1\n client: new ExactEvmScheme(account),\n },\n ],\n })\n const reportingFetch = createPaymentFailureReportingFetch(\n paymentFetch,\n account.address,\n { address: account.address, privateKey },\n )\n return authToken ? createHeaderFetch(authToken, reportingFetch) : reportingFetch\n}\n\n/**\n * Creates a bearer/debug-token fetch for local Graph MCP testing.\n *\n * The public x402 debug bypass expects X-MCP-Debug-Token.\n * Private endpoints commonly expect Authorization: Bearer <token>.\n * Sending both lets one config value work for public debug and private M2M endpoints.\n *\n * Wraps with 402 interception so that if the server still requires payment\n * (e.g. token not accepted for paid tools), the user sees actionable guidance\n * instead of a generic transport error.\n */\nexport function createMcpAuthFetchClient(authToken: string, baseFetch: FetchLike = fetch): FetchLike {\n const headerFetch = createHeaderFetch(authToken, baseFetch)\n return createPaymentFailureReportingFetch(headerFetch)\n}\n\nexport function resolveGraphMcpEndpoint(config: Pick<InvestigatorConfig, 'graphMcpEndpoint' | 'mcpEndpoint'>): string {\n const graphEndpoint = config.graphMcpEndpoint?.trim()\n return graphEndpoint || config.mcpEndpoint\n}\n\nasync function createConfiguredFetchWithToken(\n authToken: string | undefined,\n missingTokenName: string,\n): Promise<FetchLike> {\n const normalizedAuthToken = authToken?.trim()\n if (normalizedAuthToken) return createMcpAuthFetchClient(normalizedAuthToken)\n\n const { isWalletConfigured, decryptKey } = await import('../wallet/index.js')\n if (!(await isWalletConfigured())) {\n throw new Error(\n 'Hosted access is not configured. ' +\n 'Run `chain-insights access-key set <key>` for invited test access. ' +\n 'For wallet-paid access, run `chain-insights wallet import <private-key>` once, then run `chain-insights wallet ready`; ' +\n 'run `chain-insights wallet topup` if it says the wallet needs funds.',\n )\n }\n\n const privateKey = await decryptKey()\n return createMcpFetchClient(privateKey as `0x${string}`)\n}\n\nasync function createConfiguredGraphPaidOrFreeFetch(): Promise<FetchLike> {\n const { isWalletConfigured, decryptKey } = await import('../wallet/index.js')\n if (!(await isWalletConfigured())) {\n return createPaymentFailureReportingFetch(fetch)\n }\n\n const privateKey = await decryptKey()\n return createMcpFetchClient(privateKey as `0x${string}`)\n}\n\nexport async function createConfiguredMcpFetch(config: Pick<InvestigatorConfig, 'mcpAuthToken'>): Promise<FetchLike> {\n return createConfiguredFetchWithToken(config.mcpAuthToken, 'mcpAuthToken')\n}\n\nexport async function createConfiguredGraphMcpFetch(\n config: Pick<InvestigatorConfig, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode'>,\n): Promise<FetchLike> {\n if (config.graphMcpMode === 'debug') {\n const authToken = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.trim()\n if (!authToken) {\n throw new Error('Graph MCP debug mode requires graphMcpAuthToken. Run `cia access-key set <key>` or `cia debug on --token <token>`.')\n }\n return createMcpAuthFetchClient(authToken)\n }\n\n return createConfiguredGraphPaidOrFreeFetch()\n}\n"],"mappings":"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"}
@@ -143,6 +143,11 @@ async function createConfiguredFetchWithToken(authToken, missingTokenName) {
143
143
  if (!await isWalletConfigured()) throw new Error("Hosted access is not configured. Run `chain-insights access-key set <key>` for invited test access. For wallet-paid access, run `chain-insights wallet import <private-key>` once, then run `chain-insights wallet ready`; run `chain-insights wallet topup` if it says the wallet needs funds.");
144
144
  return createMcpFetchClient(await decryptKey());
145
145
  }
146
+ async function createConfiguredGraphPaidOrFreeFetch() {
147
+ const { isWalletConfigured, decryptKey } = await Promise.resolve().then(() => require("./wallet-gC2jxh7j.cjs")).then((n) => n.wallet_exports);
148
+ if (!await isWalletConfigured()) return createPaymentFailureReportingFetch(fetch);
149
+ return createMcpFetchClient(await decryptKey());
150
+ }
146
151
  async function createConfiguredMcpFetch(config) {
147
152
  return createConfiguredFetchWithToken(config.mcpAuthToken, "mcpAuthToken");
148
153
  }
@@ -152,7 +157,7 @@ async function createConfiguredGraphMcpFetch(config) {
152
157
  if (!authToken) throw new Error("Graph MCP debug mode requires graphMcpAuthToken. Run `cia access-key set <key>` or `cia debug on --token <token>`.");
153
158
  return createMcpAuthFetchClient(authToken);
154
159
  }
155
- return createConfiguredFetchWithToken(void 0, "walletPrivateKey");
160
+ return createConfiguredGraphPaidOrFreeFetch();
156
161
  }
157
162
  //#endregion
158
163
  Object.defineProperty(exports, "PaymentRequiredError", {
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@ const require_server = require("./server-BqVdWath.cjs");
5
5
  const require_wallet = require("./wallet-gC2jxh7j.cjs");
6
6
  const require_tools = require("./tools-BhTI3Lmg.cjs");
7
7
  const require_topup_server = require("./topup-server-DhYlOOBM.cjs");
8
- const require_client = require("./client-ByXzwNum.cjs");
8
+ const require_client = require("./client-Db6IV1tv.cjs");
9
9
  const require_viz = require("./viz-Da9YWN_I.cjs");
10
10
  exports.buildTopupInfo = require_tools.buildTopupInfo;
11
11
  exports.createApp = require_app.createApp;
package/dist/index.mjs CHANGED
@@ -4,6 +4,6 @@ import { n as startServer } from "./server-BXLX2j_A.mjs";
4
4
  import { a as setWalletPrivateKey, i as normalizeWalletPrivateKey, n as encryptKey, o as walletAddressFromPrivateKey, r as isWalletConfigured, t as decryptKey } from "./wallet-BL0fJC29.mjs";
5
5
  import { a as getWalletAccount, i as getBalanceUsdc, n as formatWalletBalance, o as getWalletBalanceText, r as getBalanceEth, t as buildTopupInfo } from "./tools-v6kcdojg.mjs";
6
6
  import { i as generateArtifactHtml, n as startTopupServer, t as getTopupUrl } from "./topup-server-R3dNp-p8.mjs";
7
- import { i as createMcpFetchClient } from "./client-7vkBVEIj.mjs";
7
+ import { i as createMcpFetchClient } from "./client-D4JE7fFF.mjs";
8
8
  import { t as generateVisualization } from "./viz-DkJyqlUu.mjs";
9
9
  export { buildTopupInfo, createApp, createMcpFetchClient, decryptKey, encryptKey, formatWalletBalance, generateArtifactHtml, generateVisualization, getBalanceEth, getBalanceUsdc, getTopupUrl, getWalletAccount, getWalletBalanceText, isWalletConfigured, loadConfig, normalizeWalletPrivateKey, resetConfigCache, saveConfig, setWalletPrivateKey, startServer, startTopupServer, walletAddressFromPrivateKey };
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-DakpK96I.cjs");
3
3
  const require_version = require("./version-CO9Or_YV.cjs");
4
- const require_client = require("./client-ByXzwNum.cjs");
4
+ const require_client = require("./client-Db6IV1tv.cjs");
5
5
  const require_tool_visibility = require("./tool-visibility-iAVQV3t0.cjs");
6
6
  let node_url = require("node:url");
7
7
  let node_path = require("node:path");
@@ -589,7 +589,7 @@ async function normalizeRemoteToolResult(result, config, toolName = "remote-grap
589
589
  async function createProxy() {
590
590
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
591
591
  const { activeDataDir, findActiveWorkspace } = await Promise.resolve().then(() => require("./active-BVr55kvW.cjs")).then((n) => n.active_exports);
592
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-ByXzwNum.cjs")).then((n) => n.client_exports);
592
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Db6IV1tv.cjs")).then((n) => n.client_exports);
593
593
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-CJk1EL3L.cjs"));
594
594
  const loadedConfig = await loadConfig();
595
595
  const activeWorkspace = findActiveWorkspace();
@@ -1,5 +1,5 @@
1
1
  import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
2
- import { t as PaymentRequiredError } from "./client-7vkBVEIj.mjs";
2
+ import { t as PaymentRequiredError } from "./client-D4JE7fFF.mjs";
3
3
  import { t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-BHRFLXuU.mjs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import path from "node:path";
@@ -585,7 +585,7 @@ async function normalizeRemoteToolResult(result, config, toolName = "remote-grap
585
585
  async function createProxy() {
586
586
  const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
587
587
  const { activeDataDir, findActiveWorkspace } = await import("./active-ByNgjuAg.mjs").then((n) => n.n);
588
- const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7vkBVEIj.mjs").then((n) => n.n);
588
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-D4JE7fFF.mjs").then((n) => n.n);
589
589
  const { loadSchema, saveSchema } = await import("./schema-cache-DwDvPy4e.mjs");
590
590
  const loadedConfig = await loadConfig();
591
591
  const activeWorkspace = findActiveWorkspace();
@@ -1,6 +1,6 @@
1
1
  const require_version = require("./version-CO9Or_YV.cjs");
2
2
  const require_config = require("./config-BwVx19Og.cjs");
3
- const require_client = require("./client-ByXzwNum.cjs");
3
+ const require_client = require("./client-Db6IV1tv.cjs");
4
4
  const require_viz = require("./viz-Da9YWN_I.cjs");
5
5
  const require_store = require("./store-DogLawSj.cjs");
6
6
  const require_evidence = require("./evidence-CvEesemA.cjs");
@@ -1,6 +1,6 @@
1
1
  import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
2
2
  import { n as loadConfig } from "./config-Drgc2HuF.mjs";
3
- import { r as createConfiguredMcpFetch } from "./client-7vkBVEIj.mjs";
3
+ import { r as createConfiguredMcpFetch } from "./client-D4JE7fFF.mjs";
4
4
  import { t as generateVisualization } from "./viz-DkJyqlUu.mjs";
5
5
  import { CaseStore } from "./store-BT2SCcQr.mjs";
6
6
  import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
@@ -146,4 +146,4 @@ async run(playbook, opts) {
146
146
  //#endregion
147
147
  export { PlaybookRunner };
148
148
 
149
- //# sourceMappingURL=runner-B-9g4X1q.mjs.map
149
+ //# sourceMappingURL=runner-CTFK0Qcg.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner-B-9g4X1q.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights wallet import <private-key>\\`, then run \\`chain-insights wallet ready\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runner-CTFK0Qcg.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights wallet import <private-key>\\`, then run \\`chain-insights wallet ready\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ The GraphRAG MCP public graph surface is intentionally small:
9
9
 
10
10
  | Tool | Purpose |
11
11
  | --- | --- |
12
+ | `usage_status` | Return the caller's public free graph_query quota for the current UTC day |
12
13
  | `graph_query` | Run one read-only GQL/Cypher query through the universal graph endpoint |
13
14
  | `graph_query_batch` | Run related read-only graph-language queries as one MCP call |
14
15
 
@@ -24,10 +25,24 @@ assumed to exist on the GraphRAG MCP endpoint.
24
25
  - Use `USE live_topology` for recent topology.
25
26
  - Use `USE archive_topology` for historical topology.
26
27
  - Use `USE facts` for labels, features, risk scores, assets, and enrichment.
28
+ - Use `usage_status` before public hosted reads when you need the caller's
29
+ remaining free quota.
30
+ - Hosted endpoints can expose a public free graph_query quota. The default is
31
+ 10 execution seconds per IP per UTC day.
32
+ - Use explicit LIMIT and pagination in your query when you want bounded result
33
+ sets.
34
+ - The GraphRAG MCP server does not append `LIMIT`; Chain Insights recipes own
35
+ their own limits and pagination.
27
36
  - Use `graph_query_batch` for related reads that should share one paid call.
28
- - `per_query_timeout_seconds` is optional and capped at `600`.
37
+ - `per_query_timeout_seconds` is optional and capped at `10` by default.
29
38
  - Returned rows live in `structuredContent.facts`.
30
39
 
40
+ Check public-free usage:
41
+
42
+ ```bash
43
+ chain-insights mcp call usage_status
44
+ ```
45
+
31
46
  Example single query:
32
47
 
33
48
  ```bash
@@ -52,7 +67,7 @@ Batch result facts include:
52
67
  "count": 2,
53
68
  "completed": 2,
54
69
  "failed": 0,
55
- "per_query_timeout_seconds": 600,
70
+ "per_query_timeout_seconds": 10,
56
71
  "total_query_elapsed_ms": 1345,
57
72
  "billable_seconds": 2,
58
73
  "estimated_usdc": "0.02"
package/docs/mcp-proxy.md CHANGED
@@ -93,7 +93,8 @@ The proxy:
93
93
  | `case_end_session` | End a session with findings and next steps |
94
94
 
95
95
  Remote graph tools are discovered from the configured GraphRAG MCP endpoint. The
96
- expected primitive graph tools are `graph_query` and `graph_query_batch`.
96
+ expected primitive graph tools are `usage_status`, `graph_query`, and
97
+ `graph_query_batch`.
97
98
  Chain Insights adds high-level local graph recipes such as `address_risk`,
98
99
  `stake_insights`, `track_funds`, and `scam_topology` when the remote endpoint
99
100
  only exposes primitives.
@@ -114,6 +115,23 @@ chain-insights access-key set ci_test_REDACTED --endpoint https://staging-mcp.ch
114
115
  chain-insights access-key status
115
116
  ```
116
117
 
118
+ Public free graph usage:
119
+
120
+ ```bash
121
+ chain-insights mcp call usage_status
122
+ chain-insights mcp call graph_query \
123
+ network=bittensor \
124
+ "query=USE live_topology MATCH (n) RETURN count(n) AS count LIMIT 1"
125
+ ```
126
+
127
+ Hosted GraphRAG MCP can allow anonymous `graph_query` calls before wallet
128
+ setup. The default public free graph_query quota is 10 execution seconds per IP
129
+ per UTC day, reset on the UTC calendar day. `usage_status` returns only the
130
+ current caller's quota status. Public free access does not include
131
+ `graph_query_batch`; use a tester access key or paid x402 mode for regular
132
+ usage and batches. Use explicit LIMIT and pagination in your query when you
133
+ want bounded result sets.
134
+
117
135
  Paid x402 mode:
118
136
 
119
137
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk, fund-flow tracing, case reports, and GraphRAG MCP access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {