helius-mcp 0.5.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +1 -1
  3. package/README.md +97 -21
  4. package/dist/http.d.ts +1 -0
  5. package/dist/http.js +2 -0
  6. package/dist/index.js +93 -2
  7. package/dist/scripts/validate-catalog.d.ts +13 -0
  8. package/dist/scripts/validate-catalog.js +76 -0
  9. package/dist/tools/accounts.js +114 -204
  10. package/dist/tools/assets.js +109 -123
  11. package/dist/tools/auth.d.ts +2 -0
  12. package/dist/tools/auth.js +459 -0
  13. package/dist/tools/balance.js +28 -32
  14. package/dist/tools/blocks.js +68 -87
  15. package/dist/tools/config.js +18 -79
  16. package/dist/tools/das-extras.js +56 -41
  17. package/dist/tools/docs.js +12 -54
  18. package/dist/tools/enhanced-websockets.js +104 -74
  19. package/dist/tools/fees.js +42 -61
  20. package/dist/tools/guides.js +126 -515
  21. package/dist/tools/index.js +50 -2
  22. package/dist/tools/laserstream.js +107 -53
  23. package/dist/tools/network.js +47 -69
  24. package/dist/tools/plans.d.ts +21 -0
  25. package/dist/tools/plans.js +105 -246
  26. package/dist/tools/product-catalog.d.ts +10 -0
  27. package/dist/tools/product-catalog.js +123 -0
  28. package/dist/tools/recommend.d.ts +4 -0
  29. package/dist/tools/recommend.js +233 -0
  30. package/dist/tools/shared.js +8 -3
  31. package/dist/tools/solana-knowledge.d.ts +2 -0
  32. package/dist/tools/solana-knowledge.js +544 -0
  33. package/dist/tools/tokens.js +17 -18
  34. package/dist/tools/transactions.js +232 -302
  35. package/dist/tools/transfers.d.ts +2 -0
  36. package/dist/tools/transfers.js +270 -0
  37. package/dist/tools/wallet.js +175 -177
  38. package/dist/tools/webhooks.js +80 -82
  39. package/dist/types/transaction-types.d.ts +1 -1
  40. package/dist/types/transaction-types.js +2 -1
  41. package/dist/utils/config.d.ts +27 -0
  42. package/dist/utils/config.js +76 -0
  43. package/dist/utils/docs.d.ts +24 -0
  44. package/dist/utils/docs.js +72 -0
  45. package/dist/utils/errors.d.ts +32 -0
  46. package/dist/utils/errors.js +157 -0
  47. package/dist/utils/feedback.d.ts +16 -0
  48. package/dist/utils/feedback.js +87 -0
  49. package/dist/utils/formatters.d.ts +0 -1
  50. package/dist/utils/formatters.js +0 -3
  51. package/dist/utils/helius.d.ts +15 -5
  52. package/dist/utils/helius.js +52 -45
  53. package/dist/version.d.ts +1 -0
  54. package/dist/version.js +1 -0
  55. package/package.json +17 -7
  56. package/system-prompts/helius/claude.system.md +170 -0
  57. package/system-prompts/helius/full.md +2868 -0
  58. package/system-prompts/helius/openai.developer.md +170 -0
  59. package/system-prompts/helius-dflow/claude.system.md +290 -0
  60. package/system-prompts/helius-dflow/full.md +3647 -0
  61. package/system-prompts/helius-dflow/openai.developer.md +290 -0
  62. package/system-prompts/helius-phantom/claude.system.md +348 -0
  63. package/system-prompts/helius-phantom/full.md +5472 -0
  64. package/system-prompts/helius-phantom/openai.developer.md +348 -0
  65. package/system-prompts/svm/claude.system.md +174 -0
  66. package/system-prompts/svm/full.md +699 -0
  67. package/system-prompts/svm/openai.developer.md +174 -0
@@ -1,100 +1,81 @@
1
1
  import { z } from 'zod';
2
- import { hasApiKey, getRpcUrl } from '../utils/helius.js';
2
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
3
  import { formatSol, formatTimestamp } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
