chain-insights 0.68.2 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.cjs +30 -4
  3. package/dist/cli.mjs +30 -4
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/dex-registry-05wAhSa0.mjs +383 -0
  6. package/dist/dex-registry-05wAhSa0.mjs.map +1 -0
  7. package/dist/dex-registry-KOTJhAQf.cjs +411 -0
  8. package/dist/dex-tool-DIKN4dXB.cjs +536 -0
  9. package/dist/dex-tool-DpQKS7Te.mjs +497 -0
  10. package/dist/dex-tool-DpQKS7Te.mjs.map +1 -0
  11. package/dist/{format-DB2IH2U5.mjs → format-CSesEBvS.mjs} +4 -4
  12. package/dist/format-CSesEBvS.mjs.map +1 -0
  13. package/dist/{format-BjZuYDr8.cjs → format-DfhRQq4L.cjs} +3 -3
  14. package/dist/mcp-proxy.cjs +53 -59
  15. package/dist/mcp-proxy.d.cts +2 -0
  16. package/dist/mcp-proxy.d.cts.map +1 -1
  17. package/dist/mcp-proxy.d.mts +2 -0
  18. package/dist/mcp-proxy.d.mts.map +1 -1
  19. package/dist/mcp-proxy.mjs +51 -59
  20. package/dist/mcp-proxy.mjs.map +1 -1
  21. package/dist/{tool-visibility-B4RAFMaB.cjs → tool-visibility-BopFnGsT.cjs} +5 -0
  22. package/dist/{tool-visibility-kclgGKL5.mjs → tool-visibility-Dq-Ruj-8.mjs} +6 -1
  23. package/dist/tool-visibility-Dq-Ruj-8.mjs.map +1 -0
  24. package/docs/mcp-proxy.md +71 -0
  25. package/docs/stability.md +3 -2
  26. package/package.json +3 -2
  27. package/registry/README.md +133 -0
  28. package/registry/robinhood.json +769 -0
  29. package/registry/robinhood.md +102 -0
  30. package/skills/chain-insights-dex/SKILL.md +104 -96
  31. package/skills/chain-insights-dex/agents/openai.yaml +1 -1
  32. package/skills/chain-insights-dex/reference.md +8 -27
  33. package/skills/chain-insights-dex-gauge/SKILL.md +6 -4
  34. package/skills/chain-insights-dex-launchpad/SKILL.md +6 -3
  35. package/skills/chain-insights-dex-vault/SKILL.md +4 -2
  36. package/skills/chain-insights-virtuals/SKILL.md +8 -4
  37. package/dist/format-DB2IH2U5.mjs.map +0 -1
  38. package/dist/tool-visibility-kclgGKL5.mjs.map +0 -1
@@ -2,12 +2,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
3
3
  const require_print_result = require("./print-result-qVhhuSdo.cjs");
4
4
  const require_client = require("./client-Ypw9BNbn.cjs");
5
- const require_tool_visibility = require("./tool-visibility-B4RAFMaB.cjs");
5
+ const require_tool_visibility = require("./tool-visibility-BopFnGsT.cjs");
6
6
  const require_usage_status = require("./usage-status-DonwoLR3.cjs");
7
7
  const require_subscription_status = require("./subscription-status-ru99UCPZ.cjs");
8
8
  const require_capabilities = require("./capabilities-DGavTSoW.cjs");
9
9
  const require_graph_schema = require("./graph-schema-C_7aZ8RL.cjs");
10
10
  const require_action_log = require("./action-log-HniMMF7f.cjs");
11
+ const require_dex_tool = require("./dex-tool-DIKN4dXB.cjs");
12
+ const require_dex_registry = require("./dex-registry-KOTJhAQf.cjs");
11
13
  let node_url = require("node:url");
12
14
  let node_path = require("node:path");
