Quickstart
TONNode sells direct access to The Open Network. Two products are live: MCP — an MCP server that gives an AI agent 16 tools for reading TON and building non-custodial swap transactions — and LiteServers (shown as Nodes in the console) — private ADNL endpoints that your own program talks to with standard TON libraries, no SDK fork and no HTTP gateway in the middle. One account, one balance: you connect a TON wallet, top the balance up on-chain, pick a plan, and use the key.
Which product do I need
| If you are… | Use | You talk to it with | Status |
|---|---|---|---|
| Giving Claude, ChatGPT, Cursor, Codex or your own agent the ability to read TON and prepare swaps | MCP | Any MCP client, over Streamable HTTP | Live |
| Writing a backend, bot, indexer or wallet that queries TON itself | LiteServers | ton-lite-client (JS), tonutils-go (Go), pytoniq (Python), tonlib |
Live |
| Streaming pending transactions before they land in a block | Mempool | — | Not launched. Nothing to sign up for yet. |
| Querying pre-indexed data over REST/JSON | REST API | — | Not launched. Nothing to sign up for yet. |
TON has no canonical JSON-RPC. What other chains call "RPC" is, on TON, either an HTTP wrapper (toncenter-style) or the native ADNL liteserver protocol. TONNode sells the native path.
The two are not alternatives to each other. MCP is a tool surface for a model: it decides which of the 16 tools to call and reads the JSON back. LiteServers are a transport for your code: you make the calls, you parse the cells. Plenty of accounts buy both.
1. Connect a wallet
Go to tonnode.io/dashboard and connect a TON wallet. Signing the connection proof is the login — there is no email, no password, no confirmation link. The wallet you connect owns the account, its balance and its keys. Signing in through Telegram works too, and either identity can be linked to the same account afterwards.
2. Top up and pick a plan
Everything is paid from a single USD-denominated balance. Top it up:
- GRAM or USDT on-chain, from the connected wallet;
- xRocket, inside Telegram, if you would rather not touch a wallet UI.
Then choose a plan on the product tab.
MCP — a plan is a rate limit, nothing else. There is no monthly request quota to run out of and no per-call charge; you are buying requests-per-minute.
| Plan | Price | Rate limit | Keys |
|---|---|---|---|
| Hobby | $0 | 60 rpm | 1 |
| Pro | $29 / mo | 300 rpm | unlimited |
| Scale | $199 / mo | 1200 rpm | unlimited |
LiteServers — three ways to pay. The scope is the real decision: Litenode is served by a light node, Litenode + Archive adds a node that holds all of history (see History and the archive tier below).
- Free tier — Litenode at 1 RPS with 100,000 requests per 30 days. No card, no balance required.
- Pay as you go — $0.01 per 1,000 Litenode requests (up to 30 RPS) or $0.02 per 1,000 Archive requests (up to 20 RPS), charged from your balance. The balance never expires.
- Reserved capacity — a fixed RPS ceiling for 30 days, no per-request charges:
| Plan | Scope | Rate | Price |
|---|---|---|---|
| Litenode · 10 RPS | mainnet | 10 RPS | $20 / 30 days |
| Litenode · 60 RPS | mainnet | 60 RPS | $90 / 30 days |
| Litenode · 450 RPS | mainnet | 450 RPS | $490 / 30 days |
| Litenode + Archive · 20 RPS | mainnet + archive | 20 RPS | $90 / 30 days |
| Litenode + Archive · 60 RPS | mainnet + archive | 60 RPS | $150 / 30 days |
| Litenode + Archive · 450 RPS | mainnet + archive | 450 RPS | $800 / 30 days |
Every plan includes the light node; the archive column adds the history the light node no longer holds, which is the whole difference in price. Free, pay-as-you-go, and all six reserved plans are self-serve. Anything beyond 450 RPS, or a dedicated node, is arranged through support.
Two things about the purchase are worth knowing. Buying is finished only when the gateway confirms it is serving your key: if provisioning fails the charge is reversed in the same operation, so you are never left paid-up with a credential nothing honours. And renewing before you expire adds to the time you have left rather than overwriting it, on the same keypair — your global.config.json keeps working across a renewal.
3. Use the key
What you receive depends on the product:
- MCP → an API key (
tn_live_…). Send it as a bearer token to the hosted endpoint. Revoking a key is not quite instantaneous: the endpoint re-reads its key registry on a five-second file poll, and closes the live sessions of any key that has disappeared. - LiteServers → a
global.config.jsonyou download from the Nodes tab. It is a standard mainnet config whoseliteserversarray has been replaced with two entries — both the gateway's address, both carrying an Ed25519 public key issued to you alone. Two rather than one because every TON client library treats that array as a failover pool and a single-entry list leaves it nowhere to retry. The liteserver protocol has no client authentication, so that key is your identity: the gateway terminates your ADNL connection, recognises which key you handshook against, and routes you to a light or an archive node according to your plan. Treat the file as a credential — anyone holding it is billed as you. There is no self-serve way to rotate it; if it leaks, talk to support.
MCP in 60 seconds
Hosted endpoint (nothing to install)
This is the paid product: https://mcp.tonnode.io/mcp, with your key in a header. Clients disagree about how to spell that, so take the block that matches yours — the same four are on the MCP tab of the console, ready to copy.
Cursor (.cursor/mcp.json) and Claude Code (.mcp.json in the project root) talk to a remote server directly:
{
"mcpServers": {
"ton": {
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer <your key>" }
}
}
}Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json, Settings → Developer) cannot: its config file launches local processes and has no field for a remote URL, so the connection is bridged with mcp-remote. Note the header argument has no space after the colon — Claude Desktop on Windows does not escape spaces inside args.
{
"mcpServers": {
"ton": {
"command": "npx",
"args": [
"-y", "mcp-remote", "https://mcp.tonnode.io/mcp",
"--header", "Authorization:Bearer <your key>"
]
}
}
}VS Code (.vscode/mcp.json) uses servers, not mcpServers. A copied mcpServers block is silently ignored:
{
"servers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer <your key>" }
}
}
}Codex CLI uses TOML, and reads the key from an environment variable rather than the file — merge this into ~/.codex/config.toml:
[mcp_servers.ton]
url = "https://mcp.tonnode.io/mcp"
bearer_token_env_var = "TONNODE_KEY"Two header spellings are accepted besides Authorization: Bearer <key> — a bare Authorization: <key> and X-API-Key: <key> — because some gateways reserve the Authorization header for themselves.
Local (stdio)
npx -y @tonnode/mcp runs the same tool surface as a child process of your MCP client, and it is worth being blunt about what that is: stdio mode has no authentication and no notion of your account. It starts a local server that connects to the public liteserver config, so your key is neither needed nor used, and none of the traffic reaches TONNode's nodes. It is the free, self-hosted path — useful for trying the tools out, subject to whatever the public network gives you that day. If you are paying, use the hosted endpoint above.
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}Requires Node.js 18 or newer; npx fetches the package on first run. The published version is @tonnode/mcp 0.9.1.
Check the key works
The endpoint speaks Streamable HTTP, so the first request of a session must be initialize, and the Accept header must list both media types or the transport answers 406.
curl -i https://mcp.tonnode.io/mcp \
-H "Authorization: Bearer $TONNODE_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "curl", "version": "1.0" }
}
}'A working key returns 200 with an SSE frame carrying the server info, plus an mcp-session-id response header — pass that header on every later request in the session. A bad or expired key returns 401 {"error":"invalid, missing or expired API key"}.
Call a tool from Node.js
No MCP client app involved — just the official SDK.
npm install @modelcontextprotocol/sdk// tonnode-http.mjs — node tonnode-http.mjs
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.tonnode.io/mcp"),
{ requestInit: { headers: { Authorization: `Bearer ${process.env.TONNODE_KEY}` } } }
);
const client = new Client({ name: "quickstart", version: "1.0.0" });
await client.connect(transport);
const { tools } = await client.listTools();
console.log(tools.length, "tools:", tools.map((t) => t.name).join(", "));
// The Elector — a masterchain contract that always exists.
const res = await client.callTool({
name: "get_balance",
arguments: { address: "-1:3333333333333333333333333333333333333333333333333333333333333333" },
});
console.log(JSON.parse(res.content[0].text));
await client.close();Expected output: 16 tools: … followed by { address, balance_gram, balance_nano, at_seqno }.
What the 16 tools cover
| Group | Tools |
|---|---|
| Chain reads (8) | get_masterchain_info, get_balance, get_account_state, get_transactions, run_get_method, get_jetton_balance, get_jetton_info, parse_address |
| DEX swaps (2) | get_swap_quote, build_swap_tx |
| Cross-chain swaps (5) | get_crosschain_quote, build_crosschain_swap_tx, track_crosschain_swap, disclose_crosschain_secret, build_crosschain_refund |
| Wallets (1) | generate_wallet |
Eleven of the sixteen are declared read-only to the client. The five that are not say so deliberately, because a client that auto-approves read-only tools must still stop at these: build_swap_tx, build_crosschain_swap_tx and build_crosschain_refund return armed, fund-moving artifacts, disclose_crosschain_secret is marked destructive, and generate_wallet mints secret key material and returns something different every call.
The swap tools are strictly non-custodial: they return unsigned messages in the shape tonConnectUi.sendTransaction() expects — the server never signs and never broadcasts, and holds no key of yours. (generate_wallet is the one place a private key exists at all: it is generated on the spot, returned to you in the response, never logged and never stored.) Full arguments, outputs and failure modes live in the Tool reference.
LiteServers in 60 seconds
Download global.config.json from the Nodes tab of the console. It is an ordinary TON global config, so every standard client library loads it unmodified — you are not adopting a TONNode SDK.
JavaScript / TypeScript — ton-lite-client
npm install ton-lite-client @ton/core// balance.mjs — node balance.mjs
import { readFileSync } from "node:fs";
import { Address, fromNano } from "@ton/core";
import { LiteClient, LiteRoundRobinEngine, LiteSingleEngine } from "ton-lite-client";
const config = JSON.parse(readFileSync("./global.config.json", "utf-8"));
// The global config stores IPv4 addresses as signed 32-bit integers.
const toIp = (n) => {
const u = n < 0 ? n + 2 ** 32 : n;
return [(u >>> 24) & 255, (u >>> 16) & 255, (u >>> 8) & 255, u & 255].join(".");
};
const engine = new LiteRoundRobinEngine(
config.liteservers.map(
(s) =>
new LiteSingleEngine({
host: `tcp://${toIp(s.ip)}:${s.port}`,
publicKey: Buffer.from(s.id.key, "base64"),
})
)
);
const client = new LiteClient({ engine });
const master = await client.getMasterchainInfo();
console.log("masterchain seqno:", master.last.seqno);
const elector = Address.parse("-1:3333333333333333333333333333333333333333333333333333333333333333");
const state = await client.getAccountState(elector, master.last);
console.log("balance:", fromNano(state.balance.coins), "GRAM");
engine.close(); // otherwise the open sockets keep the process aliveKeep the round-robin wrapper even with one server in the list. A LiteSingleEngine is closed from the moment you construct it until its socket finishes the ADNL handshake and fires connected, so querying one directly on the next line throws Engine is closed; LiteRoundRobinEngine is what waits for an engine to become ready before it dispatches.
Go — tonutils-go
go get github.com/xssnick/tonutils-go@v1.17.1 # needs Go 1.25+package main
import (
"context"
"log"
"time"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/liteclient"
"github.com/xssnick/tonutils-go/ton"
)
const elector = "Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"
func main() {
ctx := context.Background()
pool := liteclient.NewConnectionPool()
if err := pool.AddConnectionsFromConfigFile("global.config.json"); err != nil {
log.Fatal(err)
}
defer pool.Stop()
api := ton.NewAPIClient(pool).WithRetryTimeout(3, 10*time.Second)
master, err := api.CurrentMasterchainInfo(ctx)
if err != nil {
log.Fatal(err)
}
log.Println("masterchain seqno:", master.SeqNo)
acc, err := api.GetAccount(ctx, master, address.MustParseAddr(elector))
if err != nil {
log.Fatal(err)
}
// Check this before touching acc.State. A never-deployed address comes back
// as a non-nil *tlb.Account with IsActive false and State nil, so reading
// acc.State.Balance straight off the result panics on exactly the addresses
// you are most likely to be checking.
if !acc.IsActive {
log.Println("account is not deployed")
return
}
log.Println("balance:", acc.State.Balance.String(), "GRAM")
}address.MustParseAddr takes the friendly EQ…/UQ…/Ef… form; for the raw 0:… form use address.ParseRawAddr (or address.MustParseRawAddr).
Python — pytoniq
pip install pytoniq# balance.py — python balance.py
import asyncio, json
from pytoniq import LiteBalancer
ELECTOR = "-1:3333333333333333333333333333333333333333333333333333333333333333"
async def main():
with open("global.config.json") as f:
config = json.load(f)
client = LiteBalancer.from_config(config, trust_level=2)
await client.start_up()
try:
master = await client.get_masterchain_info()
print("masterchain seqno:", master["last"]["seqno"])
account = await client.get_account_state(ELECTOR)
print("balance:", account.balance / 1e9, "GRAM")
finally:
await client.close_all()
asyncio.run(main())History and the archive tier
The light node behind the gateway keeps a rolling window of recent history — measured in days, not weeks (about two days at the masterchain's current block rate when last measured). There is no fixed number to quote: the node prunes as it goes, so the gateway probes each light node at startup, finds the oldest block it still answers for, and sets its archive-routing threshold from that. Anything older lives only on the archive node, which holds the chain's full history back to the genesis block — nothing there is ever pruned.
The gateway sorts every liteserver method into three classes:
- current-only — the request carries no block, no
ltand noutime, so it cannot reach the past however it is called (getMasterchainInfo,getTime,getVersion,sendMessage,getLibraries, …). The light node always answers; no plan check applies. - depth decided per request — the request carries a block id or a mode-gated
lt/utime, so whether it needs archive depends on which block. The samegetAccountStateis a tip read at the head and a historical read at seqno 78,000,000. Most methods are here. - always archive —
getBlockProofwalks a key-block chain that may start at seqno 1, andgetStateis refused by the node itself above seqno 1000. Both reach back whatever their arguments.
On a plan without archive, a query that needs history the light node no longer holds comes back as liteserver error 403 — archive history is not included in this plan. It is a clean error, not a dropped socket, precisely so you can tell the difference between "wrong plan" and "network problem". The other refusals share that shape:
- 403 —
method not supported. The method table is exhaustive and the default is refusal, so a liteserver method the gateway does not recognise never reaches a node at all: unknown cost, unknown reach, nothing to bound it with. - 429 —
too many requests. You exceeded your plan's rate. The bucket is weighted rather than flat — a whole-block or proof-chain read costs many times what a clock read does — so a burst of heavy queries hits this sooner than the same number of balance lookups. - 429 —
too many concurrent requests for this key. A key's in-flight allowance scales with its plan — half its RPS, never below 8 and never above 128 — so a 60 RPS plan may hold 30 queries open at once and a 450 RPS plan 128. That is several times more than sustaining the plan's rate actually requires; see Concurrency. Client libraries multiplex over a single TCP connection, so this is a cap on queries in flight, not on connections. - 429 —
gateway is saturated, retry shortly. The gateway allows 256 queries in flight across all customers. - 502 —
backend node timeout. The query reached a node and the node did not answer in time.
getTransactions is the awkward case worth knowing about: it carries an lt but no block id, and the node walks prev_trans_lt backwards from there — on a quiet account that walk can cross years. It is bounded by the node's own storage rather than by inspection, so on a non-archive plan a deep walk simply stops with the node's own error.
Not launched
Mempool (pending-transaction stream) and the REST API (pre-indexed data over HTTP) are not shipping. They share one tab in the console, and it does nothing but point you at support — there is no waitlist to submit and nothing there mints a key. Anything you read elsewhere describing their endpoints, quotas or latency is describing something that does not exist yet.
Where to go next
- MCP → Overview and setup — the endpoint, the client configs in full, and what is and is not launched.
- MCP → Tool reference — all 16 tools: arguments, output shape, failure modes.
- LiteServers — the config file, the method classes, and what each plan may reach.
Mempool and REST API chapters are listed in the sidebar but not written, because the products behind them do not exist yet.
Source and package: github.com/tonnode · npmjs.com/package/@tonnode/mcp
