@tongateway/mcp 0.2.0 → 0.4.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/dist/index.js +154 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
5
8
|
const API_URL = process.env.AGENT_GATEWAY_API_URL ?? 'https://api.tongateway.ai';
|
|
6
|
-
|
|
9
|
+
const TOKEN_FILE = join(homedir(), '.tongateway', 'token');
|
|
10
|
+
function loadToken() {
|
|
11
|
+
if (process.env.AGENT_GATEWAY_TOKEN)
|
|
12
|
+
return process.env.AGENT_GATEWAY_TOKEN;
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(TOKEN_FILE, 'utf-8').trim();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveToken(token) {
|
|
21
|
+
try {
|
|
22
|
+
mkdirSync(join(homedir(), '.tongateway'), { recursive: true });
|
|
23
|
+
writeFileSync(TOKEN_FILE, token, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
}
|
|
27
|
+
let TOKEN = loadToken();
|
|
7
28
|
async function apiCall(path, options = {}) {
|
|
8
29
|
const headers = {
|
|
9
30
|
'Content-Type': 'application/json',
|
|
@@ -91,8 +112,9 @@ server.tool('get_auth_token', 'Check if the user has completed wallet authentica
|
|
|
91
112
|
],
|
|
92
113
|
};
|
|
93
114
|
}
|
|
94
|
-
// Store the token for future API calls
|
|
115
|
+
// Store the token for future API calls and persist to disk
|
|
95
116
|
TOKEN = data.token;
|
|
117
|
+
saveToken(TOKEN);
|
|
96
118
|
return {
|
|
97
119
|
content: [
|
|
98
120
|
{
|
|
@@ -228,5 +250,135 @@ server.tool('list_pending_requests', 'List all pending transfer requests waiting
|
|
|
228
250
|
};
|
|
229
251
|
}
|
|
230
252
|
});
|
|
253
|
+
server.tool('get_wallet_info', 'Get the connected wallet address, TON balance, and account status.', {}, async () => {
|
|
254
|
+
if (!TOKEN) {
|
|
255
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const result = await apiCall('/v1/wallet/balance');
|
|
259
|
+
const balanceTon = (BigInt(result.balance) / 1000000000n).toString();
|
|
260
|
+
const balanceFrac = (BigInt(result.balance) % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '') || '0';
|
|
261
|
+
return {
|
|
262
|
+
content: [{
|
|
263
|
+
type: 'text',
|
|
264
|
+
text: [
|
|
265
|
+
`Address: ${result.address}`,
|
|
266
|
+
`Balance: ${balanceTon}.${balanceFrac} TON (${result.balance} nanoTON)`,
|
|
267
|
+
`Status: ${result.status}`,
|
|
268
|
+
].join('\n'),
|
|
269
|
+
}],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
server.tool('get_jetton_balances', 'Get all jetton (token) balances in the connected wallet. Shows USDT, NOT, DOGS, and other tokens.', {}, async () => {
|
|
277
|
+
if (!TOKEN) {
|
|
278
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const result = await apiCall('/v1/wallet/jettons');
|
|
282
|
+
if (!result.balances?.length) {
|
|
283
|
+
return { content: [{ type: 'text', text: 'No jettons found in this wallet.' }] };
|
|
284
|
+
}
|
|
285
|
+
const lines = result.balances.map((b) => {
|
|
286
|
+
const decimals = b.decimals ?? 9;
|
|
287
|
+
const raw = BigInt(b.balance);
|
|
288
|
+
const divisor = BigInt(10 ** decimals);
|
|
289
|
+
const whole = (raw / divisor).toString();
|
|
290
|
+
const frac = (raw % divisor).toString().padStart(decimals, '0').replace(/0+$/, '') || '0';
|
|
291
|
+
return `- ${b.symbol ?? b.name ?? 'Unknown'}: ${whole}.${frac} (${b.address})`;
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: 'text', text: `Jetton balances:\n${lines.join('\n')}` }],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
server.tool('get_transactions', 'Get recent transaction history for the connected wallet.', {
|
|
302
|
+
limit: z.number().optional().describe('Number of transactions to return (default 10)'),
|
|
303
|
+
}, async ({ limit }) => {
|
|
304
|
+
if (!TOKEN) {
|
|
305
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
|
|
309
|
+
const events = result.events ?? [];
|
|
310
|
+
if (!events.length) {
|
|
311
|
+
return { content: [{ type: 'text', text: 'No recent transactions.' }] };
|
|
312
|
+
}
|
|
313
|
+
const lines = events.map((e) => {
|
|
314
|
+
const time = new Date(e.timestamp * 1000).toISOString();
|
|
315
|
+
const actions = (e.actions ?? []).map((a) => a.type).join(', ');
|
|
316
|
+
return `- ${time}: ${actions || 'unknown'} ${e.is_scam ? '[SCAM]' : ''}`;
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: 'text', text: `Recent transactions:\n${lines.join('\n')}` }],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
catch (e) {
|
|
323
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
server.tool('get_nft_items', 'List NFTs owned by the connected wallet.', {}, async () => {
|
|
327
|
+
if (!TOKEN) {
|
|
328
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const result = await apiCall('/v1/wallet/nfts');
|
|
332
|
+
const nfts = result.nfts ?? [];
|
|
333
|
+
if (!nfts.length) {
|
|
334
|
+
return { content: [{ type: 'text', text: 'No NFTs found in this wallet.' }] };
|
|
335
|
+
}
|
|
336
|
+
const lines = nfts.map((n) => `- ${n.name ?? 'Unnamed'} ${n.collection ? `(${n.collection})` : ''} — ${n.address}`);
|
|
337
|
+
return {
|
|
338
|
+
content: [{ type: 'text', text: `NFTs (${nfts.length}):\n${lines.join('\n')}` }],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
server.tool('resolve_name', 'Resolve a .ton domain name to a wallet address. Use this when the user says "send to alice.ton" instead of a raw address.', {
|
|
346
|
+
domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
|
|
347
|
+
}, async ({ domain }) => {
|
|
348
|
+
try {
|
|
349
|
+
const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
|
|
350
|
+
const data = await result.json();
|
|
351
|
+
if (!result.ok)
|
|
352
|
+
throw new Error(data.error ?? 'Failed');
|
|
353
|
+
if (!data.address) {
|
|
354
|
+
return { content: [{ type: 'text', text: `Domain "${domain}" not found or has no wallet address.` }] };
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: 'text', text: `${domain} → ${data.address}` }],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
server.tool('get_ton_price', 'Get the current price of TON in USD and other currencies.', {
|
|
365
|
+
currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
|
|
366
|
+
}, async ({ currencies }) => {
|
|
367
|
+
try {
|
|
368
|
+
const curr = currencies || 'USD';
|
|
369
|
+
const result = await fetch(`${API_URL}/v1/market/price?tokens=TON¤cies=${curr}`);
|
|
370
|
+
const data = await result.json();
|
|
371
|
+
if (!result.ok)
|
|
372
|
+
throw new Error(data.error ?? 'Failed');
|
|
373
|
+
const tonRates = data.rates?.TON?.prices ?? {};
|
|
374
|
+
const lines = Object.entries(tonRates).map(([c, p]) => `1 TON = ${p} ${c}`);
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: 'text', text: lines.length ? lines.join('\n') : 'Price data unavailable.' }],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
381
|
+
}
|
|
382
|
+
});
|
|
231
383
|
const transport = new StdioServerTransport();
|
|
232
384
|
await server.connect(transport);
|