@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.
- package/dist/index.js +51 -32
- 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.
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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", {},
|
|
1802
|
-
const res = await
|
|
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,
|
|
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", {},
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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", {},
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
49
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
50
50
|
"@noble/hashes": "1.8.0",
|
|
51
51
|
"zod": "^3.23.0"
|
|
52
52
|
},
|