@candledottv/mcp 0.10.7 → 0.10.9

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 (2) hide show
  1. package/dist/index.js +51 -32
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ function percentOfBalance(balanceRaw, percent) {
106
106
  import { randomUUID } from "node:crypto";
107
107
 
108
108
  // src/version.ts
109
- var SERVER_VERSION = "0.10.7";
109
+ var SERVER_VERSION = "0.10.9";
110
110
 
111
111
  // src/update-notice.ts
112
112
  var PLAIN_VERSION = /^\d+\.\d+\.\d+$/;
@@ -1532,7 +1532,7 @@ function buildRequest(name, args, cfg) {
1532
1532
  ...where ? { where } : {},
1533
1533
  ...sort ? { sort } : {},
1534
1534
  ...fields ? { fields } : {},
1535
- ...limit ? { limit } : {}
1535
+ ...limit !== undefined && limit !== "" ? { limit: String(limit) } : {}
1536
1536
  });
1537
1537
  return {
1538
1538
  url: `${base2}/api/v1/markets/feed?${query.toString()}`,
@@ -1641,9 +1641,9 @@ ${promotion}` : markdown;
1641
1641
  return "";
1642
1642
  }
1643
1643
  }
1644
- async function callAndRelay(name, args, cfg) {
1644
+ async function callAndRelay(name, args, cfg, doFetch) {
1645
1645
  const { url, init } = buildRequest(name, args, cfg);
1646
- const res = await fetch(url, init);
1646
+ const res = await doFetch(url, init);
1647
1647
  noteVersionHeaders(res);
1648
1648
  const text = await res.text();
1649
1649
  return {
@@ -1676,13 +1676,22 @@ var tokenForensicsShape = {
1676
1676
  chain: z2.string().describe('"solana" or "hood"'),
1677
1677
  mint: z2.string().describe("Token mint (solana) or contract address (hood)")
1678
1678
  };
1679
+ var FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200";
1680
+ var feedLimit = z2.preprocess((value) => {
1681
+ if (typeof value !== "string")
1682
+ return value;
1683
+ const trimmed = value.trim();
1684
+ if (trimmed === "")
1685
+ return;
1686
+ return /^[0-9]+$/.test(trimmed) ? Number(trimmed) : value;
1687
+ }, z2.number({ invalid_type_error: FEED_LIMIT_MESSAGE }).int(FEED_LIMIT_MESSAGE).min(1, FEED_LIMIT_MESSAGE).max(200, FEED_LIMIT_MESSAGE).optional().describe("Max rows to return, an integer from 1 to 200."));
1679
1688
  var getFeedShape = {
1680
1689
  bucket: z2.enum(["new", "graduated", "onfire", "bluechip"]),
1681
1690
  chain: z2.string().optional().describe("Optional chain filter"),
1682
1691
  where: z2.string().optional().describe('JSON filter, e.g. {"marketCap":{"lt":150000},"liquidityUsd":{"gte":25000},"mintAuthorityDisabled":{"eq":true}}. ' + "Comparators: eq, ne, lt, lte, gt, gte, present. An ABSENT field satisfies none of them except " + "present:false, so a filter for mintAuthorityDisabled eq true returns only tokens that actually say " + "true, never ones where the flag is simply missing. Use present:false to find the tokens with no data."),
1683
1692
  sort: z2.string().optional().describe('Sort as "field" or "field:asc" / "field:desc". A bare field means desc.'),
1684
1693
  fields: z2.string().optional().describe("Comma-separated fields to return, e.g. symbol,marketCap,liquidityUsd. chain, address and symbol always " + "ride along. Cuts a 135KB response to a couple of KB."),
1685
- limit: z2.string().optional().describe("Max rows to return, 1-200.")
1694
+ limit: feedLimit
1686
1695
  };
1687
1696
  var reportActivityShape = {
1688
1697
  chain: z2.string().describe('"solana" or "hood"'),
@@ -1752,28 +1761,38 @@ var sweepShape = {
1752
1761
  function registerTools(server, env = process.env) {
1753
1762
  const cfg = resolveConfig(env);
1754
1763
  const allowed = resolveToolAllowlist(env);
1755
- const register = (name, ...rest) => {
1764
+ registerToolSubset(server, { tools: [...allowed], getConfig: () => cfg, env });
1765
+ }
1766
+ function registerToolSubset(server, options) {
1767
+ const unknown = options.tools.filter((name) => !TOOL_NAMES.includes(name));
1768
+ if (unknown.length)
1769
+ throw new Error(`Unknown tool name(s): ${unknown.join(", ")}`);
1770
+ const allowed = new Set(options.tools);
1771
+ const getConfig = options.getConfig;
1772
+ const env = options.env ?? {};
1773
+ const doFetch = options.fetch ?? ((url, init) => fetch(url, init));
1774
+ const register = (name, definition, handler) => {
1756
1775
  if (!allowed.has(name))
1757
1776
  return;
1758
- return server.registerTool(name, ...rest);
1777
+ server.registerTool(name, { ...definition, ...options.metadata?.[name] }, handler);
1759
1778
  };
1760
1779
  register("candle_launch_token", {
1761
1780
  title: "Launch a token on Candle",
1762
1781
  description: "Launch a new token via the Candle headless launch API, from the account's embedded wallet. Works on every plan, Free included. Set dryRun: true to validate without spending anything.",
1763
1782
  inputSchema: launchTokenShape
1764
- }, async (args) => callAndRelay("candle_launch_token", args, cfg));
1783
+ }, async (args) => callAndRelay("candle_launch_token", args, getConfig(), doFetch));
1765
1784
  register("candle_get_market", {
1766
1785
  title: "Get market state",
1767
1786
  description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
1768
1787
  inputSchema: getMarketShape
1769
- }, async (args) => callAndRelay("candle_get_market", args, cfg));
1788
+ }, async (args) => callAndRelay("candle_get_market", args, getConfig(), doFetch));
1770
1789
  register("candle_token_forensics", {
1771
1790
  title: "Token forensics",
1772
1791
  description: `Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer; when no developer is on chain, deployer.attribution names the launchpad or issuer instead, e.g. launched via stonk.fun or issued by xStocks), their went-to-zero rate and last coins, who bought in the deploy window (the creator's own wallets are marked disclosed; strangers in the same slot are the bundle signal), same-funder insider share, same-funder deployer cluster, and safety.summary with six sourced flags (mintAuthority, freezeAuthority, tokenExtensions, lpLock, sellability, liquidityDrain). Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. launch.deployerLaunches is an inclusive informational count, never a warning. Every measurement carries a coverage note -- 'unavailable' is not 'clean'. No key needed.
1773
1792
 
1774
1793
  MARKET_NOT_FOUND means Candle has no market for that token and this could not run. That is also not 'clean': report that you could not check it, rather than reporting the token as safe. That refusal now carries error.coverage -- covered:false, a reason ('external_launchpad' when the token launched somewhere else, 'unknown_mint' when nobody has indexed it), the launchpad when known, and every check that consequently did not run. Read it instead of guessing. Most of the feed, and any Solana mint Jupiter's index knows, now answers with a partial report instead. On Hood, a token Candle did not launch names its launch account (deployer.method cvc_launch_account) for pons.family and pools.trade launches, with no record of earlier coins; other Hood launchpads return no developer.`,
1775
1794
  inputSchema: tokenForensicsShape
1776
- }, async (args) => callAndRelay("candle_token_forensics", args, cfg));
1795
+ }, async (args) => callAndRelay("candle_token_forensics", args, getConfig(), doFetch));
1777
1796
  register("candle_get_feed", {
1778
1797
  title: "Get a token feed",
1779
1798
  description: "Read one of the trade page's public feeds: new, graduated, onfire, or bluechip. Reads " + `only; moves nothing. No key needed. Start here when nobody has named a token.
@@ -1782,24 +1801,24 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1782
1801
 
1783
1802
  ` + "Filter, sort and pick fields SERVER-SIDE rather than reading the whole feed: an " + "unfiltered response is around 135KB and will not fit in a tool result. See `where`, " + "`sort` and `fields`.\n\n" + "One rule to know before screening on safety: a missing field is NOT a false one. " + "mintAuthorityDisabled and freezeAuthorityDisabled are absent on a real share of rows, " + "and absent means nobody checked, not that the authority is disabled. `where` never lets " + 'an absent field satisfy a comparison, so {"mintAuthorityDisabled":{"eq":true}} returns ' + "only tokens that actually say so.",
1784
1803
  inputSchema: getFeedShape
1785
- }, async (args) => callAndRelay("candle_get_feed", args, cfg));
1804
+ }, async (args) => callAndRelay("candle_get_feed", args, getConfig(), doFetch));
1786
1805
  register("candle_report_activity", {
1787
1806
  title: "Report on-chain activity",
1788
1807
  description: "Report a client-executed transaction (transfer, swap, stake) so Candle records and verifies it.",
1789
1808
  inputSchema: reportActivityShape
1790
- }, async (args) => callAndRelay("candle_report_activity", args, cfg));
1809
+ }, async (args) => callAndRelay("candle_report_activity", args, getConfig(), doFetch));
1791
1810
  register("candle_get_agent_profile", {
1792
1811
  title: "Get an agent profile",
1793
1812
  description: "Read a Candle user's public agent profile: whether agent features are enabled and launch counts.",
1794
1813
  inputSchema: getAgentProfileShape
1795
- }, async (args) => callAndRelay("candle_get_agent_profile", args, cfg));
1814
+ }, async (args) => callAndRelay("candle_get_agent_profile", args, getConfig(), doFetch));
1796
1815
  register("candle_get_plans", {
1797
1816
  title: "Plans: prices, fees, limits and what each can do",
1798
1817
  description: "The plan table this Candle deployment serves (GET /api/v1/agent/plans). No key needed. Reads only. For " + "each plan: price (null when not sold), feeBps (the agent fee charged on top of every trade or dev buy the " + "API builds), perpFeeBps (the Hyperliquid builder fee), limits for a new key (requests per minute, launches " + "per day, uploads per minute, linked wallets) and capabilities: buyExternalTokens, sellExternalTokens, " + "tradeBaseAssets, tradeCandleTokens, selfLaunch, atomicLaunch, createLinkedWallets, importLinkedWallets, " + "hyperliquidPerps, limitOrders, quant, freeBaseTransfers. promoMaxDays is the days of Max a first Pro " + "purchase includes (0: no promotion). Quote prices and fees from here, never from memory: they differ by " + "deployment and change at the three-plan launch (Free, Pro, Max). The account's own plan and fee are in " + "candle_execution_status's tier. A TIER_REQUIRED refusal names a capability this table shows the account's " + "plan lacks. A capability true here is plan eligibility, not deployment availability: perps and own-wallet bridges " + "are each behind a deployment switch and need their wallet, scopes and setup. Returns the server's JSON, then the same table as Markdown.",
1799
1818
  inputSchema: {}
1800
1819
  }, async () => {
1801
- const { url, init } = buildRequest("candle_get_plans", {}, cfg);
1802
- const res = await fetch(url, init);
1820
+ const { url, init } = buildRequest("candle_get_plans", {}, getConfig());
1821
+ const res = await doFetch(url, init);
1803
1822
  noteVersionHeaders(res);
1804
1823
  const text = await res.text();
1805
1824
  if (!res.ok)
@@ -1823,43 +1842,43 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1823
1842
 
1824
1843
  ` + "Amounts come back RAW, deliberately: the answer you need after a timeout is the outcome, " + "and you already know what you asked for.",
1825
1844
  inputSchema: getOperationShape
1826
- }, async (args) => callAndRelay("candle_get_operation", args, cfg));
1845
+ }, async (args) => callAndRelay("candle_get_operation", args, getConfig(), doFetch));
1827
1846
  register("candle_get_wallets", {
1828
1847
  title: "List the wallets Candle executes with",
1829
1848
  description: "The account's EMBEDDED wallets, one per chain, with their delegation state. These are " + "the wallets candle_trade, candle_swap and candle_transfer spend from, so this is how an " + "agent finds its own funding addresses. Reads only; moves nothing. Not the same as the " + "account's LINKED wallets, which are the owner's own wallets and are not spent from here. " + "Balances are not included: read a specific one with the market and balance endpoints.",
1830
1849
  inputSchema: {}
1831
- }, async () => callAndRelay("candle_get_wallets", {}, cfg));
1850
+ }, async () => callAndRelay("candle_get_wallets", {}, getConfig(), doFetch));
1832
1851
  register("candle_get_profile_wallets", {
1833
1852
  title: "Read which wallets an agent profile can spend from",
1834
1853
  description: "An agent profile (API key) either spends from EVERY wallet on its account or only from " + "the ones assigned to it. Read walletScope before drawing any conclusion from the list: " + "an empty list means 'every wallet' under scope 'all' and 'none at all' under 'selected'. " + "Reads only; moves nothing.",
1835
1854
  inputSchema: profileWalletsShape
1836
- }, async (args) => callAndRelay("candle_get_profile_wallets", args, cfg));
1855
+ }, async (args) => callAndRelay("candle_get_profile_wallets", args, getConfig(), doFetch));
1837
1856
  register("candle_set_profile_wallets", {
1838
1857
  title: "Set which wallets an agent profile can spend from",
1839
1858
  description: "REPLACES the profile's whole wallet set with the ids given, so a wallet left out of the " + "list loses access; pass an empty list to leave the profile with no wallets. NARROWING " + "ONLY: an API key can remove wallets from its OWN profile, but naming one it does not " + "already hold is a grant and needs a human in the dashboard, as does editing any other " + "profile. Takes effect only while the profile's scope is 'selected'. Wallet ids are the " + "linked-wallet ids, not addresses.",
1840
1859
  inputSchema: setProfileWalletsShape
1841
- }, async (args) => callAndRelay("candle_set_profile_wallets", args, cfg));
1860
+ }, async (args) => callAndRelay("candle_set_profile_wallets", args, getConfig(), doFetch));
1842
1861
  register("candle_get_profile_pnl", {
1843
1862
  title: "Read an agent profile's P&L",
1844
- description: "This profile's share of the account's P&L: realized profit on the sales it made, the Candle fees " + "charged against it, and the positions that belong to it (the wallet's bound key, else the key " + "whose buy opened them) with their cost basis, each MARKED at Candle's current price where one " + "exists: `markPriceUsd`, `marketValueUsd` and `unrealizedUsd` per position, and `unrealizedUsd` " + "overall. A position with no price is counted in `unmarkedPositions` and left out of unrealized, " + "never valued at zero; `oldestMarkAt` says how old the marks are. Deposits and withdrawals are " + "excluded: funding a wallet is not profit. Check `unvalued` and `truncated` before quoting the " + "number; they mean the total is partial. The figures come from the account's one P&L read, the " + "same as the console's, to the cent: `pnl.totalUsd` (realized net plus unrealized) is the " + "console's P&L. Positions live in wallets: each wallet is its own average-cost pool, the total " + "is the sum of `pnl.byWallet` (one row per wallet, main or linked, with the linked wallet's " + "label; `unknown:solana` or `unknown:hood` is the row for fills no record places in a wallet), " + "and one token held in two wallets is two positions, each with its `wallet`. A recorded move " + "between the account's wallets carries its cost and realizes nothing. Each position carries " + "`agent` (the key it belongs to) and `dust` when it is worth under one cent; dust stays listed " + "and counted, and `openPositionsExDust` leaves it out. `closed` gives the closed positions as " + "`madeUsd` + `lostUsd` + `partialSellsUsd` = `realizedNetUsd`, with `wins` and `losses`. " + "`lookback` and `truncated` are the account's activity bound, and `tradesConsidered` counts this " + "key's ledger fills. Since 2026-10-02 the total is the sum of wallets rather than one pool across " + "them, so realized figures can differ from earlier reads; a server that predates it omits " + "`totalUsd`, `closed`, `openPositionsExDust`, `wallet`, `agent` and `dust`. " + "Every open position and every `byWallet` row (and its positions) carries `chain` ('solana' or " + "'hood'), and `pnl.byChain` has the total's figures over each chain's fills alone, both chains " + "always present, with `openPositions` there a count (the positions are in `pnl.openPositions`, by " + "`chain`). The two chains sum to the total. A " + "server that predates per-chain P&L omits `chain` and `byChain`. While Hyperliquid is enabled, " + "`hyperliquid` separately reports main-perp realized gross, signed funding, fees (inclusive of builder fees), " + "and net = gross + funding - fees, for currently bound EVM TEE wallets since the current TEE binding. " + "Check `read` and `truncated`; historical bindings and unrealized perps are excluded. Reads only.",
1863
+ description: "This profile's share of the account's P&L: realized profit on the sales it made, the Candle fees " + "charged against it, and the positions that belong to it (the wallet's bound key, else the key " + "whose buy opened them) with their cost basis, each MARKED at Candle's current price where one " + "exists: `markPriceUsd`, `marketValueUsd` and `unrealizedUsd` per position, and `unrealizedUsd` " + "overall. A position with no price is counted in `unmarkedPositions` and left out of unrealized, " + "never valued at zero; `oldestMarkAt` says how old the marks are. Deposits and withdrawals are " + "excluded: funding a wallet is not profit. Check `unvalued` and `truncated` before quoting the " + "number; they mean the total is partial. The figures come from the account's one P&L read, the " + "same as the console's, to the cent: `pnl.totalUsd` (realized net plus unrealized) is the " + "console's P&L. Positions live in wallets: each wallet is its own average-cost pool, the total " + "is the sum of `pnl.byWallet` (one row per wallet, main or linked, with the linked wallet's " + "label; `unknown:solana` or `unknown:hood` is the row for fills no record places in a wallet), " + "and one token held in two wallets is two positions, each with its `wallet`. A recorded move " + "between the account's wallets carries its cost and realizes nothing. Moved-in positions and " + "`closedPositions` carry additive `transferredIn`, `basisSource` and `basisComplete`; " + "`basisSource` says whether cost comes from Candle history, on-chain trades or the arrival price; " + "`zero-cost` (with `zeroCostQuantity`) is Solana CNDL with no purchase found, carried at $0. " + "Public `realizedFromTransfersUsd` and `unrealizedFromTransfersUsd` are shares already included in " + "total P&L and rank; do not add them again. Moved-in closes count toward W/L and eligibility. Each position carries " + "`agent` (the key it belongs to) and `dust` when it is worth under one cent; dust stays listed " + "and counted, and `openPositionsExDust` leaves it out. `closed` gives the closed positions as " + "`madeUsd` + `lostUsd` + `partialSellsUsd` = `realizedNetUsd`, with `wins` and `losses`. " + "`lookback` and `truncated` are the account's activity bound, and `tradesConsidered` counts this " + "key's ledger fills. Since 2026-10-02 the total is the sum of wallets rather than one pool across " + "them, so realized figures can differ from earlier reads; a server that predates it omits " + "`totalUsd`, `closed`, `openPositionsExDust`, `wallet`, `agent` and `dust`. " + "Every open position and every `byWallet` row (and its positions) carries `chain` ('solana' or " + "'hood'), and `pnl.byChain` has the total's figures over each chain's fills alone, both chains " + "always present, with `openPositions` there a count (the positions are in `pnl.openPositions`, by " + "`chain`). The two chains sum to the total. A " + "server that predates per-chain P&L omits `chain` and `byChain`. While Hyperliquid is enabled, " + "`hyperliquid` separately reports main-perp realized gross, signed funding, fees (inclusive of builder fees), " + "and net = gross + funding - fees, for currently bound EVM TEE wallets since the current TEE binding. " + "Check `read` and `truncated`; historical bindings and unrealized perps are excluded. Reads only.",
1845
1864
  inputSchema: profilePnlShape
1846
- }, async (args) => callAndRelay("candle_get_profile_pnl", args, cfg));
1865
+ }, async (args) => callAndRelay("candle_get_profile_pnl", args, getConfig(), doFetch));
1847
1866
  register("candle_get_profile_trades", {
1848
1867
  title: "Read an agent profile's trade history",
1849
1868
  description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
1850
1869
  inputSchema: profileTradesShape
1851
- }, async (args) => callAndRelay("candle_get_profile_trades", args, cfg));
1870
+ }, async (args) => callAndRelay("candle_get_profile_trades", args, getConfig(), doFetch));
1852
1871
  register("candle_get_portfolio", {
1853
1872
  title: "Read what the account holds, on Solana and Hood",
1854
1873
  description: "Balances and prices for the wallets Candle already knows on this account, on both chains: the " + "embedded wallet and every TEE wallet. Solana wallets are in `embedded` and `tee` (SOL as raw " + "`lamports`, tokens as raw `amountRaw` with `decimals`). Hood wallets are in `hood.embedded` and " + "`hood.tee` (ETH as raw `wei`, ERC-20s including USDG), never in the top-level arrays. Every " + "wallet and holding carries `chain`. Prices are in `prices`: a Solana mint under its own " + "address, Hood ETH under `hood:native`, and a Hood token under `hood:<contract lowercased>`, so " + "lowercase the address before looking it up. An unpriced entry is `priceUsd: null`, never zero; " + "a Hood one says why in `unpricedReason` (no-market-row, unusable-price, stale-mark, " + "source-unavailable), and `hood.unpriced` / `hood.unpricedByReason` count them. A Hood token " + "mark older than six hours is not used, so many Hood tokens read unpriced. A wallet whose read " + "failed has null balances, never zero, and is listed in `unavailable` (Solana) or " + "`hood.unavailable`; check `complete` before quoting a total. `walletsComplete` is false only " + "when the wallet list itself was cut off, and is absent on a server that predates it: then " + "`complete: false` may be a cut-off list or a failed read. Vault and external wallets are " + "not included: Candle does not know their addresses (the Candle CLI's `candle portfolio` reads " + "them over your own RPC). Needs a key with the account:read scope; without it the answer is " + "SCOPE_MISSING. A server that predates Hood in the portfolio omits `hood` and `chain`. Reads " + "only; moves nothing.",
1855
1874
  inputSchema: {}
1856
- }, async () => callAndRelay("candle_get_portfolio", {}, cfg));
1875
+ }, async () => callAndRelay("candle_get_portfolio", {}, getConfig(), doFetch));
1857
1876
  register("candle_resolve_token", {
1858
1877
  title: "Resolve a contract address to a token",
1859
1878
  description: "Turn a bare contract address or mint into Candle's market for it: chain, symbol, " + "decimals, quote asset, and whether Candle can trade it. Start here when a human gives " + "you an address and nothing else. The chain is read off the address's own shape and is " + "not guessed, so it does not need to be supplied. Reads only; moves nothing. A 404 means " + "Candle has no market for that address, which is an answer, not a failure to retry.",
1860
1879
  inputSchema: resolveTokenShape
1861
1880
  }, async (args) => {
1862
- const result = await resolveToken(args, cfg, fetch);
1881
+ const result = await resolveToken(args, getConfig(), doFetch);
1863
1882
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1864
1883
  });
1865
1884
  register("candle_execution_status", {
@@ -1867,7 +1886,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1867
1886
  description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, and this key's own spend limits. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
1868
1887
  inputSchema: {}
1869
1888
  }, async () => {
1870
- const result = await executionStatus(cfg, fetch);
1889
+ const result = await executionStatus(getConfig(), doFetch);
1871
1890
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1872
1891
  });
1873
1892
  register("candle_swap", {
@@ -1886,18 +1905,18 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1886
1905
 
1887
1906
  ` + "This tool spends the embedded wallets. A TEE wallet also bridges, from SOL or USDC to ETH " + "or USDG or back, but only into the same key's TEE wallet on the other chain, with no Candle " + "fee: that runs through `candle swap` with the wallet's bound key, not this tool.",
1888
1907
  inputSchema: swapShape
1889
- }, async (args) => callAndRelay("candle_swap", args, cfg));
1908
+ }, async (args) => callAndRelay("candle_swap", args, getConfig(), doFetch));
1890
1909
  register("candle_transfer", {
1891
1910
  title: "Transfer an asset",
1892
1911
  description: "Move an asset from the account's embedded wallet to one of the account's own wallets, or to an owner-approved withdrawal address. amountRaw 'max' sweeps the spendable balance of that asset.",
1893
1912
  inputSchema: transferShape
1894
- }, async (args) => callAndRelay("candle_transfer", args, cfg));
1913
+ }, async (args) => callAndRelay("candle_transfer", args, getConfig(), doFetch));
1895
1914
  register("candle_sweep", {
1896
1915
  title: "Sweep a wallet",
1897
1916
  description: "Sweep the embedded wallet on one chain to a destination: every base asset (plus any explicitly named mints), one transfer per asset with amountRaw 'max'. Assets with nothing spendable are reported as empty, not errors.",
1898
1917
  inputSchema: sweepShape
1899
1918
  }, async (args) => {
1900
- const result = await executeSweep(args, cfg, fetch);
1919
+ const result = await executeSweep(args, getConfig(), doFetch);
1901
1920
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1902
1921
  });
