@tongateway/mcp 0.13.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 +44 -44
- 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
|
@@ -69,9 +69,9 @@ const server = new McpServer({
|
|
|
69
69
|
name: 'agent-gateway',
|
|
70
70
|
version: '0.1.0',
|
|
71
71
|
});
|
|
72
|
-
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.', {
|
|
73
73
|
label: z.string().optional().describe('Label for this agent session (e.g. "claude-agent")'),
|
|
74
|
-
}, async ({ label }) => {
|
|
74
|
+
}, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ label }) => {
|
|
75
75
|
try {
|
|
76
76
|
const result = await fetch(`${API_URL}/v1/auth/request`, {
|
|
77
77
|
method: 'POST',
|
|
@@ -94,7 +94,7 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
94
94
|
`Auth ID: ${data.authId}`,
|
|
95
95
|
`Expires: ${new Date(data.expiresAt).toISOString()}`,
|
|
96
96
|
``,
|
|
97
|
-
`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.`,
|
|
98
98
|
].join('\n'),
|
|
99
99
|
},
|
|
100
100
|
],
|
|
@@ -107,9 +107,9 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
|
-
server.tool('
|
|
111
|
-
authId: z.string().describe('The authId returned by
|
|
112
|
-
}, 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 }) => {
|
|
113
113
|
try {
|
|
114
114
|
// Retry up to 3 times with 2s delay (KV eventual consistency)
|
|
115
115
|
let data = null;
|
|
@@ -134,7 +134,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
134
134
|
`Authentication still pending.`,
|
|
135
135
|
`The user has not connected their wallet yet.`,
|
|
136
136
|
``,
|
|
137
|
-
`Wait a moment and call
|
|
137
|
+
`Wait a moment and call auth.get_token again.`,
|
|
138
138
|
].join('\n'),
|
|
139
139
|
},
|
|
140
140
|
],
|
|
@@ -152,7 +152,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
152
152
|
`Token received and configured.`,
|
|
153
153
|
`Wallet: ${data.address}`,
|
|
154
154
|
``,
|
|
155
|
-
`You can now use
|
|
155
|
+
`You can now use transfer.request, transfer.status, and transfer.pending.`,
|
|
156
156
|
].join('\n'),
|
|
157
157
|
},
|
|
158
158
|
],
|
|
@@ -165,15 +165,15 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
});
|
|
168
|
-
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.', {
|
|
169
169
|
to: z.string().describe('Destination TON address'),
|
|
170
170
|
amountNano: z.string().describe('Amount in nanoTON (1 TON = 1000000000)'),
|
|
171
171
|
payload: z.string().optional().describe('Optional BOC-encoded payload for the transaction'),
|
|
172
172
|
stateInit: z.string().optional().describe('Optional stateInit BOC for deploying new contracts'),
|
|
173
|
-
}, async ({ to, amountNano, payload, stateInit }) => {
|
|
173
|
+
}, { title: 'Request Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ to, amountNano, payload, stateInit }) => {
|
|
174
174
|
if (!TOKEN) {
|
|
175
175
|
return {
|
|
176
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
176
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
177
177
|
isError: true,
|
|
178
178
|
};
|
|
179
179
|
}
|
|
@@ -212,12 +212,12 @@ server.tool('request_transfer', 'Request a TON transfer. The user must approve i
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
});
|
|
215
|
-
server.tool('
|
|
216
|
-
id: z.string().describe('The request ID returned by
|
|
217
|
-
}, 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 }) => {
|
|
218
218
|
if (!TOKEN) {
|
|
219
219
|
return {
|
|
220
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
220
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
221
221
|
isError: true,
|
|
222
222
|
};
|
|
223
223
|
}
|
|
@@ -251,10 +251,10 @@ server.tool('get_request_status', 'Check the status of a transfer request. Statu
|
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
|
-
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 () => {
|
|
255
255
|
if (!TOKEN) {
|
|
256
256
|
return {
|
|
257
|
-
content: [{ type: 'text', text: 'No token configured. Use
|
|
257
|
+
content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
|
|
258
258
|
isError: true,
|
|
259
259
|
};
|
|
260
260
|
}
|
|
@@ -283,9 +283,9 @@ server.tool('list_pending_requests', 'List all transfer requests waiting for wal
|
|
|
283
283
|
};
|
|
284
284
|
}
|
|
285
285
|
});
|
|
286
|
-
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 () => {
|
|
287
287
|
if (!TOKEN) {
|
|
288
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
288
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
289
289
|
}
|
|
290
290
|
try {
|
|
291
291
|
const result = await apiCall('/v1/wallet/balance');
|
|
@@ -306,9 +306,9 @@ server.tool('get_wallet_info', 'Get the connected wallet address, TON balance (i
|
|
|
306
306
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
|
-
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 () => {
|
|
310
310
|
if (!TOKEN) {
|
|
311
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
311
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
312
312
|
}
|
|
313
313
|
try {
|
|
314
314
|
const result = await apiCall('/v1/wallet/jettons');
|
|
@@ -331,11 +331,11 @@ server.tool('get_jetton_balances', 'List all tokens (jettons) in the wallet —
|
|
|
331
331
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
332
332
|
}
|
|
333
333
|
});
|
|
334
|
-
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.', {
|
|
335
335
|
limit: z.number().optional().describe('Number of transactions to return (default 10)'),
|
|
336
|
-
}, async ({ limit }) => {
|
|
336
|
+
}, { title: 'Transaction History', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ limit }) => {
|
|
337
337
|
if (!TOKEN) {
|
|
338
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
338
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
339
339
|
}
|
|
340
340
|
try {
|
|
341
341
|
const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
|
|
@@ -356,9 +356,9 @@ server.tool('get_transactions', 'Get recent transaction history. Shows timestamp
|
|
|
356
356
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
357
357
|
}
|
|
358
358
|
});
|
|
359
|
-
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 () => {
|
|
360
360
|
if (!TOKEN) {
|
|
361
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
361
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
362
362
|
}
|
|
363
363
|
try {
|
|
364
364
|
const result = await apiCall('/v1/wallet/nfts');
|
|
@@ -375,9 +375,9 @@ server.tool('get_nft_items', 'List all NFTs owned by the wallet — name, collec
|
|
|
375
375
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
376
376
|
}
|
|
377
377
|
});
|
|
378
|
-
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.', {
|
|
379
379
|
domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
|
|
380
|
-
}, async ({ domain }) => {
|
|
380
|
+
}, { title: 'Resolve .ton Name', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ domain }) => {
|
|
381
381
|
try {
|
|
382
382
|
const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
|
|
383
383
|
const data = await result.json();
|
|
@@ -394,9 +394,9 @@ server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a
|
|
|
394
394
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
|
-
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.', {
|
|
398
398
|
currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
|
|
399
|
-
}, async ({ currencies }) => {
|
|
399
|
+
}, { title: 'TON Price', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ currencies }) => {
|
|
400
400
|
try {
|
|
401
401
|
const curr = currencies || 'USD';
|
|
402
402
|
const result = await fetch(`${API_URL}/v1/market/price?tokens=TON¤cies=${curr}`);
|
|
@@ -413,14 +413,14 @@ server.tool('get_ton_price', 'Get the current TON price in USD, EUR, or other cu
|
|
|
413
413
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
414
414
|
}
|
|
415
415
|
});
|
|
416
|
-
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.', {
|
|
417
417
|
fromToken: z.string().describe('Token to sell, e.g. "NOT", "TON", "USDT"'),
|
|
418
418
|
toToken: z.string().describe('Token to buy, e.g. "TON", "NOT", "AGNT"'),
|
|
419
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).'),
|
|
420
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".'),
|
|
421
|
-
}, async ({ fromToken, toToken, amount, price }) => {
|
|
421
|
+
}, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ fromToken, toToken, amount, price }) => {
|
|
422
422
|
if (!TOKEN) {
|
|
423
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
423
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
424
424
|
}
|
|
425
425
|
try {
|
|
426
426
|
const result = await apiCall('/v1/dex/order', {
|
|
@@ -448,7 +448,7 @@ server.tool('create_dex_order', 'Place a limit order on the open4dev DEX order b
|
|
|
448
448
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
449
449
|
}
|
|
450
450
|
});
|
|
451
|
-
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 () => {
|
|
452
452
|
try {
|
|
453
453
|
const result = await fetch(`${API_URL}/v1/dex/pairs`);
|
|
454
454
|
const data = await result.json();
|
|
@@ -464,9 +464,9 @@ server.tool('list_dex_pairs', 'List available trading pairs on the DEX. Shows wh
|
|
|
464
464
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
465
465
|
}
|
|
466
466
|
});
|
|
467
|
-
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 () => {
|
|
468
468
|
if (!TOKEN) {
|
|
469
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
469
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
470
470
|
}
|
|
471
471
|
try {
|
|
472
472
|
// Get owner's public key
|
|
@@ -547,13 +547,13 @@ server.tool('deploy_agent_wallet', 'Deploy an Agent Wallet smart contract — a
|
|
|
547
547
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
548
548
|
}
|
|
549
549
|
});
|
|
550
|
-
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.', {
|
|
551
551
|
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
552
552
|
to: z.string().describe('Destination TON address'),
|
|
553
553
|
amountNano: z.string().describe('Amount in nanoTON'),
|
|
554
|
-
}, async ({ walletAddress, to, amountNano }) => {
|
|
554
|
+
}, { title: 'Agent Wallet Transfer', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ walletAddress, to, amountNano }) => {
|
|
555
555
|
if (!TOKEN) {
|
|
556
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
556
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
557
557
|
}
|
|
558
558
|
try {
|
|
559
559
|
const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
|
|
@@ -644,11 +644,11 @@ server.tool('execute_agent_wallet_transfer', 'Send TON directly from an Agent Wa
|
|
|
644
644
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
645
645
|
}
|
|
646
646
|
});
|
|
647
|
-
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.', {
|
|
648
648
|
walletAddress: z.string().optional().describe('Agent wallet address. If omitted, lists all your agent wallets.'),
|
|
649
|
-
}, async ({ walletAddress }) => {
|
|
649
|
+
}, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ walletAddress }) => {
|
|
650
650
|
if (!TOKEN) {
|
|
651
|
-
return { content: [{ type: 'text', text: 'No token configured. Use
|
|
651
|
+
return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
|
|
652
652
|
}
|
|
653
653
|
try {
|
|
654
654
|
if (!walletAddress) {
|
|
@@ -656,7 +656,7 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
|
|
|
656
656
|
const result = await apiCall('/v1/agent-wallet/list');
|
|
657
657
|
const wallets = result.wallets || [];
|
|
658
658
|
if (!wallets.length) {
|
|
659
|
-
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.' }] };
|
|
660
660
|
}
|
|
661
661
|
const lines = wallets.map((w) => `- ${w.address} (created ${new Date(w.createdAt).toISOString()})`);
|
|
662
662
|
return {
|
|
@@ -700,7 +700,7 @@ if (httpPort) {
|
|
|
700
700
|
// Health check
|
|
701
701
|
if (req.method === 'GET' && req.url === '/health') {
|
|
702
702
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
703
|
-
res.end(JSON.stringify({ ok: true, version: '0.
|
|
703
|
+
res.end(JSON.stringify({ ok: true, version: '0.14.0' }));
|
|
704
704
|
return;
|
|
705
705
|
}
|
|
706
706
|
// 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,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
|
}
|