helius-mcp 0.2.4 → 0.4.1

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 (46) hide show
  1. package/README.md +21 -129
  2. package/dist/index.js +3 -11
  3. package/dist/tools/accounts.js +260 -150
  4. package/dist/tools/assets.js +214 -9
  5. package/dist/tools/balance.js +3 -5
  6. package/dist/tools/blocks.d.ts +2 -0
  7. package/dist/tools/blocks.js +100 -0
  8. package/dist/tools/config.js +81 -20
  9. package/dist/tools/das-extras.d.ts +2 -0
  10. package/dist/tools/das-extras.js +93 -0
  11. package/dist/tools/enhanced-websockets.d.ts +0 -10
  12. package/dist/tools/enhanced-websockets.js +52 -237
  13. package/dist/tools/{rpc.d.ts → fees.d.ts} +1 -1
  14. package/dist/tools/fees.js +74 -0
  15. package/dist/tools/guides.d.ts +2 -0
  16. package/dist/tools/guides.js +680 -0
  17. package/dist/tools/index.js +25 -9
  18. package/dist/tools/laserstream.d.ts +0 -14
  19. package/dist/tools/laserstream.js +77 -311
  20. package/dist/tools/network.d.ts +2 -0
  21. package/dist/tools/network.js +79 -0
  22. package/dist/tools/{das.d.ts → plans.d.ts} +1 -1
  23. package/dist/tools/plans.js +272 -0
  24. package/dist/tools/tokens.d.ts +2 -0
  25. package/dist/tools/tokens.js +60 -0
  26. package/dist/tools/transactions.js +574 -221
  27. package/dist/tools/wallet.d.ts +2 -0
  28. package/dist/tools/wallet.js +271 -0
  29. package/dist/tools/webhooks.js +19 -12
  30. package/dist/types/transaction-types.d.ts +0 -20
  31. package/dist/types/transaction-types.js +2 -22
  32. package/dist/utils/formatters.d.ts +2 -0
  33. package/dist/utils/formatters.js +10 -2
  34. package/dist/utils/helius.d.ts +3 -23
  35. package/dist/utils/helius.js +26 -48
  36. package/package.json +15 -5
  37. package/dist/tools/assets-extended.d.ts +0 -2
  38. package/dist/tools/assets-extended.js +0 -54
  39. package/dist/tools/das.js +0 -431
  40. package/dist/tools/enhanced-transactions.d.ts +0 -2
  41. package/dist/tools/enhanced-transactions.js +0 -51
  42. package/dist/tools/priority-fees.d.ts +0 -2
  43. package/dist/tools/priority-fees.js +0 -58
  44. package/dist/tools/rpc.js +0 -131
  45. package/dist/tools/transactions-enhanced.d.ts +0 -2
  46. package/dist/tools/transactions-enhanced.js +0 -165
package/README.md CHANGED
@@ -1,150 +1,42 @@
1
1
  # Helius MCP Server
2
2
 
3
- Official Model Context Protocol (MCP) server for Helius - Complete Solana blockchain data access for Claude.
3
+ MCP server for Helius - Solana blockchain data access for Claude.
4
4
 
5
- ## Features
5
+ ## Setup
6
6
 
7
- **29 Tools Covering:**
8
- - Digital Asset Standard (DAS) API - NFTs, tokens, compressed NFTs
9
- - ✅ RPC Methods - Balances, accounts, signatures
10
- - ✅ Enhanced Transactions - Human-readable parsing
11
- - ✅ Priority Fees - Real-time fee estimation
12
- - ✅ Webhooks - Event monitoring and notifications
13
- - ✅ Enhanced WebSockets - Real-time streaming (1.5-2× faster)
14
- - ✅ Laserstream gRPC - Lowest latency streaming with 24h replay
15
-
16
- **Network Support:**
17
- - Mainnet Beta
18
- - Devnet
19
-
20
- ## Installation
7
+ ```bash
8
+ claude mcp add helius npx helius-mcp@latest
9
+ ```
21
10
 
