@tongateway/mcp 0.12.0 → 0.14.0
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 +3 -1
- package/dist/index.js +84 -45
- package/dist/worker.d.ts +8 -0
- package/dist/worker.js +138 -0
- package/package.json +6 -3
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
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
5
|
import { z } from 'zod';
|
|
6
|
+
import { createServer } from 'node:http';
|
|
5
7
|
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
8
|
import { join } from 'node:path';
|
|
7
9
|
import { homedir } from 'node:os';
|
|
@@ -67,9 +69,9 @@ const server = new McpServer({
|
|
|
67
69
|
name: 'agent-gateway',
|
|
68
70
|
version: '0.1.0',
|
|
69
71
|
});
|
|
70
|
-
server.tool('
|
|
72
|
+
server.tool('auth.request', 'Authenticate with TON blockchain. Call this FIRST if you get "No token configured" errors. Generates a one-time link — ask the user to open it and connect their wallet. Then call auth.get_token to complete. Token persists across restarts.', {
|
|
71
73
|
label: z.string().optional().describe('Label for this agent session (e.g. "claude-agent")'),
|
|
72
|
-
}, async ({ label }) => {
|
|
74
|
+
}, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ label }) => {
|
|
73
75
|
try {
|
|
74
76
|
const result = await fetch(`${API_URL}/v1/auth/request`, {
|
|
75
77
|
method: 'POST',
|
|
@@ -92,7 +94,7 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
92
94
|
`Auth ID: ${data.authId}`,
|
|
93
95
|
`Expires: ${new Date(data.expiresAt).toISOString()}`,
|
|
94
96
|
``,
|
|
95
|
-
`After the user connects their wallet, call
|
|
97
|
+
`After the user connects their wallet, call auth.get_token with this authId to get the token.`,
|
|
96
98
|
].join('\n'),
|
|
97
99
|
},
|
|
98
100
|
],
|
|
@@ -105,9 +107,9 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
});
|
|
108
|
-
server.tool('
|
|
109
|
-
authId: z.string().describe('The authId returned by
|
|
110
|
-
}, async ({ authId }) => {
|
|
110
|
+
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.', {
|
|
111
|
+
authId: z.string().describe('The authId returned by auth.request'),
|
|
112
|
+
}, { title: 'Get Auth Token', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ authId }) => {
|
|
111
113
|
try {
|
|
112
114
|
// Retry up to 3 times with 2s delay (KV eventual consistency)
|
|
113
115
|
let data = null;
|
|
@@ -132,7 +134,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
132
134
|
`Authentication still pending.`,
|
|
133
135
|
`The user has not connected their wallet yet.`,
|
|
134
136
|
``,
|
|
135
|
-
`Wait a moment and call
|
|
137
|
+
`Wait a moment and call auth.get_token again.`,
|
|
136
138
|
].join('\n'),
|
|
137
139
|
},
|
|
138
140
|
],
|
|
@@ -150,7 +152,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
150
152
|
`Token received and configured.`,
|
|
151
153
|
`Wallet: ${data.address}`,
|
|
152
154
|
``,
|
|
153
|
-
`You can now use
|
|
155
|
+
`You can now use transfer.request, transfer.status, and transfer.pending.`,
|
|
154
156
|
].join('\n'),
|
|
155
157
|
},
|
|
156
158
|
],
|
|
@@ -163,15 +165,15 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
163
165
|
};
|
|
164
166
|
}
|
|
165
167
|
});
|
|
166
|
-
server.tool('
|
|
168
|
+
server.tool('transfer.request', 'Request a TON transfer. The user must approve it in their wallet app. Amount is in nanoTON (1 TON = 1000000000). Supports optional payload (BOC) and stateInit (for contract deployment). The transfer is queued — use transfer.status to check if approved.', {
|
|
167
169
|
to: z.string().describe('Destination TON address'),
|
|
168
170
|
amountNano: z.string().describe('Amount in nanoTON (1 TON = 1000000000)'),
|
|
169
171
|
payload: z.string().optional().describe('Optional BOC-encoded payload for the transaction'),
|
|
170
172
|
stateInit: z.string().optional().describe('Optional stateInit BOC for deploying new contracts'),
|
|
171
|
-
}, async ({ to, amountNano, payload, stateInit }) => {
|
|
173
|
+
}, { title: 'Request Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ to, amountNano, payload, stateInit }) => {
|
|
172
174
|
if (!TOKEN) {
|
|
173
175
|
return {
|
|
174
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
176
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
175
177
|
isError: true,
|
|
176
178
|
};
|
|
177
179
|
}
|
|
@@ -210,12 +212,12 @@ server.tool('request_transfer', 'Request a TON transfer. The user must approve i
|
|
|
210
212
|
};
|
|
211
213
|
}
|
|
212
214
|
});
|
|
213
|
-
server.tool('
|
|
214
|
-
id: z.string().describe('The request ID returned by
|
|
215
|
-
}, async ({ id }) => {
|
|
215
|
+
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.', {
|
|
216
|
+
id: z.string().describe('The request ID returned by transfer.request'),
|
|
217
|
+
}, { title: 'Transfer Status', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ id }) => {
|
|
216
218
|
if (!TOKEN) {
|
|
217
219
|
return {
|
|
218
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
220
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
219
221
|
isError: true,
|
|
220
222
|
};
|
|
221
223
|
}
|
|
@@ -249,10 +251,10 @@ server.tool('get_request_status', 'Check the status of a transfer request. Statu
|
|
|
249
251
|
};
|
|
250
252
|
}
|
|
251
253
|
});
|
|
252
|
-
server.tool('
|
|
254
|
+
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 () => {
|
|
253
255
|
if (!TOKEN) {
|
|
254
256
|
return {
|
|
255
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
257
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
256
258
|
isError: true,
|
|
257
259
|
};
|
|
258
260
|
}
|
|
@@ -281,9 +283,9 @@ server.tool('list_pending_requests', 'List all transfer requests waiting for wal
|
|
|
281
283
|
};
|
|
282
284
|
}
|
|
283
285
|
});
|
|
284
|
-
server.tool('
|
|
286
|
+
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 () => {
|
|
285
287
|
if (!TOKEN) {
|
|
286
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
288
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
287
289
|
}
|
|
288
290
|
try {
|
|
289
291
|
const result = await apiCall('/v1/wallet/balance');
|
|
@@ -304,9 +306,9 @@ server.tool('get_wallet_info', 'Get the connected wallet address, TON balance (i
|
|
|
304
306
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
305
307
|
}
|
|
306
308
|
});
|
|
307
|
-
server.tool('
|
|
309
|
+
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 () => {
|
|
308
310
|
if (!TOKEN) {
|
|
309
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
311
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
310
312
|
}
|
|
311
313
|
try {
|
|
312
314
|
const result = await apiCall('/v1/wallet/jettons');
|
|
@@ -329,11 +331,11 @@ server.tool('get_jetton_balances', 'List all tokens (jettons) in the wallet —
|
|
|
329
331
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
330
332
|
}
|
|
331
333
|
});
|
|
332
|
-
server.tool('
|
|
334
|
+
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.', {
|
|
333
335
|
limit: z.number().optional().describe('Number of transactions to return (default 10)'),
|
|
334
|
-
}, async ({ limit }) => {
|
|
336
|
+
}, { title: 'Transaction History', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ limit }) => {
|
|
335
337
|
if (!TOKEN) {
|
|
336
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
338
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
337
339
|
}
|
|
338
340
|
try {
|
|
339
341
|
const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
|
|
@@ -354,9 +356,9 @@ server.tool('get_transactions', 'Get recent transaction history. Shows timestamp
|
|
|
354
356
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
355
357
|
}
|
|
356
358
|
});
|
|
357
|
-
server.tool('
|
|
359
|
+
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 () => {
|
|
358
360
|
if (!TOKEN) {
|
|
359
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
361
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
360
362
|
}
|
|
361
363
|
try {
|
|
362
364
|
const result = await apiCall('/v1/wallet/nfts');
|
|
@@ -373,9 +375,9 @@ server.tool('get_nft_items', 'List all NFTs owned by the wallet — name, collec
|
|
|
373
375
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
374
376
|
}
|
|
375
377
|
});
|
|
376
|
-
server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a raw wallet address. ALWAYS use this before
|
|
378
|
+
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.', {
|
|
377
379
|
domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
|
|
378
|
-
}, async ({ domain }) => {
|
|
380
|
+
}, { title: 'Resolve .ton Name', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ domain }) => {
|
|
379
381
|
try {
|
|
380
382
|
const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
|
|
381
383
|
const data = await result.json();
|
|
@@ -392,9 +394,9 @@ server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a
|
|
|
392
394
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
393
395
|
}
|
|
394
396
|
});
|
|
395
|
-
server.tool('
|
|
397
|
+
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.', {
|
|
396
398
|
currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
|
|
397
|
-
}, async ({ currencies }) => {
|
|
399
|
+
}, { title: 'TON Price', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ currencies }) => {
|
|
398
400
|
try {
|
|
399
401
|
const curr = currencies || 'USD';
|
|
400
402
|
const result = await fetch(`${API_URL}/v1/market/price?tokens=TON¤cies=${curr}`);
|
|
@@ -411,14 +413,14 @@ server.tool('get_ton_price', 'Get the current TON price in USD, EUR, or other cu
|
|
|
411
413
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
412
414
|
}
|
|
413
415
|
});
|
|
414
|
-
server.tool('
|
|
416
|
+
server.tool('dex.create_order', 'Place a limit order on the open4dev DEX order book. Provide the token pair, amount in smallest units, and price in human-readable format (e.g. price=20 means "1 fromToken = 20 toToken"). The API handles all decimal conversions and slippage automatically. The order requires wallet approval.', {
|
|
415
417
|
fromToken: z.string().describe('Token to sell, e.g. "NOT", "TON", "USDT"'),
|
|
416
418
|
toToken: z.string().describe('Token to buy, e.g. "TON", "NOT", "AGNT"'),
|
|
417
419
|
amount: z.string().describe('Amount to sell in smallest unit. TON/NOT/DOGS/BUILD/AGNT use 9 decimals (1 TON = 10^9). USDT/XAUT0 use 6 decimals (1 USDT = 10^6).'),
|
|
418
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.05 means "1 AGNT = 0.05 USDT".'),
|
|
419
|
-
}, async ({ fromToken, toToken, amount, price }) => {
|
|
421
|
+
}, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ fromToken, toToken, amount, price }) => {
|
|
420
422
|
if (!TOKEN) {
|
|
421
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
423
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
422
424
|
}
|
|
423
425
|
try {
|
|
424
426
|
const result = await apiCall('/v1/dex/order', {
|
|
@@ -446,7 +448,7 @@ server.tool('create_dex_order', 'Place a limit order on the open4dev DEX order b
|
|
|
446
448
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
447
449
|
}
|
|
448
450
|
});
|
|
449
|
-
server.tool('
|
|
451
|
+
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 () => {
|
|
450
452
|
try {
|
|
451
453
|
const result = await fetch(`${API_URL}/v1/dex/pairs`);
|
|
452
454
|
const data = await result.json();
|
|
@@ -462,9 +464,9 @@ server.tool('list_dex_pairs', 'List available trading pairs on the DEX. Shows wh
|
|
|
462
464
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
463
465
|
}
|
|
464
466
|
});
|
|
465
|
-
server.tool('
|
|
467
|
+
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 () => {
|
|
466
468
|
if (!TOKEN) {
|
|
467
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
469
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
468
470
|
}
|
|
469
471
|
try {
|
|
470
472
|
// Get owner's public key
|
|
@@ -545,13 +547,13 @@ server.tool('deploy_agent_wallet', 'Deploy an Agent Wallet smart contract — a
|
|
|
545
547
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
546
548
|
}
|
|
547
549
|
});
|
|
548
|
-
server.tool('
|
|
550
|
+
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.', {
|
|
549
551
|
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
550
552
|
to: z.string().describe('Destination TON address'),
|
|
551
553
|
amountNano: z.string().describe('Amount in nanoTON'),
|
|
552
|
-
}, async ({ walletAddress, to, amountNano }) => {
|
|
554
|
+
}, { title: 'Agent Wallet Transfer', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ walletAddress, to, amountNano }) => {
|
|
553
555
|
if (!TOKEN) {
|
|
554
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
556
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
555
557
|
}
|
|
556
558
|
try {
|
|
557
559
|
const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
|
|
@@ -642,11 +644,11 @@ server.tool('execute_agent_wallet_transfer', 'Send TON directly from an Agent Wa
|
|
|
642
644
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
643
645
|
}
|
|
644
646
|
});
|
|
645
|
-
server.tool('
|
|
647
|
+
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.', {
|
|
646
648
|
walletAddress: z.string().optional().describe('Agent wallet address. If omitted, lists all your agent wallets.'),
|
|
647
|
-
}, async ({ walletAddress }) => {
|
|
649
|
+
}, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ walletAddress }) => {
|
|
648
650
|
if (!TOKEN) {
|
|
649
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
651
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
650
652
|
}
|
|
651
653
|
try {
|
|
652
654
|
if (!walletAddress) {
|
|
@@ -654,7 +656,7 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
|
|
|
654
656
|
const result = await apiCall('/v1/agent-wallet/list');
|
|
655
657
|
const wallets = result.wallets || [];
|
|
656
658
|
if (!wallets.length) {
|
|
657
|
-
return { content: [{ type: 'text', text: 'No agent wallets found. Use
|
|
659
|
+
return { content: [{ type: 'text', text: 'No agent wallets found. Use agent_wallet.deploy to create one.' }] };
|
|
658
660
|
}
|
|
659
661
|
const lines = wallets.map((w) => `- ${w.address} (created ${new Date(w.createdAt).toISOString()})`);
|
|
660
662
|
return {
|
|
@@ -682,5 +684,42 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
|
|
|
682
684
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
683
685
|
}
|
|
684
686
|
});
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
+
const httpPort = process.env.MCP_HTTP_PORT || (process.argv.includes('--http') ? '3100' : '');
|
|
688
|
+
if (httpPort) {
|
|
689
|
+
// HTTP transport for Smithery and remote clients
|
|
690
|
+
const httpServer = createServer(async (req, res) => {
|
|
691
|
+
// CORS
|
|
692
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
693
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
694
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
695
|
+
if (req.method === 'OPTIONS') {
|
|
696
|
+
res.writeHead(204);
|
|
697
|
+
res.end();
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
// Health check
|
|
701
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
702
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
703
|
+
res.end(JSON.stringify({ ok: true, version: '0.14.0' }));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
// MCP endpoint
|
|
707
|
+
if (req.url === '/mcp' || req.url === '/') {
|
|
708
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
709
|
+
res.on('close', () => { transport.close(); });
|
|
710
|
+
await server.connect(transport);
|
|
711
|
+
await transport.handleRequest(req, res);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
res.writeHead(404);
|
|
715
|
+
res.end('Not found');
|
|
716
|
+
});
|
|
717
|
+
httpServer.listen(Number(httpPort), () => {
|
|
718
|
+
console.error(`MCP HTTP server listening on port ${httpPort}`);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
// Default: stdio transport for local MCP clients
|
|
723
|
+
const transport = new StdioServerTransport();
|
|
724
|
+
await server.connect(transport);
|
|
725
|
+
}
|
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,138 @@
|
|
|
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. Provide token pair, amount in smallest units, and human-readable price. The API handles decimal conversion, 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('Amount to sell in smallest unit. TON/NOT/DOGS/BUILD/AGNT use 9 decimals. USDT/XAUT0 use 6 decimals.'),
|
|
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: 'Install: npx -y @tongateway/mcp\n\nClaude Code: claude mcp add-json tongateway \'{"command":"npx","args":["-y","@tongateway/mcp"],"env":{"AGENT_GATEWAY_API_URL":"https://api.tongateway.ai"}}\' --scope user\n\nThen say: "Send 1 TON to alice.ton"',
|
|
57
|
+
},
|
|
58
|
+
}],
|
|
59
|
+
}));
|
|
60
|
+
server.prompt('token-reference', 'Amount conversion reference for TON blockchain', async () => ({
|
|
61
|
+
messages: [{
|
|
62
|
+
role: 'user',
|
|
63
|
+
content: {
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: 'TON amounts are in nanoTON (1 TON = 10^9 nanoTON):\n0.1 TON = 100000000\n0.5 TON = 500000000\n1 TON = 1000000000\n10 TON = 10000000000\n\nUSDT/XAUT0 use 6 decimals. All other jettons use 9 decimals.',
|
|
66
|
+
},
|
|
67
|
+
}],
|
|
68
|
+
}));
|
|
69
|
+
// --- Resources ---
|
|
70
|
+
server.resource('docs', 'https://tongateway.ai/docs', async (uri) => ({
|
|
71
|
+
contents: [{
|
|
72
|
+
uri: uri.href,
|
|
73
|
+
mimeType: 'text/plain',
|
|
74
|
+
text: 'Agent Gateway documentation: https://tongateway.ai/docs\nAPI Swagger: https://api.tongateway.ai/docs\nSkill file: https://tongateway.ai/agent-gateway.md',
|
|
75
|
+
}],
|
|
76
|
+
}));
|
|
77
|
+
server.resource('skill', 'https://tongateway.ai/agent-gateway.md', async (uri) => ({
|
|
78
|
+
contents: [{
|
|
79
|
+
uri: uri.href,
|
|
80
|
+
mimeType: 'text/plain',
|
|
81
|
+
text: 'Skill file with all 16 tool descriptions, usage examples, and amount conversion: https://tongateway.ai/agent-gateway.md',
|
|
82
|
+
}],
|
|
83
|
+
}));
|
|
84
|
+
return server;
|
|
85
|
+
}
|
|
86
|
+
const CORS_HEADERS = {
|
|
87
|
+
'Access-Control-Allow-Origin': '*',
|
|
88
|
+
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
89
|
+
'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization, Mcp-Session-Id',
|
|
90
|
+
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
91
|
+
};
|
|
92
|
+
export default {
|
|
93
|
+
async fetch(request) {
|
|
94
|
+
const url = new URL(request.url);
|
|
95
|
+
if (request.method === 'OPTIONS') {
|
|
96
|
+
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
|
97
|
+
}
|
|
98
|
+
if (request.method === 'GET' && (url.pathname === '/' || url.pathname === '/health')) {
|
|
99
|
+
return new Response(JSON.stringify({
|
|
100
|
+
ok: true,
|
|
101
|
+
name: '@tongateway/mcp',
|
|
102
|
+
version: VERSION,
|
|
103
|
+
description: 'TON blockchain gateway for AI agents — 16 tools for wallet info, transfers, jettons, NFTs, DNS, prices, DEX orders, and agent wallets.',
|
|
104
|
+
tools: 16,
|
|
105
|
+
prompts: 2,
|
|
106
|
+
resources: 2,
|
|
107
|
+
transport: 'streamable-http',
|
|
108
|
+
endpoint: '/mcp',
|
|
109
|
+
install: 'npx -y @tongateway/mcp',
|
|
110
|
+
homepage: 'https://tongateway.ai',
|
|
111
|
+
docs: 'https://tongateway.ai/docs',
|
|
112
|
+
repository: 'https://github.com/tongateway/mcp',
|
|
113
|
+
}), { headers: { 'Content-Type': 'application/json', ...CORS_HEADERS } });
|
|
114
|
+
}
|
|
115
|
+
if (url.pathname === '/mcp') {
|
|
116
|
+
try {
|
|
117
|
+
const server = createMcpServer();
|
|
118
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
119
|
+
sessionIdGenerator: undefined,
|
|
120
|
+
});
|
|
121
|
+
await server.connect(transport);
|
|
122
|
+
const response = await transport.handleRequest(request);
|
|
123
|
+
const newHeaders = new Headers(response.headers);
|
|
124
|
+
for (const [k, v] of Object.entries(CORS_HEADERS)) {
|
|
125
|
+
newHeaders.set(k, v);
|
|
126
|
+
}
|
|
127
|
+
return new Response(response.body, { status: response.status, headers: newHeaders });
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
return new Response(JSON.stringify({ error: e.message }), {
|
|
131
|
+
status: 500,
|
|
132
|
+
headers: { 'Content-Type': 'application/json', ...CORS_HEADERS },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return new Response('Not found', { status: 404, headers: CORS_HEADERS });
|
|
137
|
+
},
|
|
138
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tongateway/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.14.0",
|
|
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",
|
|
@@ -33,8 +34,10 @@
|
|
|
33
34
|
"zod": "^4.3.6"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"@cloudflare/workers-types": "^4.20260317.1",
|
|
36
38
|
"@types/node": "^22.17.2",
|
|
37
39
|
"tsx": "^4.20.5",
|
|
38
|
-
"typescript": "^5.9.2"
|
|
40
|
+
"typescript": "^5.9.2",
|
|
41
|
+
"wrangler": "^4.77.0"
|
|
39
42
|
}
|
|
40
43
|
}
|