13
15
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
@@ -101,17 +103,17 @@ function rateLimitedResult(toolName, err) {
101
103
  const BUSY_CODE = "chain_busy";
102
104
  const BUSY_WAIT_MS = 1100;
103
105
  const defaultSleep = (ms) => new Promise((done) => setTimeout(done, ms));
104
- function isRecord$3(value) {
106
+ function isRecord$2(value) {
105
107
  return typeof value === "object" && value !== null && !Array.isArray(value);
106
108
  }
107
109
  function membersOf(result) {
108
- const facts = isRecord$3(result.structuredContent) ? result.structuredContent.facts : void 0;
109
- const queries = isRecord$3(facts) ? facts.queries : void 0;
110
- return Array.isArray(queries) && queries.every(isRecord$3) ? queries : null;
110
+ const facts = isRecord$2(result.structuredContent) ? result.structuredContent.facts : void 0;
111
+ const queries = isRecord$2(facts) ? facts.queries : void 0;
112
+ return Array.isArray(queries) && queries.every(isRecord$2) ? queries : null;
111
113
  }
112
114
  function isBusyMember(member) {
113
115
  const detail = member.error_detail;
114
- return isRecord$3(detail) && detail.code === BUSY_CODE;
116
+ return isRecord$2(detail) && detail.code === BUSY_CODE;
115
117
  }
116
118
  /** The ids of the members the server refused as chain_busy. */
117
119
  function busyMemberIds(result) {
@@ -142,7 +144,7 @@ function mergeBatchAnswers(first, again) {
142
144
  const merged = members.map((m) => isBusyMember(m) && byId.has(String(m.id)) ? byId.get(String(m.id)) : m);
143
145
  const structured = first.structuredContent;
144
146
  const facts = structured.facts;
145
- const batch = isRecord$3(facts.batch) ? recount(facts.batch, merged) : facts.batch;
147
+ const batch = isRecord$2(facts.batch) ? recount(facts.batch, merged) : facts.batch;
146
148
  const nextStructured = {
147
149
  ...structured,
148
150
  facts: {
@@ -186,7 +188,7 @@ async function withBusyMemberRetry(args, call, sleep = defaultSleep) {
186
188
  function isBusyResult(result) {
187
189
  if (!result.isError) return false;
188
190
  const first = Array.isArray(result.content) ? result.content[0] : void 0;
189
- const text = isRecord$3(first) && typeof first.text === "string" ? first.text : "";
191
+ const text = isRecord$2(first) && typeof first.text === "string" ? first.text : "";
190
192
  return /^(Query execution failed: )?chain_busy\b/.test(text.trim());
191
193
  }
192
194
  /**
@@ -333,7 +335,7 @@ function send(res, status, contentType, body) {
333
335
  function sendJson(res, status, value) {
334
336
  send(res, status, "application/json; charset=utf-8", JSON.stringify(value));
335
337
  }
336
- function isRecord$2(value) {
338
+ function isRecord$1(value) {
337
339
  return !!value && typeof value === "object" && !Array.isArray(value);
338
340
  }
339
341
  var BodyTooLarge = class extends Error {};
@@ -424,8 +426,8 @@ async function startViewServer(options) {
424
426
  sendJson(res, 400, { error: "invalid JSON" });
425
427
  return;
426
428
  }
427
- const args = isRecord$2(body) ? body.arguments ?? {} : void 0;
428
- if (!isRecord$2(args)) {
429
+ const args = isRecord$1(body) ? body.arguments ?? {} : void 0;
430
+ if (!isRecord$1(args)) {
429
431
  sendJson(res, 400, { error: "arguments must be an object" });
430
432
  return;
431
433
  }
@@ -606,52 +608,6 @@ async function connectRemote(options) {
606
608
  };
607
609
  }
608
610
  //#endregion
609
- //#region src/mcp/answer-hint.ts
610
- /**
611
- * The two answer rules that still slip in long answers, carried on every
612
- * graph reply in the result's `hint` field. Claude Code has a Stop hook that
613
- * reads the answer; Codex and Claude Desktop have no such hook, and in the
614
- * loop's third prompt set they broke one of these two rules in three of
615
- * thirteen conversations. The reply is the one thing every host hands the
616
- * model right before it writes, so the rules ride on it. A `hint` the
617
- * server already set is kept.
618
- */
619
- const ANSWER_HINT = "Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; never draw the flows in text, ASCII or Mermaid, the host draws them.";
620
- function isRecord$1(value) {
621
- return typeof value === "object" && value !== null && !Array.isArray(value);
622
- }
623
- /** The structured content with the hint set when the server left it empty. */
624
- function withAnswerHint(structuredContent) {
625
- if (!isRecord$1(structuredContent) || !isRecord$1(structuredContent.facts)) return structuredContent;
626
- if (typeof structuredContent.hint === "string" && structuredContent.hint.trim()) return structuredContent;
627
- return {
628
- ...structuredContent,
629
- hint: ANSWER_HINT
630
- };
631
- }
632
- /** A graph tool result whose structured content and single JSON text block both carry the hint. */
633
- function withAnswerHintResult(result) {
634
- const structured = withAnswerHint(result.structuredContent);
635
- if (structured === result.structuredContent) return result;
636
- const content = result.content ?? [];
637
- const only = content.length === 1 && content[0]?.type === "text" ? content[0] : null;
638
- let parsed = void 0;
639
- try {
640
- parsed = only?.text ? JSON.parse(only.text) : void 0;
641
- } catch {
642
- parsed = void 0;
643
- }
644
- const textIsStructured = parsed !== void 0 && JSON.stringify(parsed) === JSON.stringify(result.structuredContent);
645
- return {
646
- ...result,
647
- structuredContent: structured,
648
- content: textIsStructured ? [{
649
- ...only,
650
- text: JSON.stringify(structured)
651
- }] : content
652
- };
653
- }
654
- //#endregion
655
611
  //#region src/mcp/proxy.ts
656
612
  const LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
657
613
  "meta_network_capabilities",
@@ -660,6 +616,7 @@ const LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
660
616
  "meta_subscription_status",
661
617
  "meta_help",
662
618
  "meta_rules",
619
+ require_dex_tool.META_DEX_LOOKUP_TOOL,
663
620
  "wallet_balance",
664
621
  "wallet_topup",
665
622
  require_graph_schema.GRAPH_EXPAND_TOOL
@@ -717,15 +674,17 @@ function resolveMcpProxyMode(env = process.env) {
717
674
  }
718
675
  const GRAPH_LAYERS_TEXT = "Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool, Token and NFT labels, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for the chain node's own record of one known transaction, block, address or the head.";
719
676
  const TOOL_RULES_FIRST = "Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once before this tool. For a graph, alias the columns from_address and to_address; the host draws the picture from the answer itself. Never draw it again with another tool, such as a visualize or widget tool.";
677
+ const GRAPH_QUERY_DEX_DOOR_SENTENCE = "A question that names an exchange and no address starts with meta_dex_lookup, never with a scan of :Protocol, :Pool or labels.";
720
678
  const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
721
679
  meta_network_capabilities: "Return the current Chain Insights network and tool support matrix. Takes no arguments: send {}.",
722
680
  meta_schema: require_graph_schema.META_SCHEMA_DESCRIPTION,
681
+ [require_dex_tool.META_DEX_LOOKUP_TOOL]: require_dex_tool.META_DEX_LOOKUP_DESCRIPTION,
723
682
  meta_usage_status: "Return the caller's public free graph_query quota for the current UTC day.",
724
683
  meta_subscription_status: "Return the caller's CIA subscription window end, daily allowance, consumption, and tier.",
725
684
  meta_help: "Show a short guide to Chain Insights tools and workflow.",
726
685
  meta_rules: "Return the Chain Insights rules: how to write graph_query and graph_query_batch reads that are served, how to name columns so the answer is drawn as a graph, a time series or a table, and how to write the answer. Call it once per chat, before the first graph query, when the chain-insights-cypher skill is not loaded. Takes no arguments: send {}.",
727
686
  wallet_balance: "Show the local Chain Insights payment wallet address, payment network, token, and amount.",
728
- graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,
687
+ graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${GRAPH_QUERY_DEX_DOOR_SENTENCE} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,
729
688
  graph_query_batch: `${TOOL_RULES_FIRST} Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads. For a time series across days, send one USE facts total per day with the query id set to the day, "YYYY-MM-DD": the view joins them into one series. Write every address in full in your answer, all 42 characters.`
730
689
  };
731
690
  const KNOWN_PUBLIC_TOOL_TITLES = {
@@ -1184,7 +1143,7 @@ function normalizeRemoteToolResult(toolName, result) {
1184
1143
  _meta: sanitizeRemoteMeta(result._meta),
1185
1144
  isError: result.isError
1186
1145
  };
1187
- return toolName === "graph_query" || toolName === "graph_query_batch" ? require_print_result.withTimestampTextResult(withAnswerHintResult(normalized)) : normalized;
1146
+ return toolName === "graph_query" || toolName === "graph_query_batch" ? require_print_result.withTimestampTextResult(require_dex_tool.withAnswerHintResult(normalized)) : normalized;
1188
1147
  }
1189
1148
  function cleanNetworkCapabilities(value) {
1190
1149
  const structuredContent = isRecord(value) ? value.structuredContent : void 0;
@@ -1421,6 +1380,37 @@ async function createProxy() {
1421
1380
  openWorldHint: true
1422
1381
  }
1423
1382
  }, async (args) => require_graph_schema.handleMetaSchema(args, schemaDependencies));
1383
+ const dexDependencies = {
1384
+ loadRegistry: (network) => require_dex_registry.loadRegistry(network, { log: (line) => {
1385
+ logger.info("dex_registry.fault", { line });
1386
+ } }),
1387
+ graphQueryBatch: async (args) => {
1388
+ const options = remoteToolRequestOptions("graph_query_batch");
1389
+ const call = (requestArgs) => withRateLimitRetry(() => options ? remoteClient.callTool({
1390
+ name: "graph_query_batch",
1391
+ arguments: requestArgs
1392
+ }, void 0, options) : remoteClient.callTool({
1393
+ name: "graph_query_batch",
1394
+ arguments: requestArgs
1395
+ }));
1396
+ return await withBusyMemberRetry(args, call);
1397
+ },
1398
+ unavailable: async () => {
1399
+ await ensureRemote();
1400
+ return remoteConnected ? void 0 : `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}.`;
1401
+ },
1402
+ describeFailure: (err) => remoteCallFailureText("graph_query_batch", err)
1403
+ };
1404
+ server.registerTool(require_dex_tool.META_DEX_LOOKUP_TOOL, {
1405
+ title: require_dex_tool.META_DEX_LOOKUP_TITLE,
1406
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[require_dex_tool.META_DEX_LOOKUP_TOOL],
1407
+ inputSchema: {
1408
+ network: NETWORK_SCHEMA,
1409
+ name: zod.string().optional().describe("The exchange as the user wrote it: Aero, Aerodrome, uni v4. With no name, every covered exchange is listed."),
1410
+ pools: zod.boolean().optional().describe("Read the biggest pools of the exchange (default true). false sends only Head and the contract reads.")
1411
+ },
1412
+ annotations: require_dex_tool.META_DEX_LOOKUP_ANNOTATIONS
1413
+ }, async (args) => require_dex_tool.handleMetaDexLookup(args, dexDependencies));
1424
1414
  server.registerTool("meta_usage_status", {
1425
1415
  title: "Usage Status",
1426
1416
  description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_usage_status,
@@ -1664,6 +1654,7 @@ async function createProxy() {
1664
1654
  "Investigation tools:",
1665
1655
  "- meta_network_capabilities: supported networks and available tools. Takes no arguments.",
1666
1656
  "- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.",
1657
+ "- meta_dex_lookup: turn an exchange name (Aero, Uniswap, Virtuals) into its factories, routers, pools and first reads. Call it first when a question names an exchange and gives no address.",
1667
1658
  "- meta_usage_status: check the caller public free graph_query quota.",
1668
1659
  "- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.",
1669
1660
  "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.",
@@ -1681,6 +1672,7 @@ async function createProxy() {
1681
1672
  "Available graph-backed tools:",
1682
1673
  "- meta_network_capabilities: supported networks and available tools. Takes no arguments.",
1683
1674
  "- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.",
1675
+ "- meta_dex_lookup: turn an exchange name (Aero, Uniswap, Virtuals) into its factories, routers, pools and first reads. Call it first when a question names an exchange and gives no address.",
1684
1676
  "- meta_usage_status: check the caller public free graph_query quota.",
1685
1677
  "- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.",
1686
1678
  "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.",
@@ -1792,6 +1784,8 @@ if (process.argv[1] && require("url").pathToFileURL(__filename).href.includes(pr
1792
1784
  //#endregion
1793
1785
  exports.APP_ONLY_LOCAL_TOOL_NAMES = APP_ONLY_LOCAL_TOOL_NAMES;
1794
1786
  exports.CLAUDE_VIEW_URI = CLAUDE_VIEW_URI;
1787
+ exports.GRAPH_QUERY_DEX_DOOR_SENTENCE = GRAPH_QUERY_DEX_DOOR_SENTENCE;
1788
+ exports.KNOWN_PUBLIC_TOOL_DESCRIPTIONS = KNOWN_PUBLIC_TOOL_DESCRIPTIONS;
1795
1789
  exports.LOCAL_TOOL_NAMES = LOCAL_TOOL_NAMES;
1796
1790
  exports.MCP_APP_MIME_TYPE = MCP_APP_MIME_TYPE;
1797
1791
  exports.PICTURE_RULES = PICTURE_RULES;
@@ -11,6 +11,8 @@ export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
11
11
  export declare function readClaudeViewHtml(): string;
12
12
  export type McpProxyMode = 'workspace' | 'stateless';
13
13
  export declare function resolveMcpProxyMode(env?: NodeJS.ProcessEnv): McpProxyMode;
14
+ export declare const GRAPH_QUERY_DEX_DOOR_SENTENCE = "A question that names an exchange and no address starts with meta_dex_lookup, never with a scan of :Protocol, :Pool or labels.";
15
+ export declare const KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string>;
14
16
  type ToolInputShape = Record<string, z.ZodTypeAny>;
15
17
  export declare const PICTURE_RULES: string;
16
18
  export declare function knownPublicToolInputSchema(toolName: string): ToolInputShape | null;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.d.cts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;qBAkDa,kBAAgB;qBAYhB,2BAAyB;qBAUzB;qBACA;;;;;wBAyBG;YAkBJ;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KAsCtE,iBAAiB,eAAe,EAAE;qBAgB1B;wBA2GG,2BAA2B,mBAAmB;;;;;;;;wBAomBxC,eAAe"}
1
+ {"version":3,"file":"mcp-proxy.d.cts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;qBA4Da,kBAAgB;qBAahB,2BAAyB;qBAUzB;qBACA;;;;;wBAyBG;YAkBJ;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;qBAiB9D;qBAGA,gCAAgC;KAuBxC,iBAAiB,eAAe,EAAE;qBAgB1B;wBA2GG,2BAA2B,mBAAmB;;;;;;;;wBAomBxC,eAAe"}
@@ -11,6 +11,8 @@ export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
11
11
  export declare function readClaudeViewHtml(): string;
12
12
  export type McpProxyMode = 'workspace' | 'stateless';
13
13
  export declare function resolveMcpProxyMode(env?: NodeJS.ProcessEnv): McpProxyMode;
14
+ export declare const GRAPH_QUERY_DEX_DOOR_SENTENCE = "A question that names an exchange and no address starts with meta_dex_lookup, never with a scan of :Protocol, :Pool or labels.";
15
+ export declare const KNOWN_PUBLIC_TOOL_DESCRIPTIONS: Record<string, string>;
14
16
  type ToolInputShape = Record<string, z.ZodTypeAny>;
15
17
  export declare const PICTURE_RULES: string;
16
18
  export declare function knownPublicToolInputSchema(toolName: string): ToolInputShape | null;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-proxy.d.mts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;qBAkDa,kBAAgB;qBAYhB,2BAAyB;qBAUzB;qBACA;;;;;wBAyBG;YAkBJ;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;KAsCtE,iBAAiB,eAAe,EAAE;qBAgB1B;wBA2GG,2BAA2B,mBAAmB;;;;;;;;wBAomBxC,eAAe"}
1
+ {"version":3,"file":"mcp-proxy.d.mts","names":[],"sources":["../src/mcp/proxy.ts"],"mappings":";;qBA4Da,kBAAgB;qBAahB,2BAAyB;qBAUzB;qBACA;;;;;wBAyBG;YAkBJ;wBAEI,oBAAoB,MAAK,OAAO,aAA2B;qBAiB9D;qBAGA,gCAAgC;KAuBxC,iBAAiB,eAAe,EAAE;qBAgB1B;wBA2GG,2BAA2B,mBAAmB;;;;;;;;wBAomBxC,eAAe"}
@@ -1,11 +1,13 @@
1
1
  import { a as withTimestampTextResult, c as PACKAGE_VERSION, o as withoutUtcProperties } from "./print-result-CJ8d7lmz.mjs";
2
2
  import { t as PaymentRequiredError } from "./client-gsLi528K.mjs";
3
- import { i as isAppOnlyTool, n as PUBLIC_MCP_TOOL_ALLOWED_ARGS, r as PUBLIC_MCP_TOOL_REQUIRED_ARGS, t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-kclgGKL5.mjs";
3
+ import { i as isAppOnlyTool, n as PUBLIC_MCP_TOOL_ALLOWED_ARGS, r as PUBLIC_MCP_TOOL_REQUIRED_ARGS, t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-Dq-Ruj-8.mjs";
4
4
  import { n as primitiveBackendUsageStatus, t as freeTierSpentText } from "./usage-status-Frot6fPG.mjs";
5
5
  import { n as unavailableSubscriptionStatus } from "./subscription-status-DA9DCFrC.mjs";
6
6
  import { r as mirrorGraphNetworkCapabilities } from "./capabilities-BBVDIECk.mjs";
7
7
  import { a as handleMetaSchema, c as GRAPH_EXPAND_TITLE, l as GRAPH_EXPAND_TOOL, n as META_SCHEMA_DESCRIPTION, o as FLOWS_MAX_OFFSET, r as META_SCHEMA_TITLE, s as GRAPH_EXPAND_DESCRIPTION, t as GraphSchemaError, u as handleGraphExpand } from "./graph-schema-DHd2fGbr.mjs";
8
8
  import { r as appendActionLog, t as actionLogSignalsFromResult } from "./action-log-D49Par9P.mjs";
9
+ import { i as META_DEX_LOOKUP_TOOL, n as META_DEX_LOOKUP_DESCRIPTION, o as handleMetaDexLookup, r as META_DEX_LOOKUP_TITLE, s as withAnswerHintResult, t as META_DEX_LOOKUP_ANNOTATIONS } from "./dex-tool-DpQKS7Te.mjs";
10
+ import { r as loadRegistry } from "./dex-registry-05wAhSa0.mjs";
9
11
  import { fileURLToPath } from "node:url";
10
12
  import path from "node:path";
11
13
  import { readFileSync } from "node:fs";
@@ -97,17 +99,17 @@ function rateLimitedResult(toolName, err) {
97
99
  const BUSY_CODE = "chain_busy";
98
100
  const BUSY_WAIT_MS = 1100;
99
101
  const defaultSleep = (ms) => new Promise((done) => setTimeout(done, ms));
100
- function isRecord$3(value) {
102
+ function isRecord$2(value) {
101
103
  return typeof value === "object" && value !== null && !Array.isArray(value);
102
104
  }
103
105
  function membersOf(result) {
104
- const facts = isRecord$3(result.structuredContent) ? result.structuredContent.facts : void 0;
105
- const queries = isRecord$3(facts) ? facts.queries : void 0;
106
- return Array.isArray(queries) && queries.every(isRecord$3) ? queries : null;
106
+ const facts = isRecord$2(result.structuredContent) ? result.structuredContent.facts : void 0;
107
+ const queries = isRecord$2(facts) ? facts.queries : void 0;
108
+ return Array.isArray(queries) && queries.every(isRecord$2) ? queries : null;
107
109
  }
108
110
  function isBusyMember(member) {
109
111
  const detail = member.error_detail;
110
- return isRecord$3(detail) && detail.code === BUSY_CODE;
112
+ return isRecord$2(detail) && detail.code === BUSY_CODE;
111
113
  }
112
114
  /** The ids of the members the server refused as chain_busy. */
113
115
  function busyMemberIds(result) {
@@ -138,7 +140,7 @@ function mergeBatchAnswers(first, again) {
138
140
  const merged = members.map((m) => isBusyMember(m) && byId.has(String(m.id)) ? byId.get(String(m.id)) : m);
139
141
  const structured = first.structuredContent;
140
142
  const facts = structured.facts;
141
- const batch = isRecord$3(facts.batch) ? recount(facts.batch, merged) : facts.batch;
143
+ const batch = isRecord$2(facts.batch) ? recount(facts.batch, merged) : facts.batch;
142
144
  const nextStructured = {
143
145
  ...structured,
144
146
  facts: {
@@ -182,7 +184,7 @@ async function withBusyMemberRetry(args, call, sleep = defaultSleep) {
182
184
  function isBusyResult(result) {
183
185
  if (!result.isError) return false;
184
186
  const first = Array.isArray(result.content) ? result.content[0] : void 0;
185
- const text = isRecord$3(first) && typeof first.text === "string" ? first.text : "";
187
+ const text = isRecord$2(first) && typeof first.text === "string" ? first.text : "";
186
188
  return /^(Query execution failed: )?chain_busy\b/.test(text.trim());
187
189
  }
188
190
  /**
@@ -329,7 +331,7 @@ function send(res, status, contentType, body) {
329
331
  function sendJson(res, status, value) {
330
332
  send(res, status, "application/json; charset=utf-8", JSON.stringify(value));
331
333
  }
332
- function isRecord$2(value) {
334
+ function isRecord$1(value) {
333
335
  return !!value && typeof value === "object" && !Array.isArray(value);
334
336
  }
335
337
  var BodyTooLarge = class extends Error {};
@@ -420,8 +422,8 @@ async function startViewServer(options) {
420
422
  sendJson(res, 400, { error: "invalid JSON" });
421
423
  return;
422
424
  }
423
- const args = isRecord$2(body) ? body.arguments ?? {} : void 0;
424
- if (!isRecord$2(args)) {
425
+ const args = isRecord$1(body) ? body.arguments ?? {} : void 0;
426
+ if (!isRecord$1(args)) {
425
427
  sendJson(res, 400, { error: "arguments must be an object" });
426
428
  return;
427
429
  }
@@ -602,52 +604,6 @@ async function connectRemote(options) {
602
604
  };
603
605
  }
604
606
  //#endregion
605
- //#region src/mcp/answer-hint.ts
606
- /**
607
- * The two answer rules that still slip in long answers, carried on every
608
- * graph reply in the result's `hint` field. Claude Code has a Stop hook that
609
- * reads the answer; Codex and Claude Desktop have no such hook, and in the
610
- * loop's third prompt set they broke one of these two rules in three of
611
- * thirteen conversations. The reply is the one thing every host hands the
612
- * model right before it writes, so the rules ride on it. A `hint` the
613
- * server already set is kept.
614
- */
615
- const ANSWER_HINT = "Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; never draw the flows in text, ASCII or Mermaid, the host draws them.";
616
- function isRecord$1(value) {
617
- return typeof value === "object" && value !== null && !Array.isArray(value);
618
- }
619
- /** The structured content with the hint set when the server left it empty. */
620
- function withAnswerHint(structuredContent) {
621
- if (!isRecord$1(structuredContent) || !isRecord$1(structuredContent.facts)) return structuredContent;
622
- if (typeof structuredContent.hint === "string" && structuredContent.hint.trim()) return structuredContent;
623
- return {
624
- ...structuredContent,
625
- hint: ANSWER_HINT
626
- };
627
- }
628
- /** A graph tool result whose structured content and single JSON text block both carry the hint. */
629
- function withAnswerHintResult(result) {
630
- const structured = withAnswerHint(result.structuredContent);
631
- if (structured === result.structuredContent) return result;
632
- const content = result.content ?? [];
633
- const only = content.length === 1 && content[0]?.type === "text" ? content[0] : null;
634
- let parsed = void 0;
635
- try {
636
- parsed = only?.text ? JSON.parse(only.text) : void 0;
637
- } catch {
638
- parsed = void 0;
639
- }
640
- const textIsStructured = parsed !== void 0 && JSON.stringify(parsed) === JSON.stringify(result.structuredContent);
641
- return {
642
- ...result,
643
- structuredContent: structured,
644
- content: textIsStructured ? [{
645
- ...only,
646
- text: JSON.stringify(structured)
647
- }] : content
648
- };
649
- }
650
- //#endregion
651
607
  //#region src/mcp/proxy.ts
652
608
  const LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
653
609
  "meta_network_capabilities",
@@ -656,6 +612,7 @@ const LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
656
612
  "meta_subscription_status",
657
613
  "meta_help",
658
614
  "meta_rules",
615
+ META_DEX_LOOKUP_TOOL,
659
616
  "wallet_balance",
660
617
  "wallet_topup",
661
618
  GRAPH_EXPAND_TOOL
@@ -713,15 +670,17 @@ function resolveMcpProxyMode(env = process.env) {
713
670
  }
714
671
  const GRAPH_LAYERS_TEXT = "Use USE topology for topology (address/FLOWS_TO/OPERATED_BY/LINKED graph with SWAPPED, ADDED_LIQUIDITY, REMOVED_LIQUIDITY, BRIDGED and the Pool, Token and NFT labels, unified recent+historical) and USE facts for bounded TRANSFER, SWAP, LIQUIDITY_ADD, LIQUIDITY_REMOVE and BRIDGE_CROSSING rows and enrichment, and USE chain for the chain node's own record of one known transaction, block, address or the head.";
715
672
  const TOOL_RULES_FIRST = "Rules first: if the chain-insights-cypher skill is not loaded in this chat, call meta_rules once before this tool. For a graph, alias the columns from_address and to_address; the host draws the picture from the answer itself. Never draw it again with another tool, such as a visualize or widget tool.";
673
+ const GRAPH_QUERY_DEX_DOOR_SENTENCE = "A question that names an exchange and no address starts with meta_dex_lookup, never with a scan of :Protocol, :Pool or labels.";
716
674
  const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
717
675
  meta_network_capabilities: "Return the current Chain Insights network and tool support matrix. Takes no arguments: send {}.",
718
676
  meta_schema: META_SCHEMA_DESCRIPTION,
677
+ [META_DEX_LOOKUP_TOOL]: META_DEX_LOOKUP_DESCRIPTION,
719
678
  meta_usage_status: "Return the caller's public free graph_query quota for the current UTC day.",
720
679
  meta_subscription_status: "Return the caller's CIA subscription window end, daily allowance, consumption, and tier.",
721
680
  meta_help: "Show a short guide to Chain Insights tools and workflow.",
722
681
  meta_rules: "Return the Chain Insights rules: how to write graph_query and graph_query_batch reads that are served, how to name columns so the answer is drawn as a graph, a time series or a table, and how to write the answer. Call it once per chat, before the first graph query, when the chain-insights-cypher skill is not loaded. Takes no arguments: send {}.",
723
682
  wallet_balance: "Show the local Chain Insights payment wallet address, payment network, token, and amount.",
724
- graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,
683
+ graph_query: `${TOOL_RULES_FIRST} Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. ${GRAPH_LAYERS_TEXT} ${GRAPH_QUERY_DEX_DOOR_SENTENCE} ${ROUTING_NOT_SERVED} Preserve full addresses exactly, and write every address in full, all 42 characters, in your answer: never shorten one with ... or ….`,
725
684
  graph_query_batch: `${TOOL_RULES_FIRST} Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads. For a time series across days, send one USE facts total per day with the query id set to the day, "YYYY-MM-DD": the view joins them into one series. Write every address in full in your answer, all 42 characters.`
726
685
  };
727
686
  const KNOWN_PUBLIC_TOOL_TITLES = {
@@ -1417,6 +1376,37 @@ async function createProxy() {
1417
1376
  openWorldHint: true
1418
1377
  }
1419
1378
  }, async (args) => handleMetaSchema(args, schemaDependencies));
1379
+ const dexDependencies = {
1380
+ loadRegistry: (network) => loadRegistry(network, { log: (line) => {
1381
+ logger.info("dex_registry.fault", { line });
1382
+ } }),
1383
+ graphQueryBatch: async (args) => {
1384
+ const options = remoteToolRequestOptions("graph_query_batch");
1385
+ const call = (requestArgs) => withRateLimitRetry(() => options ? remoteClient.callTool({
1386
+ name: "graph_query_batch",
1387
+ arguments: requestArgs
1388
+ }, void 0, options) : remoteClient.callTool({
1389
+ name: "graph_query_batch",
1390
+ arguments: requestArgs
1391
+ }));
1392
+ return await withBusyMemberRetry(args, call);
1393
+ },
1394
+ unavailable: async () => {
1395
+ await ensureRemote();
1396
+ return remoteConnected ? void 0 : `${remoteUnavailableMessage ?? `Chain Insights Graph is not connected at ${graphMcpEndpoint}`}.`;
1397
+ },
1398
+ describeFailure: (err) => remoteCallFailureText("graph_query_batch", err)
1399
+ };
1400
+ server.registerTool(META_DEX_LOOKUP_TOOL, {
1401
+ title: META_DEX_LOOKUP_TITLE,
1402
+ description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS[META_DEX_LOOKUP_TOOL],
1403
+ inputSchema: {
1404
+ network: NETWORK_SCHEMA,
1405
+ name: z.string().optional().describe("The exchange as the user wrote it: Aero, Aerodrome, uni v4. With no name, every covered exchange is listed."),
1406
+ pools: z.boolean().optional().describe("Read the biggest pools of the exchange (default true). false sends only Head and the contract reads.")
1407
+ },
1408
+ annotations: META_DEX_LOOKUP_ANNOTATIONS
1409
+ }, async (args) => handleMetaDexLookup(args, dexDependencies));
1420
1410
  server.registerTool("meta_usage_status", {
1421
1411
  title: "Usage Status",
1422
1412
  description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.meta_usage_status,
@@ -1660,6 +1650,7 @@ async function createProxy() {
1660
1650
  "Investigation tools:",
1661
1651
  "- meta_network_capabilities: supported networks and available tools. Takes no arguments.",
1662
1652
  "- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.",
1653
+ "- meta_dex_lookup: turn an exchange name (Aero, Uniswap, Virtuals) into its factories, routers, pools and first reads. Call it first when a question names an exchange and gives no address.",
1663
1654
  "- meta_usage_status: check the caller public free graph_query quota.",
1664
1655
  "- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.",
1665
1656
  "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.",
@@ -1677,6 +1668,7 @@ async function createProxy() {
1677
1668
  "Available graph-backed tools:",
1678
1669
  "- meta_network_capabilities: supported networks and available tools. Takes no arguments.",
1679
1670
  "- meta_schema: read the live graph schema of one network: labels, link types and field names. Cached for 24 hours.",
1671
+ "- meta_dex_lookup: turn an exchange name (Aero, Uniswap, Virtuals) into its factories, routers, pools and first reads. Call it first when a question names an exchange and gives no address.",
1680
1672
  "- meta_usage_status: check the caller public free graph_query quota.",
1681
1673
  "- meta_subscription_status: check the caller CIA subscription window end, daily allowance, consumption, and tier.",
1682
1674
  "- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE topology or USE facts.",
@@ -1786,6 +1778,6 @@ if (process.argv[1] && import.meta.url.includes(process.argv[1].replace(/\\/g, "
1786
1778
  process.exit(1);
1787
1779
  });
1788
1780
  //#endregion
1789
- export { APP_ONLY_LOCAL_TOOL_NAMES, CLAUDE_VIEW_URI, LOCAL_TOOL_NAMES, MCP_APP_MIME_TYPE, PICTURE_RULES, createProxy, knownPublicToolInputSchema, readClaudeViewHtml, resolveMcpProxyMode };
1781
+ export { APP_ONLY_LOCAL_TOOL_NAMES, CLAUDE_VIEW_URI, GRAPH_QUERY_DEX_DOOR_SENTENCE, KNOWN_PUBLIC_TOOL_DESCRIPTIONS, LOCAL_TOOL_NAMES, MCP_APP_MIME_TYPE, PICTURE_RULES, createProxy, knownPublicToolInputSchema, readClaudeViewHtml, resolveMcpProxyMode };
1790
1782
 
1791
1783
  //# sourceMappingURL=mcp-proxy.mjs.map