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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +1 -1
- package/README.md +97 -21
- package/dist/http.d.ts +1 -0
- package/dist/http.js +2 -0
- package/dist/index.js +93 -2
- package/dist/scripts/validate-catalog.d.ts +13 -0
- package/dist/scripts/validate-catalog.js +76 -0
- package/dist/tools/accounts.js +114 -204
- package/dist/tools/assets.js +109 -123
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +459 -0
- package/dist/tools/balance.js +28 -32
- package/dist/tools/blocks.js +68 -87
- package/dist/tools/config.js +18 -79
- package/dist/tools/das-extras.js +56 -41
- package/dist/tools/docs.js +12 -54
- package/dist/tools/enhanced-websockets.js +104 -74
- package/dist/tools/fees.js +42 -61
- package/dist/tools/guides.js +126 -515
- package/dist/tools/index.js +50 -2
- package/dist/tools/laserstream.js +107 -53
- package/dist/tools/network.js +47 -69
- package/dist/tools/plans.d.ts +21 -0
- package/dist/tools/plans.js +105 -246
- package/dist/tools/product-catalog.d.ts +10 -0
- package/dist/tools/product-catalog.js +123 -0
- package/dist/tools/recommend.d.ts +4 -0
- package/dist/tools/recommend.js +233 -0
- package/dist/tools/shared.js +8 -3
- package/dist/tools/solana-knowledge.d.ts +2 -0
- package/dist/tools/solana-knowledge.js +544 -0
- package/dist/tools/tokens.js +17 -18
- package/dist/tools/transactions.js +232 -302
- package/dist/tools/transfers.d.ts +2 -0
- package/dist/tools/transfers.js +270 -0
- package/dist/tools/wallet.js +175 -177
- package/dist/tools/webhooks.js +80 -82
- package/dist/types/transaction-types.d.ts +1 -1
- package/dist/types/transaction-types.js +2 -1
- package/dist/utils/config.d.ts +27 -0
- package/dist/utils/config.js +76 -0
- package/dist/utils/docs.d.ts +24 -0
- package/dist/utils/docs.js +72 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.js +157 -0
- package/dist/utils/feedback.d.ts +16 -0
- package/dist/utils/feedback.js +87 -0
- package/dist/utils/formatters.d.ts +0 -1
- package/dist/utils/formatters.js +0 -3
- package/dist/utils/helius.d.ts +15 -5
- package/dist/utils/helius.js +52 -45
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +17 -7
- package/system-prompts/helius/claude.system.md +170 -0
- package/system-prompts/helius/full.md +2868 -0
- package/system-prompts/helius/openai.developer.md +170 -0
- package/system-prompts/helius-dflow/claude.system.md +290 -0
- package/system-prompts/helius-dflow/full.md +3647 -0
- package/system-prompts/helius-dflow/openai.developer.md +290 -0
- package/system-prompts/helius-phantom/claude.system.md +348 -0
- package/system-prompts/helius-phantom/full.md +5472 -0
- package/system-prompts/helius-phantom/openai.developer.md +348 -0
- package/system-prompts/svm/claude.system.md +174 -0
- package/system-prompts/svm/full.md +699 -0
- package/system-prompts/svm/openai.developer.md +174 -0
package/dist/tools/blocks.js
CHANGED
|
@@ -1,100 +1,81 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
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.
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
90
|
-
|
|
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
|
}
|
package/dist/tools/config.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
server.tool('
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
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 (
|
|
59
|
-
const errorMsg =
|
|
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
|
-
|
|
78
|
-
|
|
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
|
}
|
package/dist/tools/das-extras.js
CHANGED
|
@@ -1,54 +1,60 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
19
|
-
return
|
|
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
|
|
32
|
+
return mcpError('Max 1000 proofs per batch');
|
|
28
33
|
}
|
|
29
|
-
const
|
|
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
|
-
|
|
40
|
-
|
|
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', '
|
|
44
|
-
id: z.string().describe('
|
|
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
|
|
54
|
+
const helius = getHeliusClient();
|
|
55
|
+
const result = await helius.getSignaturesForAsset({ id, page, limit });
|
|
50
56
|
if (!result.items?.length) {
|
|
51
|
-
return
|
|
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
|
|
65
|
+
return mcpText(lines.join('\n'));
|
|
60
66
|
}
|
|
61
67
|
catch (err) {
|
|
62
|
-
const
|
|
63
|
-
return
|
|
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
|
|
84
|
+
const helius = getHeliusClient();
|
|
85
|
+
const result = await helius.getNftEditions({ mint, page, limit });
|
|
73
86
|
if (!result.editions?.length) {
|
|
74
|
-
return
|
|
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
|
|
96
|
+
return mcpText(lines.join('\n'));
|
|
84
97
|
}
|
|
85
98
|
catch (err) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
}
|
package/dist/tools/docs.js
CHANGED
|
@@ -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', '
|
|
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
|
|
37
|
-
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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
|