@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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # @tongateway/mcp
2
2
 
3
+ [![smithery badge](https://smithery.ai/badge/tongateway/agent)](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
- **14 tools:** wallet info, jettons, NFTs, transactions, transfers, .ton DNS, prices, agent wallets, and more.
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('request_auth', '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 get_auth_token to complete. Token persists across restarts.', {
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 get_auth_token with this authId to get the token.`,
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('get_auth_token', 'Complete authentication after the user opened the link from request_auth. 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 request_auth'),
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 get_auth_token again.`,
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 request_transfer, get_request_status, and list_pending_requests.`,
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('request_transfer', '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 get_request_status to check if approved.', {
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 request_auth first to authenticate.' }],
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('get_request_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 request_transfer'),
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 request_auth first to authenticate.' }],
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('list_pending_requests', 'List all transfer requests waiting for wallet owner approval. Use to check if there are unfinished transfers.', {}, async () => {
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 request_auth first to authenticate.' }],
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('get_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.', {}, async () => {
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 request_auth first.' }], isError: true };
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('get_jetton_balances', '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.', {}, async () => {
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 request_auth first.' }], isError: true };
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('get_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.', {
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 request_auth first.' }], isError: true };
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('get_nft_items', 'List all NFTs owned by the wallet — name, collection, and address for each. Use when the user asks about their NFTs or collectibles.', {}, async () => {
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 request_auth first.' }], isError: true };
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 request_transfer when the user gives a .ton name instead of a raw address.', {
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('get_ton_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.', {
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&currencies=${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('create_dex_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.', {
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 request_auth first.' }], isError: true };
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('list_dex_pairs', 'List available trading pairs on the DEX. Shows which token swaps are configured and available.', {}, async () => {
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('deploy_agent_wallet', '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.', {}, async () => {
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 request_auth first.' }], isError: true };
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('execute_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.', {
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 request_auth first.' }], isError: true };
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('get_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.', {
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 request_auth first.' }], isError: true };
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 deploy_agent_wallet to create one.' }] };
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.12.0' }));
703
+ res.end(JSON.stringify({ ok: true, version: '0.14.0' }));
704
704
  return;
705
705
  }
706
706
  // MCP endpoint
@@ -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.13.0",
4
- "description": "MCP server for Agent Gatewaylets AI agents request TON blockchain transfers",
3
+ "version": "0.14.0",
4
+ "description": "TON blockchain gateway for AI agents16 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
  }