@tongateway/mcp 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
5
  import { z } from 'zod';
6
+ import { createServer } from 'node:http';
5
7
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
8
  import { join } from 'node:path';
7
9
  import { homedir } from 'node:os';
@@ -67,9 +69,9 @@ const server = new McpServer({
67
69
  name: 'agent-gateway',
68
70
  version: '0.1.0',
69
71
  });
70
- server.tool('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.', {
71
73
  label: z.string().optional().describe('Label for this agent session (e.g. "claude-agent")'),
72
- }, async ({ label }) => {
74
+ }, { title: 'Request Authentication', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ label }) => {
73
75
  try {
74
76
  const result = await fetch(`${API_URL}/v1/auth/request`, {
75
77
  method: 'POST',
@@ -92,7 +94,7 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
92
94
  `Auth ID: ${data.authId}`,
93
95
  `Expires: ${new Date(data.expiresAt).toISOString()}`,
94
96
  ``,
95
- `After the user connects their wallet, call 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.`,
96
98
  ].join('\n'),
97
99
  },
98
100
  ],
@@ -105,9 +107,9 @@ server.tool('request_auth', 'Authenticate with TON blockchain. Call this FIRST i
105
107
  };
106
108
  }
107
109
  });
108
- server.tool('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.', {
109
- authId: z.string().describe('The authId returned by request_auth'),
110
- }, async ({ authId }) => {
110
+ server.tool('auth.get_token', 'Complete authentication after the user opened the link from auth.request. Pass the authId you received. Once successful, all other tools become available. You only need to authenticate once — the token is saved automatically.', {
111
+ authId: z.string().describe('The authId returned by auth.request'),
112
+ }, { title: 'Get Auth Token', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ authId }) => {
111
113
  try {
112
114
  // Retry up to 3 times with 2s delay (KV eventual consistency)
113
115
  let data = null;
@@ -132,7 +134,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
132
134
  `Authentication still pending.`,
133
135
  `The user has not connected their wallet yet.`,
134
136
  ``,
135
- `Wait a moment and call get_auth_token again.`,
137
+ `Wait a moment and call auth.get_token again.`,
136
138
  ].join('\n'),
137
139
  },
138
140
  ],
@@ -150,7 +152,7 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
150
152
  `Token received and configured.`,
151
153
  `Wallet: ${data.address}`,
152
154
  ``,
153
- `You can now use request_transfer, get_request_status, and list_pending_requests.`,
155
+ `You can now use transfer.request, transfer.status, and transfer.pending.`,
154
156
  ].join('\n'),
155
157
  },
156
158
  ],
@@ -163,15 +165,15 @@ server.tool('get_auth_token', 'Complete authentication after the user opened the
163
165
  };
164
166
  }
165
167
  });
166
- server.tool('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.', {
167
169
  to: z.string().describe('Destination TON address'),
168
170
  amountNano: z.string().describe('Amount in nanoTON (1 TON = 1000000000)'),
169
171
  payload: z.string().optional().describe('Optional BOC-encoded payload for the transaction'),
170
172
  stateInit: z.string().optional().describe('Optional stateInit BOC for deploying new contracts'),
171
- }, async ({ to, amountNano, payload, stateInit }) => {
173
+ }, { title: 'Request Transfer', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ to, amountNano, payload, stateInit }) => {
172
174
  if (!TOKEN) {
173
175
  return {
174
- content: [{ type: 'text', text: 'No token configured. Use request_auth first to authenticate.' }],
176
+ content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
175
177
  isError: true,
176
178
  };
177
179
  }
@@ -210,12 +212,12 @@ server.tool('request_transfer', 'Request a TON transfer. The user must approve i
210
212
  };
211
213
  }
212
214
  });
213
- server.tool('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.', {
214
- id: z.string().describe('The request ID returned by request_transfer'),
215
- }, async ({ id }) => {
215
+ server.tool('transfer.status', 'Check the status of a transfer request. Statuses: pending (waiting for approval), confirmed (signed and broadcast), rejected (user declined), expired (5 min timeout). Also shows broadcast result if available.', {
216
+ id: z.string().describe('The request ID returned by transfer.request'),
217
+ }, { title: 'Transfer Status', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ id }) => {
216
218
  if (!TOKEN) {
217
219
  return {
218
- content: [{ type: 'text', text: 'No token configured. Use request_auth first to authenticate.' }],
220
+ content: [{ type: 'text', text: 'No token configured. Use auth.request first to authenticate.' }],
219
221
  isError: true,
220
222
  };
221
223
  }
@@ -249,10 +251,10 @@ server.tool('get_request_status', 'Check the status of a transfer request. Statu
249
251
  };
250
252
  }
251
253
  });