11
+ Set your API key:
22
12
  ```bash
23
- npm install -g @helius-labs/mcp-server
13
+ export HELIUS_API_KEY=your-api-key
24
14
  ```
25
15
 
26
- ## Quick Start
16
+ ## Tools (29)
27
17
 
28
- ### 1. Get Helius API Key
18
+ **DAS API (12):** getAsset, getAssetBatch, getAssetsByOwner, getAssetsByGroup, getAssetsByCreator, getAssetsByAuthority, searchAssets, getAssetProof, getAssetProofBatch, getSignaturesForAsset, getNftEditions, getTokenAccounts
29
19
 
30
- Sign up at [helius.dev](https://www.helius.dev) and create an API key.
20
+ **RPC (4):** getBalance, getAccountInfo, getMultipleAccounts, getSignaturesForAddress
31
21
 
32
- ### 2. Configure Claude Desktop
22
+ **Transactions (1):** parseTransactions
33
23
 
34
- Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
24
+ **Priority Fees (1):** getPriorityFeeEstimate
35
25
 
36
- ```json
37
- {
38
- "mcpServers": {
39
- "helius": {
40
- "command": "helius-mcp",
41
- "env": {
42
- "HELIUS_API_KEY": "your-api-key-here",
43
- "HELIUS_NETWORK": "mainnet-beta"
44
- }
45
- }
46
- }
47
- }
48
- ```
26
+ **Webhooks (5):** getAllWebhooks, getWebhookByID, createWebhook, updateWebhook, deleteWebhook
49
27
 
50
- ### 3. Restart Claude Desktop
28
+ **Enhanced WebSockets (3):** transactionSubscribe, accountSubscribe, getEnhancedWebSocketInfo
51
29
 
52
- The Helius tools will now be available in Claude.
30
+ **Laserstream gRPC (2):** laserstreamSubscribe, getLaserstreamInfo
53
31
 
54
- ## Usage Examples
32
+ **Config (1):** setHeliusApiKey
55
33
 
56
- **Check wallet balance:**
57
- ```
58
- What's the SOL balance for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg?
59
- ```
60
-
61
- **Get NFT information:**
62
- ```
63
- Show me details for NFT FKZJkqv2YbXmcGWbp2RJfhVmpE6PnSaq6dMj1DKXgj1m
64
- ```
34
+ ## Networks
65
35
 
66
- **Monitor transactions:**
67
- ```
68
- Set up a webhook to track transactions for wallet ABC...XYZ
69
- ```
36
+ Mainnet Beta and Devnet. Set via `HELIUS_NETWORK` env var.
70
37
 
71
- **Real-time streaming:**
72
- ```
73
- Show me how to subscribe to Raydium swap transactions
74
- ```
38
+ ## Docs
75
39
 
76
- ## Available Tools
77
-
78
- ### Config (1 tool)
79
- - `setHeliusApiKey` - Configure API key and network
80
-
81
- ### DAS API (12 tools)
82
- - `getAsset` - NFT/token details
83
- - `getAssetBatch` - Batch fetch assets
84
- - `getAssetsByOwner` - Wallet's assets
85
- - `getAssetsByGroup` - Collection NFTs
86
- - `getAssetsByCreator` - Creator's assets
87
- - `getAssetsByAuthority` - Authority's assets
88
- - `searchAssets` - Advanced search
89
- - `getAssetProof` - cNFT Merkle proof
90
- - `getAssetProofBatch` - Batch proofs
91
- - `getSignaturesForAsset` - cNFT transaction history
92
- - `getNftEditions` - Edition NFTs
93
- - `getTokenAccounts` - Token account queries
94
-
95
- ### RPC Methods (4 tools)
96
- - `getBalance` - SOL balance
97
- - `getAccountInfo` - Account details
98
- - `getMultipleAccounts` - Batch account lookups
99
- - `getSignaturesForAddress` - Transaction signatures
100
-
101
- ### Enhanced Transactions (1 tool)
102
- - `parseTransactions` - Human-readable parsing
103
-
104
- ### Priority Fees (1 tool)
105
- - `getPriorityFeeEstimate` - Optimal fees
106
-
107
- ### Webhooks (5 tools)
108
- - `getAllWebhooks` - List webhooks
109
- - `getWebhookByID` - Webhook details
110
- - `createWebhook` - Create webhook
111
- - `updateWebhook` - Update webhook
112
- - `deleteWebhook` - Delete webhook
113
-
114
- ### Enhanced WebSockets (3 tools)
115
- - `transactionSubscribe` - Real-time transaction streaming
116
- - `accountSubscribe` - Real-time account monitoring
117
- - `getEnhancedWebSocketInfo` - WebSocket service info
118
-
119
- ### Laserstream gRPC (2 tools)
120
- - `laserstreamSubscribe` - High-performance gRPC streaming
121
- - `getLaserstreamInfo` - Laserstream service info
122
-
123
- ## Plan Requirements
124
-
125
- ### Enhanced WebSockets
126
- - Free/Developer: ❌ Not available
127
- - Business: ✅ 250 connections
128
- - Professional: ✅ 250 connections
129
-
130
- ### Laserstream gRPC
131
- - Free: ❌ Not available
132
- - Developer: ✅ Devnet only
133
- - Business: ✅ Devnet only
134
- - Professional: ✅ Mainnet + Devnet
135
-
136
- ## Documentation
137
-
138
- - [Helius API Documentation](https://docs.helius.dev)
40
+ - [Helius API](https://docs.helius.dev)
139
41
  - [Enhanced WebSockets](https://docs.helius.dev/enhanced-websockets)
140
- - [Laserstream gRPC](https://docs.helius.dev/laserstream)
141
- - [MCP Protocol](https://modelcontextprotocol.io)
142
-
143
- ## Support
144
-
145
- - GitHub Issues: [helius-labs/helius-mcp-server](https://github.com/helius-labs/helius-mcp-server/issues)
146
- - Discord: [Helius Discord](https://discord.gg/aYYStRz3YX)
147
-
148
- ## License
149
-
150
- MIT
42
+ - [Laserstream](https://docs.helius.dev/laserstream)
package/dist/index.js CHANGED
@@ -4,26 +4,18 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { registerTools } from './tools/index.js';
5
5
  import { setApiKey } from './utils/helius.js';
6
6
  const server = new McpServer({
7
- name: 'helius-claude-plugin',
8
- version: '0.2.4'
7
+ name: 'helius-mcp',
8
+ version: '0.3.0'
9
9
  });
10
- // Register all tools
11
10
  registerTools(server);
12
- // Start the server
13
11
  async function main() {
14
- // Auto-configure API key from environment if available
15
12
  if (process.env.HELIUS_API_KEY) {
16
13
  setApiKey(process.env.HELIUS_API_KEY);
17
- console.error('✅ Helius API key loaded from environment');
18
- }
19
- else {
20
- console.error('ℹ️ No API key found. Use setHeliusApiKey tool or set HELIUS_API_KEY environment variable');
21
14
  }
22
15
  const transport = new StdioServerTransport();
23
16
  await server.connect(transport);
24
- console.error('🚀 Helius MCP Server v0.2.4 running (29 tools)');
25
17
  }
26
18
  main().catch((error) => {
27
- console.error('Failed to start server:', error);
19
+ console.error('Failed to start:', error);
28
20
  process.exit(1);
29
21
  });
@@ -1,197 +1,307 @@
1
1
  import { z } from 'zod';
2
- import { getHeliusClient, hasApiKey } from '../utils/helius.js';
2
+ import { getHeliusClient, hasApiKey, getRpcUrl } from '../utils/helius.js';
3
3
  import { formatAddress, formatSol } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
+ function formatParsedAccountData(account) {
6
+ const lines = [];
7
+ const parsedData = account.data;
8
+ if (parsedData?.program) {
9
+ lines.push(` Program: ${parsedData.program}`);
10
+ }
11
+ if (parsedData?.parsed?.type) {
12
+ lines.push(` Account Type: ${parsedData.parsed.type}`);
13
+ }
14
+ if (parsedData?.parsed?.info) {
15
+ for (const [key, value] of Object.entries(parsedData.parsed.info)) {
16
+ const display = typeof value === 'object' ? JSON.stringify(value) : String(value);
17
+ lines.push(` ${key}: ${display}`);
18
+ }
19
+ }
20
+ return lines;
21
+ }
5
22
  export function registerAccountTools(server) {
6
- // Get multiple account balances at once
7
- server.tool('getMultipleAccounts', 'Get account info for multiple addresses in a single call (up to 100 addresses). More efficient than calling getBalance repeatedly. Returns SOL balance, owner, executable status, and rent epoch for each account.', {
8
- addresses: z.array(z.string()).describe('Array of Solana addresses (max 100)'),
9
- commitment: z.enum(['processed', 'confirmed', 'finalized']).optional().default('confirmed')
10
- }, async ({ addresses, commitment }) => {
23
+ // Get Account Info (single or batch) — uses direct JSON-RPC
24
+ server.tool('getAccountInfo', 'Get detailed Solana account information for one or more accounts. For a single account: returns owner program, lamport balance, data size, executable status, and rent epoch. For batch: pass up to 100 addresses in "addresses" for fast bulk lookups. Use jsonParsed encoding (default) on token mint addresses to see Token-2022 extensions, authorities, and supply data. Use this to inspect any on-chain account.', {
25
+ address: z.string().optional().describe('Single account address (base58 encoded). Use this OR addresses, not both.'),
26
+ addresses: z.array(z.string()).optional().describe('Array of account addresses for batch lookup (up to 100). Use this OR address, not both.'),
27
+ encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('jsonParsed').describe('Data encoding format')
28
+ }, async ({ address, addresses, encoding }) => {
11
29
  if (!hasApiKey())
12
30
  return noApiKeyResponse();
13
- if (addresses.length > 100) {
31
+ const url = getRpcUrl();
32
+ // Validate: must provide exactly one of address or addresses
33
+ if (!address && (!addresses || addresses.length === 0)) {
14
34
  return {
15
35
  content: [{
16
36
  type: 'text',
17
- text: '**Error**\n\nMaximum 100 addresses allowed per request'
18
- }],
19
- isError: true
37
+ text: `**Error:** Provide either \`address\` (single account) or \`addresses\` (batch of up to 100).`
38
+ }]
20
39
  };
21
40
  }
22
- try {
23
- const helius = getHeliusClient();
24
- const connection = helius.connection;
25
- if (!connection || !connection.getMultipleAccountsInfo) {
41
+ if (address && addresses && addresses.length > 0) {
42
+ return {
43
+ content: [{
44
+ type: 'text',
45
+ text: `**Error:** Provide either \`address\` or \`addresses\`, not both.`
46
+ }]
47
+ };
48
+ }
49
+ // --- Batch mode ---
50
+ if (addresses && addresses.length > 0) {
51
+ if (addresses.length > 100) {
26
52
  return {
27
53
  content: [{
28
54
  type: 'text',
29
- text: '**Error**\n\nMultiple account lookup not available'
30
- }],
31
- isError: true
55
+ text: `**Error:** Maximum 100 accounts per request. You provided ${addresses.length}.`
56
+ }]
32
57
  };
33
58
  }
34
- const accountsInfo = await connection.getMultipleAccountsInfo(addresses, { commitment });
35
- const lines = [
36
- `**Account Info (${addresses.length} addresses)**`,
37
- ``
38
- ];
59
+ const response = await fetch(url, {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({
63
+ jsonrpc: '2.0',
64
+ id: 'get-multiple-accounts',
65
+ method: 'getMultipleAccounts',
66
+ params: [addresses, { encoding }]
67
+ })
68
+ });
69
+ const data = await response.json();
70
+ if (data.error) {
71
+ return {
72
+ content: [{
73
+ type: 'text',
74
+ text: `**Error**\n\n${data.error.message}`
75
+ }]
76
+ };
77
+ }
78
+ const accounts = data.result?.value || [];
79
+ const lines = [`**Multiple Accounts** (${addresses.length} requested)`, ''];
39
80
  addresses.forEach((addr, i) => {
40
- const info = accountsInfo[i];
41
- if (!info) {
42
- lines.push(`${i + 1}. ${formatAddress(addr)} - Account does not exist`);
43
- return;
81
+ const account = accounts[i];
82
+ if (!account) {
83
+ lines.push(`**${formatAddress(addr)}:** Not found`);
44
84
  }
45
- lines.push(`${i + 1}. ${formatAddress(addr)}`);
46
- lines.push(` Balance: ${formatSol(info.lamports)}`);
47
- lines.push(` Owner: ${formatAddress(info.owner.toBase58())}`);
48
- if (info.executable) {
49
- lines.push(`Executable program`);
85
+ else {
86
+ lines.push(`**${formatAddress(addr)}**`);
87
+ lines.push(` Balance: ${formatSol(account.lamports)}`);
88
+ lines.push(` Owner: ${account.owner}`);
89
+ lines.push(` Executable: ${account.executable ? 'Yes' : 'No'}`);
90
+ if (account.space !== undefined) {
91
+ lines.push(` Data Size: ${account.space} bytes`);
92
+ }
93
+ // Show parsed data (Token-2022 extensions, mint info, etc.)
94
+ const parsedLines = formatParsedAccountData(account);
95
+ lines.push(...parsedLines);
50
96
  }
51
- lines.push(``);
97
+ lines.push('');
52
98
  });
53
99
  return {
54
- content: [{ type: 'text', text: lines.join('\n') }]
100
+ content: [{
101
+ type: 'text',
102
+ text: lines.join('\n')
103
+ }]
104
+ };
105
+ }
106
+ // --- Single account mode ---
107
+ const response = await fetch(url, {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({
111
+ jsonrpc: '2.0',
112
+ id: 'get-account-info',
113
+ method: 'getAccountInfo',
114
+ params: [address, { encoding }]
115
+ })
116
+ });
117
+ const data = await response.json();
118
+ if (data.error) {
119
+ return {
120
+ content: [{
121
+ type: 'text',
122
+ text: `**Error**\n\n${data.error.message}`
123
+ }]
55
124
  };
56
125
  }
57
- catch (error) {
126
+ const account = data.result?.value;
127
+ if (!account) {
58
128
  return {
59
- content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
60
- isError: true
129
+ content: [{
130
+ type: 'text',
131
+ text: `**Account ${formatAddress(address)}**\n\nAccount not found or has no data.`
132
+ }]
61
133
  };
62
134
  }
135
+ const lines = [
136
+ `**Account ${formatAddress(address)}**`,
137
+ '',
138
+ `**Balance:** ${formatSol(account.lamports)} (${account.lamports.toLocaleString()} lamports)`,
139
+ `**Owner:** ${account.owner}`,
140
+ `**Executable:** ${account.executable ? 'Yes' : 'No'}`,
141
+ ];
142
+ if (account.space !== undefined) {
143
+ lines.push(`**Data Size:** ${account.space} bytes`);
144
+ }
145
+ // Show parsed data details when using jsonParsed
146
+ const parsedLines = formatParsedAccountData(account);
147
+ if (parsedLines.length > 0) {
148
+ lines.push('', '**Parsed Data:**');
149
+ lines.push(...parsedLines);
150
+ }
151
+ return {
152
+ content: [{
153
+ type: 'text',
154
+ text: lines.join('\n')
155
+ }]
156
+ };
63
157
  });
64
- // Get program accounts
65
- server.tool('getProgramAccounts', 'Query all accounts owned by a program. Use filters to narrow results (e.g., token accounts for a specific mint, PDAs with certain data). Returns account addresses and data. Warning: Can be slow for programs with many accounts.', {
66
- programId: z.string().describe('Program address to query'),
67
- filters: z.array(z.object({
68
- memcmp: z.object({
69
- offset: z.number(),
70
- bytes: z.string()
71
- }).optional(),
72
- dataSize: z.number().optional()
73
- })).optional().describe('Optional filters to narrow results'),
74
- limit: z.number().optional().default(10).describe('Max accounts to return')
75
- }, async ({ programId, filters, limit }) => {
158
+ // Get Token Accounts
159
+ server.tool('getTokenAccounts', 'Query token accounts with advanced filters. Can filter by mint address, owner address, or both. Returns token account addresses and balances.', {
160
+ owner: z.string().optional().describe('Filter by owner wallet address'),
161
+ mint: z.string().optional().describe('Filter by token mint address'),
162
+ page: z.number().optional().default(1).describe('Page number (starts at 1)'),
163
+ limit: z.number().optional().default(20).describe('Results per page (max 1000)')
164
+ }, async ({ owner, mint, page, limit }) => {
76
165
  if (!hasApiKey())
77
166
  return noApiKeyResponse();
78
- try {
79
- const helius = getHeliusClient();
80
- const connection = helius.connection;
81
- if (!connection || !connection.getProgramAccounts) {
82
- return {
83
- content: [{
84
- type: 'text',
85
- text: '**Error**\n\nProgram account lookup not available'
86
- }],
87
- isError: true
88
- };
89
- }
90
- const config = {};
91
- if (filters && filters.length > 0) {
92
- config.filters = filters;
93
- }
94
- const accounts = await connection.getProgramAccounts(programId, config);
95
- if (!accounts || accounts.length === 0) {
96
- return {
97
- content: [{
98
- type: 'text',
99
- text: `**No Accounts Found**\n\nProgram: ${formatAddress(programId)}`
100
- }]
101
- };
102
- }
103
- const lines = [
104
- `**Program Accounts**`,
105
- ``,
106
- `Program: ${formatAddress(programId)}`,
107
- `Total: ${accounts.length} accounts`,
108
- ``
109
- ];
110
- const displayAccounts = accounts.slice(0, limit);
111
- displayAccounts.forEach((account, i) => {
112
- const pubkey = account.pubkey.toBase58();
113
- const lamports = account.account.lamports;
114
- const dataLength = account.account.data.length;
115
- lines.push(`${i + 1}. ${formatAddress(pubkey)}`);
116
- lines.push(` Balance: ${formatSol(lamports)}`);
117
- lines.push(` Data: ${dataLength} bytes`);
118
- });
119
- if (accounts.length > limit) {
120
- lines.push(``);
121
- lines.push(`💡 Showing first ${limit} of ${accounts.length} accounts`);
122
- }
167
+ const helius = getHeliusClient();
168
+ if (!owner && !mint) {
123
169
  return {
124
- content: [{ type: 'text', text: lines.join('\n') }]
170
+ content: [{
171
+ type: 'text',
172
+ text: `**Error:** You must provide at least one of: owner or mint address.`
173
+ }]
125
174
  };
126
175
  }
127
- catch (error) {
176
+ const params = { page, limit };
177
+ if (owner)
178
+ params.owner = owner;
179
+ if (mint)
180
+ params.mint = mint;
181
+ const response = await helius.getTokenAccounts(params);
182
+ const items = (response.token_accounts || []);
183
+ if (items.length === 0) {
184
+ const filterDesc = owner && mint
185
+ ? `owner=${formatAddress(owner)}, mint=${formatAddress(mint)}`
186
+ : owner
187
+ ? `owner=${formatAddress(owner)}`
188
+ : `mint=${formatAddress(mint)}`;
128
189
  return {
129
- content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
130
- isError: true
190
+ content: [{
191
+ type: 'text',
192
+ text: `**Token Accounts** (${filterDesc})\n\nNo token accounts found.`
193
+ }]
131
194
  };
132
195
  }
196
+ const lines = [`**Token Accounts** (${response.total || items.length} total, page ${page})`, ''];
197
+ items.forEach((account) => {
198
+ const flags = [];
199
+ if (account.frozen)
200
+ flags.push('Frozen');
201
+ if (account.delegated_amount && account.delegated_amount > 0)
202
+ flags.push('Delegated');
203
+ const flagStr = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
204
+ lines.push(`- **Account:** ${formatAddress(account.address)}${flagStr}`);
205
+ lines.push(` Mint: ${formatAddress(account.mint)}`);
206
+ lines.push(` Owner: ${formatAddress(account.owner)}`);
207
+ lines.push(` Amount: ${account.amount.toLocaleString()}`);
208
+ if (account.delegated_amount && account.delegated_amount > 0) {
209
+ lines.push(` Delegated: ${account.delegated_amount.toLocaleString()}`);
210
+ }
211
+ lines.push('');
212
+ });
213
+ return {
214
+ content: [{
215
+ type: 'text',
216
+ text: lines.join('\n')
217
+ }]
218
+ };
133
219
  });
134
- // Get account info
135
- server.tool('getAccountInfo', 'Get detailed information about a single Solana account including balance, owner, data, executable status, and rent epoch. Use this for inspecting program accounts, PDAs, or any on-chain account.', {
136
- address: z.string().describe('Solana address to query'),
137
- encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('base64')
138
- }, async ({ address, encoding }) => {
220
+ // Get Program Accounts (V2 with pagination)
221
+ server.tool('getProgramAccounts', 'Get all accounts owned by a specific program. Returns account addresses, balances, and data sizes. Use dataSize to filter by account data length (e.g. 165 for token accounts). Useful for finding all accounts created by a program like a DEX, lending protocol, or custom program.', {
222
+ programId: z.string().describe('Program ID (base58 encoded) — the owner program of the accounts to find'),
223
+ limit: z.number().optional().default(20).describe('Maximum accounts to return (default 20, max 100)'),
224
+ encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('base64').describe('Data encoding format'),
225
+ dataSize: z.number().optional().describe('Filter by exact account data size in bytes (e.g. 165 for SPL token accounts)'),
226
+ paginationKey: z.string().optional().describe('Pagination cursor from a previous response to fetch the next page')
227
+ }, async ({ programId, limit, encoding, dataSize, paginationKey }) => {
139
228
  if (!hasApiKey())
140
229
  return noApiKeyResponse();
230
+ const url = getRpcUrl();
231
+ const cappedLimit = Math.min(limit, 10_000);
232
+ const filters = [];
233
+ if (dataSize !== undefined) {
234
+ filters.push({ dataSize });
235
+ }
236
+ const rpcParams = {
237
+ encoding,
238
+ dataSlice: { offset: 0, length: 0 },
239
+ limit: cappedLimit
240
+ };
241
+ if (filters.length > 0)
242
+ rpcParams.filters = filters;
243
+ if (paginationKey)
244
+ rpcParams.paginationKey = paginationKey;
245
+ const requestBody = {
246
+ jsonrpc: '2.0',
247
+ id: 'get-program-accounts-v2',
248
+ method: 'getProgramAccountsV2',
249
+ params: [programId, rpcParams]
250
+ };
251
+ let data;
141
252
  try {
142
- const helius = getHeliusClient();
143
- const connection = helius.connection;
144
- if (!connection || !connection.getAccountInfo) {
145
- return {
146
- content: [{
147
- type: 'text',
148
- text: '**Error**\n\nAccount info lookup not available'
149
- }],
150
- isError: true
151
- };
152
- }
153
- const accountInfo = await connection.getAccountInfo(address, { encoding });
154
- if (!accountInfo) {
155
- return {
156
- content: [{
157
- type: 'text',
158
- text: `**Account Not Found**\n\n${formatAddress(address)}\n\nThis account does not exist or has been closed.`
159
- }]
160
- };
161
- }
162
- const lines = [
163
- `**Account Info**`,
164
- ``,
165
- `Address: ${formatAddress(address)}`,
166
- `Balance: ${formatSol(accountInfo.lamports)}`,
167
- `Owner: ${formatAddress(accountInfo.owner.toBase58())}`,
168
- `Data Length: ${accountInfo.data.length} bytes`,
169
- `Rent Epoch: ${accountInfo.rentEpoch}`,
170
- ``
171
- ];
172
- if (accountInfo.executable) {
173
- lines.push(`⚡ **Executable Program**`);
174
- lines.push(``);
175
- }
176
- if (encoding === 'jsonParsed' && accountInfo.data.parsed) {
177
- lines.push(`**Parsed Data:**`);
178
- lines.push('```json');
179
- lines.push(JSON.stringify(accountInfo.data.parsed, null, 2));
180
- lines.push('```');
181
- }
182
- else if (accountInfo.data.length > 0 && accountInfo.data.length <= 100) {
183
- lines.push(`**Data (first 100 bytes):**`);
184
- lines.push(`${accountInfo.data.toString('hex').slice(0, 200)}...`);
185
- }
253
+ const response = await fetch(url, {
254
+ method: 'POST',
255
+ headers: { 'Content-Type': 'application/json' },
256
+ body: JSON.stringify(requestBody)
257
+ });
258
+ data = await response.json();
259
+ }
260
+ catch (err) {
261
+ return {
262
+ content: [{
263
+ type: 'text',
264
+ text: `**Error fetching program accounts:** ${err instanceof Error ? err.message : String(err)}`
265
+ }]
266
+ };
267
+ }
268
+ if (data.error) {
186
269
  return {
187
- content: [{ type: 'text', text: lines.join('\n') }]
270
+ content: [{
271
+ type: 'text',
272
+ text: `**Error**\n\n${data.error.message}`
273
+ }]
188
274
  };
189
275
  }
190
- catch (error) {
276
+ const accounts = data.result?.accounts || [];
277
+ if (accounts.length === 0) {
191
278
  return {
192
- content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
193
- isError: true
279
+ content: [{
280
+ type: 'text',
281
+ text: `**Program Accounts for ${formatAddress(programId)}**\n\nNo accounts found.`
282
+ }]
194
283
  };
195
284
  }
285
+ const totalLabel = data.result?.totalResults
286
+ ? `${data.result.totalResults.toLocaleString()} total`
287
+ : `${accounts.length} returned`;
288
+ const lines = [`**Program Accounts for ${formatAddress(programId)}** (${totalLabel})`, ''];
289
+ accounts.forEach((item) => {
290
+ lines.push(`- **${formatAddress(item.pubkey)}**`);
291
+ lines.push(` Balance: ${formatSol(item.account.lamports)}`);
292
+ if (item.account.space !== undefined) {
293
+ lines.push(` Data Size: ${item.account.space} bytes`);
294
+ }
295
+ lines.push(` Executable: ${item.account.executable ? 'Yes' : 'No'}`);
296
+ });
297
+ if (data.result?.paginationKey) {
298
+ lines.push('', `**Next Page:** Pass \`paginationKey: "${data.result.paginationKey}"\` to fetch the next page.`);
299
+ }
300
+ return {
301
+ content: [{
302
+ type: 'text',
303
+ text: lines.join('\n')
304
+ }]
305
+ };
196
306
  });
197
307
  }