helius-mcp 1.3.0 → 2.0.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 +79 -79
- package/LICENSE +21 -21
- package/README.md +144 -132
- package/dist/http.d.ts +1 -1
- package/dist/index.js +2 -56
- package/dist/results/store.d.ts +8 -0
- package/dist/results/store.js +72 -0
- package/dist/results/types.d.ts +47 -0
- package/dist/results/types.js +1 -0
- package/dist/router/action-groups.d.ts +6 -0
- package/dist/router/action-groups.js +32 -0
- package/dist/router/action-handlers.d.ts +20 -0
- package/dist/router/action-handlers.js +125 -0
- package/dist/router/actions.d.ts +12 -0
- package/dist/router/actions.js +123 -0
- package/dist/router/catalog.d.ts +6 -0
- package/dist/router/catalog.js +388 -0
- package/dist/router/context.d.ts +5 -0
- package/dist/router/context.js +10 -0
- package/dist/router/dispatch.d.ts +4 -0
- package/dist/router/dispatch.js +276 -0
- package/dist/router/instructions.d.ts +1 -0
- package/dist/router/instructions.js +25 -0
- package/dist/router/register.d.ts +2 -0
- package/dist/router/register.js +15 -0
- package/dist/router/required-params.d.ts +9 -0
- package/dist/router/required-params.js +66 -0
- package/dist/router/responses.d.ts +29 -0
- package/dist/router/responses.js +186 -0
- package/dist/router/schemas.d.ts +216 -0
- package/dist/router/schemas.js +195 -0
- package/dist/router/telemetry.d.ts +27 -0
- package/dist/router/telemetry.js +52 -0
- package/dist/router/types.d.ts +46 -0
- package/dist/router/types.js +1 -0
- package/dist/scripts/validate-catalog.d.ts +2 -2
- package/dist/scripts/validate-catalog.js +10 -10
- package/dist/tools/accounts.js +5 -5
- package/dist/tools/assets.js +5 -5
- package/dist/tools/auth.js +392 -319
- package/dist/tools/config.js +3 -3
- package/dist/tools/das-extras.js +6 -6
- package/dist/tools/docs.js +55 -41
- package/dist/tools/enhanced-websockets.js +13 -13
- package/dist/tools/fees.js +3 -3
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +2 -80
- package/dist/tools/laserstream.js +20 -23
- package/dist/tools/network.js +10 -4
- package/dist/tools/plans.d.ts +0 -5
- package/dist/tools/plans.js +167 -12
- package/dist/tools/product-catalog.d.ts +1 -0
- package/dist/tools/product-catalog.js +51 -16
- package/dist/tools/recommend.d.ts +0 -1
- package/dist/tools/recommend.js +9 -28
- package/dist/tools/shared.d.ts +1 -0
- package/dist/tools/shared.js +21 -13
- package/dist/tools/solana-knowledge.js +23 -7
- package/dist/tools/staking.d.ts +2 -0
- package/dist/tools/staking.js +268 -0
- package/dist/tools/transactions.js +167 -3
- package/dist/tools/transfers.js +38 -43
- package/dist/tools/wallet.js +27 -16
- package/dist/tools/webhooks.js +3 -3
- package/dist/tools/zk-compression.d.ts +2 -0
- package/dist/tools/zk-compression.js +781 -0
- package/dist/utils/config.d.ts +2 -2
- package/dist/utils/config.js +68 -6
- package/dist/utils/errors.d.ts +10 -1
- package/dist/utils/errors.js +46 -12
- package/dist/utils/feedback.js +1 -4
- package/dist/utils/helius.js +2 -1
- package/dist/utils/ows.d.ts +74 -0
- package/dist/utils/ows.js +155 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +64 -64
- package/system-prompts/helius/claude.system.md +200 -170
- package/system-prompts/helius/full.md +3212 -2869
- package/system-prompts/helius/openai.developer.md +200 -170
- package/system-prompts/helius-dflow/claude.system.md +324 -290
- package/system-prompts/helius-dflow/full.md +4136 -3648
- package/system-prompts/helius-dflow/openai.developer.md +324 -290
- package/system-prompts/helius-jupiter/claude.system.md +333 -0
- package/system-prompts/helius-jupiter/full.md +5109 -0
- package/system-prompts/helius-jupiter/openai.developer.md +333 -0
- package/system-prompts/helius-okx/claude.system.md +182 -0
- package/system-prompts/helius-okx/full.md +584 -0
- package/system-prompts/helius-okx/openai.developer.md +182 -0
- package/system-prompts/helius-phantom/claude.system.md +345 -333
- package/system-prompts/helius-phantom/full.md +5625 -5473
- package/system-prompts/helius-phantom/openai.developer.md +345 -333
- package/system-prompts/svm/claude.system.md +159 -159
- package/system-prompts/svm/full.md +631 -631
- package/system-prompts/svm/openai.developer.md +159 -159
- package/dist/scripts/test-htmltotext.d.ts +0 -5
- package/dist/scripts/test-htmltotext.js +0 -67
- package/dist/scripts/test-solana-knowledge.d.ts +0 -9
- package/dist/scripts/test-solana-knowledge.js +0 -272
- package/dist/scripts/validate-templates.d.ts +0 -12
- package/dist/scripts/validate-templates.js +0 -94
package/dist/tools/config.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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';
|
|
3
|
+
import { mcpText, mcpError, validateEnum, getErrorMessage } from '../utils/errors.js';
|
|
4
4
|
import { setSharedApiKey, SHARED_CONFIG_PATH } from '../utils/config.js';
|
|
5
5
|
export function registerConfigTools(server) {
|
|
6
6
|
const keyFromEnv = hasApiKey();
|
|
7
7
|
server.tool('setHeliusApiKey', keyFromEnv
|
|
8
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
|
|
9
|
+
: 'Set an existing Helius API key for the current session. If the user does not have a key, use the signup flow instead: generateKeypair → signup (link mode prints a payment URL). Get a key at https://dashboard.helius.dev/api-keys', {
|
|
10
10
|
apiKey: z.string().describe('Your Helius API key from https://dashboard.helius.dev/api-keys'),
|
|
11
11
|
network: z.string().optional().default('mainnet-beta').describe('Network to use (default: mainnet-beta)')
|
|
12
12
|
}, async ({ apiKey, network }) => {
|
|
@@ -28,7 +28,7 @@ export function registerConfigTools(server) {
|
|
|
28
28
|
const errorMsg = getErrorMessage(e);
|
|
29
29
|
if (errorMsg.includes('invalid api key') || errorMsg.includes('401') || errorMsg.includes('Unauthorized')) {
|
|
30
30
|
setApiKey('');
|
|
31
|
-
return
|
|
31
|
+
return mcpError(`❌ Invalid API key. Please check your key and try again.\n\nGet your key at https://dashboard.helius.dev/api-keys`, { type: 'AUTH', code: 'INVALID_API_KEY', retryable: false, recovery: 'Check your API key at https://dashboard.helius.dev/api-keys and call setHeliusApiKey with a valid key.' });
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
setSharedApiKey(apiKey);
|
package/dist/tools/das-extras.js
CHANGED
|
@@ -29,7 +29,7 @@ export function registerDasExtraTools(server) {
|
|
|
29
29
|
return noApiKeyResponse();
|
|
30
30
|
try {
|
|
31
31
|
if (ids.length > 1000) {
|
|
32
|
-
return mcpError('Max 1000 proofs per batch');
|
|
32
|
+
return mcpError('Max 1000 proofs per batch', { type: 'VALIDATION', code: 'TOO_MANY_ITEMS', retryable: false, recovery: 'Reduce batch to 1000 or fewer.' });
|
|
33
33
|
}
|
|
34
34
|
const helius = getHeliusClient();
|
|
35
35
|
const result = await helius.getAssetProofBatch({ ids });
|
|
@@ -45,8 +45,8 @@ export function registerDasExtraTools(server) {
|
|
|
45
45
|
});
|
|
46
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
47
|
id: z.string().describe('Asset mint address (base58 encoded, any DAS asset)'),
|
|
48
|
-
page: z.number().optional().default(1),
|
|
49
|
-
limit: z.number().optional().default(20)
|
|
48
|
+
page: z.number().optional().default(1).describe('Page number for pagination (default: 1)'),
|
|
49
|
+
limit: z.number().optional().default(20).describe('Results per page, max 1000 (default: 20)')
|
|
50
50
|
}, async ({ id, page, limit }) => {
|
|
51
51
|
if (!hasApiKey())
|
|
52
52
|
return noApiKeyResponse();
|
|
@@ -75,8 +75,8 @@ export function registerDasExtraTools(server) {
|
|
|
75
75
|
});
|
|
76
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
77
|
mint: z.string().describe('Master NFT mint address (base58 encoded)'),
|
|
78
|
-
page: z.number().optional().default(1),
|
|
79
|
-
limit: z.number().optional().default(20)
|
|
78
|
+
page: z.number().optional().default(1).describe('Page number for pagination (default: 1)'),
|
|
79
|
+
limit: z.number().optional().default(20).describe('Results per page, max 1000 (default: 20)')
|
|
80
80
|
}, async ({ mint, page, limit }) => {
|
|
81
81
|
if (!hasApiKey())
|
|
82
82
|
return noApiKeyResponse();
|
|
@@ -98,7 +98,7 @@ export function registerDasExtraTools(server) {
|
|
|
98
98
|
catch (err) {
|
|
99
99
|
const header = `Editions for ${formatAddress(mint)}`;
|
|
100
100
|
return handleToolError(err, 'Error fetching editions', [
|
|
101
|
-
{ match: (m) => m.includes('null value was encountered'), respond: () =>
|
|
101
|
+
{ match: (m) => m.includes('null value was encountered'), respond: () => mcpError(`**${header}**\n\nThis mint is not a master edition NFT. getNftEditions only works with master edition mints.`, { type: 'VALIDATION', code: 'INVALID_PARAM', retryable: false, recovery: 'Provide a master edition mint address. getNftEditions only works with master edition NFTs.' }) },
|
|
102
102
|
notFoundError(header, 'Asset not found. This mint address does not exist or has not been indexed.'),
|
|
103
103
|
addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded mint address.'),
|
|
104
104
|
paginationError(header),
|
package/dist/tools/docs.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { fetchDoc, getDocsIndex, extractSections, truncateDoc } from '../utils/docs.js';
|
|
3
|
+
function getDocHeadings(content) {
|
|
4
|
+
return Array.from(content.matchAll(/^#{1,3}\s+(.+)$/gm))
|
|
5
|
+
.map((match) => match[1].trim())
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.slice(0, 12);
|
|
8
|
+
}
|
|
3
9
|
export function registerDocsTools(server) {
|
|
4
10
|
/**
|
|
5
11
|
* Lookup Helius documentation - fetches official docs for accurate information
|
|
@@ -24,12 +30,48 @@ export function registerDocsTools(server) {
|
|
|
24
30
|
'dedicated-nodes',
|
|
25
31
|
'shred-delivery',
|
|
26
32
|
])
|
|
33
|
+
.optional()
|
|
27
34
|
.describe('Documentation topic to fetch'),
|
|
28
35
|
section: z
|
|
29
36
|
.string()
|
|
30
37
|
.optional()
|
|
31
38
|
.describe('Optional: specific section to extract (e.g., "credits", "rate limits", "parameters"). If provided, returns only matching sections.'),
|
|
32
|
-
|
|
39
|
+
query: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Freeform query — used to infer topic if topic is not provided'),
|
|
43
|
+
}, async ({ topic: rawTopic, section, query }) => {
|
|
44
|
+
const VALID_TOPICS = [
|
|
45
|
+
'overview', 'agents', 'billing', 'das', 'rpc', 'websocket',
|
|
46
|
+
'enhanced-websockets', 'webhooks', 'enhanced-transactions', 'sender',
|
|
47
|
+
'priority-fee', 'laserstream', 'wallet-api', 'zk-compression',
|
|
48
|
+
'dedicated-nodes', 'shred-delivery',
|
|
49
|
+
];
|
|
50
|
+
let topic = rawTopic;
|
|
51
|
+
if (!topic && query) {
|
|
52
|
+
const q = query.toLowerCase();
|
|
53
|
+
topic = VALID_TOPICS.find((t) => q.includes(t))
|
|
54
|
+
?? (q.includes('grpc') || q.includes('shred') && q.includes('stream') ? 'laserstream'
|
|
55
|
+
: q.includes('nft') || q.includes('asset') ? 'das'
|
|
56
|
+
: q.includes('webhook') ? 'webhooks'
|
|
57
|
+
: q.includes('fee') || q.includes('priority') ? 'priority-fee'
|
|
58
|
+
: q.includes('wallet') ? 'wallet-api'
|
|
59
|
+
: q.includes('compress') ? 'zk-compression'
|
|
60
|
+
: q.includes('websocket') || q.includes('ws') ? 'enhanced-websockets'
|
|
61
|
+
: q.includes('transaction') || q.includes('parsed') ? 'enhanced-transactions'
|
|
62
|
+
: q.includes('send') || q.includes('submit') ? 'sender'
|
|
63
|
+
: undefined);
|
|
64
|
+
}
|
|
65
|
+
if (!topic) {
|
|
66
|
+
const topicList = VALID_TOPICS.map((t) => `\`${t}\``).join(', ');
|
|
67
|
+
return {
|
|
68
|
+
content: [{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: `Missing required parameter: topic. Valid topics: ${topicList}`,
|
|
71
|
+
}],
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
33
75
|
try {
|
|
34
76
|
const content = await fetchDoc(topic);
|
|
35
77
|
// If a section filter is provided, extract relevant parts
|
|
@@ -51,7 +93,14 @@ export function registerDocsTools(server) {
|
|
|
51
93
|
content: [
|
|
52
94
|
{
|
|
53
95
|
type: 'text',
|
|
54
|
-
text:
|
|
96
|
+
text: [
|
|
97
|
+
`No sections matching "${section}" were found in \`${topic}\` docs.`,
|
|
98
|
+
'',
|
|
99
|
+
'Available sections:',
|
|
100
|
+
...getDocHeadings(content).map((heading) => `- ${heading}`),
|
|
101
|
+
'',
|
|
102
|
+
'Tip: retry with a more specific section filter or omit `section` to receive the default truncated document summary.',
|
|
103
|
+
].join('\n'),
|
|
55
104
|
},
|
|
56
105
|
],
|
|
57
106
|
};
|
|
@@ -104,49 +153,14 @@ export function registerDocsTools(server) {
|
|
|
104
153
|
*/
|
|
105
154
|
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 () => {
|
|
106
155
|
try {
|
|
107
|
-
const content = await fetchDoc('
|
|
108
|
-
|
|
109
|
-
const lines = content.split('\n');
|
|
110
|
-
const creditsLines = [];
|
|
111
|
-
let inCreditsSection = false;
|
|
112
|
-
for (let i = 0; i < lines.length; i++) {
|
|
113
|
-
const line = lines[i];
|
|
114
|
-
// Look for Credits section
|
|
115
|
-
if (line.includes('## Credits') || line.includes('| Credits |')) {
|
|
116
|
-
inCreditsSection = true;
|
|
117
|
-
}
|
|
118
|
-
if (inCreditsSection) {
|
|
119
|
-
creditsLines.push(line);
|
|
120
|
-
// Stop at next major section
|
|
121
|
-
if (creditsLines.length > 2 && line.startsWith('## ') && !line.includes('Credits')) {
|
|
122
|
-
creditsLines.pop(); // Remove the next section header
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Also look for the credits table
|
|
128
|
-
const tableLines = [];
|
|
129
|
-
for (let i = 0; i < lines.length; i++) {
|
|
130
|
-
if (lines[i].includes('| Credits |') || lines[i].includes('| 0 |') || lines[i].includes('| 1 |') || lines[i].includes('| 3 |') || lines[i].includes('| 10 |') || lines[i].includes('| 100 |')) {
|
|
131
|
-
// Collect table lines
|
|
132
|
-
let j = i;
|
|
133
|
-
while (j < lines.length && (lines[j].includes('|') || lines[j].trim() === '')) {
|
|
134
|
-
if (lines[j].includes('|')) {
|
|
135
|
-
tableLines.push(lines[j]);
|
|
136
|
-
}
|
|
137
|
-
j++;
|
|
138
|
-
if (j - i > 20)
|
|
139
|
-
break; // Safety limit
|
|
140
|
-
}
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
156
|
+
const content = await fetchDoc('billing');
|
|
157
|
+
const credits = extractSections(content, 'credits', { includeLooseMatches: true });
|
|
144
158
|
const result = [
|
|
145
159
|
'# Helius Credit Costs (Official)',
|
|
146
160
|
'',
|
|
147
|
-
'Source: https://www.helius.dev/docs (fetched live)',
|
|
161
|
+
'Source: https://www.helius.dev/docs/billing (fetched live)',
|
|
148
162
|
'',
|
|
149
|
-
|
|
163
|
+
credits || 'Credits section not found in billing docs.',
|
|
150
164
|
].join('\n');
|
|
151
165
|
return { content: [{ type: 'text', text: result }] };
|
|
152
166
|
}
|
|
@@ -3,18 +3,18 @@ import { getEnhancedWebSocketUrl } from '../utils/helius.js';
|
|
|
3
3
|
import { mcpText, mcpError, validateEnum, handleToolError, warnInvalidAddresses, warnInvalidAddress, warnAddressConflicts } from '../utils/errors.js';
|
|
4
4
|
import { fetchDoc, extractSections, truncateDoc } from '../utils/docs.js';
|
|
5
5
|
export function registerEnhancedWebSocketTools(server) {
|
|
6
|
-
server.tool('transactionSubscribe', 'BEST FOR: real-time transaction streaming for live UI updates (WebSocket). PREFER createWebhook for fire-and-forget server-to-server notifications. PREFER laserstreamSubscribe for lowest-latency production streaming. Get Enhanced WebSocket config for real-time Solana transaction streaming. Filter by accounts, vote/failed txns, or signatures. Up to 50,000 addresses per filter.
|
|
6
|
+
server.tool('transactionSubscribe', 'BEST FOR: real-time transaction streaming for live UI updates (WebSocket). PREFER createWebhook for fire-and-forget server-to-server notifications. PREFER laserstreamSubscribe for lowest-latency production streaming. Get Enhanced WebSocket config for real-time Solana transaction streaming. Filter by accounts, vote/failed txns, or signatures. Up to 50,000 addresses per filter. Developer+ plans only. Returns connection config and code example. Data streaming cost: 2 credits per 0.1 MB received.', {
|
|
7
7
|
vote: z.boolean().optional().describe('Include vote transactions'),
|
|
8
8
|
failed: z.boolean().optional().describe('Include failed transactions'),
|
|
9
9
|
signature: z.string().optional().describe('Transaction signature (base58 encoded, 86-88 characters) to filter to'),
|
|
10
|
-
accountInclude: z.array(z.string()).optional().describe('Accounts to include (base58 encoded, up to 50,000
|
|
11
|
-
accountExclude: z.array(z.string()).optional().describe('Accounts to exclude (base58 encoded)'),
|
|
12
|
-
accountRequired: z.array(z.string()).optional().describe('Accounts required (base58 encoded
|
|
13
|
-
commitment: z.string().optional().default('confirmed'),
|
|
14
|
-
encoding: z.string().optional().default('jsonParsed'),
|
|
15
|
-
transactionDetails: z.string().optional().default('full'),
|
|
16
|
-
showRewards: z.boolean().optional().default(false),
|
|
17
|
-
maxSupportedTransactionVersion: z.number().optional().default(0)
|
|
10
|
+
accountInclude: z.array(z.string()).optional().describe('Accounts to include in filter (base58 encoded, up to 50,000). OR logic: tx matches if it involves ANY of these accounts. Use accountRequired for AND logic.'),
|
|
11
|
+
accountExclude: z.array(z.string()).optional().describe('Accounts to exclude from results (base58 encoded). Transactions involving ANY of these accounts are filtered out, even if they match accountInclude.'),
|
|
12
|
+
accountRequired: z.array(z.string()).optional().describe('Accounts required in every matched transaction (base58 encoded). AND logic: tx must involve ALL of these accounts. Use accountInclude for OR logic.'),
|
|
13
|
+
commitment: z.string().optional().default('confirmed').describe('Commitment level for transaction confirmation. Values: "processed", "confirmed" (default), "finalized"'),
|
|
14
|
+
encoding: z.string().optional().default('jsonParsed').describe('Response encoding format. Values: "base58", "base64", "jsonParsed" (default — recommended, returns decoded instruction data)'),
|
|
15
|
+
transactionDetails: z.string().optional().default('full').describe('Level of transaction detail in response. Values: "full" (default — complete data), "signatures" (just sigs), "accounts" (account keys only), "none"'),
|
|
16
|
+
showRewards: z.boolean().optional().default(false).describe('Include block rewards in transaction results (default: false)'),
|
|
17
|
+
maxSupportedTransactionVersion: z.number().optional().default(0).describe('Max transaction version to return. Defaults to 0, which includes versioned transactions (v0).')
|
|
18
18
|
}, async (params) => {
|
|
19
19
|
let err;
|
|
20
20
|
err = validateEnum(params.commitment, ['processed', 'confirmed', 'finalized'], 'Transaction Subscribe Error', 'commitment');
|
|
@@ -94,10 +94,10 @@ export function registerEnhancedWebSocketTools(server) {
|
|
|
94
94
|
return handleToolError(err, 'Error');
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
|
-
server.tool('accountSubscribe', 'BEST FOR: real-time account change monitoring for live UI updates (WebSocket). PREFER createWebhook for fire-and-forget notifications on account changes. PREFER laserstreamSubscribe for lowest-latency production streaming. Get Enhanced WebSocket config for real-time Solana account monitoring. Track balance changes and data updates.
|
|
97
|
+
server.tool('accountSubscribe', 'BEST FOR: real-time account change monitoring for live UI updates (WebSocket). PREFER createWebhook for fire-and-forget notifications on account changes. PREFER laserstreamSubscribe for lowest-latency production streaming. Get Enhanced WebSocket config for real-time Solana account monitoring. Track balance changes and data updates. Developer+ plans only. Returns connection config and code example. Data streaming cost: 2 credits per 0.1 MB received.', {
|
|
98
98
|
account: z.string().describe('Account address (base58 encoded)'),
|
|
99
|
-
encoding: z.string().optional().default('base58'),
|
|
100
|
-
commitment: z.string().optional().default('finalized')
|
|
99
|
+
encoding: z.string().optional().default('base58').describe('Account data encoding format. Values: "base58" (default), "base64", "base64+zstd" (compressed), "jsonParsed" (decoded if known program)'),
|
|
100
|
+
commitment: z.string().optional().default('finalized').describe('Commitment level for change notifications. Values: "processed", "confirmed", "finalized" (default — most reliable)')
|
|
101
101
|
}, async ({ account, encoding, commitment }) => {
|
|
102
102
|
let err;
|
|
103
103
|
err = validateEnum(encoding, ['base58', 'base64', 'base64+zstd', 'jsonParsed'], 'Account Subscribe Error', 'encoding');
|
|
@@ -152,7 +152,7 @@ export function registerEnhancedWebSocketTools(server) {
|
|
|
152
152
|
catch {
|
|
153
153
|
return mcpError('Could not fetch live Enhanced WebSocket documentation. Try:\n' +
|
|
154
154
|
'- `lookupHeliusDocs({ topic: \'enhanced-websockets\' })` for full documentation\n' +
|
|
155
|
-
'- Visit https://www.helius.dev/docs/enhanced-websockets directly');
|
|
155
|
+
'- Visit https://www.helius.dev/docs/enhanced-websockets directly', { type: 'API', code: 'FETCH_FAILED', retryable: true, recovery: 'Try lookupHeliusDocs({ topic: "enhanced-websockets" }) or visit https://www.helius.dev/docs/enhanced-websockets directly' });
|
|
156
156
|
}
|
|
157
157
|
const capabilities = extractSections(content, ['capabilities', 'features'], { includeLooseMatches: false });
|
|
158
158
|
const subscriptions = extractSections(content, ['subscriptions', 'endpoints'], { includeLooseMatches: false });
|
package/dist/tools/fees.js
CHANGED
|
@@ -6,7 +6,7 @@ export function registerFeeTools(server) {
|
|
|
6
6
|
// Get Priority Fee Estimate
|
|
7
7
|
server.tool('getPriorityFeeEstimate', 'BEST FOR: determining optimal priority fees before sending a transaction. Get optimal priority fee estimates for Solana transactions. Returns recommended fees in microlamports for different priority levels (Min, Low, Medium, High, VeryHigh, UnsafeMax). Essential for ensuring transaction confirmation during network congestion. Credit cost: 1 credit (Priority Fee API).', {
|
|
8
8
|
accountKeys: z.array(z.string()).optional().describe('Account addresses (base58 encoded) involved in transaction for more accurate estimates'),
|
|
9
|
-
priorityLevel: z.string().optional().describe('Desired priority level'),
|
|
9
|
+
priorityLevel: z.string().optional().describe('Desired priority level. Values: "Min", "Low", "Medium", "High", "VeryHigh", "UnsafeMax". Returns the estimated fee in microlamports/compute unit for that level. Used when includeAllLevels is false.'),
|
|
10
10
|
includeAllLevels: z.boolean().optional().default(true).describe('Return fees for all priority levels')
|
|
11
11
|
}, async ({ accountKeys, priorityLevel, includeAllLevels }) => {
|
|
12
12
|
if (!hasApiKey())
|
|
@@ -31,8 +31,8 @@ export function registerFeeTools(server) {
|
|
|
31
31
|
const lines = ['**Priority Fee Estimate**', ''];
|
|
32
32
|
if (result.priorityFeeLevels) {
|
|
33
33
|
const levels = result.priorityFeeLevels;
|
|
34
|
-
lines.push('| Level | Fee (microlamports) |');
|
|
35
|
-
lines.push('
|
|
34
|
+
lines.push('| Level | Fee (microlamports/CU) |');
|
|
35
|
+
lines.push('|-------|------------------------|');
|
|
36
36
|
lines.push(`| Min | ${levels.min.toLocaleString()} |`);
|
|
37
37
|
lines.push(`| Low | ${levels.low.toLocaleString()} |`);
|
|
38
38
|
lines.push(`| Medium | ${levels.medium.toLocaleString()} |`);
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
export declare function registerTools(server: McpServer): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,82 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { sendFeedbackEvent } from '../utils/feedback.js';
|
|
3
|
-
import { registerAuthTools } from './auth.js';
|
|
4
|
-
import { registerConfigTools } from './config.js';
|
|
5
|
-
import { registerBalanceTools } from './balance.js';
|
|
6
|
-
import { registerTransactionTools } from './transactions.js';
|
|
7
|
-
import { registerAssetTools } from './assets.js';
|
|
8
|
-
import { registerAccountTools } from './accounts.js';
|
|
9
|
-
import { registerFeeTools } from './fees.js';
|
|
10
|
-
import { registerNetworkTools } from './network.js';
|
|
11
|
-
import { registerBlockTools } from './blocks.js';
|
|
12
|
-
import { registerTokenTools } from './tokens.js';
|
|
13
|
-
import { registerDasExtraTools } from './das-extras.js';
|
|
14
|
-
import { registerWebhookTools } from './webhooks.js';
|
|
15
|
-
import { registerEnhancedWebSocketTools } from './enhanced-websockets.js';
|
|
16
|
-
import { registerLaserstreamTools } from './laserstream.js';
|
|
17
|
-
import { registerWalletTools } from './wallet.js';
|
|
18
|
-
import { registerPlanTools } from './plans.js';
|
|
19
|
-
import { registerDocsTools } from './docs.js';
|
|
20
|
-
import { registerGuideTools } from './guides.js';
|
|
21
|
-
import { registerRecommendTools } from './recommend.js';
|
|
22
|
-
import { registerSolanaKnowledgeTools } from './solana-knowledge.js';
|
|
23
|
-
import { registerTransferTools } from './transfers.js';
|
|
24
|
-
/**
|
|
25
|
-
* Patches McpServer to inject optional _feedback and _model params into every
|
|
26
|
-
* tool schema. Hooks into _createRegisteredTool so all overloads are covered
|
|
27
|
-
* without touching individual tool files.
|
|
28
|
-
*/
|
|
29
|
-
function patchToolWithFeedback(server) {
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
const mcpServer = server;
|
|
32
|
-
if (typeof mcpServer._createRegisteredTool !== 'function')
|
|
33
|
-
return;
|
|
34
|
-
const originalCreate = mcpServer._createRegisteredTool.bind(mcpServer);
|
|
35
|
-
mcpServer._createRegisteredTool = function (name, title, description, inputSchema, outputSchema, annotations, execution, _meta, handler) {
|
|
36
|
-
const enhancedSchema = {
|
|
37
|
-
...(inputSchema || {}),
|
|
38
|
-
_feedback: z.string().describe('Assess the previous tool response and provide feedback — what worked, what was confusing, what data was missing. On first call, describe how you discovered Helius.'),
|
|
39
|
-
_feedbackTool: z.string().describe('Which tool your feedback is about (e.g. "getBalance"). On first call, use "none".'),
|
|
40
|
-
_model: z.string().describe('Your LLM model identifier (e.g. claude-sonnet-4-20250514, gpt-4o)'),
|
|
41
|
-
};
|
|
42
|
-
const wrappedHandler = async (params, extra) => {
|
|
43
|
-
if (params && typeof params === 'object') {
|
|
44
|
-
const { _feedback, _feedbackTool, _model, ...cleanParams } = params;
|
|
45
|
-
sendFeedbackEvent({
|
|
46
|
-
type: 'tool_call',
|
|
47
|
-
toolName: name,
|
|
48
|
-
feedback: _feedback,
|
|
49
|
-
feedbackTool: _feedbackTool,
|
|
50
|
-
model: _model,
|
|
51
|
-
});
|
|
52
|
-
return handler(cleanParams, extra);
|
|
53
|
-
}
|
|
54
|
-
return handler(params, extra);
|
|
55
|
-
};
|
|
56
|
-
return originalCreate(name, title, description, enhancedSchema, outputSchema, annotations, execution, _meta, wrappedHandler);
|
|
57
|
-
};
|
|
58
|
-
}
|
|
1
|
+
import { registerRouterTools } from '../router/register.js';
|
|
59
2
|
export function registerTools(server) {
|
|
60
|
-
|
|
61
|
-
registerAuthTools(server);
|
|
62
|
-
registerConfigTools(server);
|
|
63
|
-
registerPlanTools(server);
|
|
64
|
-
registerBalanceTools(server);
|
|
65
|
-
registerTransactionTools(server);
|
|
66
|
-
registerAssetTools(server);
|
|
67
|
-
registerAccountTools(server);
|
|
68
|
-
registerFeeTools(server);
|
|
69
|
-
registerNetworkTools(server);
|
|
70
|
-
registerBlockTools(server);
|
|
71
|
-
registerTokenTools(server);
|
|
72
|
-
registerDasExtraTools(server);
|
|
73
|
-
registerWebhookTools(server);
|
|
74
|
-
registerEnhancedWebSocketTools(server);
|
|
75
|
-
registerLaserstreamTools(server);
|
|
76
|
-
registerWalletTools(server);
|
|
77
|
-
registerDocsTools(server);
|
|
78
|
-
registerGuideTools(server);
|
|
79
|
-
registerRecommendTools(server);
|
|
80
|
-
registerSolanaKnowledgeTools(server);
|
|
81
|
-
registerTransferTools(server);
|
|
3
|
+
registerRouterTools(server);
|
|
82
4
|
}
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { getLaserstreamUrl, getNetwork } from '../utils/helius.js';
|
|
2
|
+
import { getLaserstreamUrl, getNetwork, hasApiKey } from '../utils/helius.js';
|
|
3
3
|
import { mcpText, mcpError, validateEnum, handleToolError, warnInvalidAddresses, warnAddressConflicts } from '../utils/errors.js';
|
|
4
|
+
import { noApiKeyResponse } from './shared.js';
|
|
4
5
|
import { fetchDoc, extractSections, truncateDoc } from '../utils/docs.js';
|
|
5
6
|
export function registerLaserstreamTools(server) {
|
|
6
|
-
server.tool('laserstreamSubscribe', 'BEST FOR: lowest-latency production streaming via gRPC (slots, accounts, transactions, blocks). PREFER transactionSubscribe for simpler WebSocket-based streaming. PREFER createWebhook for fire-and-forget notifications. Get Laserstream gRPC config for high-performance Solana streaming. Subscribe to slots, accounts, transactions, blocks, or entries. 24h historical replay.
|
|
7
|
-
region: z.string().optional().default('ewr'),
|
|
8
|
-
commitment: z.string().optional(),
|
|
9
|
-
subscribeSlots: z.boolean().optional(),
|
|
10
|
-
filterByCommitment: z.boolean().optional(),
|
|
7
|
+
server.tool('laserstreamSubscribe', 'BEST FOR: lowest-latency production streaming via gRPC (slots, accounts, transactions, blocks). PREFER transactionSubscribe for simpler WebSocket-based streaming. PREFER createWebhook for fire-and-forget notifications. Get Laserstream gRPC config for high-performance Solana streaming. Subscribe to slots, accounts, transactions, blocks, or entries. 24h historical replay. Business+ plan for mainnet. Returns connection config and code example.', {
|
|
8
|
+
region: z.string().optional().default('ewr').describe('Closest gRPC region for lowest latency. Values: "ewr" (default, Newark), "pitt" (Pittsburgh), "slc" (Salt Lake City), "lax" (Los Angeles), "lon" (London), "ams" (Amsterdam), "fra" (Frankfurt), "tyo" (Tokyo), "sgp" (Singapore)'),
|
|
9
|
+
commitment: z.string().optional().describe('Commitment level filter. Values: "processed", "confirmed", "finalized". Used with filterByCommitment to limit slot/block updates to a specific level.'),
|
|
10
|
+
subscribeSlots: z.boolean().optional().describe('Subscribe to slot updates (notifications on each new slot the validator processes)'),
|
|
11
|
+
filterByCommitment: z.boolean().optional().describe('When true, only emit slot updates matching the specified commitment level. Requires commitment to be set.'),
|
|
11
12
|
subscribeAccounts: z.array(z.string()).optional().describe('Account public keys to watch (base58 encoded)'),
|
|
12
13
|
accountOwners: z.array(z.string()).optional().describe('Filter by owner addresses (base58 encoded)'),
|
|
13
|
-
subscribeTransactions: z.boolean().optional(),
|
|
14
|
-
transactionAccountInclude: z.array(z.string()).optional().describe('Accounts to include (base58 encoded
|
|
15
|
-
transactionAccountExclude: z.array(z.string()).optional().describe('Accounts to exclude (base58 encoded)'),
|
|
16
|
-
transactionAccountRequired: z.array(z.string()).optional().describe('Accounts required (base58 encoded
|
|
17
|
-
subscribeBlocks: z.boolean().optional(),
|
|
18
|
-
subscribeBlocksMeta: z.boolean().optional(),
|
|
19
|
-
subscribeEntries: z.boolean().optional(),
|
|
14
|
+
subscribeTransactions: z.boolean().optional().describe('Subscribe to all transactions. Produces very high volume without account filters — prefer using transactionAccountInclude or transactionAccountRequired.'),
|
|
15
|
+
transactionAccountInclude: z.array(z.string()).optional().describe('Accounts to include in filter (base58 encoded). OR logic: tx matches if it involves ANY of these accounts. Use transactionAccountRequired for AND logic.'),
|
|
16
|
+
transactionAccountExclude: z.array(z.string()).optional().describe('Accounts to exclude from results (base58 encoded). Transactions involving ANY of these accounts are filtered out, even if they match transactionAccountInclude.'),
|
|
17
|
+
transactionAccountRequired: z.array(z.string()).optional().describe('Accounts required in every matched transaction (base58 encoded). AND logic: tx must involve ALL of these accounts. Use transactionAccountInclude for OR logic.'),
|
|
18
|
+
subscribeBlocks: z.boolean().optional().describe('Subscribe to full block data (includes all transactions in each block)'),
|
|
19
|
+
subscribeBlocksMeta: z.boolean().optional().describe('Subscribe to block metadata only (slot, blockhash, rewards — without full transaction data)'),
|
|
20
|
+
subscribeEntries: z.boolean().optional().describe('Subscribe to ledger entries (shred-level data — the lowest-level stream available)'),
|
|
20
21
|
fromSlot: z.string().optional().describe('Starting slot for 24h historical replay'),
|
|
21
|
-
keepalive: z.boolean().optional().default(true)
|
|
22
|
+
keepalive: z.boolean().optional().default(true).describe('Send gRPC keepalive pings to maintain the connection (default: true). Set to false only if your client handles its own keepalive.')
|
|
22
23
|
}, async (params) => {
|
|
24
|
+
if (!hasApiKey())
|
|
25
|
+
return noApiKeyResponse();
|
|
23
26
|
let err;
|
|
24
27
|
err = validateEnum(params.region, ['ewr', 'pitt', 'slc', 'lax', 'lon', 'ams', 'fra', 'tyo', 'sgp'], 'Laserstream Error', 'region');
|
|
25
28
|
if (err)
|
|
@@ -33,7 +36,7 @@ export function registerLaserstreamTools(server) {
|
|
|
33
36
|
if (params.fromSlot !== undefined) {
|
|
34
37
|
const slotNum = Number(params.fromSlot);
|
|
35
38
|
if (!Number.isInteger(slotNum) || slotNum < 0) {
|
|
36
|
-
return
|
|
39
|
+
return mcpError(`**Laserstream Error**\n\nInvalid fromSlot "${params.fromSlot}". Must be a non-negative integer slot number.`, { type: 'VALIDATION', code: 'INVALID_PARAM', retryable: false, recovery: 'Provide a non-negative integer slot number for fromSlot.' });
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
// Check that at least one subscription type is selected
|
|
@@ -132,13 +135,7 @@ export function registerLaserstreamTools(server) {
|
|
|
132
135
|
}
|
|
133
136
|
});
|
|
134
137
|
server.tool('getLaserstreamInfo', 'Get Helius Laserstream gRPC capabilities, regions, pricing, and plan requirements. Lowest latency Solana streaming with 24h replay. Fetches live from official documentation.', {}, async () => {
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
endpoint = getLaserstreamUrl();
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
return handleToolError(err, 'Laserstream Error');
|
|
141
|
-
}
|
|
138
|
+
const endpoint = getLaserstreamUrl();
|
|
142
139
|
let content;
|
|
143
140
|
try {
|
|
144
141
|
content = await fetchDoc('laserstream');
|
|
@@ -146,7 +143,7 @@ export function registerLaserstreamTools(server) {
|
|
|
146
143
|
catch {
|
|
147
144
|
return mcpError('Could not fetch live Laserstream documentation. Try:\n' +
|
|
148
145
|
'- `lookupHeliusDocs({ topic: \'laserstream\' })` for full documentation\n' +
|
|
149
|
-
'- Visit https://www.helius.dev/docs/laserstream/grpc directly');
|
|
146
|
+
'- Visit https://www.helius.dev/docs/laserstream/grpc directly', { type: 'API', code: 'FETCH_FAILED', retryable: true, recovery: 'Try lookupHeliusDocs({ topic: "laserstream" }) or visit https://www.helius.dev/docs/laserstream/grpc directly' });
|
|
150
147
|
}
|
|
151
148
|
const capabilities = extractSections(content, ['capabilities', 'features'], { includeLooseMatches: false });
|
|
152
149
|
const regions = extractSections(content, ['regions', 'endpoints'], { includeLooseMatches: false });
|
package/dist/tools/network.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
2
3
|
import { formatSolCompact } from '../utils/formatters.js';
|
|
3
4
|
import { noApiKeyResponse } from './shared.js';
|
|
4
5
|
import { mcpText, handleToolError } from '../utils/errors.js';
|
|
5
6
|
export function registerNetworkTools(server) {
|
|
6
|
-
server.tool('getNetworkStatus', 'BEST FOR: quick Solana network health check — epoch, slot, TPS, supply, version. Get current Solana network status including epoch info (current epoch, slot, progress), current TPS (transactions per second), total SOL supply, cluster version, and current block height.
|
|
7
|
+
server.tool('getNetworkStatus', 'BEST FOR: quick Solana network health check — epoch, slot, TPS, supply, version. Get current Solana network status including epoch info (current epoch, slot, progress), current TPS (transactions per second), total SOL supply, cluster version, and current block height. Optionally pass "samples" to control the TPS averaging window (each sample ≈ 60s; default 4 ≈ 4 min, max 720 ≈ 12 hours). Credit cost: 4 credits (4 standard RPC calls).', {
|
|
8
|
+
samples: z.number().optional().default(4).describe('Number of recent performance samples for TPS calculation (each sample ≈ 60s). Default 4 (~4 min). Max 720 (~12 hours).'),
|
|
9
|
+
}, async ({ samples }) => {
|
|
7
10
|
if (!hasApiKey())
|
|
8
11
|
return noApiKeyResponse();
|
|
9
12
|
try {
|
|
@@ -14,7 +17,7 @@ export function registerNetworkTools(server) {
|
|
|
14
17
|
helius.getEpochInfo(),
|
|
15
18
|
helius.getSupply(),
|
|
16
19
|
helius.getVersion(),
|
|
17
|
-
helius.getRecentPerformanceSamples(
|
|
20
|
+
helius.getRecentPerformanceSamples(Math.max(1, Math.min(samples, 720))),
|
|
18
21
|
]);
|
|
19
22
|
const lines = ['**Solana Network Status**', ''];
|
|
20
23
|
// Epoch info — all fields are bigint from Kit
|
|
@@ -36,7 +39,7 @@ export function registerNetworkTools(server) {
|
|
|
36
39
|
}
|
|
37
40
|
lines.push('');
|
|
38
41
|
}
|
|
39
|
-
// TPS — averaged from
|
|
42
|
+
// TPS — averaged from recent performance samples (~60s each)
|
|
40
43
|
// numTransactions includes vote + non-vote; numNonVoteTransactions is real user TPS
|
|
41
44
|
if (Array.isArray(perfSamples) && perfSamples.length > 0) {
|
|
42
45
|
let totalTx = 0;
|
|
@@ -56,7 +59,10 @@ export function registerNetworkTools(server) {
|
|
|
56
59
|
}
|
|
57
60
|
if (totalSeconds > 0) {
|
|
58
61
|
const totalTps = Math.round(totalTx / totalSeconds);
|
|
59
|
-
|
|
62
|
+
const window = totalSeconds >= 3600
|
|
63
|
+
? `~${(totalSeconds / 3600).toFixed(1)} hr`
|
|
64
|
+
: `~${Math.round(totalSeconds / 60)} min`;
|
|
65
|
+
lines.push(`**TPS (avg. last ${window}):**`);
|
|
60
66
|
if (hasNonVoteData) {
|
|
61
67
|
const nonVoteTps = Math.round(totalNonVoteTx / totalSeconds);
|
|
62
68
|
lines.push(`- Real (non-vote): ~${nonVoteTps.toLocaleString()} tx/sec`);
|
package/dist/tools/plans.d.ts
CHANGED
|
@@ -14,10 +14,5 @@ export declare const HELIUS_PLANS: Record<string, {
|
|
|
14
14
|
name: string;
|
|
15
15
|
features: Record<string, boolean | string>;
|
|
16
16
|
}>;
|
|
17
|
-
/**
|
|
18
|
-
* Detect the user's current Helius plan from their JWT session.
|
|
19
|
-
* Returns the plan key (e.g. "developer") or undefined if unavailable.
|
|
20
|
-
* Best-effort — never throws.
|
|
21
|
-
*/
|
|
22
17
|
export declare function detectCurrentPlan(): Promise<string | undefined>;
|
|
23
18
|
export declare function registerPlanTools(server: McpServer): void;
|