@tongateway/mcp 0.13.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 +59 -19
- package/dist/index.js +243 -60
- package/dist/worker.d.ts +8 -0
- package/dist/worker.js +260 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @tongateway/mcp
|
|
2
2
|
|
|
3
|
+
[](https://smithery.ai/servers/tongateway/agent)
|
|
4
|
+
|
|
3
5
|
MCP server for [Agent Gateway](https://tongateway.ai) — gives AI agents full access to the TON blockchain via Model Context Protocol.
|
|
4
6
|
|
|
5
|
-
**
|
|
7
|
+
**16 tools:** wallet info, jettons, NFTs, transactions, transfers, .ton DNS, prices, DEX orders, agent wallets, and more.
|
|
6
8
|
|
|
7
9
|
## Quick Start
|
|
8
10
|
|
|
@@ -50,7 +52,7 @@ openclaw config set --strict-json plugins.entries.acpx.config.mcpServers '{
|
|
|
50
52
|
}'
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
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.
|
|
54
56
|
|
|
55
57
|
## Tools
|
|
56
58
|
|
|
@@ -58,48 +60,55 @@ No token needed upfront — the agent authenticates via `request_auth` (generate
|
|
|
58
60
|
|
|
59
61
|
| Tool | Description |
|
|
60
62
|
|------|-------------|
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
+
| `auth.request` | Generate a one-time link for wallet connection |
|
|
64
|
+
| `auth.get_token` | Retrieve token after user connects wallet |
|
|
63
65
|
|
|
64
66
|
### Wallet
|
|
65
67
|
|
|
66
68
|
| Tool | Description |
|
|
67
69
|
|------|-------------|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
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 |
|
|
72
74
|
|
|
73
75
|
### Transfers (Safe — requires wallet approval)
|
|
74
76
|
|
|
75
77
|
| Tool | Description |
|
|
76
78
|
|------|-------------|
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
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 |
|
|
80
82
|
|
|
81
83
|
### Lookup
|
|
82
84
|
|
|
83
85
|
| Tool | Description |
|
|
84
86
|
|------|-------------|
|
|
85
|
-
| `resolve_name` | Resolve .ton domain to address |
|
|
86
|
-
| `
|
|
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 |
|
|
87
96
|
|
|
88
97
|
### Agent Wallet (Autonomous — no approval needed)
|
|
89
98
|
|
|
90
99
|
| Tool | Description |
|
|
91
100
|
|------|-------------|
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
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 |
|
|
95
104
|
|
|
96
105
|
## How it works
|
|
97
106
|
|
|
98
107
|
```
|
|
99
108
|
You: "Send 1 TON to alice.ton"
|
|
100
109
|
|
|
101
|
-
Agent: resolve_name("alice.ton") → 0:83df...
|
|
102
|
-
|
|
110
|
+
Agent: lookup.resolve_name("alice.ton") → 0:83df...
|
|
111
|
+
transfer.request(to="0:83df...", amountNano="1000000000")
|
|
103
112
|
→ Transfer request created. Approve in your wallet app.
|
|
104
113
|
```
|
|
105
114
|
|
|
@@ -108,10 +117,39 @@ For agent wallets (autonomous mode):
|
|
|
108
117
|
```
|
|
109
118
|
You: "Send 0.5 TON from my agent wallet to 0:abc..."
|
|
110
119
|
|
|
111
|
-
Agent:
|
|
120
|
+
Agent: agent_wallet.transfer(wallet, to, amount)
|
|
112
121
|
→ Transfer executed. No approval needed.
|
|
113
122
|
```
|
|
114
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
|
+
|
|
115
153
|
## Links
|
|
116
154
|
|
|
117
155
|
- [tongateway.ai](https://tongateway.ai) — landing page + install guides
|
|
@@ -119,6 +157,8 @@ Agent: execute_agent_wallet_transfer(wallet, to, amount)
|
|
|
119
157
|
- [API Docs](https://api.tongateway.ai/docs) — Swagger UI
|
|
120
158
|
- [Agent Wallet Contract](https://github.com/tongateway/ton-agent-gateway-contract) — FunC smart contract
|
|
121
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
|
|
122
162
|
|
|
123
163
|
## License
|
|
124
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,9 +98,9 @@ const server = new McpServer({
|
|
|
69
98
|
name: 'agent-gateway',
|
|
70
99
|
version: '0.1.0',
|
|
71
100
|
});
|
|
72
|
-
server.tool('
|
|
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
|
-
}, async ({ label }) => {
|
|
103
|
+
}, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ label }) => {
|
|
75
104
|
try {
|
|
76
105
|
const result = await fetch(`${API_URL}/v1/auth/request`, {
|
|
77
106
|
method: 'POST',
|
|
@@ -86,15 +115,15 @@ server.tool('request_auth', '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:`,
|
|
119
|
+
``,
|
|
120
|
+
`👉 ${data.authUrl}`,
|
|
90
121
|
``,
|
|
91
|
-
`
|
|
92
|
-
data.authUrl,
|
|
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
|
],
|
|
@@ -107,9 +136,9 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
107
136
|
};
|
|
108
137
|
}
|
|
109
138
|
});
|
|
110
|
-
server.tool('
|
|
111
|
-
authId: z.string().describe('The authId returned by
|
|
112
|
-
}, async ({ authId }) => {
|
|
139
|
+
server.tool('auth.get_token', 'Complete authentication after the user opened the link from auth.request. Pass the authId you received. Once successful, all other tools become available. You only need to authenticate once — the token is saved automatically.', {
|
|
140
|
+
authId: z.string().describe('The authId returned by auth.request'),
|
|
141
|
+
}, { title: 'Get Auth Token', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ authId }) => {
|
|
113
142
|
try {
|
|
114
143
|
// Retry up to 3 times with 2s delay (KV eventual consistency)
|
|
115
144
|
let data = null;
|
|
@@ -134,7 +163,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
134
163
|
`Authentication still pending.`,
|
|
135
164
|
`The user has not connected their wallet yet.`,
|
|
136
165
|
``,
|
|
137
|
-
`Wait a moment and call
|
|
166
|
+
`Wait a moment and call auth.get_token again.`,
|
|
138
167
|
].join('\n'),
|
|
139
168
|
},
|
|
140
169
|
],
|
|
@@ -152,7 +181,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
152
181
|
`Token received and configured.`,
|
|
153
182
|
`Wallet: ${data.address}`,
|
|
154
183
|
``,
|
|
155
|
-
`You can now use
|
|
184
|
+
`You can now use transfer.request, transfer.status, and transfer.pending.`,
|
|
156
185
|
].join('\n'),
|
|
157
186
|
},
|
|
158
187
|
],
|
|
@@ -165,20 +194,23 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
165
194
|
};
|
|
166
195
|
}
|
|
167
196
|
});
|
|
168
|
-
server.tool('
|
|
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
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
206
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
177
207
|
isError: true,
|
|
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('request_transfer', '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
|
};
|
|
@@ -212,12 +244,12 @@ server.tool('request_transfer', 'Request a TON transfer. The user must approve i
|
|
|
212
244
|
};
|
|
213
245
|
}
|
|
214
246
|
});
|
|
215
|
-
server.tool('
|
|
216
|
-
id: z.string().describe('The request ID returned by
|
|
217
|
-
}, async ({ id }) => {
|
|
247
|
+
server.tool('transfer.status', 'Check the status of a transfer request. Statuses: pending (waiting for approval), confirmed (signed and broadcast), rejected (user declined), expired (5 min timeout). Also shows broadcast result if available.', {
|
|
248
|
+
id: z.string().describe('The request ID returned by transfer.request'),
|
|
249
|
+
}, { title: 'Transfer Status', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ id }) => {
|
|
218
250
|
if (!TOKEN) {
|
|
219
251
|
return {
|
|
220
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
252
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
221
253
|
isError: true,
|
|
222
254
|
};
|
|
223
255
|
}
|
|
@@ -251,10 +283,10 @@ server.tool('get_request_status', 'Check the status of a transfer request. Statu
|
|
|
251
283
|
};
|
|
252
284
|
}
|
|
253
285
|
});
|
|
254
|
-
server.tool('
|
|
286
|
+
server.tool('transfer.pending', 'List all transfer requests waiting for wallet owner approval. Use to check if there are unfinished transfers.', {}, { title: 'Pending Transfers', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => {
|
|
255
287
|
if (!TOKEN) {
|
|
256
288
|
return {
|
|
257
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
289
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
258
290
|
isError: true,
|
|
259
291
|
};
|
|
260
292
|
}
|
|
@@ -283,9 +315,59 @@ server.tool('list_pending_requests', 'List all transfer requests waiting for wal
|
|
|
283
315
|
};
|
|
284
316
|
}
|
|
285
317
|
});
|
|
286
|
-
server.tool('
|
|
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
|
+
});
|
|
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
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
370
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
289
371
|
}
|
|
290
372
|
try {
|
|
291
373
|
const result = await apiCall('/v1/wallet/balance');
|
|
@@ -306,9 +388,9 @@ server.tool('get_wallet_info', 'Get the connected wallet address, TON balance (i
|
|
|
306
388
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
307
389
|
}
|
|
308
390
|
});
|
|
309
|
-
server.tool('
|
|
391
|
+
server.tool('wallet.jettons', 'List all tokens (jettons) in the wallet — USDT, NOT, DOGS, and others. Shows symbol, name, balance, and decimals for each. Use this when the user asks about their tokens or wants to know what they hold.', {}, { title: 'Jetton Balances', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => {
|
|
310
392
|
if (!TOKEN) {
|
|
311
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
393
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
312
394
|
}
|
|
313
395
|
try {
|
|
314
396
|
const result = await apiCall('/v1/wallet/jettons');
|
|
@@ -331,11 +413,11 @@ server.tool('get_jetton_balances', 'List all tokens (jettons) in the wallet —
|
|
|
331
413
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
332
414
|
}
|
|
333
415
|
});
|
|
334
|
-
server.tool('
|
|
416
|
+
server.tool('wallet.transactions', 'Get recent transaction history. Shows timestamps, action types, and whether transactions were flagged as scam. Use when the user asks "what happened" or wants to review recent activity.', {
|
|
335
417
|
limit: z.number().optional().describe('Number of transactions to return (default 10)'),
|
|
336
|
-
}, async ({ limit }) => {
|
|
418
|
+
}, { title: 'Transaction History', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ limit }) => {
|
|
337
419
|
if (!TOKEN) {
|
|
338
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
420
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
339
421
|
}
|
|
340
422
|
try {
|
|
341
423
|
const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
|
|
@@ -356,9 +438,9 @@ server.tool('get_transactions', 'Get recent transaction history. Shows timestamp
|
|
|
356
438
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
357
439
|
}
|
|
358
440
|
});
|
|
359
|
-
server.tool('
|
|
441
|
+
server.tool('wallet.nfts', 'List all NFTs owned by the wallet — name, collection, and address for each. Use when the user asks about their NFTs or collectibles.', {}, { title: 'NFT Items', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => {
|
|
360
442
|
if (!TOKEN) {
|
|
361
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
443
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
362
444
|
}
|
|
363
445
|
try {
|
|
364
446
|
const result = await apiCall('/v1/wallet/nfts');
|
|
@@ -375,9 +457,9 @@ server.tool('get_nft_items', 'List all NFTs owned by the wallet — name, collec
|
|
|
375
457
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
376
458
|
}
|
|
377
459
|
});
|
|
378
|
-
server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a raw wallet address. ALWAYS use this before
|
|
460
|
+
server.tool('lookup.resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a raw wallet address. ALWAYS use this before transfer.request when the user gives a .ton name instead of a raw address.', {
|
|
379
461
|
domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
|
|
380
|
-
}, async ({ domain }) => {
|
|
462
|
+
}, { title: 'Resolve .ton Name', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ domain }) => {
|
|
381
463
|
try {
|
|
382
464
|
const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
|
|
383
465
|
const data = await result.json();
|
|
@@ -394,9 +476,9 @@ server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a
|
|
|
394
476
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
395
477
|
}
|
|
396
478
|
});
|
|
397
|
-
server.tool('
|
|
479
|
+
server.tool('lookup.price', 'Get the current TON price in USD, EUR, or other currencies. Use when the user asks "how much is my TON worth" or before transfers to show USD equivalents.', {
|
|
398
480
|
currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
|
|
399
|
-
}, async ({ currencies }) => {
|
|
481
|
+
}, { title: 'TON Price', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ currencies }) => {
|
|
400
482
|
try {
|
|
401
483
|
const curr = currencies || 'USD';
|
|
402
484
|
const result = await fetch(`${API_URL}/v1/market/price?tokens=TON¤cies=${curr}`);
|
|
@@ -413,14 +495,14 @@ server.tool('get_ton_price', 'Get the current TON price in USD, EUR, or other cu
|
|
|
413
495
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
414
496
|
}
|
|
415
497
|
});
|
|
416
|
-
server.tool('
|
|
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.
|
|
421
|
-
}, async ({ fromToken, toToken, amount, price }) => {
|
|
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".'),
|
|
503
|
+
}, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ fromToken, toToken, amount, price }) => {
|
|
422
504
|
if (!TOKEN) {
|
|
423
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
505
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
424
506
|
}
|
|
425
507
|
try {
|
|
426
508
|
const result = await apiCall('/v1/dex/order', {
|
|
@@ -448,7 +530,7 @@ server.tool('create_dex_order', 'Place a limit order on the open4dev DEX order b
|
|
|
448
530
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
449
531
|
}
|
|
450
532
|
});
|
|
451
|
-
server.tool('
|
|
533
|
+
server.tool('dex.pairs', 'List available trading pairs on the DEX. Shows which token swaps are configured and available.', {}, { title: 'DEX Pairs', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => {
|
|
452
534
|
try {
|
|
453
535
|
const result = await fetch(`${API_URL}/v1/dex/pairs`);
|
|
454
536
|
const data = await result.json();
|
|
@@ -464,9 +546,9 @@ server.tool('list_dex_pairs', 'List available trading pairs on the DEX. Shows wh
|
|
|
464
546
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
465
547
|
}
|
|
466
548
|
});
|
|
467
|
-
server.tool('
|
|
549
|
+
server.tool('agent_wallet.deploy', 'Deploy an Agent Wallet smart contract — a dedicated sub-wallet for autonomous operations. WARNING: The agent can spend funds from this wallet WITHOUT user approval. Only deploy if the user explicitly wants autonomous transfers. After deployment, top up the wallet with funds.', {}, { title: 'Deploy Agent Wallet', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async () => {
|
|
468
550
|
if (!TOKEN) {
|
|
469
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
551
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
470
552
|
}
|
|
471
553
|
try {
|
|
472
554
|
// Get owner's public key
|
|
@@ -547,13 +629,13 @@ server.tool('deploy_agent_wallet', 'Deploy an Agent Wallet smart contract — a
|
|
|
547
629
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
548
630
|
}
|
|
549
631
|
});
|
|
550
|
-
server.tool('
|
|
632
|
+
server.tool('agent_wallet.transfer', 'Send TON directly from an Agent Wallet — NO approval needed. The agent signs and broadcasts immediately. Only works with deployed agent wallets. Use for automated/autonomous transfers where speed matters and the user has opted in.', {
|
|
551
633
|
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
552
634
|
to: z.string().describe('Destination TON address'),
|
|
553
635
|
amountNano: z.string().describe('Amount in nanoTON'),
|
|
554
|
-
}, async ({ walletAddress, to, amountNano }) => {
|
|
636
|
+
}, { title: 'Agent Wallet Transfer', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ walletAddress, to, amountNano }) => {
|
|
555
637
|
if (!TOKEN) {
|
|
556
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
638
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
557
639
|
}
|
|
558
640
|
try {
|
|
559
641
|
const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
|
|
@@ -644,11 +726,112 @@ server.tool('execute_agent_wallet_transfer', 'Send TON directly from an Agent Wa
|
|
|
644
726
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
645
727
|
}
|
|
646
728
|
});
|
|
647
|
-
server.tool('
|
|
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
|
+
});
|
|
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
|
-
}, async ({ walletAddress }) => {
|
|
832
|
+
}, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ walletAddress }) => {
|
|
650
833
|
if (!TOKEN) {
|
|
651
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
834
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
652
835
|
}
|
|
653
836
|
try {
|
|
654
837
|
if (!walletAddress) {
|
|
@@ -656,7 +839,7 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
|
|
|
656
839
|
const result = await apiCall('/v1/agent-wallet/list');
|
|
657
840
|
const wallets = result.wallets || [];
|
|
658
841
|
if (!wallets.length) {
|
|
659
|
-
return { content: [{ type: 'text', text: 'No agent wallets found. Use
|
|
842
|
+
return { content: [{ type: 'text', text: 'No agent wallets found. Use agent_wallet.deploy to create one.' }] };
|
|
660
843
|
}
|
|
661
844
|
const lines = wallets.map((w) => `- ${w.address} (created ${new Date(w.createdAt).toISOString()})`);
|
|
662
845
|
return {
|
|
@@ -700,7 +883,7 @@ if (httpPort) {
|
|
|
700
883
|
// Health check
|
|
701
884
|
if (req.method === 'GET' && req.url === '/health') {
|
|
702
885
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
703
|
-
res.end(JSON.stringify({ ok: true, version: '0.
|
|
886
|
+
res.end(JSON.stringify({ ok: true, version: '0.14.0' }));
|
|
704
887
|
return;
|
|
705
888
|
}
|
|
706
889
|
// MCP endpoint
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker — MCP HTTP transport for Smithery and remote clients.
|
|
3
|
+
* Mirrors all 16 tools from the stdio version with full descriptions and annotations.
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: {
|
|
6
|
+
fetch(request: Request): Promise<Response>;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker — MCP HTTP transport for Smithery and remote clients.
|
|
3
|
+
* Mirrors all 16 tools from the stdio version with full descriptions and annotations.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
const VERSION = '0.14.0';
|
|
9
|
+
function createMcpServer() {
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: 'tongateway',
|
|
12
|
+
version: VERSION,
|
|
13
|
+
});
|
|
14
|
+
// --- Auth ---
|
|
15
|
+
server.tool('auth.request', 'Authenticate with TON blockchain. Generates a one-time link for the user to connect their wallet. After the user opens the link, call auth.get_token to complete authentication.', { label: z.string().optional().describe('Label for this agent session, e.g. "claude-agent"') }, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport (npx @tongateway/mcp) for full tool execution.' }] }));
|
|
16
|
+
server.tool('auth.get_token', 'Complete authentication after the user opened the link from auth.request. Returns the token which enables all other tools.', { authId: z.string().describe('The authId returned by auth.request') }, { title: 'Get Auth Token', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
17
|
+
// --- Wallet ---
|
|
18
|
+
server.tool('wallet.info', 'Get the connected wallet address, TON balance in nanoTON and human-readable format, and account status (active/uninitialized).', {}, { title: 'Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
19
|
+
server.tool('wallet.jettons', 'List all jetton (token) balances in the wallet — USDT, NOT, DOGS, BUILD, and others. Returns symbol, name, balance, and decimals for each token.', {}, { title: 'Jetton Balances', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
20
|
+
server.tool('wallet.transactions', 'Get recent transaction history for the connected wallet. Shows timestamps, action types, and scam flags.', { limit: z.number().optional().describe('Number of transactions to return (default 10, max 100)') }, { title: 'Transaction History', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
21
|
+
server.tool('wallet.nfts', 'List all NFTs owned by the connected wallet — name, collection name, and contract address for each.', {}, { title: 'NFT Items', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
22
|
+
// --- Transfers ---
|
|
23
|
+
server.tool('transfer.request', 'Request a TON transfer that the wallet owner must approve. Amount is in nanoTON (1 TON = 1000000000). Supports optional payload BOC and stateInit for contract deployment. Use transfer.status to check approval.', {
|
|
24
|
+
to: z.string().describe('Destination TON address (raw format 0:abc... or friendly EQ...)'),
|
|
25
|
+
amountNano: z.string().describe('Amount in nanoTON. 1 TON = 1000000000 nanoTON'),
|
|
26
|
+
payload: z.string().optional().describe('Optional BOC-encoded payload for the transaction'),
|
|
27
|
+
stateInit: z.string().optional().describe('Optional stateInit BOC for deploying new smart contracts'),
|
|
28
|
+
}, { title: 'Request Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
29
|
+
server.tool('transfer.status', 'Check the status of a transfer request. Returns: pending (waiting for approval), confirmed (signed and broadcast), rejected (user declined), or expired (5 min timeout). Also shows broadcast result if available.', { id: z.string().describe('The request ID returned by transfer.request') }, { title: 'Transfer Status', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
30
|
+
server.tool('transfer.pending', 'List all transfer requests currently waiting for wallet owner approval.', {}, { title: 'Pending Transfers', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
31
|
+
// --- Lookup ---
|
|
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
|
+
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
|
+
// --- DEX ---
|
|
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
|
+
fromToken: z.string().describe('Token to sell: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0'),
|
|
37
|
+
toToken: z.string().describe('Token to buy: TON, NOT, USDT, DOGS, BUILD, AGNT, CBBTC, PX, XAUT0'),
|
|
38
|
+
amount: z.string().describe('Human-readable amount to sell, e.g. "10000" for 10,000 NOT or "5" for 5 USDT'),
|
|
39
|
+
price: z.number().describe('Human-readable price: how many toToken per 1 fromToken. E.g. price=20 means "1 USDT = 20 AGNT".'),
|
|
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
|
+
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.' }] }));
|
|
42
|
+
// --- Agent Wallet ---
|
|
43
|
+
server.tool('agent_wallet.deploy', 'Deploy an Agent Wallet smart contract — a dedicated sub-wallet for autonomous transfers without approval. WARNING: The agent can spend all funds in this wallet without user confirmation. Only deploy when the user explicitly requests autonomous mode.', {}, { title: 'Deploy Agent Wallet', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
44
|
+
server.tool('agent_wallet.transfer', 'Send TON directly from an Agent Wallet — NO approval needed. Signs and broadcasts the transaction immediately. Only works with deployed agent wallets where the agent key is authorized.', {
|
|
45
|
+
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
46
|
+
to: z.string().describe('Destination TON address'),
|
|
47
|
+
amountNano: z.string().describe('Amount in nanoTON (1 TON = 1000000000)'),
|
|
48
|
+
}, { title: 'Agent Wallet Transfer', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
49
|
+
server.tool('agent_wallet.info', 'Get info about Agent Wallets — balance, seqno, and agent key status. Pass a wallet address for details, or omit to list all your agent wallets.', { walletAddress: z.string().optional().describe('Agent wallet address. Omit to list all wallets.') }, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async () => ({ content: [{ type: 'text', text: 'Use stdio transport for full tool execution.' }] }));
|
|
50
|
+
// --- Prompts ---
|
|
51
|
+
server.prompt('quickstart', 'How to get started with Agent Gateway on TON blockchain', async () => ({
|
|
52
|
+
messages: [{
|
|
53
|
+
role: 'user',
|
|
54
|
+
content: {
|
|
55
|
+
type: '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"`,
|
|
72
|
+
},
|
|
73
|
+
}],
|
|
74
|
+
}));
|
|
75
|
+
server.prompt('token-reference', 'Amount conversion and token decimals reference', async () => ({
|
|
76
|
+
messages: [{
|
|
77
|
+
role: 'user',
|
|
78
|
+
content: {
|
|
79
|
+
type: '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`,
|
|
188
|
+
},
|
|
189
|
+
}],
|
|
190
|
+
}));
|
|
191
|
+
// --- Resources ---
|
|
192
|
+
server.resource('docs', 'https://tongateway.ai/docs', async (uri) => ({
|
|
193
|
+
contents: [{
|
|
194
|
+
uri: uri.href,
|
|
195
|
+
mimeType: 'text/plain',
|
|
196
|
+
text: 'Agent Gateway documentation: https://tongateway.ai/docs\nAPI Swagger: https://api.tongateway.ai/docs\nSkill file: https://tongateway.ai/agent-gateway.md',
|
|
197
|
+
}],
|
|
198
|
+
}));
|
|
199
|
+
server.resource('skill', 'https://tongateway.ai/agent-gateway.md', async (uri) => ({
|
|
200
|
+
contents: [{
|
|
201
|
+
uri: uri.href,
|
|
202
|
+
mimeType: 'text/plain',
|
|
203
|
+
text: 'Skill file with all 16 tool descriptions, usage examples, and amount conversion: https://tongateway.ai/agent-gateway.md',
|
|
204
|
+
}],
|
|
205
|
+
}));
|
|
206
|
+
return server;
|
|
207
|
+
}
|
|
208
|
+
const CORS_HEADERS = {
|
|
209
|
+
'Access-Control-Allow-Origin': '*',
|
|
210
|
+
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
211
|
+
'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization, Mcp-Session-Id',
|
|
212
|
+
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
213
|
+
};
|
|
214
|
+
export default {
|
|
215
|
+
async fetch(request) {
|
|
216
|
+
const url = new URL(request.url);
|
|
217
|
+
if (request.method === 'OPTIONS') {
|
|
218
|
+
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
|
219
|
+
}
|
|
220
|
+
if (request.method === 'GET' && (url.pathname === '/' || url.pathname === '/health')) {
|
|
221
|
+
return new Response(JSON.stringify({
|
|
222
|
+
ok: true,
|
|
223
|
+
name: '@tongateway/mcp',
|
|
224
|
+
version: VERSION,
|
|
225
|
+
description: 'TON blockchain gateway for AI agents — 16 tools for wallet info, transfers, jettons, NFTs, DNS, prices, DEX orders, and agent wallets.',
|
|
226
|
+
tools: 16,
|
|
227
|
+
prompts: 2,
|
|
228
|
+
resources: 2,
|
|
229
|
+
transport: 'streamable-http',
|
|
230
|
+
endpoint: '/mcp',
|
|
231
|
+
install: 'npx -y @tongateway/mcp',
|
|
232
|
+
homepage: 'https://tongateway.ai',
|
|
233
|
+
docs: 'https://tongateway.ai/docs',
|
|
234
|
+
repository: 'https://github.com/tongateway/mcp',
|
|
235
|
+
}), { headers: { 'Content-Type': 'application/json', ...CORS_HEADERS } });
|
|
236
|
+
}
|
|
237
|
+
if (url.pathname === '/mcp') {
|
|
238
|
+
try {
|
|
239
|
+
const server = createMcpServer();
|
|
240
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
241
|
+
sessionIdGenerator: undefined,
|
|
242
|
+
});
|
|
243
|
+
await server.connect(transport);
|
|
244
|
+
const response = await transport.handleRequest(request);
|
|
245
|
+
const newHeaders = new Headers(response.headers);
|
|
246
|
+
for (const [k, v] of Object.entries(CORS_HEADERS)) {
|
|
247
|
+
newHeaders.set(k, v);
|
|
248
|
+
}
|
|
249
|
+
return new Response(response.body, { status: response.status, headers: newHeaders });
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
return new Response(JSON.stringify({ error: e.message }), {
|
|
253
|
+
status: 500,
|
|
254
|
+
headers: { 'Content-Type': 'application/json', ...CORS_HEADERS },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return new Response('Not found', { status: 404, headers: CORS_HEADERS });
|
|
259
|
+
},
|
|
260
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tongateway/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.19.2",
|
|
4
|
+
"description": "TON blockchain gateway for AI agents — 16 MCP tools: wallet info, transfers, jettons, NFTs, DNS, prices, DEX orders, agent wallets",
|
|
5
|
+
"homepage": "https://tongateway.ai",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsc",
|
|
27
|
-
"dev": "tsx src/index.ts"
|
|
28
|
+
"dev": "tsx src/index.ts",
|
|
29
|
+
"test": "vitest run"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
@@ -33,8 +35,11 @@
|
|
|
33
35
|
"zod": "^4.3.6"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
38
|
+
"@cloudflare/workers-types": "^4.20260317.1",
|
|
36
39
|
"@types/node": "^22.17.2",
|
|
37
40
|
"tsx": "^4.20.5",
|
|
38
|
-
"typescript": "^5.9.2"
|
|
41
|
+
"typescript": "^5.9.2",
|
|
42
|
+
"vitest": "^4.1.1",
|
|
43
|
+
"wrangler": "^4.77.0"
|
|
39
44
|
}
|
|
40
45
|
}
|