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
@@ -2,9 +2,10 @@ import { z } from 'zod';
2
2
  import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
3
  import { formatAddress } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
+ import { mcpText, handleToolError, addressError, paginationError, notFoundError } from '../utils/errors.js';
5
6
  export function registerAssetTools(server) {
6
7
  // Get Assets by Owner (NFTs and tokens via DAS)
7
- server.tool('getAssetsByOwner', 'Get all NFTs and digital assets owned by a Solana wallet using the DAS (Digital Asset Standard) API. Returns asset names, types (NFT, cNFT, Fungible, etc.), and mint addresses. Supports both regular NFTs and compressed NFTs (cNFTs). Use this to see what NFTs/collectibles a wallet owns. For fungible token balances, use getTokenBalances instead.', {
8
+ server.tool('getAssetsByOwner', 'BEST FOR: listing NFTs/digital assets owned by a wallet. PREFER getTokenBalances for fungible tokens, getAsset for a specific mint. Get all NFTs and digital assets owned by a wallet. Returns asset names, types, and mint addresses. Supports regular NFTs and cNFTs. Credit cost: 10 credits (DAS API).', {
8
9
  address: z.string().describe('Solana wallet address (base58 encoded)'),
9
10
  limit: z.number().optional().default(20).describe('Number of assets to return (default 20). Increase for wallets with many NFTs.'),
10
11
  page: z.number().optional().default(1).describe('Page number (starts at 1)')
@@ -12,18 +13,23 @@ export function registerAssetTools(server) {
12
13
  if (!hasApiKey())
13
14
  return noApiKeyResponse();
14
15
  const helius = getHeliusClient();
15
- const response = await helius.getAssetsByOwner({
16
- ownerAddress: address,
17
- page,
18
- limit
19
- });
16
+ let response;
17
+ try {
18
+ response = await helius.getAssetsByOwner({
19
+ ownerAddress: address,
20
+ page,
21
+ limit
22
+ });
23
+ }
24
+ catch (err) {
25
+ const header = `Assets for ${formatAddress(address)}`;
26
+ return handleToolError(err, 'Error fetching assets', [
27
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded wallet address.'),
28
+ paginationError(header),
29
+ ]);
30
+ }
20
31
  if (!response.items || response.items.length === 0) {
21
- return {
22
- content: [{
23
- type: 'text',
24
- text: `**Assets for ${formatAddress(address)}**\n\nNo assets found.`
25
- }]
26
- };
32
+ return mcpText(`**Assets for ${formatAddress(address)}**\n\nNo assets found.`);
27
33
  }
28
34
  const items = response.items;
29
35
  // Enrich assets missing name/symbol
@@ -51,57 +57,41 @@ export function registerAssetTools(server) {
51
57
  lines.push(`- **${name}** (${asset.interface})`);
52
58
  lines.push(` ID: ${formatAddress(asset.id)}`);
53
59
  });
54
- return {
55
- content: [{
56
- type: 'text',
57
- text: lines.join('\n')
58
- }]
59
- };
60
+ return mcpText(lines.join('\n'));
60
61
  });
61
62
  // Get Asset (single or batch)
62
- server.tool('getAsset', 'Get detailed information about one or more NFTs/tokens by mint address. For a single asset: returns name, symbol, description, image, owner, creators, authorities, supply, decimals, royalties, mutability. For batch: pass an array of up to 1000 mint addresses in "ids" for fast bulk lookups. Use this to find who created/deployed a token, verify token details, or get full NFT metadata.', {
63
+ server.tool('getAsset', 'Get detailed information about one or more NFTs/tokens by mint address. For a single asset: returns name, symbol, description, image, owner, creators, authorities, supply, decimals, royalties, mutability. For batch: pass an array of up to 1000 mint addresses in "ids" for fast bulk lookups. Use this to find who created/deployed a token, verify token details, or get full NFT metadata. DAS API (10 credits/call).', {
63
64
  id: z.string().optional().describe('Single asset mint address (base58 encoded). Use this OR ids, not both.'),
64
- ids: z.array(z.string()).optional().describe('Array of asset mint addresses for batch lookup (up to 1000). Use this OR id, not both.')
65
+ ids: z.array(z.string()).optional().describe('Array of asset mint addresses for batch lookup (base58 encoded, up to 1000). Use this OR id, not both.')
65
66
  }, async ({ id, ids }) => {
66
67
  if (!hasApiKey())
67
68
  return noApiKeyResponse();
68
69
  const helius = getHeliusClient();
69
70
  // Validate: must provide exactly one of id or ids
70
71
  if (!id && (!ids || ids.length === 0)) {
71
- return {
72
- content: [{
73
- type: 'text',
74
- text: `**Error:** Provide either "id" (single asset) or "ids" (batch of up to 1000).`
75
- }]
76
- };
72
+ return mcpText(`**Error:** Provide either "id" (single asset) or "ids" (batch of up to 1000).`);
77
73
  }
78
74
  if (id && ids && ids.length > 0) {
79
- return {
80
- content: [{
81
- type: 'text',
82
- text: `**Error:** Provide either "id" or "ids", not both.`
83
- }]
84
- };
75
+ return mcpText(`**Error:** Provide either "id" or "ids", not both.`);
85
76
  }
86
77
  // --- Batch mode ---
87
78
  if (ids && ids.length > 0) {
88
79
  if (ids.length > 1000) {
89
- return {
90
- content: [{
91
- type: 'text',
92
- text: `**Error:** Maximum 1000 assets per batch. You provided ${ids.length}.`
93
- }]
94
- };
80
+ return mcpText(`**Error:** Maximum 1000 assets per batch. You provided ${ids.length}.`);
81
+ }
82
+ let assets;
83
+ try {
84
+ assets = await helius.getAssetBatch({ ids });
85
+ }
86
+ catch (err) {
87
+ return handleToolError(err, 'Error fetching assets', [
88
+ addressError('Asset Batch Error', 'One or more provided IDs are not valid Solana addresses. Please check the mint addresses and try again.'),
89
+ notFoundError('Asset Batch Results', 'One or more assets were not found. Please check the mint addresses and try again.'),
90
+ ]);
95
91
  }
96
- const assets = await helius.getAssetBatch({ ids });
97
92
  const items = (assets || []);
98
93
  if (items.length === 0) {
99
- return {
100
- content: [{
101
- type: 'text',
102
- text: `**Asset Batch Results**\n\nNo assets found.`
103
- }]
104
- };
94
+ return mcpText(`**Asset Batch Results**\n\nNo assets found.`);
105
95
  }
106
96
  const lines = [`**Asset Batch Results** (${items.length} assets)`, ''];
107
97
  items.forEach((asset) => {
@@ -114,22 +104,22 @@ export function registerAssetTools(server) {
114
104
  lines.push(` Owner: ${formatAddress(asset.ownership.owner)}`);
115
105
  }
116
106
  });
117
- return {
118
- content: [{
119
- type: 'text',
120
- text: lines.join('\n')
121
- }]
122
- };
107
+ return mcpText(lines.join('\n'));
123
108
  }
124
109
  // --- Single asset mode ---
125
- const asset = await helius.getAsset({ id: id });
110
+ let asset;
111
+ try {
112
+ asset = await helius.getAsset({ id: id });
113
+ }
114
+ catch (err) {
115
+ const header = `Asset ${formatAddress(id)}`;
116
+ return handleToolError(err, 'Error fetching asset', [
117
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded mint address.'),
118
+ notFoundError(header, 'Asset not found. This mint address does not exist or has not been indexed.'),
119
+ ]);
120
+ }
126
121
  if (!asset) {
127
- return {
128
- content: [{
129
- type: 'text',
130
- text: `Asset ${formatAddress(id)} not found.`
131
- }]
132
- };
122
+ return mcpText(`Asset ${formatAddress(id)} not found.`);
133
123
  }
134
124
  const metadata = asset.content?.metadata;
135
125
  const tokenInfo = asset.token_info;
@@ -204,18 +194,13 @@ export function registerAssetTools(server) {
204
194
  if (flags.length > 0) {
205
195
  lines.push(`**Status:** ${flags.join(', ')}`);
206
196
  }
207
- return {
208
- content: [{
209
- type: 'text',
210
- text: lines.join('\n')
211
- }]
212
- };
197
+ return mcpText(lines.join('\n'));
213
198
  });
214
199
  // Search Assets — unified search with smart routing for creator/authority/general queries
215
- server.tool('searchAssets', 'Advanced search for digital assets (NFTs, tokens) with multiple filters. Search by owner, creator, authority, name, compression status, burnt status, or frozen status. Also replaces getAssetsByCreator and getAssetsByAuthority pass creatorAddress (with optional onlyVerified) to find assets by creator, or authorityAddress to find assets controlled by an authority.', {
216
- ownerAddress: z.string().optional().describe('Filter by owner wallet address'),
217
- creatorAddress: z.string().optional().describe('Filter by creator address'),
218
- authorityAddress: z.string().optional().describe('Filter by authority address (finds assets this address has update/freeze control over)'),
200
+ server.tool('searchAssets', 'BEST FOR: filtered multi-criteria asset search. PREFER getAssetsByOwner for simple wallet NFT listing, getAssetsByGroup for collection browsing. Search digital assets by owner, creator, authority, name, compression, burnt, or frozen status. Also supports getAssetsByCreator and getAssetsByAuthority queries. Credit cost: 10 credits (DAS API).', {
201
+ ownerAddress: z.string().optional().describe('Filter by owner wallet address (base58 encoded)'),
202
+ creatorAddress: z.string().optional().describe('Filter by creator address (base58 encoded)'),
203
+ authorityAddress: z.string().optional().describe('Filter by authority address (base58 encoded, finds assets this address has update/freeze control over)'),
219
204
  onlyVerified: z.boolean().optional().default(false).describe('Only return assets where the creator is verified (used with creatorAddress)'),
220
205
  name: z.string().optional().describe('Search by asset name (partial match). Requires ownerAddress to be provided.'),
221
206
  compressed: z.boolean().optional().describe('Filter for compressed NFTs (cNFTs) only'),
@@ -231,32 +216,45 @@ export function registerAssetTools(server) {
231
216
  let headerLabel;
232
217
  // Smart routing: authority-only → getAssetsByAuthority
233
218
  if (authorityAddress && !creatorAddress && !ownerAddress && !name && compressed === undefined && burnt === undefined && frozen === undefined) {
234
- response = await helius.getAssetsByAuthority({
235
- authorityAddress,
236
- page,
237
- limit
238
- });
219
+ try {
220
+ response = await helius.getAssetsByAuthority({
221
+ authorityAddress,
222
+ page,
223
+ limit
224
+ });
225
+ }
226
+ catch (err) {
227
+ const header = `Assets by Authority ${formatAddress(authorityAddress)}`;
228
+ return handleToolError(err, 'Error searching assets', [
229
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded address.'),
230
+ paginationError(header),
231
+ ]);
232
+ }
239
233
  headerLabel = `Assets by Authority ${formatAddress(authorityAddress)}`;
240
234
  }
241
235
  // Smart routing: creator (with optional onlyVerified) → getAssetsByCreator
242
236
  else if (creatorAddress && !authorityAddress && !ownerAddress && !name && compressed === undefined && burnt === undefined && frozen === undefined) {
243
- response = await helius.getAssetsByCreator({
244
- creatorAddress,
245
- onlyVerified,
246
- page,
247
- limit
248
- });
237
+ try {
238
+ response = await helius.getAssetsByCreator({
239
+ creatorAddress,
240
+ onlyVerified,
241
+ page,
242
+ limit
243
+ });
244
+ }
245
+ catch (err) {
246
+ const header = `Assets by Creator ${formatAddress(creatorAddress)}`;
247
+ return handleToolError(err, 'Error searching assets', [
248
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded address.'),
249
+ paginationError(header),
250
+ ]);
251
+ }
249
252
  headerLabel = `Assets by Creator ${formatAddress(creatorAddress)}`;
250
253
  }
251
254
  // General search → searchAssets
252
255
  else {
253
256
  if (name && !ownerAddress) {
254
- return {
255
- content: [{
256
- type: 'text',
257
- text: `**Search Error:** Searching by name requires an owner address. Please also provide \`ownerAddress\` to search for assets named "${name}".`
258
- }]
259
- };
257
+ return mcpText(`**Search Error:** Searching by name requires an owner address. Please also provide \`ownerAddress\` to search for assets named "${name}".`);
260
258
  }
261
259
  const params = { page, limit };
262
260
  if (ownerAddress)
@@ -275,23 +273,16 @@ export function registerAssetTools(server) {
275
273
  response = await helius.searchAssets(params);
276
274
  }
277
275
  catch (err) {
278
- return {
279
- content: [{
280
- type: 'text',
281
- text: `Error searching assets: ${err instanceof Error ? err.message : String(err)}`
282
- }]
283
- };
276
+ return handleToolError(err, 'Error searching assets', [
277
+ addressError('Asset Search'),
278
+ paginationError('Asset Search'),
279
+ ]);
284
280
  }
285
281
  headerLabel = 'Asset Search Results';
286
282
  }
287
283
  const items = (response.items || []);
288
284
  if (items.length === 0) {
289
- return {
290
- content: [{
291
- type: 'text',
292
- text: `**${headerLabel}**\n\nNo assets found matching the criteria.`
293
- }]
294
- };
285
+ return mcpText(`**${headerLabel}**\n\nNo assets found matching the criteria.`);
295
286
  }
296
287
  const lines = [`**${headerLabel}** (${response.total || items.length} total, page ${page})`, ''];
297
288
  items.forEach((asset) => {
@@ -310,37 +301,37 @@ export function registerAssetTools(server) {
310
301
  lines.push(` Owner: ${formatAddress(asset.ownership.owner)}`);
311
302
  }
312
303
  });
313
- return {
314
- content: [{
315
- type: 'text',
316
- text: lines.join('\n')
317
- }]
318
- };
304
+ return mcpText(lines.join('\n'));
319
305
  });
320
306
  // Get Assets by Group (Collection)
321
- server.tool('getAssetsByGroup', 'Get all NFTs in a collection by group key/value. The groupKey is usually "collection" and groupValue is the collection mint address. Use this to browse all NFTs in a specific collection.', {
307
+ server.tool('getAssetsByGroup', 'Get all NFTs in a collection by group key/value. The groupKey is usually "collection" and groupValue is the collection mint address. Use this to browse all NFTs in a specific collection. DAS API (10 credits/call).', {
322
308
  groupKey: z.string().describe('Group key - usually "collection"'),
323
- groupValue: z.string().describe('Group value - usually the collection mint address'),
309
+ groupValue: z.string().describe('Group value - usually the collection mint address (base58 encoded)'),
324
310
  page: z.number().optional().default(1).describe('Page number (starts at 1)'),
325
311
  limit: z.number().optional().default(20).describe('Results per page (max 1000)')
326
312
  }, async ({ groupKey, groupValue, page, limit }) => {
327
313
  if (!hasApiKey())
328
314
  return noApiKeyResponse();
329
315
  const helius = getHeliusClient();
330
- const response = await helius.getAssetsByGroup({
331
- groupKey,
332
- groupValue,
333
- page,
334
- limit
335
- });
316
+ let response;
317
+ try {
318
+ response = await helius.getAssetsByGroup({
319
+ groupKey,
320
+ groupValue,
321
+ page,
322
+ limit
323
+ });
324
+ }
325
+ catch (err) {
326
+ const header = `Assets in Group ${groupKey}=${formatAddress(groupValue)}`;
327
+ return handleToolError(err, 'Error fetching group assets', [
328
+ addressError(header, 'Invalid Solana address. Please provide a valid base58-encoded address.'),
329
+ paginationError(header),
330
+ ]);
331
+ }
336
332
  const items = (response.items || []);
337
333
  if (items.length === 0) {
338
- return {
339
- content: [{
340
- type: 'text',
341
- text: `**Assets in Group ${groupKey}=${formatAddress(groupValue)}**\n\nNo assets found.`
342
- }]
343
- };
334
+ return mcpText(`**Assets in Group ${groupKey}=${formatAddress(groupValue)}**\n\nNo assets found.`);
344
335
  }
345
336
  const lines = [`**Assets in Group ${groupKey}=${formatAddress(groupValue)}** (${response.total || items.length} total, page ${page})`, ''];
346
337
  items.forEach((asset) => {
@@ -351,11 +342,6 @@ export function registerAssetTools(server) {
351
342
  lines.push(` Owner: ${formatAddress(asset.ownership.owner)}`);
352
343
  }
353
344
  });
354
- return {
355
- content: [{
356
- type: 'text',
357
- text: lines.join('\n')
358
- }]
359
- };
345
+ return mcpText(lines.join('\n'));
360
346
  });
361
347
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAuthTools(server: McpServer): void;