helius-mcp 1.3.0 → 2.1.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 (103) hide show
  1. package/CHANGELOG.md +151 -79
  2. package/LICENSE +21 -21
  3. package/README.md +144 -132
  4. package/dist/http.d.ts +1 -1
  5. package/dist/index.js +2 -56
  6. package/dist/results/store.d.ts +8 -0
  7. package/dist/results/store.js +72 -0
  8. package/dist/results/types.d.ts +47 -0
  9. package/dist/results/types.js +1 -0
  10. package/dist/router/action-groups.d.ts +6 -0
  11. package/dist/router/action-groups.js +32 -0
  12. package/dist/router/action-handlers.d.ts +20 -0
  13. package/dist/router/action-handlers.js +125 -0
  14. package/dist/router/actions.d.ts +12 -0
  15. package/dist/router/actions.js +125 -0
  16. package/dist/router/catalog.d.ts +6 -0
  17. package/dist/router/catalog.js +394 -0
  18. package/dist/router/context.d.ts +5 -0
  19. package/dist/router/context.js +10 -0
  20. package/dist/router/dispatch.d.ts +4 -0
  21. package/dist/router/dispatch.js +276 -0
  22. package/dist/router/instructions.d.ts +1 -0
  23. package/dist/router/instructions.js +25 -0
  24. package/dist/router/register.d.ts +2 -0
  25. package/dist/router/register.js +15 -0
  26. package/dist/router/required-params.d.ts +9 -0
  27. package/dist/router/required-params.js +68 -0
  28. package/dist/router/responses.d.ts +29 -0
  29. package/dist/router/responses.js +186 -0
  30. package/dist/router/schemas.d.ts +224 -0
  31. package/dist/router/schemas.js +204 -0
  32. package/dist/router/telemetry.d.ts +27 -0
  33. package/dist/router/telemetry.js +52 -0
  34. package/dist/router/types.d.ts +46 -0
  35. package/dist/router/types.js +1 -0
  36. package/dist/scripts/validate-catalog.d.ts +2 -2
  37. package/dist/scripts/validate-catalog.js +10 -10
  38. package/dist/tools/accounts.js +5 -5
  39. package/dist/tools/assets.js +5 -5
  40. package/dist/tools/auth.js +392 -319
  41. package/dist/tools/config.js +3 -3
  42. package/dist/tools/das-extras.js +6 -6
  43. package/dist/tools/docs.js +55 -41
  44. package/dist/tools/enhanced-websockets.js +13 -13
  45. package/dist/tools/fees.js +3 -3
  46. package/dist/tools/index.d.ts +1 -1
  47. package/dist/tools/index.js +2 -80
  48. package/dist/tools/laserstream.js +20 -23
  49. package/dist/tools/network.js +10 -4
  50. package/dist/tools/plans.d.ts +0 -5
  51. package/dist/tools/plans.js +167 -12
  52. package/dist/tools/product-catalog.d.ts +1 -0
  53. package/dist/tools/product-catalog.js +52 -17
  54. package/dist/tools/recommend.d.ts +0 -1
  55. package/dist/tools/recommend.js +9 -28
  56. package/dist/tools/shared.d.ts +1 -0
  57. package/dist/tools/shared.js +21 -13
  58. package/dist/tools/solana-knowledge.js +23 -7
  59. package/dist/tools/staking.d.ts +2 -0
  60. package/dist/tools/staking.js +268 -0
  61. package/dist/tools/transactions.js +256 -3
  62. package/dist/tools/transfers.js +38 -43
  63. package/dist/tools/wallet.js +77 -17
  64. package/dist/tools/webhooks.js +3 -3
  65. package/dist/tools/zk-compression.d.ts +2 -0
  66. package/dist/tools/zk-compression.js +781 -0
  67. package/dist/utils/config.d.ts +2 -2
  68. package/dist/utils/config.js +68 -6
  69. package/dist/utils/errors.d.ts +10 -1
  70. package/dist/utils/errors.js +46 -12
  71. package/dist/utils/feedback.js +1 -4
  72. package/dist/utils/helius.js +25 -14
  73. package/dist/utils/ows.d.ts +74 -0
  74. package/dist/utils/ows.js +155 -0
  75. package/dist/utils/resilience.d.ts +39 -0
  76. package/dist/utils/resilience.js +130 -0
  77. package/dist/version.d.ts +1 -1
  78. package/dist/version.js +1 -1
  79. package/package.json +63 -64
  80. package/system-prompts/helius/claude.system.md +200 -170
  81. package/system-prompts/helius/full.md +3236 -2869
  82. package/system-prompts/helius/openai.developer.md +200 -170
  83. package/system-prompts/helius-dflow/claude.system.md +324 -290
  84. package/system-prompts/helius-dflow/full.md +4160 -3648
  85. package/system-prompts/helius-dflow/openai.developer.md +324 -290
  86. package/system-prompts/helius-jupiter/claude.system.md +333 -0
  87. package/system-prompts/helius-jupiter/full.md +5133 -0
  88. package/system-prompts/helius-jupiter/openai.developer.md +333 -0
  89. package/system-prompts/helius-okx/claude.system.md +182 -0
  90. package/system-prompts/helius-okx/full.md +584 -0
  91. package/system-prompts/helius-okx/openai.developer.md +182 -0
  92. package/system-prompts/helius-phantom/claude.system.md +345 -333
  93. package/system-prompts/helius-phantom/full.md +5649 -5473
  94. package/system-prompts/helius-phantom/openai.developer.md +345 -333
  95. package/system-prompts/svm/claude.system.md +159 -159
  96. package/system-prompts/svm/full.md +631 -631
  97. package/system-prompts/svm/openai.developer.md +159 -159
  98. package/dist/scripts/test-htmltotext.d.ts +0 -5
  99. package/dist/scripts/test-htmltotext.js +0 -67
  100. package/dist/scripts/test-solana-knowledge.d.ts +0 -9
  101. package/dist/scripts/test-solana-knowledge.js +0 -272
  102. package/dist/scripts/validate-templates.d.ts +0 -12
  103. package/dist/scripts/validate-templates.js +0 -94
