chain-insights 0.2.28 → 0.2.30
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 +11 -2
- package/dist/{capabilities-CYv7k4wI.mjs → capabilities-BC3Y5EOi.mjs} +2 -2
- package/dist/{capabilities-CYv7k4wI.mjs.map → capabilities-BC3Y5EOi.mjs.map} +1 -1
- package/dist/{capabilities-BAZ16MFI.cjs → capabilities-D5PSx9Hj.cjs} +1 -1
- package/dist/cli.cjs +4 -4
- package/dist/cli.mjs +4 -4
- package/dist/{client-7vkBVEIj.mjs → client-D4JE7fFF.mjs} +7 -2
- package/dist/{client-7vkBVEIj.mjs.map → client-D4JE7fFF.mjs.map} +1 -1
- package/dist/{client-ByXzwNum.cjs → client-Db6IV1tv.cjs} +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/mcp-proxy.cjs +2 -2
- package/dist/mcp-proxy.mjs +2 -2
- package/dist/{runner-BKHImisp.cjs → runner-BCDeBYsR.cjs} +1 -1
- package/dist/{runner-B-9g4X1q.mjs → runner-CTFK0Qcg.mjs} +2 -2
- package/dist/{runner-B-9g4X1q.mjs.map → runner-CTFK0Qcg.mjs.map} +1 -1
- package/docs/graph-tools.md +17 -2
- package/docs/mcp-proxy.md +19 -1
- package/package.json +1 -1
- package/skills/chain-insights-investigation/SKILL.md +4 -0
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.
|
|
186
|
-
|
|
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-
|
|
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-
|
|
84
|
+
//# sourceMappingURL=capabilities-BC3Y5EOi.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities-
|
|
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"}
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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":";;;;;;;;;;;;;;;;AAWA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAEA,SAAS,kBAAkB,WAAmB,WAAiC;CAC7E,QAAQ,OAAO,OAAmB,SAAqB;EACrD,MAAM,iBAAiB,iBAAiB,UAAU,MAAM,UAAU,KAAA;EAClE,MAAM,UAAU,IAAI,QAAQ,MAAM,WAAW,cAAc;EAC3D,QAAQ,IAAI,qBAAqB,SAAS;EAC1C,QAAQ,IAAI,iBAAiB,UAAU,WAAW;EAElD,OAAO,UAAU,OAAO;GACtB,GAAG;GACH;EACF,CAAC;CACH;AACF;AAEA,MAAa,qBACX;AAaF,SAAS,+BAA+B,UAAsD;CAC5F,MAAM,UAAU,SAAS,QAAQ,IAAI,kBAAkB;CACvD,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI;EACF,MAAM,UAAU,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,MAAM;EAC9D,MAAM,SAAS,KAAK,MAAM,OAAO;EAIjC,MAAM,SAAS,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,IAAI,OAAO,MAAM,KAAK,IAAI;EAC/F,MAAM,mBAAmB,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,QAAQ,KAAK,KAAA;EAC7E,MAAM,SAAS,OAAO,kBAAkB,WAAW,WAAW,iBAAiB,OAAO,KAAK,IAAI,KAAA;EAC/F,OAAO;GACL;GACA,QAAQ,OAAO,kBAAkB,WAAW,WAAW,iBAAiB,SAAS,KAAA;GACjF,SAAS,OAAO,kBAAkB,YAAY,WAAW,iBAAiB,UAAU,KAAA;GACpF;GACA,aAAa,UAAU,QAAQ,KAAK,MAAM,IAAI,OAAO,MAAM,IAAI,KAAA;GAC/D,OAAO,OAAO,kBAAkB,UAAU,WAAW,iBAAiB,MAAM,KAAK,IAAI,KAAA;EACvF;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,gCAAgC,UAAoB,cAA+B;CAC1F,MAAM,cAAc,+BAA+B,QAAQ;CAC3D,IAAI,CAAC,aAAa,OAAO,2EAA2E;CAEpG,IAAI;EACF,MAAM,EAAE,QAAQ,UAAU;EAC1B,IAAI,gBAAgB,SAAS,aAAa,YAAY,MAAM,MAAM,YAAY,GAC5E,OAAO;EAET,MAAM,UAAU;GACd,YAAY,SAAS,UAAU,YAAY,WAAW,KAAA;GACtD,YAAY,UAAU,WAAW,YAAY,YAAY,KAAA;GACzD,YAAY,SAAS,UAAU,YAAY,WAAW,KAAA;EACxD,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;EAC1B,MAAM,UAAU,UAAU,wBAAwB,OAAO,IAAI,QAAQ,KAAK,wBAAwB;EAClG,IAAI,OAAO,SAAS,oBAAoB,GACtC,OAAO,GAAG,QAAQ;EAEpB,IAAI,WAAW,oBACb,OAAO,GAAG,QAAQ,IAAI;EAExB,OAAO,GAAG,QAAQ,IAAI;CACxB,QAAQ;EACN,OAAO,2EAA2E;CACpF;AACF;AAEA,SAAS,mCACP,WACA,cACA,eACW;CACX,MAAM,kBAAkB,OAAO,OAAmB,SAAqB;EACrE,MAAM,WAAW,MAAM,UAAU,OAAO,IAAI;EAC5C,IAAI,SAAS,WAAW,KAAK,OAAO;EACpC,MAAM,cAAc,+BAA+B,QAAQ;EAC3D,IAAI,iBAAiB,aAAa,OAAO,SAAS,oBAAoB,GAAG;GACvE,IAAI;IACF,MAAM,0BAA0B;KAC9B,SAAS;KACT,GAAI,YAAY,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,sBAAsB,YAAY,YAAY;IACnG,CAAC;GACH,SAAS,KAAK;IACZ,MAAM,IAAI,qBACR,sFACI,IAAc,SACpB;GACF;GACA,MAAM,gBAAgB,MAAM,UAAU,OAAO,IAAI;GACjD,IAAI,cAAc,WAAW,KAAK,OAAO;GACzC,MAAM,IAAI,qBAAqB,gCAAgC,eAAe,YAAY,CAAC;EAC7F;EACA,MAAM,IAAI,qBAAqB,gCAAgC,UAAU,YAAY,CAAC;CACxF;CACA,OAAO,OAAO,OAAO,gBAAgB,SAAS;AAChD;;;;;;;;;;;AAYA,SAAgB,qBAAqB,YAA2B,WAAoB;CAClF,MAAM,UAAU,oBAAoB,UAAU;CAa9C,MAAM,iBAAiB,mCAZF,+BAA+B,OAAO,EACzD,SAAS,CACP;EACE,SAAS;EACT,QAAQ,IAAI,cAAc,OAAO;CACnC,GACA;EACE,SAAS;EACT,QAAQ,IAAI,eAAe,OAAO;CACpC,CACF,EACF,CAEa,GACX,QAAQ,SACR;EAAE,SAAS,QAAQ;EAAS;CAAW,CACzC;CACA,OAAO,YAAY,kBAAkB,WAAW,cAAc,IAAI;AACpE;;;;;;;;;;;;AAaA,SAAgB,yBAAyB,WAAmB,YAAuB,OAAkB;CAEnG,OAAO,mCADa,kBAAkB,WAAW,SACG,CAAC;AACvD;AAEA,SAAgB,wBAAwB,QAA8E;CAEpH,OADsB,OAAO,kBAAkB,KAAK,KAC5B,OAAO;AACjC;AAEA,eAAe,+BACb,WACA,kBACoB;CACpB,MAAM,sBAAsB,WAAW,KAAK;CAC5C,IAAI,qBAAqB,OAAO,yBAAyB,mBAAmB;CAE5E,MAAM,EAAE,oBAAoB,eAAe,MAAM,OAAO,yBAAA,MAAA,MAAA,EAAA,CAAA;CACxD,IAAI,CAAE,MAAM,mBAAmB,GAC7B,MAAM,IAAI,MACR,iSAIF;CAIF,OAAO,qBAAqB,MADH,WAAW,CACmB;AACzD;AAEA,eAAsB,yBAAyB,QAAsE;CACnH,OAAO,+BAA+B,OAAO,cAAc,cAAc;AAC3E;AAEA,eAAsB,8BACpB,QACoB;CACpB,IAAI,OAAO,iBAAiB,SAAS;EACnC,MAAM,YAAY,OAAO,mBAAmB,KAAK,KAAK,OAAO,cAAc,KAAK;EAChF,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,oHAAoH;EAEtI,OAAO,yBAAyB,SAAS;CAC3C;CAEA,OAAO,+BAA+B,KAAA,GAAW,kBAAkB;AACrE"}
|
|
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
|
|
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-
|
|
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-
|
|
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 };
|
package/dist/mcp-proxy.cjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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();
|
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
|
|
2
|
-
import { t as PaymentRequiredError } from "./client-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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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"}
|
package/docs/graph-tools.md
CHANGED
|
@@ -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 `
|
|
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":
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.2.30",
|
|
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": {
|
|
@@ -84,6 +84,10 @@ cia debug off
|
|
|
84
84
|
## Hard Rules
|
|
85
85
|
|
|
86
86
|
- Always preserve full blockchain addresses exactly.
|
|
87
|
+
- Bittensor contains both native Substrate/SS58 addresses such as `5...` and
|
|
88
|
+
EVM-pallet `0x...` addresses in the same investigation network. Use
|
|
89
|
+
`network=bittensor` for both; do not switch networks based only on address
|
|
90
|
+
format. Preserve the exact address and any returned `address_type` evidence.
|
|
87
91
|
- Python GraphRAG MCP is the golden behavior for `address_risk` and
|
|
88
92
|
`track_funds`. Chain Insights MCP may expose its own high-level tools, but
|
|
89
93
|
their graph semantics must be a faithful port of the Python tools.
|