252
- server.tool('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 () => {
253
255
  if (!TOKEN) {
254
256
  return {
255
- 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.' }],
256
258
  isError: true,
257
259
  };
258
260
  }
@@ -281,9 +283,9 @@ server.tool('list_pending_requests', 'List all transfer requests waiting for wal
281
283
  };
282
284
  }
283
285
  });
284
- server.tool('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 () => {
285
287
  if (!TOKEN) {
286
- 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 };
287
289
  }
288
290
  try {
289
291
  const result = await apiCall('/v1/wallet/balance');
@@ -304,9 +306,9 @@ server.tool('get_wallet_info', 'Get the connected wallet address, TON balance (i
304
306
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
305
307
  }
306
308
  });
307
- server.tool('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 () => {
308
310
  if (!TOKEN) {
309
- 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 };
310
312
  }
311
313
  try {
312
314
  const result = await apiCall('/v1/wallet/jettons');
@@ -329,11 +331,11 @@ server.tool('get_jetton_balances', 'List all tokens (jettons) in the wallet —
329
331
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
330
332
  }
331
333
  });
332
- server.tool('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.', {
333
335
  limit: z.number().optional().describe('Number of transactions to return (default 10)'),
334
- }, async ({ limit }) => {
336
+ }, { title: 'Transaction History', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ limit }) => {
335
337
  if (!TOKEN) {
336
- return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
338
+ return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
337
339
  }
338
340
  try {
339
341
  const result = await apiCall(`/v1/wallet/transactions?limit=${limit ?? 10}`);
@@ -354,9 +356,9 @@ server.tool('get_transactions', 'Get recent transaction history. Shows timestamp
354
356
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
355
357
  }
356
358
  });
357
- server.tool('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 () => {
358
360
  if (!TOKEN) {
359
- 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 };
360
362
  }
361
363
  try {
362
364
  const result = await apiCall('/v1/wallet/nfts');
@@ -373,9 +375,9 @@ server.tool('get_nft_items', 'List all NFTs owned by the wallet — name, collec
373
375
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
374
376
  }
375
377
  });
376
- server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a raw wallet address. ALWAYS use this before 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.', {
377
379
  domain: z.string().describe('The .ton domain name to resolve (e.g. "alice.ton")'),
378
- }, async ({ domain }) => {
380
+ }, { title: 'Resolve .ton Name', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ domain }) => {
379
381
  try {
380
382
  const result = await fetch(`${API_URL}/v1/dns/${encodeURIComponent(domain)}/resolve`);
381
383
  const data = await result.json();
@@ -392,9 +394,9 @@ server.tool('resolve_name', 'Resolve a .ton domain name (like "alice.ton") to a
392
394
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
393
395
  }
394
396
  });
395
- server.tool('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.', {
396
398
  currencies: z.string().optional().describe('Comma-separated currencies (default "USD")'),
397
- }, async ({ currencies }) => {
399
+ }, { title: 'TON Price', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ currencies }) => {
398
400
  try {
399
401
  const curr = currencies || 'USD';
400
402
  const result = await fetch(`${API_URL}/v1/market/price?tokens=TON&currencies=${curr}`);
@@ -411,14 +413,14 @@ server.tool('get_ton_price', 'Get the current TON price in USD, EUR, or other cu
411
413
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
412
414
  }
413
415
  });
