@tongateway/mcp 0.6.0 → 0.7.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 +238 -0
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,24 @@ function saveToken(token) {
|
|
|
25
25
|
catch { }
|
|
26
26
|
}
|
|
27
27
|
let TOKEN = loadToken();
|
|
28
|
+
// Compiled AgentVault contract code (embedded for security — no external dependency)
|
|
29
|
+
const AGENT_WALLET_CODE_HEX = 'b5ee9c7241020a01000210000114ff00f4a413f4bcf2c80b01020120020702014803060188d020d749c120915b8eb920d70b1f20821061677374bd2182106172766bbdb0218210616f776bbdb0925f03e0821005f5e10070fb0202d0d3030171b0925f03e0fa4030e20401a2ed44d0d200d31fd31fd3fffa40d3ffd31f305172c705f2e19c078020d7218040d72128821061677374ba8e2336363603d3ffd31f301036102506c8ca0015cb1f13cb1fcbff01cf16cbffcb1fc9ed54e30e0500b02882106172766bba8e2230353535702010365e22102306c8ca0015cb1f13cb1fcbff01cf16cbffcb1fc9ed54e032078210616f776bba8e1bd3ff30552306c8ca0015cb1f13cb1fcbff01cf16cbffcb1fc9ed54e05f07f2000017a0992fda89a0e3ae43ae163f0106f2db3c0801f620d70b1f82107369676ebaf2e195208308d722018308d723208020d721d31fd31fd31fed44d0d200d31fd31fd3fffa40d3ffd31f3026b3f2d1905185baf2e1915193baf2e19207f823bbf2d19408f901547098f9107029c300953051a8f91092323ae25290b1f2e19308b397f82324bcf2d19adef800a4506510470900e0470306c8ca0015cb1f13cb1fcbff01cf16cbffcb1fc9ed54f80ff40430206e91308e4c7f21d73930709421c700b38e2d01d72820761e436c20d749c008f2e19d20d74ac002f2e19d20d71d06c712c2005230b0f2d19ed74cd7393001a4e86c128407bbf2e19dd74ac000f2e19ded55e2c6472d0b';
|
|
30
|
+
// Local wallet storage — agent secret keys never leave the machine
|
|
31
|
+
const WALLETS_FILE = join(homedir(), '.tongateway', 'wallets.json');
|
|
32
|
+
function loadLocalWallets() {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(WALLETS_FILE, 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveLocalWallet(address, agentSecretKey, walletId) {
|
|
41
|
+
const wallets = loadLocalWallets();
|
|
42
|
+
wallets[address] = { agentSecretKey, walletId };
|
|
43
|
+
mkdirSync(join(homedir(), '.tongateway'), { recursive: true });
|
|
44
|
+
writeFileSync(WALLETS_FILE, JSON.stringify(wallets, null, 2), 'utf-8');
|
|
45
|
+
}
|
|
28
46
|
async function apiCall(path, options = {}) {
|
|
29
47
|
const headers = {
|
|
30
48
|
'Content-Type': 'application/json',
|
|
@@ -385,5 +403,225 @@ server.tool('get_ton_price', 'Get the current price of TON in USD and other curr
|
|
|
385
403
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
386
404
|
}
|
|
387
405
|
});
|
|
406
|
+
server.tool('deploy_agent_wallet', 'Deploy a new Agent Wallet smart contract. This creates an autonomous wallet that the agent can use to send TON without requiring approval for each transaction. DANGER: funds in this wallet can be spent by the agent without approval.', {}, async () => {
|
|
407
|
+
if (!TOKEN) {
|
|
408
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
// Get owner's public key
|
|
412
|
+
const meResult = await apiCall('/v1/auth/me');
|
|
413
|
+
const ownerAddress = meResult.address;
|
|
414
|
+
// Get owner public key from tonapi
|
|
415
|
+
const pubKeyRes = await fetch(`https://tonapi.io/v2/wallet/${encodeURIComponent(ownerAddress)}/get-account-public-key`);
|
|
416
|
+
const pubKeyData = await pubKeyRes.json();
|
|
417
|
+
if (!pubKeyData.public_key)
|
|
418
|
+
throw new Error('Could not get owner public key');
|
|
419
|
+
const ownerPublicKey = pubKeyData.public_key;
|
|
420
|
+
// Generate agent keypair on server
|
|
421
|
+
const deployResult = await apiCall('/v1/agent-wallet/deploy', {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
body: JSON.stringify({ ownerPublicKey }),
|
|
424
|
+
});
|
|
425
|
+
const { agentPublicKey, agentSecretKey, walletId } = deployResult;
|
|
426
|
+
// Build stateInit using embedded compiled code
|
|
427
|
+
const { Cell, beginCell, Address, contractAddress, storeStateInit } = await import('@ton/core');
|
|
428
|
+
const code = Cell.fromBoc(Buffer.from(AGENT_WALLET_CODE_HEX, 'hex'))[0];
|
|
429
|
+
const ownerPubBuf = Buffer.from(ownerPublicKey, 'hex');
|
|
430
|
+
const agentPubBuf = Buffer.from(agentPublicKey, 'hex');
|
|
431
|
+
const adminAddr = Address.parse(ownerAddress);
|
|
432
|
+
const data = beginCell()
|
|
433
|
+
.storeBit(true) // signatureAllowed
|
|
434
|
+
.storeUint(0, 32) // seqno
|
|
435
|
+
.storeUint(walletId, 32) // walletId
|
|
436
|
+
.storeBuffer(ownerPubBuf, 32) // ownerPublicKey
|
|
437
|
+
.storeAddress(adminAddr) // adminAddress
|
|
438
|
+
.storeBuffer(agentPubBuf, 32) // agentPublicKey (set from the start)
|
|
439
|
+
.storeUint(Math.floor(Date.now() / 1000) + 10 * 365 * 24 * 3600, 32) // agentValidUntil (10 years)
|
|
440
|
+
.endCell();
|
|
441
|
+
const init = { code, data };
|
|
442
|
+
const address = contractAddress(0, init);
|
|
443
|
+
const stateInitCell = beginCell().store(storeStateInit(init)).endCell();
|
|
444
|
+
const stateInitBoc = stateInitCell.toBoc().toString('base64');
|
|
445
|
+
// Deploy via safe transfer (user approves)
|
|
446
|
+
const transferResult = await apiCall('/v1/safe/tx/transfer', {
|
|
447
|
+
method: 'POST',
|
|
448
|
+
body: JSON.stringify({
|
|
449
|
+
to: address.toRawString(),
|
|
450
|
+
amountNano: '100000000', // 0.1 TON for deployment
|
|
451
|
+
stateInit: stateInitBoc,
|
|
452
|
+
}),
|
|
453
|
+
});
|
|
454
|
+
// Register the wallet on the server
|
|
455
|
+
await apiCall('/v1/agent-wallet/register', {
|
|
456
|
+
method: 'POST',
|
|
457
|
+
body: JSON.stringify({
|
|
458
|
+
address: address.toRawString(),
|
|
459
|
+
agentSecretKey,
|
|
460
|
+
agentPublicKey,
|
|
461
|
+
ownerPublicKey,
|
|
462
|
+
walletId,
|
|
463
|
+
}),
|
|
464
|
+
});
|
|
465
|
+
// Save secret key locally — never leaves this machine
|
|
466
|
+
saveLocalWallet(address.toRawString(), agentSecretKey, walletId);
|
|
467
|
+
return {
|
|
468
|
+
content: [{
|
|
469
|
+
type: 'text',
|
|
470
|
+
text: [
|
|
471
|
+
'Agent Wallet deployment requested!',
|
|
472
|
+
'',
|
|
473
|
+
`Address: ${address.toString()}`,
|
|
474
|
+
`Raw: ${address.toRawString()}`,
|
|
475
|
+
`Request ID: ${transferResult.id}`,
|
|
476
|
+
'',
|
|
477
|
+
'Approve the deployment in your wallet app (0.1 TON).',
|
|
478
|
+
'After approval, top up the wallet with funds the agent can spend.',
|
|
479
|
+
'',
|
|
480
|
+
'WARNING: The agent can spend funds from this wallet without your approval.',
|
|
481
|
+
].join('\n'),
|
|
482
|
+
}],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
catch (e) {
|
|
486
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
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 where the agent key is set.', {
|
|
490
|
+
walletAddress: z.string().describe('The agent wallet contract address'),
|
|
491
|
+
to: z.string().describe('Destination TON address'),
|
|
492
|
+
amountNano: z.string().describe('Amount in nanoTON'),
|
|
493
|
+
}, async ({ walletAddress, to, amountNano }) => {
|
|
494
|
+
if (!TOKEN) {
|
|
495
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const { Cell, beginCell, Address, SendMode, external, storeMessage } = await import('@ton/core');
|
|
499
|
+
const { sign, keyPairFromSeed } = await import('@ton/crypto');
|
|
500
|
+
// Get wallet config from local storage
|
|
501
|
+
const localWallets = loadLocalWallets();
|
|
502
|
+
const localConfig = localWallets[walletAddress];
|
|
503
|
+
if (!localConfig)
|
|
504
|
+
throw new Error('Agent wallet secret key not found locally. Was it deployed from this machine?');
|
|
505
|
+
// Get current seqno from server
|
|
506
|
+
const infoResult = await apiCall(`/v1/agent-wallet/${encodeURIComponent(walletAddress)}/info`);
|
|
507
|
+
const seqno = infoResult.seqno;
|
|
508
|
+
const walletId = localConfig.walletId;
|
|
509
|
+
const secretKeyBuf = Buffer.from(localConfig.agentSecretKey, 'hex');
|
|
510
|
+
// Normalize: if 64 bytes use as-is, if 32 bytes derive from seed
|
|
511
|
+
let secretKey;
|
|
512
|
+
if (secretKeyBuf.length === 64) {
|
|
513
|
+
secretKey = secretKeyBuf;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const kp = keyPairFromSeed(secretKeyBuf);
|
|
517
|
+
secretKey = kp.secretKey;
|
|
518
|
+
}
|
|
519
|
+
const vaultAddr = Address.parse(walletAddress);
|
|
520
|
+
const destAddr = Address.parse(to);
|
|
521
|
+
// Build transfer message
|
|
522
|
+
const transferMsg = beginCell()
|
|
523
|
+
.storeUint(0x18, 6)
|
|
524
|
+
.storeAddress(destAddr)
|
|
525
|
+
.storeCoins(BigInt(amountNano))
|
|
526
|
+
.storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
527
|
+
.endCell();
|
|
528
|
+
// Build actions list
|
|
529
|
+
const sendMode = SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS;
|
|
530
|
+
const actionsList = beginCell()
|
|
531
|
+
.storeRef(beginCell().endCell()) // empty previous
|
|
532
|
+
.storeUint(0x0ec3c86d, 32) // action_send_msg prefix
|
|
533
|
+
.storeUint(sendMode, 8)
|
|
534
|
+
.storeRef(transferMsg)
|
|
535
|
+
.endCell();
|
|
536
|
+
// Build unsigned body
|
|
537
|
+
const validUntil = Math.floor(Date.now() / 1000) + 300;
|
|
538
|
+
const unsignedBody = beginCell()
|
|
539
|
+
.storeUint(0x7369676e, 32) // prefix::signed_external
|
|
540
|
+
.storeUint(walletId, 32)
|
|
541
|
+
.storeUint(validUntil, 32)
|
|
542
|
+
.storeUint(seqno, 32)
|
|
543
|
+
.storeMaybeRef(actionsList)
|
|
544
|
+
.endCell();
|
|
545
|
+
// Sign
|
|
546
|
+
const signature = sign(unsignedBody.hash(), secretKey);
|
|
547
|
+
const signedBody = beginCell()
|
|
548
|
+
.storeSlice(unsignedBody.beginParse())
|
|
549
|
+
.storeBuffer(signature)
|
|
550
|
+
.endCell();
|
|
551
|
+
// Build external message
|
|
552
|
+
const extMsg = external({ to: vaultAddr, body: signedBody });
|
|
553
|
+
const boc = beginCell().store(storeMessage(extMsg)).endCell().toBoc().toString('base64');
|
|
554
|
+
// Broadcast
|
|
555
|
+
const broadcastRes = await fetch('https://toncenter.com/api/v2/sendBoc', {
|
|
556
|
+
method: 'POST',
|
|
557
|
+
headers: { 'Content-Type': 'application/json' },
|
|
558
|
+
body: JSON.stringify({ boc }),
|
|
559
|
+
});
|
|
560
|
+
const broadcastData = await broadcastRes.json();
|
|
561
|
+
if (!broadcastData.ok) {
|
|
562
|
+
throw new Error(`Broadcast failed: ${broadcastData.error || JSON.stringify(broadcastData)}`);
|
|
563
|
+
}
|
|
564
|
+
const tonAmount = (BigInt(amountNano) / 1000000000n).toString() + '.' +
|
|
565
|
+
(BigInt(amountNano) % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '');
|
|
566
|
+
return {
|
|
567
|
+
content: [{
|
|
568
|
+
type: 'text',
|
|
569
|
+
text: [
|
|
570
|
+
'Transfer executed from Agent Wallet!',
|
|
571
|
+
'',
|
|
572
|
+
`From: ${walletAddress}`,
|
|
573
|
+
`To: ${to}`,
|
|
574
|
+
`Amount: ${tonAmount} TON`,
|
|
575
|
+
`Seqno: ${seqno}`,
|
|
576
|
+
'',
|
|
577
|
+
'Transaction broadcast successfully. No approval was needed.',
|
|
578
|
+
].join('\n'),
|
|
579
|
+
}],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
server.tool('get_agent_wallet_info', 'Get info about an Agent Wallet — balance, seqno, agent key status.', {
|
|
587
|
+
walletAddress: z.string().optional().describe('Agent wallet address. If omitted, lists all your agent wallets.'),
|
|
588
|
+
}, async ({ walletAddress }) => {
|
|
589
|
+
if (!TOKEN) {
|
|
590
|
+
return { content: [{ type: 'text', text: 'No token configured. Use request_auth first.' }], isError: true };
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
if (!walletAddress) {
|
|
594
|
+
// List all wallets
|
|
595
|
+
const result = await apiCall('/v1/agent-wallet/list');
|
|
596
|
+
const wallets = result.wallets || [];
|
|
597
|
+
if (!wallets.length) {
|
|
598
|
+
return { content: [{ type: 'text', text: 'No agent wallets found. Use deploy_agent_wallet to create one.' }] };
|
|
599
|
+
}
|
|
600
|
+
const lines = wallets.map((w) => `- ${w.address} (created ${new Date(w.createdAt).toISOString()})`);
|
|
601
|
+
return {
|
|
602
|
+
content: [{ type: 'text', text: `Agent wallets (${wallets.length}):\n${lines.join('\n')}` }],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
const result = await apiCall(`/v1/agent-wallet/${encodeURIComponent(walletAddress)}/info`);
|
|
606
|
+
const balanceTon = (BigInt(result.balance) / 1000000000n).toString();
|
|
607
|
+
const balanceFrac = (BigInt(result.balance) % 1000000000n).toString().padStart(9, '0').replace(/0+$/, '') || '0';
|
|
608
|
+
return {
|
|
609
|
+
content: [{
|
|
610
|
+
type: 'text',
|
|
611
|
+
text: [
|
|
612
|
+
`Agent Wallet: ${result.address}`,
|
|
613
|
+
`Balance: ${balanceTon}.${balanceFrac} TON`,
|
|
614
|
+
`Status: ${result.status}`,
|
|
615
|
+
`Seqno: ${result.seqno}`,
|
|
616
|
+
`Agent Key: ${result.agentPublicKey}`,
|
|
617
|
+
`Created: ${new Date(result.createdAt).toISOString()}`,
|
|
618
|
+
].join('\n'),
|
|
619
|
+
}],
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch (e) {
|
|
623
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
624
|
+
}
|
|
625
|
+
});
|
|
388
626
|
const transport = new StdioServerTransport();
|
|
389
627
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tongateway/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP server for Agent Gateway — lets AI agents request TON blockchain transfers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
31
|
+
"@ton/core": "^0.63.1",
|
|
32
|
+
"@ton/crypto": "^3.3.0",
|
|
31
33
|
"zod": "^4.3.6"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|