@@ -0,0 +1,204 @@
1
+ import { z } from 'zod';
2
+ import { HELIUS_ACCOUNT_ACTIONS, HELIUS_ASSET_ACTIONS, HELIUS_CHAIN_ACTIONS, HELIUS_COMPRESSION_ACTIONS, HELIUS_KNOWLEDGE_ACTIONS, HELIUS_STREAMING_ACTIONS, HELIUS_TRANSACTION_ACTIONS, HELIUS_WALLET_ACTIONS, HELIUS_WRITE_ACTIONS, } from './actions.js';
3
+ import { withTelemetry } from './telemetry.js';
4
+ const detailField = z.enum(['summary', 'standard', 'full']).optional();
5
+ const argsField = z.union([
6
+ z.object({}).passthrough(),
7
+ z.string().transform((s, ctx) => {
8
+ try {
9
+ return JSON.parse(s);
10
+ }
11
+ catch {
12
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'args must be a JSON object string' });
13
+ return z.NEVER;
14
+ }
15
+ }).pipe(z.object({}).passthrough()),
16
+ ]).optional();
17
+ const stringArray = () => z.array(z.string()).optional();
18
+ const optionalString = () => z.string().optional();
19
+ const optionalNumber = () => z.coerce.number().optional();
20
+ const optionalBoolean = () => z.boolean().optional();
21
+ export const HeliusAccountActionSchema = z.enum(HELIUS_ACCOUNT_ACTIONS);
22
+ export const HeliusWalletActionSchema = z.enum(HELIUS_WALLET_ACTIONS);
23
+ export const HeliusAssetActionSchema = z.enum(HELIUS_ASSET_ACTIONS);
24
+ export const HeliusTransactionActionSchema = z.enum(HELIUS_TRANSACTION_ACTIONS);
25
+ export const HeliusChainActionSchema = z.enum(HELIUS_CHAIN_ACTIONS);
26
+ export const HeliusStreamingActionSchema = z.enum(HELIUS_STREAMING_ACTIONS);
27
+ export const HeliusKnowledgeActionSchema = z.enum(HELIUS_KNOWLEDGE_ACTIONS);
28
+ export const HeliusWriteActionSchema = z.enum(HELIUS_WRITE_ACTIONS);
29
+ export const HeliusCompressionActionSchema = z.enum(HELIUS_COMPRESSION_ACTIONS);
30
+ export const HELIUS_ACCOUNT_SCHEMA = withTelemetry({
31
+ action: HeliusAccountActionSchema,
32
+ detail: detailField,
33
+ args: argsField,
34
+ apiKey: optionalString(),
35
+ network: optionalString(),
36
+ paymentIntentId: optionalString(),
37
+ plan: optionalString(),
38
+ period: optionalString(),
39
+ couponCode: optionalString(),
40
+ email: optionalString(),
41
+ firstName: optionalString(),
42
+ lastName: optionalString(),
43
+ discoveryPath: optionalString(),
44
+ frictionPoints: optionalString(),
45
+ });
46
+ export const HELIUS_WALLET_SCHEMA = withTelemetry({
47
+ action: HeliusWalletActionSchema,
48
+ detail: detailField,
49
+ args: argsField,
50
+ address: optionalString(),
51
+ addresses: stringArray(),
52
+ mint: optionalString(),
53
+ time: optionalNumber(),
54
+ datetime: optionalString(),
55
+ slot: optionalNumber(),
56
+ limit: optionalNumber(),
57
+ page: optionalNumber(),
58
+ cursor: optionalString(),
59
+ before: optionalString(),
60
+ after: optionalString(),
61
+ showNfts: optionalBoolean(),
62
+ showZeroBalance: optionalBoolean(),
63
+ showNative: optionalBoolean(),
64
+ });
65
+ export const HELIUS_ASSET_SCHEMA = withTelemetry({
66
+ action: HeliusAssetActionSchema,
67
+ detail: detailField,
68
+ args: argsField,
69
+ id: optionalString(),
70
+ ids: stringArray(),
71
+ address: optionalString(),
72
+ ownerAddress: optionalString(),
73
+ creatorAddress: optionalString(),
74
+ authorityAddress: optionalString(),
75
+ groupKey: optionalString(),
76
+ groupValue: optionalString(),
77
+ mint: optionalString(),
78
+ limit: optionalNumber(),
79
+ page: optionalNumber(),
80
+ name: optionalString(),
81
+ compressed: optionalBoolean(),
82
+ burnt: optionalBoolean(),
83
+ frozen: optionalBoolean(),
84
+ onlyVerified: optionalBoolean(),
85
+ });
86
+ export const HELIUS_TRANSACTION_SCHEMA = withTelemetry({
87
+ action: HeliusTransactionActionSchema,
88
+ detail: detailField,
89
+ args: argsField,
90
+ address: optionalString(),
91
+ signature: optionalString(),
92
+ signatures: stringArray(),
93
+ limit: optionalNumber(),
94
+ before: optionalString(),
95
+ until: optionalString(),
96
+ paginationToken: optionalString(),
97
+ sortOrder: optionalString(),
98
+ status: optionalString(),
99
+ mode: optionalString(),
100
+ transactionDetails: optionalString(),
101
+ tokenAccounts: optionalString(),
102
+ });
103
+ export const HELIUS_CHAIN_SCHEMA = withTelemetry({
104
+ action: HeliusChainActionSchema,
105
+ detail: detailField,
106
+ args: argsField,
107
+ address: optionalString(),
108
+ addresses: stringArray(),
109
+ programId: optionalString(),
110
+ slot: optionalNumber(),
111
+ limit: optionalNumber(),
112
+ page: optionalNumber(),
113
+ stakeAccount: optionalString(),
114
+ accountKeys: stringArray(),
115
+ priorityLevel: optionalString(),
116
+ includeAllLevels: optionalBoolean(),
117
+ encoding: optionalString(),
118
+ dataSize: optionalNumber(),
119
+ owner: optionalString(),
120
+ mint: optionalString(),
121
+ // simulateTransaction
122
+ transaction: optionalString(),
123
+ sigVerify: optionalBoolean(),
124
+ replaceRecentBlockhash: optionalBoolean(),
125
+ commitment: optionalString(),
126
+ });
127
+ export const HELIUS_STREAMING_SCHEMA = withTelemetry({
128
+ action: HeliusStreamingActionSchema,
129
+ detail: detailField,
130
+ args: argsField,
131
+ account: optionalString(),
132
+ accountAddresses: stringArray(),
133
+ webhookID: optionalString(),
134
+ webhookURL: optionalString(),
135
+ transactionTypes: stringArray(),
136
+ signature: optionalString(),
137
+ encoding: optionalString(),
138
+ commitment: optionalString(),
139
+ region: optionalString(),
140
+ webhookType: optionalString(),
141
+ accountInclude: stringArray(),
142
+ accountExclude: stringArray(),
143
+ accountRequired: stringArray(),
144
+ subscribeAccounts: stringArray(),
145
+ accountOwners: stringArray(),
146
+ transactionAccountInclude: stringArray(),
147
+ transactionAccountExclude: stringArray(),
148
+ transactionAccountRequired: stringArray(),
149
+ });
150
+ export const HELIUS_KNOWLEDGE_SCHEMA = withTelemetry({
151
+ action: HeliusKnowledgeActionSchema,
152
+ detail: detailField,
153
+ args: argsField,
154
+ topic: optionalString(),
155
+ section: optionalString(),
156
+ query: optionalString(),
157
+ slug: optionalString(),
158
+ number: optionalString(),
159
+ path: optionalString(),
160
+ category: optionalString(),
161
+ repo: optionalString(),
162
+ branch: optionalString(),
163
+ description: optionalString(),
164
+ budget: optionalString(),
165
+ complexity: optionalString(),
166
+ scale: optionalString(),
167
+ remember: optionalBoolean(),
168
+ errorCode: optionalString(),
169
+ });
170
+ export const HELIUS_WRITE_SCHEMA = withTelemetry({
171
+ action: HeliusWriteActionSchema,
172
+ detail: detailField,
173
+ args: argsField,
174
+ recipientAddress: optionalString(),
175
+ mintAddress: optionalString(),
176
+ amount: optionalNumber(),
177
+ sendMax: optionalBoolean(),
178
+ stakeAccount: optionalString(),
179
+ destination: optionalString(),
180
+ owsWallet: optionalString(),
181
+ });
182
+ export const HELIUS_COMPRESSION_SCHEMA = withTelemetry({
183
+ action: HeliusCompressionActionSchema,
184
+ detail: detailField,
185
+ args: argsField,
186
+ address: optionalString(),
187
+ addresses: stringArray(),
188
+ hash: optionalString(),
189
+ hashes: stringArray(),
190
+ owner: optionalString(),
191
+ delegate: optionalString(),
192
+ mint: optionalString(),
193
+ limit: optionalNumber(),
194
+ cursor: optionalString(),
195
+ });
196
+ export const EXPAND_RESULT_SCHEMA = withTelemetry({
197
+ resultId: z.string(),
198
+ section: optionalString(),
199
+ item: optionalNumber(),
200
+ page: optionalNumber(),
201
+ range: optionalString(),
202
+ continuation: optionalString(),
203
+ detail: detailField,
204
+ });
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ export declare const TELEMETRY_FIELDS: {
3
+ readonly _feedback: z.ZodString;
4
+ readonly _feedbackTool: z.ZodString;
5
+ readonly _model: z.ZodString;
6
+ };
7
+ export type TelemetryPayload = {
8
+ _feedback: string;
9
+ _feedbackTool: string;
10
+ _model: string;
11
+ };
12
+ type PublicToolResponse = {
13
+ content: Array<{
14
+ type: 'text';
15
+ text: string;
16
+ }>;
17
+ isError?: boolean;
18
+ _meta?: Record<string, unknown>;
19
+ };
20
+ export declare function withTelemetry<T extends Record<string, z.ZodTypeAny>>(shape: T): T & typeof TELEMETRY_FIELDS;
21
+ export declare function splitTelemetry(params: Record<string, unknown>): {
22
+ telemetry: TelemetryPayload;
23
+ cleanParams: Record<string, unknown>;
24
+ };
25
+ export declare function normalizeTelemetry(toolName: string, params: Record<string, unknown>, telemetry: TelemetryPayload): TelemetryPayload;
26
+ export declare function withTelemetryHandler(toolName: string, handler: (params: Record<string, unknown>, extra: unknown, telemetry: TelemetryPayload) => Promise<PublicToolResponse> | PublicToolResponse): (params: Record<string, unknown>, extra: unknown) => Promise<PublicToolResponse>;
27
+ export {};
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { sendFeedbackEvent } from '../utils/feedback.js';
3
+ const requiredTelemetryString = (description) => z.string().trim().min(1).describe(description);
4
+ export const TELEMETRY_FIELDS = {
5
+ _feedback: requiredTelemetryString('Short reason for this call or takeaway from the previous result, e.g. "initial balance check" or "balance looked healthy, checking history".'),
6
+ _feedbackTool: requiredTelemetryString('Current public tool and action in "tool.action" form, e.g. "heliusWallet.getBalance".'),
7
+ _model: requiredTelemetryString('LLM model identifier, for example claude-opus-4-6 or gpt-4o.'),
8
+ };
9
+ export function withTelemetry(shape) {
10
+ return {
11
+ ...shape,
12
+ ...TELEMETRY_FIELDS,
13
+ };
14
+ }
15
+ export function splitTelemetry(params) {
16
+ const { _feedback, _feedbackTool, _model, ...cleanParams } = params;
17
+ return {
18
+ telemetry: {
19
+ _feedback: String(_feedback ?? '').trim(),
20
+ _feedbackTool: String(_feedbackTool ?? '').trim(),
21
+ _model: String(_model ?? '').trim(),
22
+ },
23
+ cleanParams,
24
+ };
25
+ }
26
+ export function normalizeTelemetry(toolName, params, telemetry) {
27
+ const action = typeof params.action === 'string' ? params.action : undefined;
28
+ const currentTarget = action ? `${toolName}.${action}` : toolName;
29
+ const feedbackTool = telemetry._feedbackTool === 'none' ? currentTarget : telemetry._feedbackTool;
30
+ const feedback = telemetry._feedback === 'first_call'
31
+ ? `initial ${currentTarget}`
32
+ : telemetry._feedback;
33
+ return {
34
+ _feedback: feedback,
35
+ _feedbackTool: feedbackTool,
36
+ _model: telemetry._model,
37
+ };
38
+ }
39
+ export function withTelemetryHandler(toolName, handler) {
40
+ return async (params, extra) => {
41
+ const { telemetry, cleanParams } = splitTelemetry(params);
42
+ const normalizedTelemetry = normalizeTelemetry(toolName, cleanParams, telemetry);
43
+ sendFeedbackEvent({
44
+ type: 'tool_call',
45
+ toolName,
46
+ feedback: normalizedTelemetry._feedback,
47
+ feedbackTool: normalizedTelemetry._feedbackTool,
48
+ model: normalizedTelemetry._model,
49
+ });
50
+ return handler(cleanParams, extra, normalizedTelemetry);
51
+ };
52
+ }
@@ -0,0 +1,46 @@
1
+ import type { ActionName } from './actions.js';
2
+ import type { RoutedPublicToolName } from './action-groups.js';
3
+ export type DetailLevel = 'summary' | 'standard' | 'full';
4
+ export type ResponseFamily = 'scalar' | 'mutationReceipt' | 'record' | 'list' | 'history' | 'document' | 'streamingConfig' | 'catalog';
5
+ export type AuthRequirement = 'none' | 'apiKey' | 'jwt' | 'signer' | 'jwtAndSigner';
6
+ export type Mutability = 'read' | 'write';
7
+ export type ContinuationModel = 'none' | 'transactionHistory' | 'page';
8
+ export type GatePredicate = {
9
+ kind: 'always';
10
+ } | {
11
+ kind: 'network';
12
+ oneOf: Array<'devnet' | 'mainnet-beta'>;
13
+ } | {
14
+ kind: 'paramPresent';
15
+ field: string;
16
+ } | {
17
+ kind: 'paramEquals';
18
+ field: string;
19
+ value: string | number | boolean;
20
+ } | {
21
+ kind: 'and';
22
+ all: GatePredicate[];
23
+ };
24
+ export type CapabilityVariant = {
25
+ id: string;
26
+ minimumPlan: 'agent' | 'developer' | 'business' | 'professional';
27
+ predicate: GatePredicate;
28
+ label: string;
29
+ };
30
+ export type CapabilityGate = {
31
+ baseline: CapabilityVariant;
32
+ variants?: CapabilityVariant[];
33
+ };
34
+ export type ActionCatalogEntry = {
35
+ action: ActionName;
36
+ publicTool: RoutedPublicToolName;
37
+ aliases?: string[];
38
+ authRequirement: AuthRequirement;
39
+ capabilityGate: CapabilityGate;
40
+ mutability: Mutability;
41
+ responseFamily: ResponseFamily;
42
+ defaultDetail: DetailLevel;
43
+ handleEligibility: boolean;
44
+ normalizer: ResponseFamily;
45
+ continuationModel: ContinuationModel;
46
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -3,11 +3,11 @@
3
3
  * Validates the product catalog for correctness.
4
4
  *
5
5
  * Checks:
6
- * 1. Every mcpTools entry exists in KNOWN_TOOLS
6
+ * 1. Every mcpTools entry exists in the action registry
7
7
  * 2. Every referenceFile exists on disk
8
8
  * 3. Every docKey exists in DOCS_INDEX
9
9
  * 4. Every minimumPlan is a valid key in PLAN_RANK and HELIUS_PLANS
10
- * 5. Plan-feature compatibility (Laserstream mainnet → professional, Enhanced WebSockets → business+)
10
+ * 5. Plan-feature compatibility (Laserstream mainnet → business+, Enhanced WebSockets → developer+)
11
11
  * 6. No empty mcpTools arrays
12
12
  */
13
13
  export {};
@@ -3,17 +3,17 @@
3
3
  * Validates the product catalog for correctness.
4
4
  *
5
5
  * Checks:
6
- * 1. Every mcpTools entry exists in KNOWN_TOOLS
6
+ * 1. Every mcpTools entry exists in the action registry
7
7
  * 2. Every referenceFile exists on disk
8
8
  * 3. Every docKey exists in DOCS_INDEX
9
9
  * 4. Every minimumPlan is a valid key in PLAN_RANK and HELIUS_PLANS
10
- * 5. Plan-feature compatibility (Laserstream mainnet → professional, Enhanced WebSockets → business+)
10
+ * 5. Plan-feature compatibility (Laserstream mainnet → business+, Enhanced WebSockets → developer+)
11
11
  * 6. No empty mcpTools arrays
12
12
  */
13
13
  import fs from 'fs';
14
14
  import path from 'path';
15
- import { PRODUCT_CATALOG } from '../tools/product-catalog.js';
16
- import { KNOWN_TOOLS, PLAN_RANK } from '../tools/recommend.js';
15
+ import { PRODUCT_CATALOG, PLAN_RANK } from '../tools/product-catalog.js';
16
+ import { ACTION_NAME_SET } from '../router/actions.js';
17
17
  import { HELIUS_PLANS } from '../tools/plans.js';
18
18
  import { DOCS_INDEX } from '../utils/docs.js';
19
19
  // Resolve skill references relative to repo root
@@ -24,9 +24,9 @@ function error(productKey, msg) {
24
24
  errors.push(`[${productKey}] ${msg}`);
25
25
  }
26
26
  for (const [key, product] of Object.entries(PRODUCT_CATALOG)) {
27
- // 1. MCP tool names exist in KNOWN_TOOLS
27
+ // 1. MCP tool names exist in the canonical action registry
28
28
  for (const tool of product.mcpTools) {
29
- if (!KNOWN_TOOLS.has(tool)) {
29
+ if (!ACTION_NAME_SET.has(tool)) {
30
30
  error(key, `Unknown MCP tool "${tool}"`);
31
31
  }
32
32
  }
@@ -50,11 +50,11 @@ for (const [key, product] of Object.entries(PRODUCT_CATALOG)) {
50
50
  }
51
51
  // 5. Plan-feature compatibility
52
52
  const nameLower = product.name.toLowerCase();
53
- if (nameLower.includes('laserstream') && nameLower.includes('mainnet') && product.minimumPlan !== 'professional') {
54
- error(key, `Laserstream mainnet requires professional plan, but has "${product.minimumPlan}"`);
53
+ if (nameLower.includes('laserstream') && nameLower.includes('mainnet') && (PLAN_RANK[product.minimumPlan] ?? 0) < PLAN_RANK['business']) {
54
+ error(key, `Laserstream mainnet requires business+ plan, but has "${product.minimumPlan}"`);
55
55
  }
56
- if (nameLower.includes('enhanced websocket') && (PLAN_RANK[product.minimumPlan] ?? 0) < PLAN_RANK['business']) {
57
- error(key, `Enhanced WebSockets requires business+ plan, but has "${product.minimumPlan}"`);
56
+ if (nameLower.includes('enhanced websocket') && (PLAN_RANK[product.minimumPlan] ?? 0) < PLAN_RANK['developer']) {
57
+ error(key, `Enhanced WebSockets requires developer+ plan, but has "${product.minimumPlan}"`);
58
58
  }
59
59
  // 6. No empty mcpTools
60
60
  if (product.mcpTools.length === 0) {
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
3
  import { formatAddress, formatSol } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
- import { mcpText, validateEnum, handleToolError, addressError, paginationError } from '../utils/errors.js';
5
+ import { mcpText, validateEnum, handleToolError, addressError, paginationError, missingParamError, exclusiveParamError, batchLimitError } from '../utils/errors.js';
6
6
  function formatParsedAccountData(account) {
7
7
  const lines = [];
8
8
  const parsedData = account.data;
@@ -34,17 +34,17 @@ export function registerAccountTools(server) {
34
34
  return err;
35
35
  // Validate: must provide exactly one of address or addresses
36
36
  if (!address && (!addresses || addresses.length === 0)) {
37
- return mcpText(`**Error:** Provide either \`address\` (single account) or \`addresses\` (batch of up to 100).`);
37
+ return missingParamError('getAccountInfo', 'Provide either `address` (single account) or `addresses` (batch of up to 100).');
38
38
  }
39
39
  if (address && addresses && addresses.length > 0) {
40
- return mcpText(`**Error:** Provide either \`address\` or \`addresses\`, not both.`);
40
+ return exclusiveParamError('getAccountInfo', 'address', 'addresses');
41
41
  }
42
42
  try {
43
43
  const helius = getHeliusClient();
44
44
  // --- Batch mode ---
45
45
  if (addresses && addresses.length > 0) {
46
46
  if (addresses.length > 100) {
47
- return mcpText(`**Error:** Maximum 100 accounts per request. You provided ${addresses.length}.`);
47
+ return batchLimitError('getAccountInfo', 100, addresses.length);
48
48
  }
49
49
  const result = await helius.getMultipleAccounts(addresses, { encoding });
50
50
  const accounts = result?.value || [];
@@ -110,7 +110,7 @@ export function registerAccountTools(server) {
110
110
  return noApiKeyResponse();
111
111
  const helius = getHeliusClient();
112
112
  if (!owner && !mint) {
113
- return mcpText(`**Error:** You must provide at least one of: owner or mint address.`);
113
+ return missingParamError('getTokenAccounts', 'Provide at least one of: `owner` or `mint` address.');
114
114
  }
115
115
  const params = { page, limit };
116
116
  if (owner)
@@ -2,7 +2,7 @@ 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
+ import { mcpText, mcpError, handleToolError, addressError, paginationError, notFoundError, missingParamError, exclusiveParamError, batchLimitError } from '../utils/errors.js';
6
6
  export function registerAssetTools(server) {
7
7
  // Get Assets by Owner (NFTs and tokens via DAS)
8
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).', {
@@ -69,15 +69,15 @@ export function registerAssetTools(server) {
69
69
  const helius = getHeliusClient();
70
70
  // Validate: must provide exactly one of id or ids
71
71
  if (!id && (!ids || ids.length === 0)) {
72
- return mcpText(`**Error:** Provide either "id" (single asset) or "ids" (batch of up to 1000).`);
72
+ return missingParamError('getAsset', 'Provide either `id` (single asset) or `ids` (batch of up to 1000).');
73
73
  }
74
74
  if (id && ids && ids.length > 0) {
75
- return mcpText(`**Error:** Provide either "id" or "ids", not both.`);
75
+ return exclusiveParamError('getAsset', 'id', 'ids');
76
76
  }
77
77
  // --- Batch mode ---
78
78
  if (ids && ids.length > 0) {
79
79
  if (ids.length > 1000) {
80
- return mcpText(`**Error:** Maximum 1000 assets per batch. You provided ${ids.length}.`);
80
+ return batchLimitError('getAsset', 1000, ids.length);
81
81
  }
82
82
  let assets;
83
83
  try {
@@ -254,7 +254,7 @@ export function registerAssetTools(server) {
254
254
  // General search → searchAssets
255
255
  else {
256
256
  if (name && !ownerAddress) {
257
- return mcpText(`**Search Error:** Searching by name requires an owner address. Please also provide \`ownerAddress\` to search for assets named "${name}".`);
257
+ return mcpError(`**Search Error:** Searching by name requires an owner address. Please also provide \`ownerAddress\` to search for assets named "${name}".`, { type: 'VALIDATION', code: 'MISSING_PARAM', retryable: false, recovery: 'Provide `ownerAddress` when searching by name.' });
258
258
  }
259
259
  const params = { page, limit };
260
260
  if (ownerAddress)