@tongateway/mcp 0.14.0 → 0.19.2
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/README.md +56 -18
- package/dist/index.js +204 -21
- package/dist/worker.js +127 -5
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ openclaw config set --strict-json plugins.entries.acpx.config.mcpServers '{
|
|
|
52
52
|
}'
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
No token needed upfront — the agent authenticates via `
|
|
55
|
+
No token needed upfront — the agent authenticates via `auth.request` (generates a one-time link, user connects wallet). Token persists in `~/.tongateway/token` across restarts.
|
|
56
56
|
|
|
57
57
|
## Tools
|
|
58
58
|
|
|
@@ -60,48 +60,55 @@ No token needed upfront — the agent authenticates via `request_auth` (generate
|
|
|
60
60
|
|
|
61
61
|
| Tool | Description |
|
|
62
62
|
|------|-------------|
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
63
|
+
| `auth.request` | Generate a one-time link for wallet connection |
|
|
64
|
+
| `auth.get_token` | Retrieve token after user connects wallet |
|
|
65
65
|
|
|
66
66
|
### Wallet
|
|
67
67
|
|
|
68
68
|
| Tool | Description |
|
|
69
69
|
|------|-------------|
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
70
|
+
| `wallet.info` | Wallet address, TON balance, account status |
|
|
71
|
+
| `wallet.jettons` | All token balances (USDT, NOT, DOGS, etc.) |
|
|
72
|
+
| `wallet.transactions` | Recent transaction history |
|
|
73
|
+
| `wallet.nfts` | NFTs owned by the wallet |
|
|
74
74
|
|
|
75
75
|
### Transfers (Safe — requires wallet approval)
|
|
76
76
|
|
|
77
77
|
| Tool | Description |
|
|
78
78
|
|------|-------------|
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
79
|
+
| `transfer.request` | Request a TON transfer (to, amountNano, payload?, stateInit?) |
|
|
80
|
+
| `transfer.status` | Check transfer status by ID |
|
|
81
|
+
| `transfer.pending` | List all pending requests |
|
|
82
82
|
|
|
83
83
|
### Lookup
|
|
84
84
|
|
|
85
85
|
| Tool | Description |
|
|
86
86
|
|------|-------------|
|
|
87
|
-
| `resolve_name` | Resolve .ton domain to address |
|
|
88
|
-
| `
|
|
87
|
+
| `lookup.resolve_name` | Resolve .ton domain to address |
|
|
88
|
+
| `lookup.price` | Current TON price in USD/EUR |
|
|
89
|
+
|
|
90
|
+
### DEX (open4dev order book)
|
|
91
|
+
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `dex.create_order` | Place a limit order (fromToken, toToken, amount, price) |
|
|
95
|
+
| `dex.pairs` | List available trading pairs |
|
|
89
96
|
|
|
90
97
|
### Agent Wallet (Autonomous — no approval needed)
|
|
91
98
|
|
|
92
99
|
| Tool | Description |
|
|
93
100
|
|------|-------------|
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
101
|
+
| `agent_wallet.deploy` | Deploy a dedicated wallet contract for the agent |
|
|
102
|
+
| `agent_wallet.transfer` | Send TON directly from agent wallet |
|
|
103
|
+
| `agent_wallet.info` | Balance, seqno, agent key status |
|
|
97
104
|
|
|
98
105
|
## How it works
|
|
99
106
|
|
|
100
107
|
```
|
|
101
108
|
You: "Send 1 TON to alice.ton"
|
|
102
109
|
|
|
103
|
-
Agent: resolve_name("alice.ton") → 0:83df...
|
|
104
|
-
|
|
110
|
+
Agent: lookup.resolve_name("alice.ton") → 0:83df...
|
|
111
|
+
transfer.request(to="0:83df...", amountNano="1000000000")
|
|
105
112
|
→ Transfer request created. Approve in your wallet app.
|
|
106
113
|
```
|
|
107
114
|
|
|
@@ -110,10 +117,39 @@ For agent wallets (autonomous mode):
|
|
|
110
117
|
```
|
|
111
118
|
You: "Send 0.5 TON from my agent wallet to 0:abc..."
|
|
112
119
|
|
|
113
|
-
Agent:
|
|
120
|
+
Agent: agent_wallet.transfer(wallet, to, amount)
|
|
114
121
|
→ Transfer executed. No approval needed.
|
|
115
122
|
```
|
|
116
123
|
|
|
124
|
+
## Build from Source
|
|
125
|
+
|
|
126
|
+
If you prefer not to use `npx`, you can build and run locally:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/tongateway/mcp
|
|
130
|
+
cd mcp
|
|
131
|
+
npm install
|
|
132
|
+
npm run build
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then configure your MCP client to use the local build:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"tongateway": {
|
|
141
|
+
"command": "node",
|
|
142
|
+
"args": ["/path/to/mcp/dist/index.js"],
|
|
143
|
+
"env": {
|
|
144
|
+
"AGENT_GATEWAY_API_URL": "https://api.tongateway.ai"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
See [SECURITY.md](SECURITY.md) for the full security model.
|
|
152
|
+
|
|
117
153
|
## Links
|
|
118
154
|
|
|
119
155
|
- [tongateway.ai](https://tongateway.ai) — landing page + install guides
|
|
@@ -121,6 +157,8 @@ Agent: execute_agent_wallet_transfer(wallet, to, amount)
|
|
|
121
157
|
- [API Docs](https://api.tongateway.ai/docs) — Swagger UI
|
|
122
158
|
- [Agent Wallet Contract](https://github.com/tongateway/ton-agent-gateway-contract) — FunC smart contract
|
|
123
159
|
- [Skill File](https://tongateway.ai/agent-gateway.md) — context file for AI agents
|
|
160
|
+
- [Smithery](https://smithery.ai/servers/tongateway/agent) — MCP marketplace listing
|
|
161
|
+
- [MCP HTTP Endpoint](https://tongateway.run.tools) — remote MCP transport
|
|
124
162
|
|
|
125
163
|
## License
|
|
126
164
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Handle --help and --version before importing anything
|
|
3
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
4
|
+
console.log(`@tongateway/mcp — TON blockchain gateway for AI agents
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
npx @tongateway/mcp Start MCP server (stdio transport)
|
|
8
|
+
npx @tongateway/mcp --http Start HTTP server (port 3100)
|
|
9
|
+
|
|
10
|
+
Environment variables:
|
|
11
|
+
AGENT_GATEWAY_API_URL API base URL (default: https://api.tongateway.ai)
|
|
12
|
+
AGENT_GATEWAY_TOKEN Pre-configured auth token (optional)
|
|
13
|
+
MCP_HTTP_PORT HTTP server port (default: 3100 with --http)
|
|
14
|
+
|
|
15
|
+
Tools (16):
|
|
16
|
+
auth.request, auth.get_token
|
|
17
|
+
wallet.info, wallet.jettons, wallet.transactions, wallet.nfts
|
|
18
|
+
transfer.request, transfer.status, transfer.pending
|
|
19
|
+
lookup.resolve_name, lookup.price
|
|
20
|
+
dex.create_order, dex.pairs
|
|
21
|
+
agent_wallet.deploy, agent_wallet.transfer, agent_wallet.info
|
|
22
|
+
|
|
23
|
+
Docs: https://tongateway.ai/docs
|
|
24
|
+
GitHub: https://github.com/tongateway/mcp`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
28
|
+
console.log('0.15.0');
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
2
31
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
32
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
33
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -69,7 +98,7 @@ const server = new McpServer({
|
|
|
69
98
|
name: 'agent-gateway',
|
|
70
99
|
version: '0.1.0',
|
|
71
100
|
});
|
|
72
|
-
server.tool('auth.request', 'Authenticate with TON blockchain. Call this FIRST if you get "No token configured" errors.
|
|
101
|
+
server.tool('auth.request', 'Authenticate with TON blockchain. Call this tool FIRST if you get "No token configured" errors. It returns a URL that you MUST display to the user as a clickable link. The user opens it in their browser to connect their wallet. After they confirm, call auth.get_token with the authId. Do NOT use curl or fetch — use this MCP tool. Do NOT poll in a loop — just call auth.get_token once after the user says they connected.', {
|
|
73
102
|
label: z.string().optional().describe('Label for this agent session (e.g. "claude-agent")'),
|
|
74
103
|
}, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ label }) => {
|
|
75
104
|
try {
|
|
@@ -86,15 +115,15 @@ server.tool('auth.request', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
86
115
|
{
|
|
87
116
|
type: 'text',
|
|
88
117
|
text: [
|
|
89
|
-
`
|
|
118
|
+
`IMPORTANT: Show this link to the user NOW:`,
|
|
90
119
|
``,
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
`👉 ${data.authUrl}`,
|
|
121
|
+
``,
|
|
122
|
+
`Tell the user: "Open this link and connect your wallet. Let me know when done."`,
|
|
93
123
|
``,
|
|
94
124
|
`Auth ID: ${data.authId}`,
|
|
95
|
-
`Expires: ${new Date(data.expiresAt).toISOString()}`,
|
|
96
125
|
``,
|
|
97
|
-
`
|
|
126
|
+
`When the user confirms they connected, call auth.get_token with authId "${data.authId}"`,
|
|
98
127
|
].join('\n'),
|
|
99
128
|
},
|
|
100
129
|
],
|
|
@@ -165,12 +194,13 @@ server.tool('auth.get_token', 'Complete authentication after the user opened the
|
|
|
165
194
|
};
|
|
166
195
|
}
|
|
167
196
|
});
|
|
168
|
-
server.tool('transfer.request', '
|
|
169
|
-
to: z.string().describe('Destination TON address'),
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
server.tool('transfer.request', 'Send a TON transfer. The user approves in their wallet app. Use human-readable amounts (e.g. amount="1.5"). Supports .ton domain names directly (e.g. to="alice.ton"). The API handles name resolution and decimal conversion automatically.', {
|
|
198
|
+
to: z.string().describe('Destination: TON address or .ton domain (e.g. "alice.ton" or "EQD...abc")'),
|
|
199
|
+
amount: z.string().describe('Human-readable amount (e.g. "1.5" for 1.5 TON, "100" for 100 TON)'),
|
|
200
|
+
comment: z.string().optional().describe('Text comment/message to attach (e.g. "Payment for services")'),
|
|
201
|
+
payload: z.string().optional().describe('Optional BOC-encoded payload (advanced — use comment for text)'),
|
|
202
|
+
stateInit: z.string().optional().describe('Optional stateInit BOC for contract deployment'),
|
|
203
|
+
}, { title: 'Request Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ to, amount, comment, payload, stateInit }) => {
|
|
174
204
|
if (!TOKEN) {
|
|
175
205
|
return {
|
|
176
206
|
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
@@ -178,7 +208,9 @@ server.tool('transfer.request', 'Request a TON transfer. The user must approve i
|
|
|
178
208
|
};
|
|
179
209
|
}
|
|
180
210
|
try {
|
|
181
|
-
const body = { to,
|
|
211
|
+
const body = { to, amount };
|
|
212
|
+
if (comment)
|
|
213
|
+
body.comment = comment;
|
|
182
214
|
if (payload)
|
|
183
215
|
body.payload = payload;
|
|
184
216
|
if (stateInit)
|
|
@@ -194,13 +226,13 @@ server.tool('transfer.request', 'Request a TON transfer. The user must approve i
|
|
|
194
226
|
text: [
|
|
195
227
|
`Transfer request created.`,
|
|
196
228
|
`ID: ${result.id}`,
|
|
197
|
-
`To: ${result.to}`,
|
|
198
|
-
`Amount: ${result.amountNano} nanoTON`,
|
|
229
|
+
`To: ${result.resolved ? `${result.resolved.from} → ${result.resolved.to}` : result.to}`,
|
|
230
|
+
`Amount: ${amount} TON (${result.amountNano} nanoTON)`,
|
|
199
231
|
`Status: ${result.status}`,
|
|
200
|
-
`
|
|
232
|
+
comment ? `Comment: ${comment}` : null,
|
|
201
233
|
``,
|
|
202
|
-
`
|
|
203
|
-
].join('\n'),
|
|
234
|
+
`Approve in your wallet app.`,
|
|
235
|
+
].filter(Boolean).join('\n'),
|
|
204
236
|
},
|
|
205
237
|
],
|
|
206
238
|
};
|
|
@@ -283,6 +315,56 @@ server.tool('transfer.pending', 'List all transfer requests waiting for wallet o
|
|
|
283
315
|
};
|
|
284
316
|
}
|
|
285
317
|
});
|
|
318
|
+
server.tool('transfer.batch', 'Send multiple TON transfers in a SINGLE wallet approval. Up to 4 transfers per batch (v4 wallet). All transfers appear as one transaction to sign. Use for batch payments, multi-recipient sends, or multiple DEX orders at once.', {
|
|
319
|
+
transfers: z.string().describe('JSON array of transfers: [{"to":"addr","amountNano":"1000000000","comment":"optional text"},...]'),
|
|
320
|
+
}, { title: 'Batch Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ transfers: transfersJson }) => {
|
|
321
|
+
if (!TOKEN) {
|
|
322
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const transfers = JSON.parse(transfersJson);
|
|
326
|
+
if (!transfers.length)
|
|
327
|
+
throw new Error('No transfers provided');
|
|
328
|
+
if (transfers.length > 4)
|
|
329
|
+
throw new Error('Max 4 transfers per batch (v4 wallet). Use agent_wallet.batch_transfer for more.');
|
|
330
|
+
// Encode comments as payloads
|
|
331
|
+
const processed = [];
|
|
332
|
+
for (const t of transfers) {
|
|
333
|
+
const entry = { to: t.to, amountNano: t.amountNano };
|
|
334
|
+
if (t.comment) {
|
|
335
|
+
const { beginCell } = await import('@ton/core');
|
|
336
|
+
const commentCell = beginCell().storeUint(0, 32).storeStringTail(t.comment).endCell();
|
|
337
|
+
entry.payload = commentCell.toBoc().toString('base64');
|
|
338
|
+
}
|
|
339
|
+
processed.push(entry);
|
|
340
|
+
}
|
|
341
|
+
const result = await apiCall('/v1/safe/tx/batch', {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
body: JSON.stringify({ transfers: processed }),
|
|
344
|
+
});
|
|
345
|
+
const totalNano = BigInt(result.batch?.totalNano ?? '0');
|
|
346
|
+
const totalTon = (totalNano / 1000000000n).toString() + '.' +
|
|
347
|
+
(totalNano % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '') || '0';
|
|
348
|
+
return {
|
|
349
|
+
content: [{
|
|
350
|
+
type: 'text',
|
|
351
|
+
text: [
|
|
352
|
+
`Batch transfer created! ${transfers.length} transfers in 1 approval.`,
|
|
353
|
+
'',
|
|
354
|
+
`Total: ${totalTon} TON`,
|
|
355
|
+
`Request ID: ${result.id}`,
|
|
356
|
+
'',
|
|
357
|
+
...transfers.map((t, i) => ` ${i + 1}. ${t.amountNano} nanoTON → ${t.to.slice(0, 16)}...${t.comment ? ` "${t.comment}"` : ''}`),
|
|
358
|
+
'',
|
|
359
|
+
'Approve in your wallet app — one signature for all transfers.',
|
|
360
|
+
].join('\n'),
|
|
361
|
+
}],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
catch (e) {
|
|
365
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
366
|
+
}
|
|
367
|
+
});
|
|
286
368
|
server.tool('wallet.info', 'Get the connected wallet address, TON balance (in nanoTON and TON), and account status. Use this to check how much TON the user has before sending transfers.', {}, { title: 'Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => {
|
|
287
369
|
if (!TOKEN) {
|
|
288
370
|
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
@@ -413,11 +495,11 @@ server.tool('lookup.price', 'Get the current TON price in USD, EUR, or other cur
|
|
|
413
495
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
414
496
|
}
|
|
415
497
|
});
|
|
416
|
-
server.tool('dex.create_order', 'Place a limit order on the open4dev DEX order book.
|
|
498
|
+
server.tool('dex.create_order', 'Place a limit order on the open4dev DEX order book. Both amount and price are human-readable — the API converts to raw units automatically. The order requires wallet approval. Slippage (4% including fees) is applied automatically.', {
|
|
417
499
|
fromToken: z.string().describe('Token to sell, e.g. "NOT", "TON", "USDT"'),
|
|
418
500
|
toToken: z.string().describe('Token to buy, e.g. "TON", "NOT", "AGNT"'),
|
|
419
|
-
amount: z.string().describe('
|
|
420
|
-
price: z.number().describe('Human-readable price: how many toToken per 1 fromToken. E.g. price=20 means "1 USDT = 20 AGNT". price=0.
|
|
501
|
+
amount: z.string().describe('Human-readable amount to sell, e.g. "10000" for 10,000 NOT or "5" for 5 USDT'),
|
|
502
|
+
price: z.number().describe('Human-readable price: how many toToken per 1 fromToken. E.g. price=20 means "1 USDT = 20 AGNT". price=0.000289 means "1 NOT = 0.000289 TON".'),
|
|
421
503
|
}, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ fromToken, toToken, amount, price }) => {
|
|
422
504
|
if (!TOKEN) {
|
|
423
505
|
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
@@ -644,6 +726,107 @@ server.tool('agent_wallet.transfer', 'Send TON directly from an Agent Wallet —
|
|
|
644
726
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
645
727
|
}
|
|
646
728
|
});
|
|
729
|
+
server.tool('agent_wallet.batch_transfer', 'Send multiple TON transfers in a SINGLE transaction from an Agent Wallet — NO approval needed. All transfers are executed atomically in one external message. Max 255 actions per transaction.', {
|
|
730
|
+
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
731
|
+
transfers: z.string().describe('JSON array of transfers: [{"to":"addr","amountNano":"1000000000"},...]'),
|
|
732
|
+
}, { title: 'Batch Transfer from Agent Wallet', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ walletAddress, transfers: transfersJson }) => {
|
|
733
|
+
if (!TOKEN) {
|
|
734
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
|
|
738
|
+
const { sign, keyPairFromSeed } = await import('@ton/crypto');
|
|
739
|
+
const transfers = JSON.parse(transfersJson);
|
|
740
|
+
if (!transfers.length)
|
|
741
|
+
throw new Error('No transfers provided');
|
|
742
|
+
if (transfers.length > 255)
|
|
743
|
+
throw new Error('Max 255 transfers per batch');
|
|
744
|
+
const localWallets = loadLocalWallets();
|
|
745
|
+
const localConfig = localWallets[walletAddress];
|
|
746
|
+
if (!localConfig)
|
|
747
|
+
throw new Error('Agent wallet secret key not found locally.');
|
|
748
|
+
const infoResult = await apiCall(`/v1/agent-wallet/${encodeURIComponent(walletAddress)}/info`);
|
|
749
|
+
const seqno = infoResult.seqno;
|
|
750
|
+
const walletId = localConfig.walletId;
|
|
751
|
+
const secretKeyBuf = Buffer.from(localConfig.agentSecretKey, 'hex');
|
|
752
|
+
let secretKey;
|
|
753
|
+
if (secretKeyBuf.length === 64) {
|
|
754
|
+
secretKey = secretKeyBuf;
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
const kp = keyPairFromSeed(secretKeyBuf);
|
|
758
|
+
secretKey = kp.secretKey;
|
|
759
|
+
}
|
|
760
|
+
// Build actions list with all transfers
|
|
761
|
+
const sendMode = SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS;
|
|
762
|
+
let actionsList = beginCell().endCell(); // empty tail
|
|
763
|
+
for (const t of transfers) {
|
|
764
|
+
const destAddr = Address.parse(t.to);
|
|
765
|
+
const transferMsg = beginCell()
|
|
766
|
+
.storeUint(0x18, 6)
|
|
767
|
+
.storeAddress(destAddr)
|
|
768
|
+
.storeCoins(BigInt(t.amountNano))
|
|
769
|
+
.storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
770
|
+
.endCell();
|
|
771
|
+
actionsList = beginCell()
|
|
772
|
+
.storeRef(actionsList)
|
|
773
|
+
.storeUint(0x0ec3c86d, 32)
|
|
774
|
+
.storeUint(sendMode, 8)
|
|
775
|
+
.storeRef(transferMsg)
|
|
776
|
+
.endCell();
|
|
777
|
+
}
|
|
778
|
+
// Build and sign external message
|
|
779
|
+
const validUntil = Math.floor(Date.now() / 1000) + 300;
|
|
780
|
+
const unsignedBody = beginCell()
|
|
781
|
+
.storeUint(0x7369676e, 32)
|
|
782
|
+
.storeUint(walletId, 32)
|
|
783
|
+
.storeUint(validUntil, 32)
|
|
784
|
+
.storeUint(seqno, 32)
|
|
785
|
+
.storeMaybeRef(actionsList)
|
|
786
|
+
.endCell();
|
|
787
|
+
const signature = sign(unsignedBody.hash(), secretKey);
|
|
788
|
+
const signedBody = beginCell()
|
|
789
|
+
.storeSlice(unsignedBody.beginParse())
|
|
790
|
+
.storeBuffer(signature)
|
|
791
|
+
.endCell();
|
|
792
|
+
const vaultAddr = Address.parse(walletAddress);
|
|
793
|
+
const extMsg = external({ to: vaultAddr, body: signedBody });
|
|
794
|
+
const boc = beginCell().store(storeMessage(extMsg)).endCell().toBoc().toString('base64');
|
|
795
|
+
// Broadcast
|
|
796
|
+
const broadcastRes = await fetch('https://toncenter.com/api/v2/sendBoc', {
|
|
797
|
+
method: 'POST',
|
|
798
|
+
headers: { 'Content-Type': 'application/json' },
|
|
799
|
+
body: JSON.stringify({ boc }),
|
|
800
|
+
});
|
|
801
|
+
const broadcastData = await broadcastRes.json();
|
|
802
|
+
if (!broadcastData.ok) {
|
|
803
|
+
throw new Error(`Broadcast failed: ${broadcastData.error || JSON.stringify(broadcastData)}`);
|
|
804
|
+
}
|
|
805
|
+
const totalNano = transfers.reduce((sum, t) => sum + BigInt(t.amountNano), 0n);
|
|
806
|
+
const totalTon = (totalNano / 1000000000n).toString() + '.' +
|
|
807
|
+
(totalNano % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '');
|
|
808
|
+
return {
|
|
809
|
+
content: [{
|
|
810
|
+
type: 'text',
|
|
811
|
+
text: [
|
|
812
|
+
`Batch transfer executed! ${transfers.length} transfers in 1 transaction.`,
|
|
813
|
+
'',
|
|
814
|
+
`From: ${walletAddress}`,
|
|
815
|
+
`Total: ${totalTon} TON`,
|
|
816
|
+
`Transfers: ${transfers.length}`,
|
|
817
|
+
`Seqno: ${seqno}`,
|
|
818
|
+
'',
|
|
819
|
+
...transfers.map((t, i) => ` ${i + 1}. ${t.amountNano} nanoTON → ${t.to.slice(0, 12)}...`),
|
|
820
|
+
'',
|
|
821
|
+
'All broadcast successfully. No approval needed.',
|
|
822
|
+
].join('\n'),
|
|
823
|
+
}],
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
catch (e) {
|
|
827
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
828
|
+
}
|
|
829
|
+
});
|
|
647
830
|
server.tool('agent_wallet.info', 'Get info about Agent Wallets — balance, seqno, agent key status. Pass a wallet address for details, or omit to list all agent wallets.', {
|
|
648
831
|
walletAddress: z.string().optional().describe('Agent wallet address. If omitted, lists all your agent wallets.'),
|
|
649
832
|
}, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ walletAddress }) => {
|
package/dist/worker.js
CHANGED
|
@@ -32,10 +32,10 @@ function createMcpServer() {
|
|
|
32
32
|
server.tool('lookup.resolve_name', 'Resolve a .ton domain name (e.g. "alice.ton") to a raw wallet address. Always use this before transfer.request when the user provides a .ton name.', { domain: z.string().describe('The .ton domain name to resolve, e.g. "alice.ton" or "foundation.ton"') }, { title: 'Resolve .ton Name', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
33
33
|
server.tool('lookup.price', 'Get the current price of TON in USD, EUR, or other fiat currencies. Use to show users the value of their holdings.', { currencies: z.string().optional().describe('Comma-separated currency codes, e.g. "USD,EUR". Default: "USD"') }, { title: 'TON Price', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
34
34
|
// --- DEX ---
|
|
35
|
-
server.tool('dex.create_order', 'Place a limit order on the open4dev DEX order book.
|
|
35
|
+
server.tool('dex.create_order', 'Place a limit order on the open4dev DEX order book. Both amount and price are human-readable. The API converts to raw units, handles slippage (4% including fees), and gas automatically.', {
|
|
36
36
|
fromToken: z.string().describe('Token to sell: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0'),
|
|
37
37
|
toToken: z.string().describe('Token to buy: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0'),
|
|
38
|
-
amount: z.string().describe('
|
|
38
|
+
amount: z.string().describe('Human-readable amount to sell, e.g. "10000" for 10,000 NOT or "5" for 5 USDT'),
|
|
39
39
|
price: z.number().describe('Human-readable price: how many toToken per 1 fromToken. E.g. price=20 means "1 USDT = 20 AGNT".'),
|
|
40
40
|
}, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
41
41
|
server.tool('dex.pairs', 'List all available trading pairs and tokens on the open4dev DEX. Returns supported tokens: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0.', {}, { title: 'DEX Pairs', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
@@ -53,16 +53,138 @@ function createMcpServer() {
|
|
|
53
53
|
role: 'user',
|
|
54
54
|
content: {
|
|
55
55
|
type: 'text',
|
|
56
|
-
text:
|
|
56
|
+
text: `# Quick Start
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
\`\`\`bash
|
|
60
|
+
claude mcp add-json tongateway '{"command":"npx","args":["-y","@tongateway/mcp"],"env":{"AGENT_GATEWAY_API_URL":"https://api.tongateway.ai"}}' --scope user
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
## First use
|
|
64
|
+
The agent authenticates automatically — it generates a link, you open it and connect your wallet once. Token persists across restarts.
|
|
65
|
+
|
|
66
|
+
## Try these commands:
|
|
67
|
+
- "What's my TON balance?"
|
|
68
|
+
- "Show my tokens"
|
|
69
|
+
- "Send 1 TON to alice.ton"
|
|
70
|
+
- "What's the current TON price?"
|
|
71
|
+
- "Show my NFTs"`,
|
|
57
72
|
},
|
|
58
73
|
}],
|
|
59
74
|
}));
|
|
60
|
-
server.prompt('token-reference', 'Amount conversion
|
|
75
|
+
server.prompt('token-reference', 'Amount conversion and token decimals reference', async () => ({
|
|
61
76
|
messages: [{
|
|
62
77
|
role: 'user',
|
|
63
78
|
content: {
|
|
64
79
|
type: 'text',
|
|
65
|
-
text:
|
|
80
|
+
text: `# Token Reference
|
|
81
|
+
|
|
82
|
+
## Amount conversion (nanoTON)
|
|
83
|
+
| TON | nanoTON |
|
|
84
|
+
|-------|----------------|
|
|
85
|
+
| 0.1 | 100000000 |
|
|
86
|
+
| 0.5 | 500000000 |
|
|
87
|
+
| 1 | 1000000000 |
|
|
88
|
+
| 10 | 10000000000 |
|
|
89
|
+
|
|
90
|
+
## Token decimals
|
|
91
|
+
- 9 decimals: TON, NOT, DOGS, BUILD, AGNT, PX, CBBTC
|
|
92
|
+
- 6 decimals: USDT, XAUT0
|
|
93
|
+
|
|
94
|
+
## DEX price format
|
|
95
|
+
Price is human-readable: price=20 means "1 fromToken = 20 toToken"
|
|
96
|
+
Example: USDT→AGNT at price=20 means "1 USDT = 20 AGNT"`,
|
|
97
|
+
},
|
|
98
|
+
}],
|
|
99
|
+
}));
|
|
100
|
+
server.prompt('example-transfer', 'Example: Send TON to a .ton domain with price check', async () => ({
|
|
101
|
+
messages: [{
|
|
102
|
+
role: 'user',
|
|
103
|
+
content: {
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `# Example: Send TON to alice.ton
|
|
106
|
+
|
|
107
|
+
## Step 1: Check balance
|
|
108
|
+
wallet.info()
|
|
109
|
+
→ Address: 0:9d43...0c02, Balance: 823.18 TON, Status: active
|
|
110
|
+
|
|
111
|
+
## Step 2: Check price
|
|
112
|
+
lookup.price({ currencies: "USD" })
|
|
113
|
+
→ 1 TON = $2.45 USD
|
|
114
|
+
|
|
115
|
+
## Step 3: Resolve .ton domain
|
|
116
|
+
lookup.resolve_name({ domain: "alice.ton" })
|
|
117
|
+
→ alice.ton → 0:83df...31a8
|
|
118
|
+
|
|
119
|
+
## Step 4: Send transfer
|
|
120
|
+
transfer.request({ to: "0:83df...31a8", amountNano: "500000000" })
|
|
121
|
+
→ Transfer request created (ID: abc-123). Approve in your wallet app.
|
|
122
|
+
|
|
123
|
+
## Step 5: Check status
|
|
124
|
+
transfer.status({ id: "abc-123" })
|
|
125
|
+
→ Status: confirmed, Broadcast: success`,
|
|
126
|
+
},
|
|
127
|
+
}],
|
|
128
|
+
}));
|
|
129
|
+
server.prompt('example-dex-order', 'Example: Place a DEX order to swap tokens', async () => ({
|
|
130
|
+
messages: [{
|
|
131
|
+
role: 'user',
|
|
132
|
+
content: {
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: `# Example: Swap 10,000 NOT for TON
|
|
135
|
+
|
|
136
|
+
## Step 1: Check available tokens
|
|
137
|
+
dex.pairs()
|
|
138
|
+
→ Available tokens: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0
|
|
139
|
+
|
|
140
|
+
## Step 2: Check your NOT balance
|
|
141
|
+
wallet.jettons()
|
|
142
|
+
→ NOT: 3,186,370.60, USDT: 107.79, BUILD: 45,277.57
|
|
143
|
+
|
|
144
|
+
## Step 3: Get current price
|
|
145
|
+
lookup.price({ currencies: "USD" })
|
|
146
|
+
→ 1 TON = $2.45 USD
|
|
147
|
+
(NOT ≈ 0.000289 TON per NOT)
|
|
148
|
+
|
|
149
|
+
## Step 4: Place order
|
|
150
|
+
dex.create_order({
|
|
151
|
+
fromToken: "NOT",
|
|
152
|
+
toToken: "TON",
|
|
153
|
+
amount: "10000", // human-readable, API converts to raw
|
|
154
|
+
price: 0.000289 // TON per NOT
|
|
155
|
+
})
|
|
156
|
+
→ Order placed! Approve in your wallet app.`,
|
|
157
|
+
},
|
|
158
|
+
}],
|
|
159
|
+
}));
|
|
160
|
+
server.prompt('example-agent-wallet', 'Example: Deploy and use an autonomous Agent Wallet', async () => ({
|
|
161
|
+
messages: [{
|
|
162
|
+
role: 'user',
|
|
163
|
+
content: {
|
|
164
|
+
type: 'text',
|
|
165
|
+
text: `# Example: Autonomous Agent Wallet
|
|
166
|
+
|
|
167
|
+
⚠️ WARNING: Agent Wallet allows spending WITHOUT approval!
|
|
168
|
+
|
|
169
|
+
## Step 1: Deploy
|
|
170
|
+
agent_wallet.deploy()
|
|
171
|
+
→ Agent Wallet deployed at EQCT1... Approve 0.1 TON deploy fee in wallet.
|
|
172
|
+
|
|
173
|
+
## Step 2: Top up
|
|
174
|
+
transfer.request({ to: "EQCT1...", amountNano: "1000000000" })
|
|
175
|
+
→ Transfer 1 TON to agent wallet. Approve in wallet.
|
|
176
|
+
|
|
177
|
+
## Step 3: Send from agent wallet (NO approval needed!)
|
|
178
|
+
agent_wallet.transfer({
|
|
179
|
+
walletAddress: "0:93d4...",
|
|
180
|
+
to: "0:abc...",
|
|
181
|
+
amountNano: "500000000"
|
|
182
|
+
})
|
|
183
|
+
→ Transfer executed. No approval needed.
|
|
184
|
+
|
|
185
|
+
## Check balance
|
|
186
|
+
agent_wallet.info({ walletAddress: "0:93d4..." })
|
|
187
|
+
→ Balance: 0.5 TON, Seqno: 1, Status: active`,
|
|
66
188
|
},
|
|
67
189
|
}],
|
|
68
190
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tongateway/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.2",
|
|
4
4
|
"description": "TON blockchain gateway for AI agents — 16 MCP tools: wallet info, transfers, jettons, NFTs, DNS, prices, DEX orders, agent wallets",
|
|
5
5
|
"homepage": "https://tongateway.ai",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsc",
|
|
28
|
-
"dev": "tsx src/index.ts"
|
|
28
|
+
"dev": "tsx src/index.ts",
|
|
29
|
+
"test": "vitest run"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"@types/node": "^22.17.2",
|
|
39
40
|
"tsx": "^4.20.5",
|
|
40
41
|
"typescript": "^5.9.2",
|
|
42
|
+
"vitest": "^4.1.1",
|
|
41
43
|
"wrangler": "^4.77.0"
|
|
42
44
|
}
|
|
43
45
|
}
|