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/plans.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { fetchDoc, extractSections } from '../utils/docs.js';
|
|
3
|
-
import { mcpError } from '../utils/errors.js';
|
|
3
|
+
import { mcpText, mcpError } from '../utils/errors.js';
|
|
4
|
+
import { hasApiKey } from '../utils/helius.js';
|
|
4
5
|
import { getJwt } from '../utils/config.js';
|
|
5
6
|
import { listProjects } from 'helius-sdk/auth/listProjects';
|
|
6
7
|
import { getProject } from 'helius-sdk/auth/getProject';
|
|
7
8
|
import { MCP_USER_AGENT } from '../http.js';
|
|
9
|
+
import { PRODUCT_CATALOG, PLAN_RANK } from './product-catalog.js';
|
|
8
10
|
/**
|
|
9
11
|
* Static plan metadata — NOT the source of truth for pricing or billing data.
|
|
10
12
|
*
|
|
@@ -17,8 +19,8 @@ import { MCP_USER_AGENT } from '../http.js';
|
|
|
17
19
|
* from the billing docs via fetchDoc('billing').
|
|
18
20
|
*/
|
|
19
21
|
export const HELIUS_PLANS = {
|
|
20
|
-
|
|
21
|
-
name: '
|
|
22
|
+
agent: {
|
|
23
|
+
name: 'Agent',
|
|
22
24
|
features: { webhooks: true, standardWebSockets: true, enhancedWebSockets: false, laserstream: false, stakedConnections: true, archivalData: true },
|
|
23
25
|
},
|
|
24
26
|
developer: {
|
|
@@ -37,11 +39,30 @@ export const HELIUS_PLANS = {
|
|
|
37
39
|
const BILLING_FETCH_ERROR = 'Could not fetch live billing data. Try:\n' +
|
|
38
40
|
'- `lookupHeliusDocs({ topic: \'billing\' })` for full billing documentation\n' +
|
|
39
41
|
'- Visit https://www.helius.dev/docs/billing directly';
|
|
42
|
+
const BILLING_FETCH_META = {
|
|
43
|
+
type: 'API',
|
|
44
|
+
code: 'FETCH_FAILED',
|
|
45
|
+
retryable: true,
|
|
46
|
+
recovery: 'Try lookupHeliusDocs({ topic: "billing" }) or visit https://www.helius.dev/docs/billing directly',
|
|
47
|
+
};
|
|
40
48
|
/**
|
|
41
49
|
* Detect the user's current Helius plan from their JWT session.
|
|
42
50
|
* Returns the plan key (e.g. "developer") or undefined if unavailable.
|
|
43
51
|
* Best-effort — never throws.
|
|
44
52
|
*/
|
|
53
|
+
/**
|
|
54
|
+
* Normalize backend plan keys (e.g. `agent_v4`, `developer_v4`) to the
|
|
55
|
+
* canonical short keys used in HELIUS_PLANS / PLAN_RANK / PRODUCT_CATALOG.
|
|
56
|
+
* Backend appends `_v4` for current-generation plans; the MCP/CLI surface
|
|
57
|
+
* uses the short form throughout.
|
|
58
|
+
*/
|
|
59
|
+
function normalizePlanKey(raw) {
|
|
60
|
+
if (!raw)
|
|
61
|
+
return undefined;
|
|
62
|
+
const trimmed = raw.trim().toLowerCase();
|
|
63
|
+
// Strip `_v4` (and any future `_v<N>`) suffix.
|
|
64
|
+
return trimmed.replace(/_v\d+$/, '');
|
|
65
|
+
}
|
|
45
66
|
export async function detectCurrentPlan() {
|
|
46
67
|
const jwt = getJwt();
|
|
47
68
|
if (!jwt)
|
|
@@ -50,9 +71,9 @@ export async function detectCurrentPlan() {
|
|
|
50
71
|
const projects = await listProjects(jwt, MCP_USER_AGENT);
|
|
51
72
|
if (projects.length > 0) {
|
|
52
73
|
const details = await getProject(jwt, projects[0].id, MCP_USER_AGENT);
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
return
|
|
74
|
+
const normalized = normalizePlanKey(details.subscriptionPlanDetails?.currentPlan);
|
|
75
|
+
if (normalized && normalized in HELIUS_PLANS)
|
|
76
|
+
return normalized;
|
|
56
77
|
}
|
|
57
78
|
}
|
|
58
79
|
catch {
|
|
@@ -62,19 +83,19 @@ export async function detectCurrentPlan() {
|
|
|
62
83
|
}
|
|
63
84
|
export function registerPlanTools(server) {
|
|
64
85
|
server.tool('getHeliusPlanInfo', 'BEST FOR: pricing and plan questions. PREFER compareHeliusPlans for side-by-side category comparisons, getRateLimitInfo for per-method credit costs. Get Helius plan pricing, credits, rate limits, and feature availability. Fetches live from billing docs.', {
|
|
65
|
-
plan: z.enum(['
|
|
86
|
+
plan: z.enum(['agent', 'developer', 'business', 'professional', 'all']).optional().default('all').describe('Specific plan to show details for, or "all" for comparison. Note: the MCP/CLI signup flow uses Agent ($10 one-time) as the entry tier; the dashboard\'s Free tier is not available through this path.'),
|
|
66
87
|
}, async ({ plan }) => {
|
|
67
88
|
let billingDoc;
|
|
68
89
|
try {
|
|
69
90
|
billingDoc = await fetchDoc('billing');
|
|
70
91
|
}
|
|
71
92
|
catch {
|
|
72
|
-
return mcpError(BILLING_FETCH_ERROR);
|
|
93
|
+
return mcpError(BILLING_FETCH_ERROR, BILLING_FETCH_META);
|
|
73
94
|
}
|
|
74
95
|
// Required: plans table
|
|
75
96
|
const plansTable = extractSections(billingDoc, ['standard plans', 'standard pricing'], { includeLooseMatches: false });
|
|
76
97
|
if (!plansTable) {
|
|
77
|
-
return mcpError(BILLING_FETCH_ERROR);
|
|
98
|
+
return mcpError(BILLING_FETCH_ERROR, BILLING_FETCH_META);
|
|
78
99
|
}
|
|
79
100
|
// Optional sections
|
|
80
101
|
const credits = extractSections(billingDoc, ['credits system', 'credit costs'], { includeLooseMatches: false });
|
|
@@ -98,7 +119,7 @@ export function registerPlanTools(server) {
|
|
|
98
119
|
sections.push('', addOns);
|
|
99
120
|
if (rateLimits)
|
|
100
121
|
sections.push('', rateLimits);
|
|
101
|
-
sections.push('', '---', '', '## Actions', '', '- To upgrade, use the `upgradePlan` tool with a plan name (developer, business, professional)', '- To preview pricing before upgrading, use the `previewUpgrade` tool', '', 'Source: https://www.helius.dev/docs/billing (fetched live)');
|
|
122
|
+
sections.push('', '---', '', '## Actions', '', '- To sign up for the Agent plan ($10 one-time, 1M credits), use the `signup` tool', '- To upgrade an existing account, use the `upgradePlan` tool with a plan name (developer, business, professional)', '- To preview pricing before upgrading, use the `previewUpgrade` tool', '', 'Source: https://www.helius.dev/docs/billing (fetched live)');
|
|
102
123
|
return { content: [{ type: 'text', text: sections.join('\n') }] };
|
|
103
124
|
});
|
|
104
125
|
server.tool('compareHeliusPlans', 'BEST FOR: side-by-side plan comparison in a specific category. Compare Helius plans for rate limits, features, or pricing. Fetches live from billing docs.', {
|
|
@@ -109,7 +130,7 @@ export function registerPlanTools(server) {
|
|
|
109
130
|
billingDoc = await fetchDoc('billing');
|
|
110
131
|
}
|
|
111
132
|
catch {
|
|
112
|
-
return mcpError(BILLING_FETCH_ERROR);
|
|
133
|
+
return mcpError(BILLING_FETCH_ERROR, BILLING_FETCH_META);
|
|
113
134
|
}
|
|
114
135
|
const sectionMap = {
|
|
115
136
|
rates: ['rate limits', 'standard rate limits', 'special rate limits'],
|
|
@@ -118,7 +139,7 @@ export function registerPlanTools(server) {
|
|
|
118
139
|
};
|
|
119
140
|
const extracted = extractSections(billingDoc, sectionMap[category], { includeLooseMatches: false });
|
|
120
141
|
if (!extracted) {
|
|
121
|
-
return mcpError(BILLING_FETCH_ERROR);
|
|
142
|
+
return mcpError(BILLING_FETCH_ERROR, BILLING_FETCH_META);
|
|
122
143
|
}
|
|
123
144
|
const lines = [];
|
|
124
145
|
const currentPlanKey = await detectCurrentPlan();
|
|
@@ -130,4 +151,138 @@ export function registerPlanTools(server) {
|
|
|
130
151
|
lines.push('', '---', 'Source: https://www.helius.dev/docs/billing (fetched live)');
|
|
131
152
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
132
153
|
});
|
|
154
|
+
// ── getAccountPlan — lightweight pre-flight check ──
|
|
155
|
+
server.tool('getAccountPlan', 'Lightweight pre-flight check: returns current plan, credit balance, and which MCP tools require an upgrade. 0 credits. Call before gated tools (transactionSubscribe, laserstreamSubscribe, etc.).', {}, async () => {
|
|
156
|
+
// ── Tier 1: not authenticated at all ──
|
|
157
|
+
if (!hasApiKey()) {
|
|
158
|
+
return mcpText(`## Account Plan\n\n` +
|
|
159
|
+
`**Auth:** Not authenticated\n\n` +
|
|
160
|
+
`No API key or session found. To get started:\n` +
|
|
161
|
+
`- If you have a key: use the \`setHeliusApiKey\` tool\n` +
|
|
162
|
+
`- If you need an account: use \`generateKeypair\` → \`signup\` (link mode prints a payment URL)`);
|
|
163
|
+
}
|
|
164
|
+
// ── Tier 2: API key present but no JWT ──
|
|
165
|
+
const jwt = getJwt();
|
|
166
|
+
if (!jwt) {
|
|
167
|
+
return mcpText(`## Account Plan\n\n` +
|
|
168
|
+
`**Auth:** API key configured, plan unknown\n\n` +
|
|
169
|
+
`To see your plan and tool eligibility, call \`signup\`.\n` +
|
|
170
|
+
`Your existing account will be detected automatically — no payment needed.`);
|
|
171
|
+
}
|
|
172
|
+
// ── Tier 3: full status via JWT ──
|
|
173
|
+
try {
|
|
174
|
+
const projects = await listProjects(jwt, MCP_USER_AGENT);
|
|
175
|
+
if (projects.length === 0) {
|
|
176
|
+
return mcpError('No projects found. Call `signup` to create an account first.');
|
|
177
|
+
}
|
|
178
|
+
const projectId = projects[0].id;
|
|
179
|
+
const details = await getProject(jwt, projectId, MCP_USER_AGENT);
|
|
180
|
+
const planKey = normalizePlanKey(details.subscriptionPlanDetails?.currentPlan) ?? 'unknown';
|
|
181
|
+
const planInfo = HELIUS_PLANS[planKey];
|
|
182
|
+
if (!planInfo) {
|
|
183
|
+
console.warn(`[getAccountPlan] Unrecognized plan "${planKey}" — tool eligibility may be inaccurate`);
|
|
184
|
+
}
|
|
185
|
+
const planName = planInfo?.name ?? planKey;
|
|
186
|
+
// Credit info
|
|
187
|
+
const totalCredits = details.subscriptionPlanDetails?.totalCredits ?? 0;
|
|
188
|
+
const usedCredits = details.subscriptionPlanDetails?.usedCredits ?? 0;
|
|
189
|
+
const remainingCredits = totalCredits - usedCredits;
|
|
190
|
+
const usedPct = totalCredits > 0 ? ((usedCredits / totalCredits) * 100).toFixed(1) : '0.0';
|
|
191
|
+
// Tool eligibility
|
|
192
|
+
const eligibility = computeToolEligibility(planKey);
|
|
193
|
+
const lines = [
|
|
194
|
+
`## Account Plan`,
|
|
195
|
+
``,
|
|
196
|
+
`**Plan:** ${planName}`,
|
|
197
|
+
``,
|
|
198
|
+
`### Credits`,
|
|
199
|
+
`- **Remaining:** ${remainingCredits.toLocaleString()} / ${totalCredits.toLocaleString()} (${usedPct}% used)`,
|
|
200
|
+
``,
|
|
201
|
+
`### Gated Tool Eligibility`,
|
|
202
|
+
`All Agent-tier tools are available on every plan.`,
|
|
203
|
+
``,
|
|
204
|
+
];
|
|
205
|
+
if (eligibility.length > 0) {
|
|
206
|
+
lines.push(`| Tool | Status | Requires |`, `|------|--------|----------|`);
|
|
207
|
+
for (const e of eligibility) {
|
|
208
|
+
lines.push(`| ${e.tool} | ${e.status} | ${e.requires} |`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push(`All tools are available on your plan.`);
|
|
213
|
+
}
|
|
214
|
+
// Next steps — find tools that need upgrade
|
|
215
|
+
const upgradeNeeded = eligibility.filter(e => e.status.includes('UPGRADE REQUIRED'));
|
|
216
|
+
if (upgradeNeeded.length > 0) {
|
|
217
|
+
lines.push(``, `### Next Steps`);
|
|
218
|
+
const toolNames = upgradeNeeded.map(e => e.tool);
|
|
219
|
+
const uniqueRequires = [...new Set(upgradeNeeded.map(e => e.requires))];
|
|
220
|
+
lines.push(`To unlock ${toolNames.join(', ')}, upgrade to ${uniqueRequires.join(' or ')}.`);
|
|
221
|
+
lines.push(`→ Use \`previewUpgrade\` to see pricing`);
|
|
222
|
+
}
|
|
223
|
+
return mcpText(lines.join('\n'));
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
return mcpError(`Failed to fetch account plan: ${err instanceof Error ? err.message : String(err)}`);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function computeToolEligibility(planKey) {
|
|
231
|
+
const userRank = PLAN_RANK[planKey] ?? 0;
|
|
232
|
+
// Collect all non-free product entries per tool
|
|
233
|
+
const toolProducts = {};
|
|
234
|
+
for (const product of Object.values(PRODUCT_CATALOG)) {
|
|
235
|
+
if (product.minimumPlan === 'agent')
|
|
236
|
+
continue;
|
|
237
|
+
for (const tool of product.mcpTools) {
|
|
238
|
+
if (!toolProducts[tool])
|
|
239
|
+
toolProducts[tool] = [];
|
|
240
|
+
toolProducts[tool].push({ productName: product.name, minimumPlan: product.minimumPlan });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const results = [];
|
|
244
|
+
for (const [tool, products] of Object.entries(toolProducts)) {
|
|
245
|
+
// Also check if this tool appears in any baseline product (Agent tier)
|
|
246
|
+
const inBaselineToo = Object.values(PRODUCT_CATALOG).some(p => p.minimumPlan === 'agent' && p.mcpTools.includes(tool));
|
|
247
|
+
if (products.length === 1) {
|
|
248
|
+
const p = products[0];
|
|
249
|
+
const available = userRank >= (PLAN_RANK[p.minimumPlan] ?? 99);
|
|
250
|
+
const planDisplay = HELIUS_PLANS[p.minimumPlan]?.name ?? p.minimumPlan;
|
|
251
|
+
if (inBaselineToo && available) {
|
|
252
|
+
// Tool is available at baseline tier AND at this gated tier — show available
|
|
253
|
+
results.push({ tool, status: 'AVAILABLE', requires: planDisplay });
|
|
254
|
+
}
|
|
255
|
+
else if (inBaselineToo && !available) {
|
|
256
|
+
// Tool works at baseline tier but the enhanced version needs upgrade
|
|
257
|
+
results.push({ tool, status: `AVAILABLE (basic) / UPGRADE REQUIRED (${p.productName})`, requires: planDisplay });
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
results.push({ tool, status: available ? 'AVAILABLE' : 'UPGRADE REQUIRED', requires: planDisplay });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Multiple gated products (e.g. laserstreamSubscribe in devnet + mainnet)
|
|
265
|
+
const statuses = [];
|
|
266
|
+
const requires = [];
|
|
267
|
+
for (const p of products) {
|
|
268
|
+
const available = userRank >= (PLAN_RANK[p.minimumPlan] ?? 99);
|
|
269
|
+
const planDisplay = HELIUS_PLANS[p.minimumPlan]?.name ?? p.minimumPlan;
|
|
270
|
+
const label = p.productName.includes('(') ? p.productName.replace(/.*\(/, '').replace(/\).*/, '').toLowerCase() : p.productName;
|
|
271
|
+
statuses.push(available ? `AVAILABLE (${label})` : `UPGRADE REQUIRED (${label})`);
|
|
272
|
+
requires.push(planDisplay);
|
|
273
|
+
}
|
|
274
|
+
const allAvailable = statuses.every(s => s.startsWith('AVAILABLE'));
|
|
275
|
+
const allUnavailable = statuses.every(s => s.startsWith('UPGRADE'));
|
|
276
|
+
if (allAvailable) {
|
|
277
|
+
results.push({ tool, status: 'AVAILABLE', requires: requires.join(' / ') });
|
|
278
|
+
}
|
|
279
|
+
else if (allUnavailable) {
|
|
280
|
+
results.push({ tool, status: 'UPGRADE REQUIRED', requires: requires[requires.length - 1] });
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
results.push({ tool, status: statuses.join(' / '), requires: requires.join(' / ') });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return results;
|
|
133
288
|
}
|
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
// Centralized product definitions — each Helius product defined once with its
|
|
4
4
|
// invariant properties. The recommend tool groups products by minimumPlan into
|
|
5
5
|
// tiers and uses the description field to give AI consumers rich context.
|
|
6
|
+
// ─── Plan Ranking ───
|
|
7
|
+
// Pure data constant — lives here (zero imports) to avoid circular dependencies.
|
|
8
|
+
// Plan tiers, lowest to highest. The MCP/CLI signup flow uses Agent as the
|
|
9
|
+
// entry tier (the dashboard's Free tier is not reachable through this surface).
|
|
10
|
+
export const PLAN_RANK = { agent: 0, developer: 1, business: 2, professional: 3 };
|
|
6
11
|
export const PRODUCT_CATALOG = {
|
|
7
12
|
'das-api': {
|
|
8
13
|
name: 'DAS API',
|
|
9
14
|
mcpTools: ['getAssetsByOwner', 'getAssetsByGroup', 'searchAssets', 'getAsset', 'getTokenBalances', 'getTokenAccounts'],
|
|
10
15
|
creditCostPerCall: '10 credits',
|
|
11
|
-
minimumPlan: '
|
|
16
|
+
minimumPlan: 'agent',
|
|
12
17
|
docKey: 'das',
|
|
13
18
|
referenceFile: 'references/das.md',
|
|
14
19
|
description: 'Query tokens, NFTs, collections, and digital assets. Fetches wallet token holdings with names, symbols, and prices. Browse NFT collections, search assets by creator/authority/owner.',
|
|
@@ -17,7 +22,7 @@ export const PRODUCT_CATALOG = {
|
|
|
17
22
|
name: 'Standard RPC',
|
|
18
23
|
mcpTools: ['getBalance', 'getAccountInfo', 'getBlock', 'getNetworkStatus'],
|
|
19
24
|
creditCostPerCall: '1-10 credits',
|
|
20
|
-
minimumPlan: '
|
|
25
|
+
minimumPlan: 'agent',
|
|
21
26
|
docKey: 'rpc',
|
|
22
27
|
description: 'Core Solana RPC for native SOL balances, account data, block info, and network status. Foundation for any Solana app.',
|
|
23
28
|
},
|
|
@@ -25,7 +30,7 @@ export const PRODUCT_CATALOG = {
|
|
|
25
30
|
name: 'Enhanced Transactions API',
|
|
26
31
|
mcpTools: ['parseTransactions', 'getTransactionHistory'],
|
|
27
32
|
creditCostPerCall: '100 credits',
|
|
28
|
-
minimumPlan: '
|
|
33
|
+
minimumPlan: 'agent',
|
|
29
34
|
docKey: 'enhanced-transactions',
|
|
30
35
|
referenceFile: 'references/enhanced-transactions.md',
|
|
31
36
|
description: 'Parse any transaction into human-readable format with types (SWAP, TRANSFER, NFT_SALE), descriptions, token transfers, and fees. Powers transaction history views and trade verification.',
|
|
@@ -41,9 +46,9 @@ export const PRODUCT_CATALOG = {
|
|
|
41
46
|
},
|
|
42
47
|
'webhooks': {
|
|
43
48
|
name: 'Webhooks',
|
|
44
|
-
mcpTools: ['createWebhook', 'getAllWebhooks', 'updateWebhook', 'deleteWebhook'],
|
|
49
|
+
mcpTools: ['createWebhook', 'getAllWebhooks', 'getWebhookByID', 'updateWebhook', 'deleteWebhook'],
|
|
45
50
|
creditCostPerCall: '100 credits to create, 1 credit per event',
|
|
46
|
-
minimumPlan: '
|
|
51
|
+
minimumPlan: 'agent',
|
|
47
52
|
docKey: 'webhooks',
|
|
48
53
|
referenceFile: 'references/webhooks.md',
|
|
49
54
|
description: 'HTTP POST notifications when monitored addresses have on-chain activity. Event-driven architecture without polling. Monitor up to 100k addresses per webhook. Filter by 150+ transaction types (SWAP, NFT_SALE, TRANSFER, etc.).',
|
|
@@ -51,8 +56,8 @@ export const PRODUCT_CATALOG = {
|
|
|
51
56
|
'enhanced-websockets': {
|
|
52
57
|
name: 'Enhanced WebSockets',
|
|
53
58
|
mcpTools: ['transactionSubscribe', 'accountSubscribe', 'getEnhancedWebSocketInfo'],
|
|
54
|
-
creditCostPerCall: '
|
|
55
|
-
minimumPlan: '
|
|
59
|
+
creditCostPerCall: '2 credits per 0.1 MB streamed',
|
|
60
|
+
minimumPlan: 'developer',
|
|
56
61
|
docKey: 'enhanced-websockets',
|
|
57
62
|
referenceFile: 'references/websockets.md',
|
|
58
63
|
description: 'Real-time transaction and account streaming over persistent WebSocket connections. 1.5-2x faster than standard WebSockets with advanced filtering (up to 50k addresses per filter). Powers live dashboards and real-time UIs.',
|
|
@@ -61,7 +66,7 @@ export const PRODUCT_CATALOG = {
|
|
|
61
66
|
name: 'Helius Sender',
|
|
62
67
|
mcpTools: ['getSenderInfo'],
|
|
63
68
|
creditCostPerCall: '0 credits',
|
|
64
|
-
minimumPlan: '
|
|
69
|
+
minimumPlan: 'agent',
|
|
65
70
|
docKey: 'sender',
|
|
66
71
|
referenceFile: 'references/sender.md',
|
|
67
72
|
description: 'Submit transactions with SWQoS routing, staked connections, and Jito tip integration for optimal landing rates. Essential for trading bots, token launches, and any app that sends transactions.',
|
|
@@ -70,7 +75,7 @@ export const PRODUCT_CATALOG = {
|
|
|
70
75
|
name: 'Priority Fee API',
|
|
71
76
|
mcpTools: ['getPriorityFeeEstimate'],
|
|
72
77
|
creditCostPerCall: '1 credit',
|
|
73
|
-
minimumPlan: '
|
|
78
|
+
minimumPlan: 'agent',
|
|
74
79
|
docKey: 'priority-fee',
|
|
75
80
|
referenceFile: 'references/priority-fees.md',
|
|
76
81
|
description: 'Real-time fee estimates for transaction prioritization during network congestion. Returns recommended fees at multiple priority levels (Min, Low, Medium, High, VeryHigh).',
|
|
@@ -78,8 +83,8 @@ export const PRODUCT_CATALOG = {
|
|
|
78
83
|
'standard-websockets': {
|
|
79
84
|
name: 'Standard WebSockets',
|
|
80
85
|
mcpTools: ['transactionSubscribe'],
|
|
81
|
-
creditCostPerCall: '
|
|
82
|
-
minimumPlan: '
|
|
86
|
+
creditCostPerCall: '2 credits per 0.1 MB streamed',
|
|
87
|
+
minimumPlan: 'agent',
|
|
83
88
|
docKey: 'websocket',
|
|
84
89
|
referenceFile: 'references/websockets.md',
|
|
85
90
|
description: 'Persistent connection for real-time transaction and account updates using standard Solana WebSocket subscriptions. Good for confirmation tracking and basic streaming.',
|
|
@@ -87,7 +92,7 @@ export const PRODUCT_CATALOG = {
|
|
|
87
92
|
'laserstream-devnet': {
|
|
88
93
|
name: 'Laserstream (Devnet)',
|
|
89
94
|
mcpTools: ['laserstreamSubscribe', 'getLaserstreamInfo'],
|
|
90
|
-
creditCostPerCall: '
|
|
95
|
+
creditCostPerCall: '2 credits per 0.1 MB streamed',
|
|
91
96
|
minimumPlan: 'developer',
|
|
92
97
|
docKey: 'laserstream',
|
|
93
98
|
referenceFile: 'references/laserstream.md',
|
|
@@ -96,8 +101,8 @@ export const PRODUCT_CATALOG = {
|
|
|
96
101
|
'laserstream-mainnet': {
|
|
97
102
|
name: 'Laserstream (Mainnet)',
|
|
98
103
|
mcpTools: ['laserstreamSubscribe', 'getLaserstreamInfo'],
|
|
99
|
-
creditCostPerCall: '
|
|
100
|
-
minimumPlan: '
|
|
104
|
+
creditCostPerCall: '2 credits per 0.1 MB streamed',
|
|
105
|
+
minimumPlan: 'business',
|
|
101
106
|
docKey: 'laserstream',
|
|
102
107
|
referenceFile: 'references/laserstream.md',
|
|
103
108
|
description: 'Lowest-latency gRPC streaming with 24h historical replay for production indexers, trading infrastructure, and data pipelines. Subscribe to all transactions, accounts, blocks, or entries on mainnet.',
|
|
@@ -106,16 +111,46 @@ export const PRODUCT_CATALOG = {
|
|
|
106
111
|
name: 'Token Transfers',
|
|
107
112
|
mcpTools: ['transferSol', 'transferToken'],
|
|
108
113
|
creditCostPerCall: '~3-13 credits + on-chain fees',
|
|
109
|
-
minimumPlan: '
|
|
114
|
+
minimumPlan: 'agent',
|
|
110
115
|
docKey: 'sender',
|
|
111
116
|
referenceFile: 'references/sender.md',
|
|
112
117
|
description: 'Send native SOL or SPL tokens from the MCP keypair to any Solana address. Uses Helius Sender for optimal landing rates. Requires a configured keypair.',
|
|
113
118
|
},
|
|
119
|
+
'zk-compression': {
|
|
120
|
+
name: 'ZK Compression',
|
|
121
|
+
mcpTools: [
|
|
122
|
+
'getCompressedAccount', 'getCompressedAccountsByOwner', 'getMultipleCompressedAccounts',
|
|
123
|
+
'getCompressedBalance', 'getCompressedBalanceByOwner',
|
|
124
|
+
'getCompressedMintTokenHolders', 'getCompressedTokenAccountBalance',
|
|
125
|
+
'getCompressedTokenAccountsByOwner', 'getCompressedTokenAccountsByDelegate',
|
|
126
|
+
'getCompressedTokenBalancesByOwnerV2',
|
|
127
|
+
'getCompressedAccountProof', 'getMultipleCompressedAccountProofs', 'getMultipleNewAddressProofs',
|
|
128
|
+
'getCompressionSignaturesForAccount', 'getCompressionSignaturesForAddress',
|
|
129
|
+
'getCompressionSignaturesForOwner', 'getCompressionSignaturesForTokenOwner',
|
|
130
|
+
'getLatestCompressionSignatures', 'getLatestNonVotingSignatures',
|
|
131
|
+
'getTransactionWithCompressionInfo', 'getValidityProof',
|
|
132
|
+
'getIndexerHealth', 'getIndexerSlot',
|
|
133
|
+
],
|
|
134
|
+
creditCostPerCall: '10 credits',
|
|
135
|
+
minimumPlan: 'agent',
|
|
136
|
+
docKey: 'zk-compression',
|
|
137
|
+
referenceFile: 'references/zk-compression.md',
|
|
138
|
+
description: 'Query compressed accounts, token balances, Merkle proofs, validity proofs, and compression transaction history via the ZK Compression / Light Protocol indexer. Powers state compression for cost-efficient on-chain data storage.',
|
|
139
|
+
},
|
|
140
|
+
'native-staking': {
|
|
141
|
+
name: 'Native Staking',
|
|
142
|
+
mcpTools: ['stakeSOL', 'unstakeSOL', 'withdrawStake', 'getStakeAccounts', 'getWithdrawableAmount'],
|
|
143
|
+
creditCostPerCall: '~3-10 credits + on-chain fees',
|
|
144
|
+
minimumPlan: 'agent',
|
|
145
|
+
docKey: 'sender',
|
|
146
|
+
referenceFile: 'references/sender.md',
|
|
147
|
+
description: 'Stake, unstake, and withdraw native SOL via the Helius validator. Earn staking yield with one-click delegation. Query stake accounts and withdrawable amounts.',
|
|
148
|
+
},
|
|
114
149
|
'token-holders': {
|
|
115
150
|
name: 'Token Holders',
|
|
116
151
|
mcpTools: ['getTokenHolders'],
|
|
117
152
|
creditCostPerCall: '20 credits',
|
|
118
|
-
minimumPlan: '
|
|
153
|
+
minimumPlan: 'agent',
|
|
119
154
|
docKey: 'das',
|
|
120
155
|
referenceFile: 'references/das.md',
|
|
121
156
|
description: 'Top 20 holders of any SPL token. Check token distribution, find whale wallets, verify decentralization. Useful for token launches and analytics.',
|
package/dist/tools/recommend.js
CHANGED
|
@@ -3,31 +3,12 @@ import { mcpText } from '../utils/errors.js';
|
|
|
3
3
|
import { hasApiKey } from '../utils/helius.js';
|
|
4
4
|
import { getPreferences, savePreferences } from '../utils/config.js';
|
|
5
5
|
import { HELIUS_PLANS, detectCurrentPlan } from './plans.js';
|
|
6
|
-
import { PRODUCT_CATALOG } from './product-catalog.js';
|
|
6
|
+
import { PRODUCT_CATALOG, PLAN_RANK } from './product-catalog.js';
|
|
7
|
+
import { ACTION_NAME_SET } from '../router/actions.js';
|
|
7
8
|
// fetchDoc/extractSections no longer needed — live billing fetch removed
|
|
8
9
|
// ─── Known MCP Tools (for validation script) ───
|
|
9
|
-
export const KNOWN_TOOLS =
|
|
10
|
-
|
|
11
|
-
'agenticSignup', 'getAccountStatus', 'previewUpgrade', 'upgradePlan', 'payRenewal',
|
|
12
|
-
'getBalance', 'getTokenBalances', 'getWalletBalances',
|
|
13
|
-
'parseTransactions', 'getTransactionHistory', 'getWalletHistory', 'getWalletTransfers',
|
|
14
|
-
'getAsset', 'getAssetsByOwner', 'searchAssets', 'getAssetsByGroup',
|
|
15
|
-
'getAssetProof', 'getAssetProofBatch', 'getSignaturesForAsset', 'getNftEditions',
|
|
16
|
-
'getAccountInfo', 'getTokenAccounts', 'getProgramAccounts', 'getTokenHolders',
|
|
17
|
-
'getBlock', 'getNetworkStatus',
|
|
18
|
-
'getPriorityFeeEstimate',
|
|
19
|
-
'createWebhook', 'getAllWebhooks', 'getWebhookByID', 'updateWebhook', 'deleteWebhook',
|
|
20
|
-
'transactionSubscribe', 'accountSubscribe', 'getEnhancedWebSocketInfo',
|
|
21
|
-
'laserstreamSubscribe', 'getLaserstreamInfo',
|
|
22
|
-
'getWalletIdentity', 'batchWalletIdentity', 'getWalletFundedBy',
|
|
23
|
-
'getHeliusPlanInfo', 'compareHeliusPlans',
|
|
24
|
-
'lookupHeliusDocs', 'listHeliusDocTopics', 'getHeliusCreditsInfo', 'getRateLimitInfo',
|
|
25
|
-
'troubleshootError', 'getSenderInfo', 'getWebhookGuide', 'getLatencyComparison', 'getPumpFunGuide',
|
|
26
|
-
'recommendStack',
|
|
27
|
-
'transferSol', 'transferToken',
|
|
28
|
-
]);
|
|
29
|
-
// ─── Plan Ranking ───
|
|
30
|
-
export const PLAN_RANK = { free: 0, developer: 1, business: 2, professional: 3 };
|
|
10
|
+
export const KNOWN_TOOLS = ACTION_NAME_SET;
|
|
11
|
+
// ─── Plan Ranking (re-exported from product-catalog.ts) ───
|
|
31
12
|
function planAtOrBelow(plan, maxPlan) {
|
|
32
13
|
return (PLAN_RANK[plan] ?? 99) <= (PLAN_RANK[maxPlan] ?? 99);
|
|
33
14
|
}
|
|
@@ -57,8 +38,8 @@ const TIER_MAP = {
|
|
|
57
38
|
business: 'production',
|
|
58
39
|
professional: 'production',
|
|
59
40
|
};
|
|
60
|
-
const TIER_DISPLAY = { budget: '
|
|
61
|
-
const TIER_PLANS = { budget: '
|
|
41
|
+
const TIER_DISPLAY = { budget: 'Agent', standard: 'Developer', production: 'Business / Professional' };
|
|
42
|
+
const TIER_PLANS = { budget: 'agent', standard: 'developer', production: 'business' };
|
|
62
43
|
function groupCatalogByTier() {
|
|
63
44
|
const groups = { budget: [], standard: [], production: [] };
|
|
64
45
|
for (const product of Object.values(PRODUCT_CATALOG)) {
|
|
@@ -67,7 +48,7 @@ function groupCatalogByTier() {
|
|
|
67
48
|
}
|
|
68
49
|
const tiers = [];
|
|
69
50
|
if (groups.budget.length > 0)
|
|
70
|
-
tiers.push({ tier: 'budget', tierPlan: '
|
|
51
|
+
tiers.push({ tier: 'budget', tierPlan: 'agent', products: groups.budget });
|
|
71
52
|
if (groups.standard.length > 0)
|
|
72
53
|
tiers.push({ tier: 'standard', tierPlan: 'developer', products: groups.standard });
|
|
73
54
|
if (groups.production.length > 0)
|
|
@@ -133,7 +114,7 @@ function formatCatalog(availableTiers, upgradeTiers, description, complexity, de
|
|
|
133
114
|
}
|
|
134
115
|
lines.push('---', '');
|
|
135
116
|
if (complexity) {
|
|
136
|
-
const label = { low: '
|
|
117
|
+
const label = { low: 'agent tier only', medium: 'agent + developer tiers', high: 'all tiers' };
|
|
137
118
|
lines.push(`_Showing ${label[complexity]}_`, '', '---', '');
|
|
138
119
|
}
|
|
139
120
|
for (let i = 0; i < availableTiers.length; i++) {
|
|
@@ -170,7 +151,7 @@ const SCALE_TIERS = {
|
|
|
170
151
|
export function registerRecommendTools(server) {
|
|
171
152
|
server.tool('recommendStack', 'BEST FOR: project architecture when user describes a Solana app to build. PREFER getHeliusPlanInfo for pricing-only, lookupHeliusDocs for API docs. Get tiered architecture recommendations. Returns Helius products, MCP tools, credit costs, minimum plan, and reference files.', {
|
|
172
153
|
description: z.string().describe('What the user wants to build, in their own words'),
|
|
173
|
-
budget: z.enum(['
|
|
154
|
+
budget: z.enum(['agent', 'developer', 'business', 'professional']).optional(),
|
|
174
155
|
complexity: z.enum(['low', 'medium', 'high']).optional(),
|
|
175
156
|
scale: z.enum(['budget', 'standard', 'production', 'all']).optional().default('all'),
|
|
176
157
|
remember: z.boolean().optional().describe('Save budget/complexity preferences for future sessions'),
|
package/dist/tools/shared.d.ts
CHANGED
package/dist/tools/shared.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
1
|
|
12
|
-
|
|
1
|
+
const NO_API_KEY_META = {
|
|
2
|
+
type: 'AUTH',
|
|
3
|
+
code: 'NO_API_KEY',
|
|
4
|
+
retryable: false,
|
|
5
|
+
recovery: 'Call generateKeypair + signup, or setHeliusApiKey',
|
|
6
|
+
};
|
|
7
|
+
const NO_API_KEY_MESSAGE = `**Helius API Key Required**
|
|
8
|
+
|
|
9
|
+
I need a Helius API key to query the Solana blockchain.
|
|
10
|
+
|
|
11
|
+
**Option 1 — Sign up here (recommended):**
|
|
12
|
+
1. Call \`generateKeypair\` to create a signup wallet
|
|
13
|
+
2. Fund the wallet with ~0.001 SOL + 1 USDC
|
|
14
|
+
3. Call \`signup\` to create your account — the API key will be configured automatically
|
|
15
|
+
|
|
16
|
+
**Option 2 — Use an existing key:**
|
|
17
|
+
1. Go to https://dashboard.helius.dev/api-keys
|
|
18
|
+
2. Copy your API key
|
|
13
19
|
3. Call \`setHeliusApiKey\` with your key`;
|
|
14
20
|
export function noApiKeyResponse() {
|
|
21
|
+
const body = '```json\n' + JSON.stringify(NO_API_KEY_META) + '\n```\n\n' + NO_API_KEY_MESSAGE;
|
|
15
22
|
return {
|
|
16
|
-
content: [{ type: 'text', text:
|
|
23
|
+
content: [{ type: 'text', text: body }],
|
|
24
|
+
isError: true,
|
|
17
25
|
};
|
|
18
26
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { MCP_USER_AGENT } from '../http.js';
|
|
3
|
-
import { mcpText, handleToolError } from '../utils/errors.js';
|
|
3
|
+
import { mcpText, mcpError, handleToolError } from '../utils/errors.js';
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Cache
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -271,7 +271,7 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
271
271
|
})
|
|
272
272
|
.map((e) => ` SIMD-${e.number}: ${e.slug}`)
|
|
273
273
|
.join('\n');
|
|
274
|
-
return
|
|
274
|
+
return mcpError(`SIMD-${paddedNumber} not found.\n\n${nearby ? `Nearby proposals:\n${nearby}` : `Total proposals available: ${index.length}`}`, { type: 'NOT_FOUND', code: 'RESOURCE_NOT_FOUND', retryable: false, recovery: 'Check the SIMD number. Use listSIMDs to see available proposals.' });
|
|
275
275
|
}
|
|
276
276
|
const url = `${SIMD_RAW_BASE}/${entry.filename}`;
|
|
277
277
|
const content = await cachedFetch(url);
|
|
@@ -331,7 +331,7 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
331
331
|
}, async ({ path, repo, branch }) => {
|
|
332
332
|
try {
|
|
333
333
|
if (path.includes('..')) {
|
|
334
|
-
return
|
|
334
|
+
return mcpError('Invalid path: must not contain ".." segments.', { type: 'VALIDATION', code: 'INVALID_PARAM', retryable: false, recovery: 'Remove ".." segments from the path.' });
|
|
335
335
|
}
|
|
336
336
|
const repoMap = {
|
|
337
337
|
agave: { fullName: 'anza-xyz/agave', defaultBranch: 'master' },
|
|
@@ -366,7 +366,7 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
366
366
|
return handleToolError(error, 'Error reading source file', [
|
|
367
367
|
{
|
|
368
368
|
match: (msg) => msg.includes('404'),
|
|
369
|
-
respond: () =>
|
|
369
|
+
respond: () => mcpError(`**File not found:** \`${path}\`\n\nTips:\n- Check the path is correct\n- Browse https://github.com/${repoInfo} to find the right path\n- The default branch for ${repo} is "${defaultBr}"`, { type: 'NOT_FOUND', code: 'HTTP_404', retryable: false, recovery: `Check the file path. Browse https://github.com/${repoInfo} to find the right path.` }),
|
|
370
370
|
},
|
|
371
371
|
]);
|
|
372
372
|
}
|
|
@@ -476,7 +476,8 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
476
476
|
server.tool('fetchHeliusBlog', 'Fetch a technical blog post from Helius (helius.dev/blog). The Helius blog covers SVM internals, consensus, transactions, fees, MEV, validator economics, development frameworks, security, and more. Use "list" to see available posts or "fetch" to read one.', {
|
|
477
477
|
action: z
|
|
478
478
|
.enum(['fetch', 'list'])
|
|
479
|
-
.
|
|
479
|
+
.optional()
|
|
480
|
+
.describe('"fetch" to read a specific post, "list" to see available posts by category. Defaults to "fetch" if slug provided, "list" otherwise.'),
|
|
480
481
|
slug: z
|
|
481
482
|
.string()
|
|
482
483
|
.optional()
|
|
@@ -485,7 +486,9 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
485
486
|
.enum(['all', 'runtime', 'consensus', 'transactions', 'validators', 'data', 'security', 'development', 'tokens'])
|
|
486
487
|
.default('all')
|
|
487
488
|
.describe('Filter by category when listing posts'),
|
|
488
|
-
}, async ({ action, slug, category }) => {
|
|
489
|
+
}, async ({ action: rawAction, slug: rawSlug, category }) => {
|
|
490
|
+
let slug = rawSlug;
|
|
491
|
+
const action = rawAction ?? (slug ? 'fetch' : 'list');
|
|
489
492
|
try {
|
|
490
493
|
if (action === 'list') {
|
|
491
494
|
const entries = Object.entries(BLOG_INDEX);
|
|
@@ -524,7 +527,20 @@ export function registerSolanaKnowledgeTools(server) {
|
|
|
524
527
|
}
|
|
525
528
|
// Fetch a specific post
|
|
526
529
|
if (!slug) {
|
|
527
|
-
return
|
|
530
|
+
return mcpError('Please provide a slug. Use `fetchHeliusBlog` with action "list" to see available posts.', { type: 'VALIDATION', code: 'MISSING_PARAM', retryable: false, recovery: 'Provide a slug. Use fetchHeliusBlog with action "list" to see available posts.' });
|
|
531
|
+
}
|
|
532
|
+
// Fuzzy slug matching — if exact slug isn't in index, find close matches
|
|
533
|
+
if (!BLOG_INDEX[slug]) {
|
|
534
|
+
const lower = slug.toLowerCase();
|
|
535
|
+
const matches = Object.keys(BLOG_INDEX).filter((k) => k.includes(lower) || lower.includes(k) || k.split('-').some((w) => lower.includes(w) && w.length > 3));
|
|
536
|
+
if (matches.length === 1) {
|
|
537
|
+
// Single match — auto-correct
|
|
538
|
+
slug = matches[0];
|
|
539
|
+
}
|
|
540
|
+
else if (matches.length > 1) {
|
|
541
|
+
return mcpError(`No exact blog post "${slug}". Did you mean one of these?\n${matches.map((m) => `- \`${m}\` — ${BLOG_INDEX[m].title}`).join('\n')}`, { type: 'VALIDATION', code: 'UNKNOWN_SLUG', retryable: true, recovery: 'Retry with one of the suggested slugs.' });
|
|
542
|
+
}
|
|
543
|
+
// If no matches, let it proceed — the fetch will 404 with a helpful message
|
|
528
544
|
}
|
|
529
545
|
const url = `https://www.helius.dev/blog/${slug}`;
|
|
530
546
|
const html = await cachedFetch(url);
|