+ import { mcpText, validateEnum, handleToolError } from '../utils/errors.js';
5
6
  export function registerBlockTools(server) {
6
- server.tool('getBlock', 'Get detailed information about a specific Solana block by slot number. Returns block time, blockhash, parent slot, transaction count, and reward summary. Use transactionDetails to control how much transaction data is included: "none" for just block metadata, "signatures" for a list of transaction signatures, or "full" for complete transaction data.', {
7
+ server.tool('getBlock', 'BEST FOR: inspecting a specific block by slot number. Get detailed information about a specific Solana block by slot number. Returns block time, blockhash, parent slot, transaction count, and reward summary. Use transactionDetails to control how much transaction data is included: "none" for just block metadata, "signatures" for a list of transaction signatures, or "full" for complete transaction data. Credit cost: 10 credits/call (historical data).', {
7
8
  slot: z.number().describe('Slot number of the block to fetch'),
8
- transactionDetails: z.enum(['none', 'signatures', 'full']).optional().default('signatures').describe('"none" = block metadata only, "signatures" = list of tx signatures (default), "full" = complete transaction data')
9
+ transactionDetails: z.string().optional().default('signatures').describe('"none" = block metadata only, "signatures" = list of tx signatures (default), "full" = complete transaction data')
9
10
  }, async ({ slot, transactionDetails }) => {
10
11
  if (!hasApiKey())
11
12
  return noApiKeyResponse();
12
- const url = getRpcUrl();
13
- const requestBody = {
14
- jsonrpc: '2.0',
15
- id: 'get-block',
16
- method: 'getBlock',
17
- params: [
18
- slot,
19
- {
20
- encoding: 'jsonParsed',
21
- transactionDetails,
22
- maxSupportedTransactionVersion: 0,
23
- rewards: true
24
- }
25
- ]
26
- };
27
- const response = await fetch(url, {
28
- method: 'POST',
29
- headers: { 'Content-Type': 'application/json' },
30
- body: JSON.stringify(requestBody)
31
- });
32
- const data = await response.json();
33
- if (data.error) {
34
- return {
35
- content: [{
36
- type: 'text',
37
- text: `**Error**\n\n${data.error.message}`
38
- }]
39
- };
40
- }
41
- const block = data.result;
42
- if (!block) {
43
- return {
44
- content: [{
45
- type: 'text',
46
- text: `**Block at Slot ${slot.toLocaleString()}**\n\nBlock not found. It may have been skipped or not yet confirmed.`
47
- }]
48
- };
49
- }
50
- const lines = [
51
- `**Block at Slot ${slot.toLocaleString()}**`,
52
- '',
53
- `**Blockhash:** ${block.blockhash}`,
54
- `**Parent Slot:** ${block.parentSlot.toLocaleString()}`,
55
- `**Previous Blockhash:** ${block.previousBlockhash}`,
56
- ];
57
- if (block.blockHeight !== null) {
58
- lines.push(`**Block Height:** ${block.blockHeight.toLocaleString()}`);
59
- }
60
- if (block.blockTime) {
61
- lines.push(`**Block Time:** ${formatTimestamp(block.blockTime)}`);
62
- }
63
- // Transaction count
64
- if (transactionDetails === 'signatures' && block.signatures) {
65
- lines.push(`**Transactions:** ${block.signatures.length}`);
66
- }
67
- else if (transactionDetails === 'full' && block.transactions) {
68
- lines.push(`**Transactions:** ${block.transactions.length}`);
69
- }
70
- // Rewards summary
71
- if (block.rewards && block.rewards.length > 0) {
72
- const totalRewards = block.rewards.reduce((sum, r) => sum + r.lamports, 0);
73
- const rewardTypes = new Map();
74
- block.rewards.forEach((r) => {
75
- rewardTypes.set(r.rewardType, (rewardTypes.get(r.rewardType) || 0) + r.lamports);
76
- });
77
- lines.push('', `**Rewards:** ${formatSol(totalRewards)} total (${block.rewards.length} recipients)`);
78
- rewardTypes.forEach((lamports, type) => {
79
- lines.push(`- ${type}: ${formatSol(lamports)}`);
80
- });
81
- }
82
- // Show signatures if in signatures mode
83
- if (transactionDetails === 'signatures' && block.signatures && block.signatures.length > 0) {
84
- const maxShow = 20;
85
- lines.push('', `**Transaction Signatures** (${block.signatures.length} total):`);
86
- block.signatures.slice(0, maxShow).forEach((sig) => {
87
- lines.push(`- ${sig}`);
13
+ const err = validateEnum(transactionDetails, ['none', 'signatures', 'full'], 'Block Error', 'transactionDetails');
14
+ if (err)
15
+ return err;
16
+ try {
17
+ const helius = getHeliusClient();
18
+ // Kit requires BigInt for slot parameter
19
+ const block = await helius.getBlock(BigInt(slot), {
20
+ encoding: 'jsonParsed',
21
+ transactionDetails,
22
+ maxSupportedTransactionVersion: 0,
23
+ rewards: true
88
24
  });
89
- if (block.signatures.length > maxShow) {
90
- lines.push(`... +${block.signatures.length - maxShow} more`);
25
+ if (!block) {
26
+ return mcpText(`**Block at Slot ${slot.toLocaleString()}**\n\nBlock not found. It may have been skipped or not yet confirmed.`);
27
+ }
28
+ const parentSlot = Number(block.parentSlot);
29
+ const blockHeight = block.blockHeight != null ? Number(block.blockHeight) : null;
30
+ const blockTime = block.blockTime != null ? Number(block.blockTime) : null;
31
+ const lines = [
32
+ `**Block at Slot ${slot.toLocaleString()}**`,
33
+ '',
34
+ `**Blockhash:** ${block.blockhash}`,
35
+ `**Parent Slot:** ${parentSlot.toLocaleString()}`,
36
+ `**Previous Blockhash:** ${block.previousBlockhash}`,
37
+ ];
38
+ if (blockHeight !== null) {
39
+ lines.push(`**Block Height:** ${blockHeight.toLocaleString()}`);
40
+ }
41
+ if (blockTime) {
42
+ lines.push(`**Block Time:** ${formatTimestamp(blockTime)}`);
43
+ }
44
+ // Transaction count
45
+ if (transactionDetails === 'signatures' && block.signatures) {
46
+ lines.push(`**Transactions:** ${block.signatures.length}`);
47
+ }
48
+ else if (transactionDetails === 'full' && block.transactions) {
49
+ lines.push(`**Transactions:** ${block.transactions.length}`);
91
50
  }
51
+ // Rewards summary
52
+ if (block.rewards && block.rewards.length > 0) {
53
+ const totalRewards = block.rewards.reduce((sum, r) => sum + Number(r.lamports), 0);
54
+ const rewardTypes = new Map();
55
+ block.rewards.forEach((r) => {
56
+ const lamports = Number(r.lamports);
57
+ rewardTypes.set(r.rewardType, (rewardTypes.get(r.rewardType) || 0) + lamports);
58
+ });
59
+ lines.push('', `**Rewards:** ${formatSol(totalRewards)} total (${block.rewards.length} recipients)`);
60
+ rewardTypes.forEach((lamports, type) => {
61
+ lines.push(`- ${type}: ${formatSol(lamports)}`);
62
+ });
63
+ }
64
+ // Show signatures if in signatures mode
65
+ if (transactionDetails === 'signatures' && block.signatures && block.signatures.length > 0) {
66
+ const maxShow = 20;
67
+ lines.push('', `**Transaction Signatures** (${block.signatures.length} total):`);
68
+ block.signatures.slice(0, maxShow).forEach((sig) => {
69
+ lines.push(`- ${sig}`);
70
+ });
71
+ if (block.signatures.length > maxShow) {
72
+ lines.push(`... +${block.signatures.length - maxShow} more`);
73
+ }
74
+ }
75
+ return mcpText(lines.join('\n'));
76
+ }
77
+ catch (err) {
78
+ return handleToolError(err, 'Error fetching block');
92
79
  }
93
- return {
94
- content: [{
95
- type: 'text',
96
- text: lines.join('\n')
97
- }]
98
- };
99
80
  });
100
81
  }
@@ -1,98 +1,37 @@
1
1
  import { z } from 'zod';
2
2
  import { setApiKey, setNetwork, hasApiKey, getHeliusClient } from '../utils/helius.js';
3
+ import { mcpText, validateEnum, getErrorMessage } from '../utils/errors.js';
4
+ import { setSharedApiKey, SHARED_CONFIG_PATH } from '../utils/config.js';
3
5
  export function registerConfigTools(server) {
4
- // Check API key status tool - always works, helps users understand state
5
- server.tool('getHeliusApiKeyStatus', 'Check if a Helius API key is configured. Use this to see if you need to set an API key before using data tools.', {}, async () => {
6
- const hasKey = hasApiKey();
7
- const fromEnv = !!process.env.HELIUS_API_KEY;
8
- if (hasKey) {
9
- return {
10
- content: [{
11
- type: 'text',
12
- text: fromEnv
13
- ? 'API key is configured via environment variable. Ready to use all Helius tools.'
14
- : 'API key is configured for this session. Ready to use all Helius tools.'
15
- }]
16
- };
17
- }
18
- return {
19
- content: [{
20
- type: 'text',
21
- text: [
22
- 'No Helius API key configured.',
23
- '',
24
- 'To use Helius data tools, you need to set an API key:',
25
- '',
26
- '**Option 1: Use setHeliusApiKey tool**',
27
- '```',
28
- 'Tool: setHeliusApiKey',
29
- 'Arguments: { "apiKey": "your-api-key" }',
30
- '```',
31
- '',
32
- '**Option 2: Set environment variable**',
33
- 'Add to your MCP config:',
34
- '```',
35
- '"env": { "HELIUS_API_KEY": "your-api-key" }',
36
- '```',
37
- '',
38
- '**Get your free API key at: https://dashboard.helius.dev/api-keys**',
39
- '',
40
- 'Note: Guide tools (getRateLimitInfo, getSenderInfo, etc.) work without an API key.',
41
- ].join('\n')
42
- }]
43
- };
44
- });
45
- server.tool('setHeliusApiKey', 'Set the Helius API key for the current session. Required before using data tools (getBalance, getAsset, etc.). Get your free API key at https://dashboard.helius.dev/api-keys', {
6
+ const keyFromEnv = hasApiKey();
7
+ server.tool('setHeliusApiKey', keyFromEnv
8
+ ? 'API key is already configured via environment. You do NOT need to call this tool - just use the other Helius tools directly.'
9
+ : 'Set an existing Helius API key for the current session. If the user does not have a key, use the agentic signup flow instead: generateKeypair → fund wallet → agenticSignup. Get a key at https://dashboard.helius.dev/api-keys', {
46
10
  apiKey: z.string().describe('Your Helius API key from https://dashboard.helius.dev/api-keys'),
47
- network: z.enum(['mainnet', 'devnet']).optional().default('mainnet').describe('Network to use (default: mainnet)')
11
+ network: z.string().optional().default('mainnet-beta').describe('Network to use (default: mainnet-beta)')
48
12
  }, async ({ apiKey, network }) => {
13
+ const err = validateEnum(network, ['mainnet-beta', 'devnet'], 'API Key Error', 'network');
14
+ if (err)
15
+ return err;
16
+ if (hasApiKey() && process.env.HELIUS_API_KEY) {
17
+ return mcpText(`✅ API key is already configured via environment. You don't need to set it - just use the other Helius tools directly (getBalance, parseTransactions, getAsset, etc.)`);
18
+ }
49
19
  setApiKey(apiKey);
50
20
  if (network) {
51
21
  setNetwork(network);
52
22
  }
53
- // Validate the key by making a simple request
54
23
  try {
55
24
  const helius = getHeliusClient();
56
25
  await helius.getBlockHeight();
57
26
  }
58
- catch (err) {
59
- const errorMsg = err instanceof Error ? err.message : String(err);
27
+ catch (e) {
28
+ const errorMsg = getErrorMessage(e);
60
29
  if (errorMsg.includes('invalid api key') || errorMsg.includes('401') || errorMsg.includes('Unauthorized')) {
61
30
  setApiKey('');
62
- return {
63
- content: [{
64
- type: 'text',
65
- text: [
66
- 'Invalid API key. The key was rejected by the Helius API.',
67
- '',
68
- 'Please check your key and try again.',
69
- '',
70
- 'Get your free API key at: https://dashboard.helius.dev/api-keys',
71
- ].join('\n')
72
- }],
73
- isError: true
74
- };
31
+ return mcpText(`❌ Invalid API key. Please check your key and try again.\n\nGet your key at https://dashboard.helius.dev/api-keys`);
75
32
  }
76
33
  }
77
- return {
78
- content: [{
79
- type: 'text',
80
- text: [
81
- `Helius API key configured. (default network: ${network})`,
82
- '',
83
- 'Your API key works for both mainnet and devnet. To switch networks:',
84
- '- mainnet: https://mainnet.helius-rpc.com/?api-key=YOUR_KEY',
85
- '- devnet: https://devnet.helius-rpc.com/?api-key=YOUR_KEY',
86
- '',
87
- 'You can now use all Helius tools:',
88
- '- getBalance, getTokenBalances - Check wallet balances',
89
- '- getAsset, searchAssets - Query NFTs and tokens',
90
- '- parseTransactions - Decode transaction history',
91
- '- getWalletHistory, getWalletIdentity - Wallet analysis',
92
- '- createWebhook, getAllWebhooks - Manage webhooks',
93
- '- And many more! Ask me anything about Solana.',
94
- ].join('\n')
95
- }]
96
- };
34
+ setSharedApiKey(apiKey);
35
+ return mcpText(`✅ Helius API key configured for ${network} and saved to \`${SHARED_CONFIG_PATH}\`. You can now query the Solana blockchain.`);
97
36
  });
98
37
  }
@@ -1,54 +1,60 @@
1
1
  import { z } from 'zod';
2
- import { dasRequest } from '../utils/helius.js';
2
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
3
  import { formatAddress } from '../utils/formatters.js';
4
+ import { mcpText, mcpError, handleToolError, addressError, notFoundError, paginationError } from '../utils/errors.js';
5
+ import { noApiKeyResponse } from './shared.js';
4
6
  export function registerDasExtraTools(server) {
5
- server.tool('getAssetProof', 'Get Merkle proof for a compressed NFT. Required for transferring or burning cNFTs.', {
6
- id: z.string().describe('Compressed NFT mint address')
7
+ server.tool('getAssetProof', 'BEST FOR: getting Merkle proof required for transferring or burning a cNFT. PREFER getAssetProofBatch when you need proofs for multiple cNFTs. Get Merkle proof for a compressed NFT. Required for transferring or burning cNFTs. DAS API (10 credits/call).', {
8
+ id: z.string().describe('Compressed NFT mint address (base58 encoded)')
7
9
  }, async ({ id }) => {
10
+ if (!hasApiKey())
11
+ return noApiKeyResponse();
8
12
  try {
9
- const proof = await dasRequest('getAssetProof', { id });
10
- return {
11
- content: [{
12
- type: 'text',
13
- text: `**Merkle Proof for ${formatAddress(id)}**\n\n**Root:** ${formatAddress(proof.root)}\n**Leaf:** ${formatAddress(proof.leaf)}\n**Tree ID:** ${formatAddress(proof.tree_id)}\n**Proof Length:** ${proof.proof.length} nodes`
14
- }]
15
- };
13
+ const helius = getHeliusClient();
14
+ const proof = await helius.getAssetProof({ id });
15
+ return mcpText(`**Merkle Proof for ${formatAddress(id)}**\n\n**Root:** ${formatAddress(proof.root)}\n**Leaf:** ${formatAddress(proof.leaf)}\n**Tree ID:** ${formatAddress(proof.tree_id)}\n**Proof Length:** ${proof.proof.length} nodes`);
16
16
  }
17
17
  catch (err) {
18
- const errorMsg = err instanceof Error ? err.message : String(err);
19
- return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true };
18
+ const header = `Merkle Proof for ${formatAddress(id)}`;
19
+ return handleToolError(err, 'Error fetching asset proof', [
20
+ notFoundError(header, 'Asset proof not found. This asset may not be a compressed NFT (cNFT), or the mint address does not exist.'),
21
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded mint address.'),
22
+ ]);
20
23
  }
21
24
  });
22
- server.tool('getAssetProofBatch', 'Get Merkle proofs for multiple compressed NFTs in one request (up to 1000).', {
23
- ids: z.array(z.string()).describe('Array of cNFT mint addresses (up to 1000)')
25
+ server.tool('getAssetProofBatch', 'BEST FOR: batch Merkle proofs for multiple cNFT operations. PREFER getAssetProof for a single cNFT. Get Merkle proofs for multiple compressed NFTs in one request (up to 1000). DAS API (10 credits/call).', {
26
+ ids: z.array(z.string()).describe('Array of cNFT mint addresses (base58 encoded, up to 1000)')
24
27
  }, async ({ ids }) => {
28
+ if (!hasApiKey())
29
+ return noApiKeyResponse();
25
30
  try {
26
31
  if (ids.length > 1000) {
27
- return { content: [{ type: 'text', text: 'Max 1000 proofs per batch' }], isError: true };
32
+ return mcpError('Max 1000 proofs per batch');
28
33
  }
29
- const result = await dasRequest('getAssetProofBatch', { ids });
34
+ const helius = getHeliusClient();
35
+ const result = await helius.getAssetProofBatch({ ids });
30
36
  const proofsArray = Array.isArray(result) ? result : Object.values(result);
31
- return {
32
- content: [{
33
- type: 'text',
34
- text: `**Batch Merkle Proofs** (${proofsArray.length})\n\n${proofsArray.map((p, i) => `${i + 1}. ${formatAddress(ids[i])}\n Root: ${formatAddress(p.root)}`).join('\n\n')}`
35
- }]
36
- };
37
+ return mcpText(`**Batch Merkle Proofs** (${proofsArray.length})\n\n${proofsArray.map((p, i) => `${i + 1}. ${formatAddress(ids[i])}\n Root: ${formatAddress(p.root)}`).join('\n\n')}`);
37
38
  }
38
39
  catch (err) {
39
- const errorMsg = err instanceof Error ? err.message : String(err);
40
- return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true };
40
+ return handleToolError(err, 'Error fetching asset proofs', [
41
+ notFoundError('Batch Merkle Proofs', 'One or more asset proofs were not found. Some assets may not be compressed NFTs (cNFTs), or the mint addresses may not exist.'),
42
+ addressError('Batch Merkle Proofs', 'One or more provided IDs are not valid Solana addresses. Please check the mint addresses and try again.'),
43
+ ]);
41
44
  }
42
45
  });
43
- server.tool('getSignaturesForAsset', 'Get transaction history for a compressed NFT.', {
44
- id: z.string().describe('Compressed NFT mint address'),
46
+ server.tool('getSignaturesForAsset', 'BEST FOR: transaction history for a specific asset by mint address. PREFER getTransactionHistory for wallet-level transaction history. Get transaction history for any DAS asset. DAS API (10 credits/call).', {
47
+ id: z.string().describe('Asset mint address (base58 encoded, any DAS asset)'),
45
48
  page: z.number().optional().default(1),
46
49
  limit: z.number().optional().default(20)
47
50
  }, async ({ id, page, limit }) => {
51
+ if (!hasApiKey())
52
+ return noApiKeyResponse();
48
53
  try {
49
- const result = await dasRequest('getSignaturesForAsset', { id, page, limit });
54
+ const helius = getHeliusClient();
55
+ const result = await helius.getSignaturesForAsset({ id, page, limit });
50
56
  if (!result.items?.length) {
51
- return { content: [{ type: 'text', text: `**Signatures for ${formatAddress(id)}**\n\nNo transactions found.` }] };
57
+ return mcpText(`**Signatures for ${formatAddress(id)}**\n\nNo transactions found.`);
52
58
  }
53
59
  const lines = [`**Signatures for ${formatAddress(id)}** (${result.total} total)`, ''];
54
60
  result.items.forEach((sig, i) => {
@@ -56,22 +62,29 @@ export function registerDasExtraTools(server) {
56
62
  if (sig.blockTime)
57
63
  lines.push(` Time: ${new Date(sig.blockTime * 1000).toLocaleString()}`);
58
64
  });
59
- return { content: [{ type: 'text', text: lines.join('\n') }] };
65
+ return mcpText(lines.join('\n'));
60
66
  }
61
67
  catch (err) {
62
- const errorMsg = err instanceof Error ? err.message : String(err);
63
- return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true };
68
+ const header = `Signatures for ${formatAddress(id)}`;
69
+ return handleToolError(err, 'Error fetching signatures', [
70
+ notFoundError(header, 'Asset not found. This mint address does not exist or has not been indexed.'),
71
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded mint address.'),
72
+ paginationError(header),
73
+ ]);
64
74
  }
65
75
  });
66
- server.tool('getNftEditions', 'Get all edition NFTs for a master NFT.', {
67
- mint: z.string().describe('Master NFT mint address'),
76
+ server.tool('getNftEditions', 'BEST FOR: finding numbered edition prints of a master NFT. Get all edition NFTs for a master NFT. DAS API (10 credits/call).', {
77
+ mint: z.string().describe('Master NFT mint address (base58 encoded)'),
68
78
  page: z.number().optional().default(1),
69
79
  limit: z.number().optional().default(20)
70
80
  }, async ({ mint, page, limit }) => {
81
+ if (!hasApiKey())
82
+ return noApiKeyResponse();
71
83
  try {
72
- const result = await dasRequest('getNftEditions', { mint, page, limit });
84
+ const helius = getHeliusClient();
85
+ const result = await helius.getNftEditions({ mint, page, limit });
73
86
  if (!result.editions?.length) {
74
- return { content: [{ type: 'text', text: `**Editions for ${formatAddress(mint)}**\n\nNo editions found.` }] };
87
+ return mcpText(`**Editions for ${formatAddress(mint)}**\n\nNo editions found.`);
75
88
  }
76
89
  const lines = [`**Editions for ${formatAddress(mint)}** (${result.total} total)`, ''];
77
90
  result.editions.forEach((edition, i) => {
@@ -80,14 +93,16 @@ export function registerDasExtraTools(server) {
80
93
  if (edition.owner)
81
94
  lines.push(` Owner: ${formatAddress(edition.owner)}`);
82
95
  });
83
- return { content: [{ type: 'text', text: lines.join('\n') }] };
96
+ return mcpText(lines.join('\n'));
84
97
  }
85
98
  catch (err) {
86
- const errorMsg = err instanceof Error ? err.message : String(err);
87
- if (errorMsg.includes('null value was encountered while decoding')) {
88
- return { content: [{ type: 'text', text: `**Editions for ${formatAddress(mint)}**\n\nThis mint is not a master edition NFT. getNftEditions only works with master edition mints.` }] };
89
- }
90
- return { content: [{ type: 'text', text: `Error: ${errorMsg}` }], isError: true };
99
+ const header = `Editions for ${formatAddress(mint)}`;
100
+ return handleToolError(err, 'Error fetching editions', [
101
+ { match: (m) => m.includes('null value was encountered'), respond: () => mcpText(`**${header}**\n\nThis mint is not a master edition NFT. getNftEditions only works with master edition mints.`) },
102
+ notFoundError(header, 'Asset not found. This mint address does not exist or has not been indexed.'),
103
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded mint address.'),
104
+ paginationError(header),
105
+ ]);
91
106
  }
92
107
  });
93
108
  }
@@ -1,13 +1,14 @@
1
1
  import { z } from 'zod';
2
- import { fetchDoc, getDocsIndex } from '../utils/docs.js';
2
+ import { fetchDoc, getDocsIndex, extractSections, truncateDoc } from '../utils/docs.js';
3
3
  export function registerDocsTools(server) {
4
4
  /**
5
5
  * Lookup Helius documentation - fetches official docs for accurate information
6
6
  */
7
- server.tool('lookupHeliusDocs', 'Fetch official Helius documentation for accurate, up-to-date information. Use this when you need precise details about APIs, pricing, rate limits, or features. Returns the official llms.txt documentation which is optimized for AI consumption.', {
7
+ server.tool('lookupHeliusDocs', 'BEST FOR: API documentation and technical details. NOT for pricing (use getHeliusPlanInfo) or errors (use troubleshootError). Fetch official Helius documentation by topic. Use `section` parameter to fetch only relevant sections and save tokens.', {
8
8
  topic: z
9
9
  .enum([
10
10
  'overview',
11
+ 'agents',
11
12
  'billing',
12
13
  'das',
13
14
  'rpc',
@@ -33,43 +34,12 @@ export function registerDocsTools(server) {
33
34
  const content = await fetchDoc(topic);
34
35
  // If a section filter is provided, extract relevant parts
35
36
  if (section) {
36
- const sectionLower = section.toLowerCase();
37
- const lines = content.split('\n');
38
- const relevantLines = [];
39
- let inRelevantSection = false;
40
- let sectionDepth = 0;
41
- for (const line of lines) {
42
- // Check if this is a header
43
- const headerMatch = line.match(/^(#{1,4})\s+(.+)/);
44
- if (headerMatch) {
45
- const depth = headerMatch[1].length;
46
- const title = headerMatch[2].toLowerCase();
47
- if (title.includes(sectionLower)) {
48
- inRelevantSection = true;
49
- sectionDepth = depth;
50
- relevantLines.push(line);
51
- }
52
- else if (inRelevantSection && depth <= sectionDepth) {
53
- // We've hit a new section at same or higher level
54
- inRelevantSection = false;
55
- }
56
- else if (inRelevantSection) {
57
- relevantLines.push(line);
58
- }
59
- }
60
- else if (inRelevantSection) {
61
- relevantLines.push(line);
62
- }
63
- else if (line.toLowerCase().includes(sectionLower)) {
64
- // Include lines that mention the section keyword even outside headers
65
- relevantLines.push(line);
66
- }
67
- }
68
- if (relevantLines.length > 0) {
37
+ const extracted = extractSections(content, section, { includeLooseMatches: true });
38
+ if (extracted) {
69
39
  const result = [
70
40
  `# Helius Docs: ${topic} (filtered by "${section}")`,
71
41
  '',
72
- ...relevantLines,
42
+ extracted,
73
43
  '',
74
44
  '---',
75
45
  `Source: https://www.helius.dev/docs (${topic})`,
@@ -87,14 +57,16 @@ export function registerDocsTools(server) {
87
57
  };
88
58
  }
89
59
  }
90
- // Return full documentation
60
+ // Return full documentation (truncated to save tokens)
91
61
  const result = [
92
62
  `# Helius Docs: ${topic}`,
93
63
  '',
94
- content,
64
+ truncateDoc(content),
95
65
  '',
96
66
  '---',
97
67
  `Source: https://www.helius.dev/docs`,
68
+ '',
69
+ '*Tip: Use the `section` parameter (e.g., section: "rate limits") to fetch only what you need and save tokens.*',
98
70
  ].join('\n');
99
71
  return { content: [{ type: 'text', text: result }] };
100
72
  }
@@ -119,32 +91,18 @@ export function registerDocsTools(server) {
119
91
  const lines = [
120
92
  '# Available Helius Documentation Topics',
121
93
  '',
122
- 'Use `lookupHeliusDocs` with any of these topics to fetch official documentation:',
94
+ 'Use `lookupHeliusDocs` with any of these topics. Add `section` to filter.',
123
95
  '',
124
96
  '| Topic | Description |',
125
97
  '|-------|-------------|',
126
98
  ...index.map((doc) => `| \`${doc.key}\` | ${doc.description} |`),
127
- '',
128
- '## Usage Examples',
129
- '',
130
- '```',
131
- '// Get overview with plans, credits, rate limits',
132
- 'lookupHeliusDocs({ topic: "overview" })',
133
- '',
134
- '// Get DAS API documentation',
135
- 'lookupHeliusDocs({ topic: "das" })',
136
- '',
137
- '// Get specific section from docs',
138
- 'lookupHeliusDocs({ topic: "overview", section: "credits" })',
139
- 'lookupHeliusDocs({ topic: "das", section: "rate limits" })',
140
- '```',
141
99
  ];
142
100
  return { content: [{ type: 'text', text: lines.join('\n') }] };
143
101
  });
144
102
  /**
145
103
  * Get credits information from official docs
146
104
  */
147
- server.tool('getHeliusCreditsInfo', 'Get official Helius credit costs from documentation. Fetches the latest pricing information directly from Helius docs.', {}, async () => {
105
+ server.tool('getHeliusCreditsInfo', 'BEST FOR: credit cost lookup table. PREFER getRateLimitInfo for per-method rate limits, getHeliusPlanInfo for plan pricing. Get official Helius credit costs from documentation.', {}, async () => {
148
106
  try {
149
107
  const content = await fetchDoc('overview');
150
108
  // Extract credits section