414
- server.tool('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.', {
415
417
  fromToken: z.string().describe('Token to sell, e.g. "NOT", "TON", "USDT"'),
416
418
  toToken: z.string().describe('Token to buy, e.g. "TON", "NOT", "AGNT"'),
417
419
  amount: z.string().describe('Amount to sell in smallest unit. TON/NOT/DOGS/BUILD/AGNT use 9 decimals (1 TON = 10^9). USDT/XAUT0 use 6 decimals (1 USDT = 10^6).'),
418
420
  price: z.number().describe('Human-readable price: how many toToken per 1 fromToken. E.g. price=20 means "1 USDT = 20 AGNT". price=0.05 means "1 AGNT = 0.05 USDT".'),
419
- }, async ({ fromToken, toToken, amount, price }) => {
421
+ }, { title: 'Create DEX Order', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ fromToken, toToken, amount, price }) => {
420
422
  if (!TOKEN) {
421
- return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
423
+ return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
422
424
  }
423
425
  try {
424
426
  const result = await apiCall('/v1/dex/order', {
@@ -446,7 +448,7 @@ server.tool('create_dex_order', 'Place a limit order on the open4dev DEX order b
446
448
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
447
449
  }
448
450
  });
449
- server.tool('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 () => {
450
452
  try {
451
453
  const result = await fetch(`${API_URL}/v1/dex/pairs`);
452
454
  const data = await result.json();
@@ -462,9 +464,9 @@ server.tool('list_dex_pairs', 'List available trading pairs on the DEX. Shows wh
462
464
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
463
465
  }
464
466
  });
465
- server.tool('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 () => {
466
468
  if (!TOKEN) {
467
- 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 };
468
470
  }
469
471
  try {
470
472
  // Get owner's public key
@@ -545,13 +547,13 @@ server.tool('deploy_agent_wallet', 'Deploy an Agent Wallet smart contract — a
545
547
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
546
548
  }
547
549
  });
548
- server.tool('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.', {
549
551
  walletAddress: z.string().describe('The agent wallet contract address'),
550
552
  to: z.string().describe('Destination TON address'),
551
553
  amountNano: z.string().describe('Amount in nanoTON'),
552
- }, async ({ walletAddress, to, amountNano }) => {
554
+ }, { title: 'Agent Wallet Transfer', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ walletAddress, to, amountNano }) => {
553
555
  if (!TOKEN) {
554
- return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
556
+ return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
555
557
  }
556
558
  try {
557
559
  const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
@@ -642,11 +644,11 @@ server.tool('execute_agent_wallet_transfer', 'Send TON directly from an Agent Wa
642
644
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
643
645
  }
644
646
  });
645
- server.tool('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.', {
646
648
  walletAddress: z.string().optional().describe('Agent wallet address. If omitted, lists all your agent wallets.'),
647
- }, async ({ walletAddress }) => {
649
+ }, { title: 'Agent Wallet Info', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ walletAddress }) => {
648
650
  if (!TOKEN) {
649
- return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
651
+ return { content: [{ type: 'text', text: 'No token configured. Use auth.request first.' }], isError: true };
650
652
  }
651
653
  try {
652
654
  if (!walletAddress) {
@@ -654,7 +656,7 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
654
656
  const result = await apiCall('/v1/agent-wallet/list');
655
657
  const wallets = result.wallets || [];
656
658
  if (!wallets.length) {
657
- return { content: [{ type: 'text', text: 'No agent wallets found. Use deploy_agent_wallet to create one.' }] };
659
+ return { content: [{ type: 'text', text: 'No agent wallets found. Use agent_wallet.deploy to create one.' }] };
658
660
  }
659
661
  const lines = wallets.map((w) => `- ${w.address} (created ${new Date(w.createdAt).toISOString()})`);
660
662
  return {
@@ -682,5 +684,42 @@ server.tool('get_agent_wallet_info', 'Get info about Agent Wallets — balance,
682
684
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
683
685
  }
684
686
  });
685
- const transport = new StdioServerTransport();
686
- await server.connect(transport);
687
+ const httpPort = process.env.MCP_HTTP_PORT || (process.argv.includes('--http') ? '3100' : '');
688
+ if (httpPort) {
689
+ // HTTP transport for Smithery and remote clients
690
+ const httpServer = createServer(async (req, res) => {
691
+ // CORS
692
+ res.setHeader('Access-Control-Allow-Origin', '*');
693
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
694
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
695
+ if (req.method === 'OPTIONS') {
696
+ res.writeHead(204);
697
+ res.end();
698
+ return;
699
+ }
700
+ // Health check
701
+ if (req.method === 'GET' && req.url === '/health') {
702
+ res.writeHead(200, { 'Content-Type': 'application/json' });
703
+ res.end(JSON.stringify({ ok: true, version: '0.14.0' }));
704
+ return;
705
+ }
706
+ // MCP endpoint
707
+ if (req.url === '/mcp' || req.url === '/') {
708
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
709
+ res.on('close', () => { transport.close(); });
710
+ await server.connect(transport);
711
+ await transport.handleRequest(req, res);
712
+ return;
713
+ }
714
+ res.writeHead(404);
715
+ res.end('Not found');
716
+ });
717
+ httpServer.listen(Number(httpPort), () => {
718
+ console.error(`MCP HTTP server listening on port ${httpPort}`);
719
+ });
720
+ }
721
+ else {
722
+ // Default: stdio transport for local MCP clients
723
+ const transport = new StdioServerTransport();
724
+ await server.connect(transport);
725
+ }
@@ -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.12.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
  }