1903
1922
  register("candle_trade", {
@@ -1920,7 +1939,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1920
1939
  ` + "- If you no longer hold the result, do not re-send to find out what happened. Ask " + "candle_get_operation with the clientTradeId; a 404 there means the trade never reached " + "the rail and nothing moved.",
1921
1940
  inputSchema: tradeShape
1922
1941
  }, async (args) => {
1923
- const result = await executeTrade(args, cfg, fetch);
1942
+ const result = await executeTrade(args, getConfig(), doFetch);
1924
1943
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1925
1944
  });
1926
1945
  register("candle_launch_and_seed", {
@@ -1928,11 +1947,11 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1928
1947
  description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed (in the " + "launch transaction on solana, a best-effort follow-up on hood). Works on every plan, Free included (this is not the atomic launch). Then " + "return the fresh market state and token links in one result. MOVES REAL FUNDS unless " + "dryRun. Seeds above the platform dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH); " + "launch, then top up with candle_trade.",
1929
1948
  inputSchema: launchAndSeedShape
1930
1949
  }, async (args) => {
1931
- const result = await executeLaunchAndSeed(args, cfg, fetch);
1950
+ const result = await executeLaunchAndSeed(args, getConfig(), doFetch);
1932
1951
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1933
1952
  });
1934
1953
  const perpsTool = (tool) => async (args) => {
1935
- const result = await executePerps(tool, args, cfg, env, fetch);
1954
+ const result = await executePerps(tool, args, getConfig(), env, doFetch);
1936
1955
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1937
1956
  };
1938
1957
  const perpsWrite = " MOVES REAL FUNDS on Hyperliquid unless submit is false. Candle builds the action within this key's limits " + "(USD window, maximum leverage, maximum position, slippage and price bands, main-exchange markets only); this " + "server recomputes its hash and checks the action type and Candle's builder before signing, then submits it to " + "Hyperliquid itself. Needs perps:write on the key and CANDLE_KEY_SIGNER_PEM_FILE. A refusal names the limit " + "that refused it (error.limit).";
@@ -1976,7 +1995,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
1976
1995
  description: "MOVES REAL FUNDS unless submit is false. Deposit onto the key's Hyperliquid account through Relay: SOL or " + "USDC from its Solana TEE wallet, or ETH or USDG from its Hood TEE wallet, credited as Hyperliquid perps " + "USDC at the key's EVM TEE wallet's own address (Candle resolves it; no recipient is taken). Refused below a " + "floor that covers Hyperliquid's 1 USDC first-deposit charge. No Candle fee. Needs swap:write and a raw cap " + "on the asset, and CANDLE_KEY_SIGNER_PEM_FILE to sign. There is no withdrawal from Hyperliquid yet.",
1977
1996
  inputSchema: perpsDepositShape
1978
1997
  }, async (args) => {
1979
- const result = await executePerpsDeposit(args, cfg, env, fetch);
1998
+ const result = await executePerpsDeposit(args, getConfig(), env, doFetch);
1980
1999
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
1981
2000
  });
1982
2001
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/mcp",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "description": "MCP server for the Candle agent rail: launch tokens, read markets, report activity",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -46,7 +46,7 @@
46
46
  "clean": "rm -rf dist"
47
47
  },
48
48
  "dependencies": {
49
- "@modelcontextprotocol/sdk": "1.22.0",
49
+ "@modelcontextprotocol/sdk": "1.26.0",
50
50
  "@noble/hashes": "1.8.0",
51
51
  "zod": "^3.23.0"
52
52
  },