@tongateway/mcp 0.1.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +43 -0
  2. package/dist/index.js +243 -7
  3. package/package.json +14 -5
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @tongateway/mcp
2
+
3
+ MCP server for [Agent Gateway](https://github.com/pewpewgogo/ton-agent-gateway) — lets AI agents request TON blockchain transfers via Model Context Protocol.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @tongateway/mcp
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ Add to your MCP client config (Claude Code, Cursor, etc.):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "tongateway": {
19
+ "command": "tongateway-mcp",
20
+ "env": {
21
+ "AGENT_GATEWAY_TOKEN": "YOUR_TOKEN_HERE",
22
+ "AGENT_GATEWAY_API_URL": "https://api.tongateway.ai"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ Get your token at [tongateway.ai/app.html](https://tongateway.ai/app.html).
30
+
31
+ ## Tools
32
+
33
+ | Tool | Description |
34
+ |---|---|
35
+ | `request_transfer` | Request a TON transfer (to, amountNano, payloadBoc?) |
36
+ | `get_request_status` | Check status of a request by ID |
37
+ | `list_pending_requests` | List all pending requests |
38
+
39
+ ## Links
40
+
41
+ - [Agent Gateway](https://github.com/pewpewgogo/ton-agent-gateway) — main repo with all links
42
+ - [Dashboard](https://tongateway.ai) — connect wallet & manage tokens
43
+ - [API Docs](https://api.tongateway.ai/docs) — Swagger UI
package/dist/index.js CHANGED
@@ -3,17 +3,18 @@ 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
5
  const API_URL = process.env.AGENT_GATEWAY_API_URL ?? 'https://api.tongateway.ai';
6
- const TOKEN = process.env.AGENT_GATEWAY_TOKEN;
7
- if (!TOKEN) {
8
- console.error('AGENT_GATEWAY_TOKEN environment variable is required');
9
- process.exit(1);
10
- }
6
+ let TOKEN = process.env.AGENT_GATEWAY_TOKEN || '';
11
7
  async function apiCall(path, options = {}) {
8
+ const headers = {
9
+ 'Content-Type': 'application/json',
10
+ };
11
+ if (TOKEN) {
12
+ headers['Authorization'] = `Bearer ${TOKEN}`;
13
+ }
12
14
  const res = await fetch(`${API_URL}${path}`, {
13
15
  ...options,
14
16
  headers: {
15
- 'Content-Type': 'application/json',
16
- Authorization: `Bearer ${TOKEN}`,
17
+ ...headers,
17
18
  ...options.headers,
18
19
  },
19
20
  });
@@ -27,11 +28,104 @@ const server = new McpServer({
27
28
  name: 'agent-gateway',
28
29
  version: '0.1.0',
29
30
  });
31
+ server.tool('request_auth', 'Request wallet authentication. Generates a one-time link for the user to connect their TON wallet. After the user connects, use get_auth_token to retrieve the token. Use this when no token is configured.', {
32
+ label: z.string().optional().describe('Label for this agent session (e.g. "claude-agent")'),
33
+ }, async ({ label }) => {
34
+ try {
35
+ const result = await fetch(`${API_URL}/v1/auth/request`, {
36
+ method: 'POST',
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: JSON.stringify({ label: label || 'agent' }),
39
+ });
40
+ const data = await result.json();
41
+ if (!result.ok)
42
+ throw new Error(data.error ?? 'Failed');
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: [
48
+ `Authentication requested.`,
49
+ ``,
50
+ `Ask the user to open this link:`,
51
+ data.authUrl,
52
+ ``,
53
+ `Auth ID: ${data.authId}`,
54
+ `Expires: ${new Date(data.expiresAt).toISOString()}`,
55
+ ``,
56
+ `After the user connects their wallet, call get_auth_token with this authId to get the token.`,
57
+ ].join('\n'),
58
+ },
59
+ ],
60
+ };
61
+ }
62
+ catch (e) {
63
+ return {
64
+ content: [{ type: 'text', text: `Error: ${e.message}` }],
65
+ isError: true,
66
+ };
67
+ }
68
+ });
69
+ server.tool('get_auth_token', 'Check if the user has completed wallet authentication and retrieve the token. Call this after request_auth once the user has opened the link and connected their wallet.', {
70
+ authId: z.string().describe('The authId returned by request_auth'),
71
+ }, async ({ authId }) => {
72
+ try {
73
+ const result = await fetch(`${API_URL}/v1/auth/check/${authId}`, {
74
+ headers: { 'Content-Type': 'application/json' },
75
+ });
76
+ const data = await result.json();
77
+ if (!result.ok)
78
+ throw new Error(data.error ?? 'Failed');
79
+ if (data.status === 'pending') {
80
+ return {
81
+ content: [
82
+ {
83
+ type: 'text',
84
+ text: [
85
+ `Authentication still pending.`,
86
+ `The user has not connected their wallet yet.`,
87
+ ``,
88
+ `Wait a moment and try again.`,
89
+ ].join('\n'),
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ // Store the token for future API calls
95
+ TOKEN = data.token;
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: [
101
+ `Authentication complete!`,
102
+ `Token received and configured.`,
103
+ `Wallet: ${data.address}`,
104
+ ``,
105
+ `You can now use request_transfer, get_request_status, and list_pending_requests.`,
106
+ ].join('\n'),
107
+ },
108
+ ],
109
+ };
110
+ }
111
+ catch (e) {
112
+ return {
113
+ content: [{ type: 'text', text: `Error: ${e.message}` }],
114
+ isError: true,
115
+ };
116
+ }
117
+ });
30
118
  server.tool('request_transfer', 'Request a TON transfer from the wallet owner. The request will be queued and the owner must approve it via TON Connect.', {
31
119
  to: z.string().describe('Destination TON address'),
32
120
  amountNano: z.string().describe('Amount in nanoTON (1 TON = 1000000000)'),
33
121
  payloadBoc: z.string().optional().describe('Optional BOC-encoded payload for the transaction'),
34
122
  }, async ({ to, amountNano, payloadBoc }) => {
123
+ if (!TOKEN) {
124
+ return {
125
+ content: [{ type: 'text', text: 'No token configured. Use request_auth first to authenticate.' }],
126
+ isError: true,
127
+ };
128
+ }
35
129
  try {
36
130
  const body = { to, amountNano };
37
131
  if (payloadBoc)
@@ -68,6 +162,12 @@ server.tool('request_transfer', 'Request a TON transfer from the wallet owner. T
68
162
  server.tool('get_request_status', 'Check the status of a previously submitted transfer request.', {
69
163
  id: z.string().describe('The request ID returned by request_transfer'),
70
164
  }, async ({ id }) => {
165
+ if (!TOKEN) {
166
+ return {
167
+ content: [{ type: 'text', text: 'No token configured. Use request_auth first to authenticate.' }],
168
+ isError: true,
169
+ };
170
+ }
71
171
  try {
72
172
  const result = await apiCall(`/v1/safe/tx/${id}`);
73
173
  return {
@@ -97,6 +197,12 @@ server.tool('get_request_status', 'Check the status of a previously submitted tr
97
197
  }
98
198
  });
99
199
  server.tool('list_pending_requests', 'List all pending transfer requests waiting for wallet owner approval.', {}, async () => {
200
+ if (!TOKEN) {
201
+ return {
202
+ content: [{ type: 'text', text: 'No token configured. Use request_auth first to authenticate.' }],
203
+ isError: true,
204
+ };
205
+ }
100
206
  try {
101
207
  const data = await apiCall('/v1/safe/tx/pending');
102
208
  const requests = data.requests;
@@ -122,5 +228,135 @@ server.tool('list_pending_requests', 'List all pending transfer requests waiting
122
228
  };
123
229
  }
124
230
  });
231
+ server.tool('get_wallet_info', 'Get the connected wallet address, TON balance, and account status.', {}, async () => {
232
+ if (!TOKEN) {
233
+ return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
234
+ }
235
+ try {
236
+ const result = await apiCall('/v1/wallet/balance');
237
+ const balanceTon = (BigInt(result.balance) / 1000000000n).toString();
238
+ const balanceFrac = (BigInt(result.balance) % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '') || '0';
239
+ return {
240
+ content: [{
241
+ type: 'text',
242
+ text: [
243
+ `Address: ${result.address}`,
244
+ `Balance: ${balanceTon}.${balanceFrac} TON (${result.balance} nanoTON)`,
245
+ `Status: ${result.status}`,
246
+ ].join('\n'),
247
+ }],
248
+ };
249
+ }
250
+ catch (e) {
251
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
252
+ }
253
+ });
254
+ server.tool('get_jetton_balances', 'Get all jetton (token) balances in the connected wallet. Shows USDT, NOT, DOGS, and other tokens.', {}, async () => {
255
+ if (!TOKEN) {
256
+ return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
257
+ }
258
+ try {
259
+ const result = await apiCall('/v1/wallet/jettons');
260
+ if (!result.balances?.length) {
261
+ return { content: [{ type: 'text', text: 'No jettons found in this wallet.' }] };
262
+ }
263
+ const lines = result.balances.map((b) => {
264
+ const decimals = b.decimals ?? 9;
265
+ const raw = BigInt(b.balance);
266
+ const divisor = BigInt(10 ** decimals);
267
+ const whole = (raw / divisor).toString();
268
+ const frac = (raw % divisor).toString().padStart(decimals, '0').replace(/0+$/, '') || '0';
269
+ return `- ${b.symbol ?? b.name ?? 'Unknown'}: ${whole}.${frac} (${b.address})`;
270
+ });
271
+ return {
272
+ content: [{ type: 'text', text: `Jetton balances:\n${lines.join('\n')}` }],
273
+ };
274
+ }
275
+ catch (e) {
276
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
277
+ }
278
+ });
279
+ server.tool('get_transactions', 'Get recent transaction history for the connected wallet.', {
280
+ limit: z.number().optional().describe('Number of transactions to return (default 10)'),
281
+ }, async ({ limit }) => {
282
+ if (!TOKEN) {
283
+ return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
284
+ }
285
+ try {
286
+ const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
287
+ const events = result.events ?? [];
288
+ if (!events.length) {
289
+ return { content: [{ type: 'text', text: 'No recent transactions.' }] };
290
+ }
291
+ const lines = events.map((e) => {
292
+ const time = new Date(e.timestamp * 1000).toISOString();
293
+ const actions = (e.actions ?? []).map((a) => a.type).join(', ');
294
+ return `- ${time}: ${actions || 'unknown'} ${e.is_scam ? '[SCAM]' : ''}`;
295
+ });
296
+ return {
297
+ content: [{ type: 'text', text: `Recent transactions:\n${lines.join('\n')}` }],
298
+ };
299
+ }
300
+ catch (e) {
301
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
302
+ }
303
+ });
304
+ server.tool('get_nft_items', 'List NFTs owned by the connected wallet.', {}, async () => {
305
+ if (!TOKEN) {
306
+ return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
307
+ }
308
+ try {
309
+ const result = await apiCall('/v1/wallet/nfts');
310
+ const nfts = result.nfts ?? [];
311
+ if (!nfts.length) {
312
+ return { content: [{ type: 'text', text: 'No NFTs found in this wallet.' }] };
313
+ }
314
+ const lines = nfts.map((n) => `- ${n.name ?? 'Unnamed'} ${n.collection ? `(${n.collection})` : ''} — ${n.address}`);
315
+ return {
316
+ content: [{ type: 'text', text: `NFTs (${nfts.length}):\n${lines.join('\n')}` }],
317
+ };
318
+ }
319
+ catch (e) {
320
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
321
+ }
322
+ });
323
+ 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.', {
324
+ domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
325
+ }, async ({ domain }) => {
326
+ try {
327
+ const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
328
+ const data = await result.json();
329
+ if (!result.ok)
330
+ throw new Error(data.error ?? 'Failed');
331
+ if (!data.address) {
332
+ return { content: [{ type: 'text', text: `Domain "${domain}" not found or has no wallet address.` }] };
333
+ }
334
+ return {
335
+ content: [{ type: 'text', text: `${domain} → ${data.address}` }],
336
+ };
337
+ }
338
+ catch (e) {
339
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
340
+ }
341
+ });
342
+ server.tool('get_ton_price', 'Get the current price of TON in USD and other currencies.', {
343
+ currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
344
+ }, async ({ currencies }) => {
345
+ try {
346
+ const curr = currencies || 'USD';
347
+ const result = await fetch(`${API_URL}/v1/market/price?tokens=TON&currencies=${curr}`);
348
+ const data = await result.json();
349
+ if (!result.ok)
350
+ throw new Error(data.error ?? 'Failed');
351
+ const tonRates = data.rates?.TON?.prices ?? {};
352
+ const lines = Object.entries(tonRates).map(([c, p]) => `1 TON = ${p} ${c}`);
353
+ return {
354
+ content: [{ type: 'text', text: lines.length ? lines.join('\n') : 'Price data unavailable.' }],
355
+ };
356
+ }
357
+ catch (e) {
358
+ return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
359
+ }
360
+ });
125
361
  const transport = new StdioServerTransport();
126
362
  await server.connect(transport);
package/package.json CHANGED
@@ -1,21 +1,30 @@
1
1
  {
2
2
  "name": "@tongateway/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for Agent Gateway — lets AI agents request TON blockchain transfers",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/tongateway/agent-gateway-mcp"
8
+ "url": "https://github.com/tongateway/mcp"
9
9
  },
10
- "keywords": ["mcp", "ton", "blockchain", "agent", "ai", "wallet"],
10
+ "keywords": [
11
+ "mcp",
12
+ "ton",
13
+ "blockchain",
14
+ "agent",
15
+ "ai",
16
+ "wallet"
17
+ ],
11
18
  "type": "module",
12
19
  "bin": {
13
20
  "agent-gateway-mcp": "./dist/index.js"
14
21
  },
15
- "files": ["dist"],
22
+ "files": [
23
+ "dist"
24
+ ],
16
25
  "scripts": {
17
26
  "build": "tsc",
18
- "dev": "tsx src/index.ts"
27
+ "dev": "tsx src/index.ts"
19
28
  },
20
29
  "dependencies": {
21
30
  "@modelcontextprotocol/sdk": "^1.12.1",