paralith-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.mjs CHANGED
@@ -9801,9 +9801,12 @@ var ShardedVenue = class {
9801
9801
  return { cancelled: { cancelledIds }, account: await this.summed(address, targets) };
9802
9802
  }
9803
9803
  /** Housekeeping, on every pair: anyone may call it, nobody is paid to. */
9804
- async maintain(limit = 25) {
9804
+ /** `pairIds` sweeps only those pairs: one signed message each, so name the ones holding expired orders. */
9805
+ async maintain(limit = 25, pairIds) {
9805
9806
  let expired = 0;
9806
- for (const pair2 of Object.values(await this.pairs())) {
9807
+ const pairs = await this.pairs();
9808
+ const due = pairIds ? pairIds.filter((id) => pairs[id]).map((id) => pairs[id]) : Object.values(pairs);
9809
+ for (const pair2 of due) {
9807
9810
  const reply = unwrap4(await this.transport.send(pair2.process, tags2({
9808
9811
  Action: "Order.Maintain",
9809
9812
  Limit: String(Math.max(1, Math.floor(limit)))
@@ -10016,9 +10019,9 @@ var cancelAllVenueOrders = async (process, item) => {
10016
10019
  ...item ? { Item: item } : {}
10017
10020
  });
10018
10021
  };
10019
- var maintainVenueOrders = async (process, limit = 25) => {
10022
+ var maintainVenueOrders = async (process, limit = 25, pairIds) => {
10020
10023
  const s = await shard(process);
10021
- if (s) return s.maintain(limit);
10024
+ if (s) return s.maintain(limit, pairIds);
10022
10025
  return write4(process, {
10023
10026
  Action: "Order.Maintain",
10024
10027
  Limit: String(Math.max(1, Math.floor(limit)))
package/dist/http.mjs CHANGED
@@ -277,7 +277,7 @@ async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
277
277
 
278
278
  // src/build.mjs
279
279
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
280
- import { z as z7 } from "zod";
280
+ import { z as z8 } from "zod";
281
281
 
282
282
  // src/tool.mjs
283
283
  import { z } from "zod";
@@ -1420,11 +1420,11 @@ function registerDao(server) {
1420
1420
  exec_process: z5.string().regex(ID).optional().describe("Process the passed proposal sends its one message to."),
1421
1421
  exec_tags: z5.record(z5.string(), z5.string()).optional().describe('Tags of that message, e.g. {"Action": "Admin.SetRules", ...}. At most 16; required with exec_process.')
1422
1422
  },
1423
- async ({ title, body, link, exec_process, exec_tags }) => send({
1423
+ async ({ title, body, link: link2, exec_process, exec_tags }) => send({
1424
1424
  Action: "Propose",
1425
1425
  Title: title,
1426
1426
  Body: body,
1427
- Link: link,
1427
+ Link: link2,
1428
1428
  "Exec-Process": exec_process,
1429
1429
  "Exec-Tags": exec_tags && JSON.stringify(exec_tags)
1430
1430
  }),
@@ -1626,6 +1626,79 @@ function registerVaults(server) {
1626
1626
  );
1627
1627
  }
1628
1628
 
1629
+ // src/show.mjs
1630
+ import fs3 from "node:fs";
1631
+ import path3 from "node:path";
1632
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1633
+ import { z as z7 } from "zod";
1634
+ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
1635
+ var GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
1636
+ var TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
1637
+ var URI = "ui://paralith/view";
1638
+ var HERE2 = path3.dirname(fileURLToPath2(import.meta.url));
1639
+ function viewHtml() {
1640
+ for (const file of [path3.join(HERE2, "view.html"), path3.join(HERE2, "..", "dist", "view.html")]) {
1641
+ if (fs3.existsSync(file)) return fs3.readFileSync(file, "utf8");
1642
+ }
1643
+ throw new Error("view.html is missing: run npm run build");
1644
+ }
1645
+ var link = (base2, page, params) => {
1646
+ const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
1647
+ return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
1648
+ };
1649
+ var VIEWS = {
1650
+ battle: (a) => a.battle_id && a.worker ? link(GAME, "battle", { w: a.worker, b: a.battle_id, a: a.address }) : link(GAME, "battle", { a: need2(a, "address") }),
1651
+ companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
1652
+ hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
1653
+ market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
1654
+ vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
1655
+ position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
1656
+ bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
1657
+ };
1658
+ function need2(args, key) {
1659
+ if (!args[key]) throw new Error(`${key} is required to show this.`);
1660
+ return args[key];
1661
+ }
1662
+ async function ownAddress() {
1663
+ if (READ_ONLY) return void 0;
1664
+ const { wallets } = await runtime();
1665
+ return wallets?.ao.address;
1666
+ }
1667
+ function registerShow(server) {
1668
+ const csp = { frameDomains: [GAME, TRADE] };
1669
+ registerAppResource(
1670
+ server,
1671
+ "Paralith view",
1672
+ URI,
1673
+ { description: "Live game and exchange visuals." },
1674
+ async () => ({ contents: [{ uri: URI, mimeType: RESOURCE_MIME_TYPE, text: viewHtml(), _meta: { ui: { csp } } }] })
1675
+ );
1676
+ registerAppTool(server, "show", {
1677
+ description: "Show something with the game's and exchange's own visuals, live: battle (follows a player's arena fights, animated), companion (card), hunt, market (order book and chart), vault, position, bridge (transfers). Returns a link; clients that support MCP Apps also display it inline. Address defaults to yours.",
1678
+ inputSchema: {
1679
+ what: z7.enum(Object.keys(VIEWS)),
1680
+ address: z7.string().optional().describe("AO address; omit for yours."),
1681
+ monster_id: z7.string().optional().describe("companion: which one; omit for the active companion."),
1682
+ battle_id: z7.string().optional().describe("battle: a specific battle, with worker."),
1683
+ worker: z7.string().optional().describe("battle: the battle worker process id."),
1684
+ venue: z7.enum(["game", "token"]).optional().describe("market/vault/position. Default token."),
1685
+ market: z7.string().optional().describe("market/vault: the market, as exchange_markets lists it."),
1686
+ vault: z7.enum(["amm", "active", "grid"]).optional(),
1687
+ chain: z7.enum(["base", "solana", "sui"]).optional().describe("bridge: with tx, one transfer."),
1688
+ tx: z7.string().optional()
1689
+ },
1690
+ annotations: { readOnlyHint: true, openWorldHint: true },
1691
+ _meta: { ui: { resourceUri: URI } }
1692
+ }, async (args) => {
1693
+ try {
1694
+ const view = VIEWS[args.what]({ ...args, address: args.address ?? await ownAddress() });
1695
+ return { structuredContent: { what: args.what, view }, content: [{ type: "text", text: `${args.what}: ${view}` }] };
1696
+ } catch (error) {
1697
+ return { isError: true, content: [{ type: "text", text: error.message }] };
1698
+ }
1699
+ });
1700
+ }
1701
+
1629
1702
  // src/build.mjs
1630
1703
  var DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
1631
1704
  var INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
@@ -1642,13 +1715,15 @@ Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_pos
1642
1715
  Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
1643
1716
  DAO (stake TEST-RUNE; a passed proposal may send one attached message): dao_info, dao_proposals, dao_stake stake/unstake/withdraw, dao_propose, dao_vote.
1644
1717
 
1718
+ To let the user SEE something (an arena fight as it happens, a companion, a hunt, a market's book and chart, a vault, a position, bridge transfers), call show: it returns a live link and displays inline where the client supports it.
1719
+
1645
1720
  Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
1646
1721
  A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
1647
1722
  var READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
1648
1723
  To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
1649
1724
  function buildServer() {
1650
1725
  const server = new McpServer(
1651
- { name: "paralith", version: "0.1.0" },
1726
+ { name: "paralith", version: "0.2.0" },
1652
1727
  { instructions: READ_ONLY ? `${READ_ONLY_NOTE}
1653
1728
 
1654
1729
  ${INSTRUCTIONS}` : INSTRUCTIONS }
@@ -1679,7 +1754,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1679
1754
  server,
1680
1755
  "read_state",
1681
1756
  `Read any published key of any process, free: /<process>~process@1.0/now/<key>. process is an id or an alias (${Object.keys(PROCESS_ALIASES).join(", ")}). Returns null when absent.`,
1682
- { process: z7.string(), key: z7.string() },
1757
+ { process: z8.string(), key: z8.string() },
1683
1758
  async ({ process: process2, key }) => readKey(process2, key),
1684
1759
  READ
1685
1760
  );
@@ -1688,10 +1763,10 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1688
1763
  "send_message",
1689
1764
  "Escape hatch: sign and send a raw message to any process and return its reply. Prefer the specific tools; use this only for an action they do not cover. Tag values must be strings; never use a tag named Target.",
1690
1765
  {
1691
- process: z7.string(),
1692
- tags: z7.record(z7.string(), z7.string()).describe('e.g. {"Action": "Pass.Info"}'),
1693
- data: z7.string().optional(),
1694
- push_outbox: z7.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1766
+ process: z8.string(),
1767
+ tags: z8.record(z8.string(), z8.string()).describe('e.g. {"Action": "Pass.Info"}'),
1768
+ data: z8.string().optional(),
1769
+ push_outbox: z8.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1695
1770
  },
1696
1771
  async ({ process: process2, tags: tags2, data, push_outbox }) => {
1697
1772
  if (Object.keys(tags2).some((k) => k.toLowerCase() === "target")) throw new Error("Target is not a usable tag name.");
@@ -1720,6 +1795,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1720
1795
  registerBridge(server);
1721
1796
  registerDao(server);
1722
1797
  registerVaults(server);
1798
+ registerShow(server);
1723
1799
  return server;
1724
1800
  }
1725
1801
 
package/dist/server.mjs CHANGED
@@ -276,7 +276,7 @@ async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
276
276
 
277
277
  // src/build.mjs
278
278
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
279
- import { z as z7 } from "zod";
279
+ import { z as z8 } from "zod";
280
280
 
281
281
  // src/tool.mjs
282
282
  import { z } from "zod";
@@ -1419,11 +1419,11 @@ function registerDao(server) {
1419
1419
  exec_process: z5.string().regex(ID).optional().describe("Process the passed proposal sends its one message to."),
1420
1420
  exec_tags: z5.record(z5.string(), z5.string()).optional().describe('Tags of that message, e.g. {"Action": "Admin.SetRules", ...}. At most 16; required with exec_process.')
1421
1421
  },
1422
- async ({ title, body, link, exec_process, exec_tags }) => send({
1422
+ async ({ title, body, link: link2, exec_process, exec_tags }) => send({
1423
1423
  Action: "Propose",
1424
1424
  Title: title,
1425
1425
  Body: body,
1426
- Link: link,
1426
+ Link: link2,
1427
1427
  "Exec-Process": exec_process,
1428
1428
  "Exec-Tags": exec_tags && JSON.stringify(exec_tags)
1429
1429
  }),
@@ -1625,6 +1625,79 @@ function registerVaults(server) {
1625
1625
  );
1626
1626
  }
1627
1627
 
1628
+ // src/show.mjs
1629
+ import fs3 from "node:fs";
1630
+ import path3 from "node:path";
1631
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1632
+ import { z as z7 } from "zod";
1633
+ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
1634
+ var GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
1635
+ var TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
1636
+ var URI = "ui://paralith/view";
1637
+ var HERE2 = path3.dirname(fileURLToPath2(import.meta.url));
1638
+ function viewHtml() {
1639
+ for (const file of [path3.join(HERE2, "view.html"), path3.join(HERE2, "..", "dist", "view.html")]) {
1640
+ if (fs3.existsSync(file)) return fs3.readFileSync(file, "utf8");
1641
+ }
1642
+ throw new Error("view.html is missing: run npm run build");
1643
+ }
1644
+ var link = (base2, page, params) => {
1645
+ const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
1646
+ return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
1647
+ };
1648
+ var VIEWS = {
1649
+ battle: (a) => a.battle_id && a.worker ? link(GAME, "battle", { w: a.worker, b: a.battle_id, a: a.address }) : link(GAME, "battle", { a: need2(a, "address") }),
1650
+ companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
1651
+ hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
1652
+ market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
1653
+ vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
1654
+ position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
1655
+ bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
1656
+ };
1657
+ function need2(args, key) {
1658
+ if (!args[key]) throw new Error(`${key} is required to show this.`);
1659
+ return args[key];
1660
+ }
1661
+ async function ownAddress() {
1662
+ if (READ_ONLY) return void 0;
1663
+ const { wallets } = await runtime();
1664
+ return wallets?.ao.address;
1665
+ }
1666
+ function registerShow(server) {
1667
+ const csp = { frameDomains: [GAME, TRADE] };
1668
+ registerAppResource(
1669
+ server,
1670
+ "Paralith view",
1671
+ URI,
1672
+ { description: "Live game and exchange visuals." },
1673
+ async () => ({ contents: [{ uri: URI, mimeType: RESOURCE_MIME_TYPE, text: viewHtml(), _meta: { ui: { csp } } }] })
1674
+ );
1675
+ registerAppTool(server, "show", {
1676
+ description: "Show something with the game's and exchange's own visuals, live: battle (follows a player's arena fights, animated), companion (card), hunt, market (order book and chart), vault, position, bridge (transfers). Returns a link; clients that support MCP Apps also display it inline. Address defaults to yours.",
1677
+ inputSchema: {
1678
+ what: z7.enum(Object.keys(VIEWS)),
1679
+ address: z7.string().optional().describe("AO address; omit for yours."),
1680
+ monster_id: z7.string().optional().describe("companion: which one; omit for the active companion."),
1681
+ battle_id: z7.string().optional().describe("battle: a specific battle, with worker."),
1682
+ worker: z7.string().optional().describe("battle: the battle worker process id."),
1683
+ venue: z7.enum(["game", "token"]).optional().describe("market/vault/position. Default token."),
1684
+ market: z7.string().optional().describe("market/vault: the market, as exchange_markets lists it."),
1685
+ vault: z7.enum(["amm", "active", "grid"]).optional(),
1686
+ chain: z7.enum(["base", "solana", "sui"]).optional().describe("bridge: with tx, one transfer."),
1687
+ tx: z7.string().optional()
1688
+ },
1689
+ annotations: { readOnlyHint: true, openWorldHint: true },
1690
+ _meta: { ui: { resourceUri: URI } }
1691
+ }, async (args) => {
1692
+ try {
1693
+ const view = VIEWS[args.what]({ ...args, address: args.address ?? await ownAddress() });
1694
+ return { structuredContent: { what: args.what, view }, content: [{ type: "text", text: `${args.what}: ${view}` }] };
1695
+ } catch (error) {
1696
+ return { isError: true, content: [{ type: "text", text: error.message }] };
1697
+ }
1698
+ });
1699
+ }
1700
+
1628
1701
  // src/build.mjs
1629
1702
  var DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
1630
1703
  var INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
@@ -1641,13 +1714,15 @@ Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_pos
1641
1714
  Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
1642
1715
  DAO (stake TEST-RUNE; a passed proposal may send one attached message): dao_info, dao_proposals, dao_stake stake/unstake/withdraw, dao_propose, dao_vote.
1643
1716
 
1717
+ To let the user SEE something (an arena fight as it happens, a companion, a hunt, a market's book and chart, a vault, a position, bridge transfers), call show: it returns a live link and displays inline where the client supports it.
1718
+
1644
1719
  Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
1645
1720
  A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
1646
1721
  var READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
1647
1722
  To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
1648
1723
  function buildServer() {
1649
1724
  const server = new McpServer(
1650
- { name: "paralith", version: "0.1.0" },
1725
+ { name: "paralith", version: "0.2.0" },
1651
1726
  { instructions: READ_ONLY ? `${READ_ONLY_NOTE}
1652
1727
 
1653
1728
  ${INSTRUCTIONS}` : INSTRUCTIONS }
@@ -1678,7 +1753,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1678
1753
  server,
1679
1754
  "read_state",
1680
1755
  `Read any published key of any process, free: /<process>~process@1.0/now/<key>. process is an id or an alias (${Object.keys(PROCESS_ALIASES).join(", ")}). Returns null when absent.`,
1681
- { process: z7.string(), key: z7.string() },
1756
+ { process: z8.string(), key: z8.string() },
1682
1757
  async ({ process: process2, key }) => readKey(process2, key),
1683
1758
  READ
1684
1759
  );
@@ -1687,10 +1762,10 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1687
1762
  "send_message",
1688
1763
  "Escape hatch: sign and send a raw message to any process and return its reply. Prefer the specific tools; use this only for an action they do not cover. Tag values must be strings; never use a tag named Target.",
1689
1764
  {
1690
- process: z7.string(),
1691
- tags: z7.record(z7.string(), z7.string()).describe('e.g. {"Action": "Pass.Info"}'),
1692
- data: z7.string().optional(),
1693
- push_outbox: z7.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1765
+ process: z8.string(),
1766
+ tags: z8.record(z8.string(), z8.string()).describe('e.g. {"Action": "Pass.Info"}'),
1767
+ data: z8.string().optional(),
1768
+ push_outbox: z8.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1694
1769
  },
1695
1770
  async ({ process: process2, tags: tags2, data, push_outbox }) => {
1696
1771
  if (Object.keys(tags2).some((k) => k.toLowerCase() === "target")) throw new Error("Target is not a usable tag name.");
@@ -1719,6 +1794,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1719
1794
  registerBridge(server);
1720
1795
  registerDao(server);
1721
1796
  registerVaults(server);
1797
+ registerShow(server);
1722
1798
  return server;
1723
1799
  }
1724
1800