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/utils/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface HeliusConfig {
|
|
|
6
6
|
network?: string;
|
|
7
7
|
projectId?: string;
|
|
8
8
|
preferences?: {
|
|
9
|
-
budget?: '
|
|
9
|
+
budget?: 'agent' | 'developer' | 'business' | 'professional';
|
|
10
10
|
complexity?: 'low' | 'medium' | 'high';
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@ export declare function setSharedApiKey(apiKey: string): void;
|
|
|
17
17
|
export declare function getJwt(): string | undefined;
|
|
18
18
|
export declare function setJwt(jwt: string): void;
|
|
19
19
|
export declare function getPreferences(): {
|
|
20
|
-
budget?: "
|
|
20
|
+
budget?: "agent" | "developer" | "business" | "professional";
|
|
21
21
|
complexity?: "low" | "medium" | "high";
|
|
22
22
|
};
|
|
23
23
|
export declare function savePreferences(prefs: HeliusConfig['preferences']): void;
|
package/dist/utils/config.js
CHANGED
|
@@ -9,17 +9,79 @@ function ensureDir() {
|
|
|
9
9
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
/** Best-effort recovery when config.json is corrupted (matches helius-cli behavior). */
|
|
13
|
+
function recoverPartialConfig(raw) {
|
|
14
|
+
const recovered = {};
|
|
15
|
+
const patterns = [
|
|
16
|
+
{ key: "jwt", regex: /"jwt"\s*:\s*"([^"]+)"/ },
|
|
17
|
+
{ key: "apiKey", regex: /"apiKey"\s*:\s*"([^"]+)"/ },
|
|
18
|
+
{
|
|
19
|
+
key: "network",
|
|
20
|
+
regex: /"network"\s*:\s*"(mainnet|mainnet-beta|devnet)"/,
|
|
21
|
+
},
|
|
22
|
+
{ key: "projectId", regex: /"projectId"\s*:\s*"([^"]+)"/ },
|
|
23
|
+
];
|
|
24
|
+
for (const { key, regex } of patterns) {
|
|
25
|
+
const match = raw.match(regex);
|
|
26
|
+
if (match) {
|
|
27
|
+
recovered[key] = match[1];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const budgetMatch = raw.match(/"budget"\s*:\s*"(free|developer|business|professional)"/);
|
|
31
|
+
const complexityMatch = raw.match(/"complexity"\s*:\s*"(low|medium|high)"/);
|
|
32
|
+
if (budgetMatch || complexityMatch) {
|
|
33
|
+
recovered.preferences = {};
|
|
34
|
+
if (budgetMatch) {
|
|
35
|
+
recovered.preferences.budget = budgetMatch[1];
|
|
36
|
+
}
|
|
37
|
+
if (complexityMatch) {
|
|
38
|
+
recovered.preferences.complexity = complexityMatch[1];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return recovered;
|
|
42
|
+
}
|
|
43
|
+
function recoveredFieldSummary(cfg) {
|
|
44
|
+
const keys = [];
|
|
45
|
+
for (const k of Object.keys(cfg)) {
|
|
46
|
+
if (k === "preferences" && cfg.preferences) {
|
|
47
|
+
for (const pk of Object.keys(cfg.preferences)) {
|
|
48
|
+
keys.push(`preferences.${pk}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (k !== "preferences") {
|
|
52
|
+
keys.push(k);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return keys.join(", ");
|
|
56
|
+
}
|
|
12
57
|
export function loadConfig() {
|
|
58
|
+
if (!fs.existsSync(SHARED_CONFIG_PATH)) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
let raw;
|
|
13
62
|
try {
|
|
14
|
-
|
|
15
|
-
const data = fs.readFileSync(SHARED_CONFIG_PATH, "utf-8");
|
|
16
|
-
return JSON.parse(data);
|
|
17
|
-
}
|
|
63
|
+
raw = fs.readFileSync(SHARED_CONFIG_PATH, "utf-8");
|
|
18
64
|
}
|
|
19
65
|
catch {
|
|
20
|
-
|
|
66
|
+
console.error(`Warning: ${SHARED_CONFIG_PATH} exists but could not be read.`);
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(raw);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
const recovered = recoverPartialConfig(raw);
|
|
74
|
+
const summary = recoveredFieldSummary(recovered);
|
|
75
|
+
if (summary.length > 0) {
|
|
76
|
+
console.error(`Warning: ${SHARED_CONFIG_PATH} is corrupted (invalid JSON). Recovered fields: ${summary}.`);
|
|
77
|
+
saveConfig(recovered);
|
|
78
|
+
console.error("Repaired config saved. Fields that did not match known keys may have been lost.");
|
|
79
|
+
return recovered;
|
|
80
|
+
}
|
|
81
|
+
console.error(`Warning: ${SHARED_CONFIG_PATH} is corrupted (invalid JSON) and could not be recovered.`);
|
|
82
|
+
console.error('Run Helius MCP config helpers or edit the file manually; see CLI `helius config clear` if you use helius-cli.');
|
|
83
|
+
return {};
|
|
21
84
|
}
|
|
22
|
-
return {};
|
|
23
85
|
}
|
|
24
86
|
export function saveConfig(config) {
|
|
25
87
|
ensureDir();
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -5,8 +5,14 @@ type McpToolResponse = {
|
|
|
5
5
|
}];
|
|
6
6
|
isError?: boolean;
|
|
7
7
|
};
|
|
8
|
+
export type ErrorMeta = {
|
|
9
|
+
type: 'VALIDATION' | 'NOT_FOUND' | 'AUTH' | 'RATE_LIMIT' | 'INSUFFICIENT_FUNDS' | 'UNSUPPORTED' | 'HTTP' | 'API';
|
|
10
|
+
code: string;
|
|
11
|
+
retryable: boolean;
|
|
12
|
+
recovery: string;
|
|
13
|
+
};
|
|
8
14
|
export declare function mcpText(text: string): McpToolResponse;
|
|
9
|
-
export declare function mcpError(text: string): McpToolResponse;
|
|
15
|
+
export declare function mcpError(text: string, meta?: ErrorMeta): McpToolResponse;
|
|
10
16
|
export declare function getErrorMessage(err: unknown): string;
|
|
11
17
|
export declare function isAddressError(msg: string): boolean;
|
|
12
18
|
export declare function isPaginationError(msg: string): boolean;
|
|
@@ -29,4 +35,7 @@ export declare function isValidAddressFormat(address: string): boolean;
|
|
|
29
35
|
export declare function warnInvalidAddresses(paramName: string, addresses: string[]): string[];
|
|
30
36
|
export declare function warnInvalidAddress(paramName: string, address: string): string | null;
|
|
31
37
|
export declare function warnAddressConflicts(includeName: string, includeAddresses: string[] | undefined, excludeName: string, excludeAddresses: string[] | undefined): string | null;
|
|
38
|
+
export declare function missingParamError(toolName: string, recovery: string): McpToolResponse;
|
|
39
|
+
export declare function exclusiveParamError(toolName: string, paramA: string, paramB: string): McpToolResponse;
|
|
40
|
+
export declare function batchLimitError(toolName: string, max: number, actual: number): McpToolResponse;
|
|
32
41
|
export type { ErrorHandler, McpToolResponse };
|
package/dist/utils/errors.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
export function mcpText(text) {
|
|
3
3
|
return { content: [{ type: 'text', text }] };
|
|
4
4
|
}
|
|
5
|
-
export function mcpError(text) {
|
|
6
|
-
|
|
5
|
+
export function mcpError(text, meta) {
|
|
6
|
+
const body = meta
|
|
7
|
+
? '```json\n' + JSON.stringify(meta) + '\n```\n\n' + text
|
|
8
|
+
: text;
|
|
9
|
+
return { content: [{ type: 'text', text: body }], isError: true };
|
|
7
10
|
}
|
|
8
11
|
// ─── Error Message Extraction ───
|
|
9
12
|
export function getErrorMessage(err) {
|
|
@@ -48,14 +51,14 @@ export function parseHttp400Messages(msg) {
|
|
|
48
51
|
// ─── Enum Validation ───
|
|
49
52
|
export function validateEnum(value, validOptions, context, fieldName) {
|
|
50
53
|
if (!validOptions.includes(value)) {
|
|
51
|
-
return
|
|
54
|
+
return mcpError(`**${context}**\n\nInvalid ${fieldName} "${value}". Valid options: ${validOptions.join(', ')}`, { type: 'VALIDATION', code: 'INVALID_ENUM', retryable: false, recovery: `Use one of: ${validOptions.join(', ')}` });
|
|
52
55
|
}
|
|
53
56
|
return null;
|
|
54
57
|
}
|
|
55
58
|
// ─── HTTP Status Guidance ───
|
|
56
59
|
const HTTP_GUIDANCE = {
|
|
57
60
|
'401': 'API key is invalid or expired. Call `setHeliusApiKey` with a valid key, or call `getAccountStatus` to check your current auth state.',
|
|
58
|
-
'403': 'This endpoint is restricted on your current plan. Call `
|
|
61
|
+
'403': 'This endpoint is restricted on your current plan. Call `getAccountPlan` for a quick plan/feature check, or `getAccountStatus` for full details. Some endpoints require Developer plan or higher. Call `getHeliusPlanInfo` to compare plans, or `previewUpgrade` to see upgrade pricing.',
|
|
59
62
|
'429': 'Rate limited. Call `getAccountStatus` to check your remaining credits and rate limits. Back off and retry, or call `previewUpgrade` to see upgrade options for higher limits.',
|
|
60
63
|
'502': 'Backend temporarily unavailable. Retry after a few seconds.',
|
|
61
64
|
'504': 'Gateway timeout — the request took too long. Try reducing the query scope (fewer addresses, smaller limit, narrower time range).',
|
|
@@ -68,6 +71,21 @@ function extractHttpGuidance(msg) {
|
|
|
68
71
|
}
|
|
69
72
|
return null;
|
|
70
73
|
}
|
|
74
|
+
const HTTP_META = {
|
|
75
|
+
'401': { type: 'AUTH', code: 'HTTP_401', retryable: false, recovery: HTTP_GUIDANCE['401'] },
|
|
76
|
+
'403': { type: 'AUTH', code: 'HTTP_403', retryable: false, recovery: HTTP_GUIDANCE['403'] },
|
|
77
|
+
'429': { type: 'RATE_LIMIT', code: 'HTTP_429', retryable: true, recovery: HTTP_GUIDANCE['429'] },
|
|
78
|
+
'502': { type: 'HTTP', code: 'HTTP_502', retryable: true, recovery: HTTP_GUIDANCE['502'] },
|
|
79
|
+
'504': { type: 'HTTP', code: 'HTTP_504', retryable: true, recovery: HTTP_GUIDANCE['504'] },
|
|
80
|
+
};
|
|
81
|
+
function extractHttpMeta(msg, _guidance) {
|
|
82
|
+
for (const [status, meta] of Object.entries(HTTP_META)) {
|
|
83
|
+
if (msg.includes(`HTTP ${status}`) || msg.includes(`status: ${status}`) || msg.includes(`(${status})`)) {
|
|
84
|
+
return meta;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { type: 'API', code: 'UNKNOWN', retryable: false, recovery: 'Check the error message for details' };
|
|
88
|
+
}
|
|
71
89
|
export function handleToolError(err, fallbackPrefix, handlers) {
|
|
72
90
|
const msg = getErrorMessage(err);
|
|
73
91
|
if (handlers) {
|
|
@@ -78,33 +96,37 @@ export function handleToolError(err, fallbackPrefix, handlers) {
|
|
|
78
96
|
}
|
|
79
97
|
const guidance = extractHttpGuidance(msg);
|
|
80
98
|
if (guidance) {
|
|
81
|
-
|
|
99
|
+
const meta = extractHttpMeta(msg, guidance);
|
|
100
|
+
return mcpError(`**${fallbackPrefix}:** ${msg}\n\n${guidance}`, meta);
|
|
82
101
|
}
|
|
83
|
-
return mcpError(`**${fallbackPrefix}:** ${msg}
|
|
102
|
+
return mcpError(`**${fallbackPrefix}:** ${msg}`, { type: 'API', code: 'UNKNOWN', retryable: false, recovery: 'Check the error message for details' });
|
|
84
103
|
}
|
|
85
104
|
// ─── Pre-built Handler Factories ───
|
|
86
105
|
export function addressError(header, detail) {
|
|
106
|
+
const recovery = detail || 'Invalid Solana address. Please provide a valid base58-encoded address.';
|
|
87
107
|
return {
|
|
88
108
|
match: isAddressError,
|
|
89
|
-
respond: () =>
|
|
109
|
+
respond: () => mcpError(`**${header}**\n\n${recovery}`, { type: 'VALIDATION', code: 'INVALID_ADDRESS', retryable: false, recovery }),
|
|
90
110
|
};
|
|
91
111
|
}
|
|
92
112
|
export function paginationError(header, detail) {
|
|
113
|
+
const recovery = detail || 'Invalid pagination parameters. Page must be at least 1 and limit must be between 1 and 1000.';
|
|
93
114
|
return {
|
|
94
115
|
match: isPaginationError,
|
|
95
|
-
respond: () =>
|
|
116
|
+
respond: () => mcpError(`**${header}**\n\n${recovery}`, { type: 'VALIDATION', code: 'INVALID_PAGINATION', retryable: false, recovery }),
|
|
96
117
|
};
|
|
97
118
|
}
|
|
98
119
|
export function notFoundError(header, detail) {
|
|
120
|
+
const recovery = detail || 'Asset not found. This mint address does not exist or has not been indexed.';
|
|
99
121
|
return {
|
|
100
122
|
match: isNotFoundError,
|
|
101
|
-
respond: () =>
|
|
123
|
+
respond: () => mcpError(`**${header}**\n\n${recovery}`, { type: 'NOT_FOUND', code: 'RESOURCE_NOT_FOUND', retryable: false, recovery }),
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
export function http404Error(header, detail) {
|
|
105
127
|
return {
|
|
106
128
|
match: isHttp404Error,
|
|
107
|
-
respond: () => header ?
|
|
129
|
+
respond: () => mcpError(header ? `**${header}**\n\n${detail}` : detail, { type: 'NOT_FOUND', code: 'HTTP_404', retryable: false, recovery: detail }),
|
|
108
130
|
};
|
|
109
131
|
}
|
|
110
132
|
export function http400Error(header) {
|
|
@@ -113,9 +135,9 @@ export function http400Error(header) {
|
|
|
113
135
|
respond: (msg) => {
|
|
114
136
|
const messages = parseHttp400Messages(msg);
|
|
115
137
|
if (messages) {
|
|
116
|
-
return
|
|
138
|
+
return mcpError(`**${header}**\n\n${messages.map((m) => `- ${m}`).join('\n')}`, { type: 'API', code: 'BAD_REQUEST', retryable: false, recovery: 'Fix the request parameters' });
|
|
117
139
|
}
|
|
118
|
-
return mcpError(`**${header}:** ${msg}
|
|
140
|
+
return mcpError(`**${header}:** ${msg}`, { type: 'API', code: 'BAD_REQUEST', retryable: false, recovery: 'Fix the request parameters' });
|
|
119
141
|
},
|
|
120
142
|
};
|
|
121
143
|
}
|
|
@@ -155,3 +177,15 @@ export function warnAddressConflicts(includeName, includeAddresses, excludeName,
|
|
|
155
177
|
}
|
|
156
178
|
return null;
|
|
157
179
|
}
|
|
180
|
+
// ─── Convenience Error Helpers ───
|
|
181
|
+
export function missingParamError(toolName, recovery) {
|
|
182
|
+
return mcpError(`**${toolName} Error**\n\n${recovery}`, { type: 'VALIDATION', code: 'MISSING_PARAM', retryable: false, recovery });
|
|
183
|
+
}
|
|
184
|
+
export function exclusiveParamError(toolName, paramA, paramB) {
|
|
185
|
+
const recovery = `Provide either \`${paramA}\` or \`${paramB}\`, not both.`;
|
|
186
|
+
return mcpError(`**${toolName} Error**\n\n${recovery}`, { type: 'VALIDATION', code: 'EXCLUSIVE_PARAMS', retryable: false, recovery });
|
|
187
|
+
}
|
|
188
|
+
export function batchLimitError(toolName, max, actual) {
|
|
189
|
+
const recovery = `Reduce batch to ${max} or fewer items.`;
|
|
190
|
+
return mcpError(`**${toolName} Error**\n\nMaximum ${max} items per request. You provided ${actual}.`, { type: 'VALIDATION', code: 'TOO_MANY_ITEMS', retryable: false, recovery });
|
|
191
|
+
}
|
package/dist/utils/feedback.js
CHANGED
|
@@ -8,7 +8,6 @@ const POSTHOG_API_KEY = 'phc_aLmID5mMwUZi3pVhG4HomDeZaWZ1PqAEkWTempDogzi';
|
|
|
8
8
|
const HELIUS_DIR = path.join(os.homedir(), '.helius');
|
|
9
9
|
const ANON_ID_PATH = path.join(HELIUS_DIR, 'anon-id');
|
|
10
10
|
let clientInfo = null;
|
|
11
|
-
let feedbackEnabled = true;
|
|
12
11
|
let walletAddress = null;
|
|
13
12
|
let identifySent = false;
|
|
14
13
|
// Persistent anonymous ID shared with helius-cli.
|
|
@@ -47,8 +46,6 @@ function getDistinctId() {
|
|
|
47
46
|
return walletAddress || sessionId;
|
|
48
47
|
}
|
|
49
48
|
function posthogCapture(event, properties) {
|
|
50
|
-
if (!feedbackEnabled)
|
|
51
|
-
return;
|
|
52
49
|
fetch(POSTHOG_ENDPOINT, {
|
|
53
50
|
method: 'POST',
|
|
54
51
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -58,7 +55,7 @@ function posthogCapture(event, properties) {
|
|
|
58
55
|
properties,
|
|
59
56
|
}),
|
|
60
57
|
}).catch(() => {
|
|
61
|
-
|
|
58
|
+
/* ignore delivery failure */
|
|
62
59
|
});
|
|
63
60
|
}
|
|
64
61
|
export function sendFeedbackEvent(event) {
|
package/dist/utils/helius.js
CHANGED
|
@@ -59,7 +59,8 @@ export function getEnhancedWebSocketUrl() {
|
|
|
59
59
|
return `wss://atlas-mainnet.helius-rpc.com/?api-key=${apiKey}`;
|
|
60
60
|
}
|
|
61
61
|
export function getLaserstreamUrl(region) {
|
|
62
|
-
|
|
62
|
+
// Endpoint host is public; clients pass apiKey separately (e.g. @helius/laserstream subscribe options).
|
|
63
|
+
// Do not call getApiKey() here or docs tools like getLaserstreamInfo fail unnecessarily.
|
|
63
64
|
const network = getNetwork();
|
|
64
65
|
if (network === 'devnet') {
|
|
65
66
|
return `https://laserstream-devnet-ewr.helius-rpc.com`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Wallet Standard (OWS) integration.
|
|
3
|
+
*
|
|
4
|
+
* Provides an optional, additive signing path that delegates to the `ows` CLI
|
|
5
|
+
* for policy-gated, key-isolated transaction signing. When an `owsWallet`
|
|
6
|
+
* parameter is supplied, transfers and staking tools use an OWS-backed signer
|
|
7
|
+
* instead of the local keypair. If OWS is not installed, callers get a clear
|
|
8
|
+
* error — existing keypair flows are unaffected.
|
|
9
|
+
*
|
|
10
|
+
* The adapter implements the same duck-typed signer interface that @solana/kit
|
|
11
|
+
* expects ({ address, signTransactions, signMessages }), so it plugs directly
|
|
12
|
+
* into the Helius SDK's sendTransactionWithSender / sendSmartTransaction
|
|
13
|
+
* without losing priority-fee estimation, SWQoS routing, or Jito tips.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: The CLI helpers (isOwsInstalled, getOwsSolanaAddress) are mirrored in
|
|
16
|
+
* helius-cli/src/lib/ows.ts. Keep the two copies in sync when changing
|
|
17
|
+
* argument handling, CAIP-2 lookup logic, or OWS CLI flags.
|
|
18
|
+
*/
|
|
19
|
+
import { type Address } from '@solana/kit';
|
|
20
|
+
import { mcpError } from './errors.js';
|
|
21
|
+
/**
|
|
22
|
+
* True when the `ows` binary is reachable on PATH.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isOwsInstalled(): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Return the Solana address for the named OWS wallet.
|
|
27
|
+
* Uses the current MCP session network (mainnet or devnet) to select
|
|
28
|
+
* the correct CAIP-2 chain key.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getOwsSolanaAddress(walletName: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* A duck-typed @solana/kit TransactionPartialSigner backed by OWS.
|
|
33
|
+
*
|
|
34
|
+
* Passes `isTransactionSigner()` checks and works with
|
|
35
|
+
* `setTransactionMessageFeePayerSigner`, `signTransactionMessageWithSigners`,
|
|
36
|
+
* `sendTransactionWithSender`, and `sendSmartTransaction`.
|
|
37
|
+
*
|
|
38
|
+
* IMPORTANT: `signTransactions` and `signMessages` return **partial signature
|
|
39
|
+
* maps** (`{ [address]: signatureBytes }`), NOT full transaction/message objects.
|
|
40
|
+
* This matches the contract that `@solana/kit`'s `signTransactionMessageWithSigners`
|
|
41
|
+
* expects — it merges the partial maps back into the compiled transaction itself.
|
|
42
|
+
*/
|
|
43
|
+
export interface OwsSigner {
|
|
44
|
+
address: Address;
|
|
45
|
+
signTransactions: (transactions: readonly {
|
|
46
|
+
messageBytes: Uint8Array;
|
|
47
|
+
}[]) => Promise<readonly Record<string, Uint8Array>[]>;
|
|
48
|
+
signMessages: (messages: readonly {
|
|
49
|
+
content: Uint8Array;
|
|
50
|
+
}[]) => Promise<readonly Record<string, Uint8Array>[]>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a signer from an OWS wallet name or the local Helius keypair.
|
|
54
|
+
* Returns a result-union so callers can return the MCP error directly.
|
|
55
|
+
*
|
|
56
|
+
* The returned `signer` is typed as `any` to avoid @solana/kit branded-type
|
|
57
|
+
* friction — it satisfies `isTransactionSigner()` at runtime.
|
|
58
|
+
*
|
|
59
|
+
* ⚠ If @solana/kit changes its signer interface (signTransactions /
|
|
60
|
+
* signMessages argument or return shapes), these casts will pass type-checking
|
|
61
|
+
* but fail at runtime. After any @solana/kit upgrade, verify the OWS signer
|
|
62
|
+
* still passes `isTransactionSigner()` and produces valid signatures in the
|
|
63
|
+
* full `signTransactionMessageWithSigners` pipeline.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveOwsOrKeypairSigner(owsWallet?: string): Promise<{
|
|
66
|
+
ok: true;
|
|
67
|
+
signer: any;
|
|
68
|
+
walletAddress: string;
|
|
69
|
+
owsWallet?: string;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
error: ReturnType<typeof mcpError>;
|
|
73
|
+
}>;
|
|
74
|
+
export declare function createOwsSigner(walletName: string): Promise<OwsSigner>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Wallet Standard (OWS) integration.
|
|
3
|
+
*
|
|
4
|
+
* Provides an optional, additive signing path that delegates to the `ows` CLI
|
|
5
|
+
* for policy-gated, key-isolated transaction signing. When an `owsWallet`
|
|
6
|
+
* parameter is supplied, transfers and staking tools use an OWS-backed signer
|
|
7
|
+
* instead of the local keypair. If OWS is not installed, callers get a clear
|
|
8
|
+
* error — existing keypair flows are unaffected.
|
|
9
|
+
*
|
|
10
|
+
* The adapter implements the same duck-typed signer interface that @solana/kit
|
|
11
|
+
* expects ({ address, signTransactions, signMessages }), so it plugs directly
|
|
12
|
+
* into the Helius SDK's sendTransactionWithSender / sendSmartTransaction
|
|
13
|
+
* without losing priority-fee estimation, SWQoS routing, or Jito tips.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: The CLI helpers (isOwsInstalled, getOwsSolanaAddress) are mirrored in
|
|
16
|
+
* helius-cli/src/lib/ows.ts. Keep the two copies in sync when changing
|
|
17
|
+
* argument handling, CAIP-2 lookup logic, or OWS CLI flags.
|
|
18
|
+
*/
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
20
|
+
import { promisify } from 'node:util';
|
|
21
|
+
import { address, createKeyPairSignerFromBytes } from '@solana/kit';
|
|
22
|
+
import { loadSignerOrFail, getNetwork } from './helius.js';
|
|
23
|
+
import { mcpError } from './errors.js';
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
// CAIP-2 chain identifiers for Solana networks.
|
|
26
|
+
// See OWS spec 07-supported-chains.md.
|
|
27
|
+
const SOLANA_CAIP2_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
|
|
28
|
+
const SOLANA_CAIP2_DEVNET = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG';
|
|
29
|
+
/** Alphanumeric, hyphens, and underscores — matches OWS wallet naming rules. */
|
|
30
|
+
const WALLET_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
31
|
+
// ── CLI helpers ──
|
|
32
|
+
/**
|
|
33
|
+
* True when the `ows` binary is reachable on PATH.
|
|
34
|
+
*/
|
|
35
|
+
export async function isOwsInstalled() {
|
|
36
|
+
try {
|
|
37
|
+
await execFileAsync('ows', ['--version'], { timeout: 5_000 });
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validate wallet name format before passing to execFile.
|
|
46
|
+
* While execFile prevents shell injection, a garbage name produces
|
|
47
|
+
* confusing OWS CLI errors — better to catch it early.
|
|
48
|
+
*/
|
|
49
|
+
function validateWalletName(name) {
|
|
50
|
+
if (!WALLET_NAME_RE.test(name)) {
|
|
51
|
+
throw new Error(`Invalid OWS wallet name "${name}". ` +
|
|
52
|
+
`Names must be 1-64 characters using letters, digits, hyphens, or underscores.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Return the Solana address for the named OWS wallet.
|
|
57
|
+
* Uses the current MCP session network (mainnet or devnet) to select
|
|
58
|
+
* the correct CAIP-2 chain key.
|
|
59
|
+
*/
|
|
60
|
+
export async function getOwsSolanaAddress(walletName) {
|
|
61
|
+
validateWalletName(walletName);
|
|
62
|
+
const { stdout } = await execFileAsync('ows', ['wallet', 'info', '--wallet', walletName, '--json'], { timeout: 10_000 });
|
|
63
|
+
const info = JSON.parse(stdout);
|
|
64
|
+
// The CLI outputs accounts keyed by CAIP-2 chain id.
|
|
65
|
+
// Pick the key matching the active network.
|
|
66
|
+
const network = getNetwork();
|
|
67
|
+
const primaryKey = network === 'devnet' ? SOLANA_CAIP2_DEVNET : SOLANA_CAIP2_MAINNET;
|
|
68
|
+
const account = info.accounts?.[primaryKey] ??
|
|
69
|
+
info.accounts?.solana ??
|
|
70
|
+
// Fallback: scan for any key containing "solana"
|
|
71
|
+
Object.entries(info.accounts ?? {}).find(([k]) => k.includes('solana'))?.[1];
|
|
72
|
+
if (!account) {
|
|
73
|
+
throw new Error(`OWS wallet "${walletName}" has no Solana account. ` +
|
|
74
|
+
`Run \`ows wallet info --wallet ${walletName}\` to inspect.`);
|
|
75
|
+
}
|
|
76
|
+
// Account may be a string (address) or an object with an address field.
|
|
77
|
+
const addr = typeof account === 'string' ? account : account.address;
|
|
78
|
+
if (!addr) {
|
|
79
|
+
throw new Error(`Could not extract Solana address from OWS wallet "${walletName}".`);
|
|
80
|
+
}
|
|
81
|
+
return addr;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Sign raw message bytes via OWS. Returns the 64-byte Ed25519 signature.
|
|
85
|
+
* Assumes walletName was already validated by the caller (createOwsSigner).
|
|
86
|
+
*/
|
|
87
|
+
async function owsSignBytes(walletName, messageHex) {
|
|
88
|
+
const { stdout } = await execFileAsync('ows', ['sign', 'message', '--wallet', walletName, '--chain', 'solana', '--message', messageHex, '--encoding', 'hex', '--json'], { timeout: 30_000 });
|
|
89
|
+
const result = JSON.parse(stdout);
|
|
90
|
+
const sigHex = result.signature;
|
|
91
|
+
if (!sigHex) {
|
|
92
|
+
throw new Error('OWS sign returned no signature');
|
|
93
|
+
}
|
|
94
|
+
return Uint8Array.from(Buffer.from(sigHex, 'hex'));
|
|
95
|
+
}
|
|
96
|
+
// ── Shared signer resolution ──
|
|
97
|
+
/**
|
|
98
|
+
* Resolve a signer from an OWS wallet name or the local Helius keypair.
|
|
99
|
+
* Returns a result-union so callers can return the MCP error directly.
|
|
100
|
+
*
|
|
101
|
+
* The returned `signer` is typed as `any` to avoid @solana/kit branded-type
|
|
102
|
+
* friction — it satisfies `isTransactionSigner()` at runtime.
|
|
103
|
+
*
|
|
104
|
+
* ⚠ If @solana/kit changes its signer interface (signTransactions /
|
|
105
|
+
* signMessages argument or return shapes), these casts will pass type-checking
|
|
106
|
+
* but fail at runtime. After any @solana/kit upgrade, verify the OWS signer
|
|
107
|
+
* still passes `isTransactionSigner()` and produces valid signatures in the
|
|
108
|
+
* full `signTransactionMessageWithSigners` pipeline.
|
|
109
|
+
*/
|
|
110
|
+
export async function resolveOwsOrKeypairSigner(owsWallet) {
|
|
111
|
+
if (owsWallet) {
|
|
112
|
+
if (!WALLET_NAME_RE.test(owsWallet)) {
|
|
113
|
+
return { ok: false, error: mcpError(`Invalid OWS wallet name "${owsWallet}". Names must be 1-64 characters using letters, digits, hyphens, or underscores.`, { type: 'VALIDATION', code: 'INVALID_WALLET_NAME', retryable: false, recovery: 'Provide a valid wallet name (letters, digits, hyphens, underscores).' }) };
|
|
114
|
+
}
|
|
115
|
+
if (!await isOwsInstalled()) {
|
|
116
|
+
return { ok: false, error: mcpError('OWS CLI is not installed. Install it with `curl -fsSL https://docs.openwallet.sh/install.sh | bash` or `npm install -g @open-wallet-standard/core`.', { type: 'AUTH', code: 'OWS_NOT_INSTALLED', retryable: false, recovery: 'Install the OWS CLI, then retry.' }) };
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const signer = await createOwsSigner(owsWallet);
|
|
120
|
+
return { ok: true, signer, walletAddress: signer.address, owsWallet };
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return { ok: false, error: mcpError(`Failed to load OWS wallet "${owsWallet}": ${err.message}`, { type: 'AUTH', code: 'OWS_WALLET_ERROR', retryable: false, recovery: 'Run `ows wallet list` to see available wallets.' }) };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const signerData = await loadSignerOrFail();
|
|
128
|
+
const signer = await createKeyPairSignerFromBytes(signerData.secretKey);
|
|
129
|
+
return { ok: true, signer, walletAddress: signerData.walletAddress };
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { ok: false, error: mcpError('No keypair found. Call `generateKeypair` first to create a wallet.', { type: 'AUTH', code: 'NO_KEYPAIR', retryable: false, recovery: 'Call `generateKeypair` to create a wallet.' }) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function createOwsSigner(walletName) {
|
|
136
|
+
const solAddress = await getOwsSolanaAddress(walletName);
|
|
137
|
+
const addr = address(solAddress);
|
|
138
|
+
return {
|
|
139
|
+
address: addr,
|
|
140
|
+
async signTransactions(transactions) {
|
|
141
|
+
return Promise.all(transactions.map(async (tx) => {
|
|
142
|
+
const msgHex = Buffer.from(tx.messageBytes).toString('hex');
|
|
143
|
+
const sig = await owsSignBytes(walletName, msgHex);
|
|
144
|
+
return { [addr]: sig };
|
|
145
|
+
}));
|
|
146
|
+
},
|
|
147
|
+
async signMessages(messages) {
|
|
148
|
+
return Promise.all(messages.map(async (msg) => {
|
|
149
|
+
const hex = Buffer.from(msg.content).toString('hex');
|
|
150
|
+
const sig = await owsSignBytes(walletName, hex);
|
|
151
|
+
return { [addr]: sig };
|
|
152
|
+
}));
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "2.0.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '2.0.0';
|
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "helius-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Official Helius MCP Server - Complete Solana blockchain data access for AI assistants",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"helius-mcp": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"prebuild": "node -p \"'export const version = \\'' + require('./package.json').version + '\\';'\" > src/version.ts",
|
|
12
|
-
"build": "tsc",
|
|
13
|
-
"dev": "tsc --watch",
|
|
14
|
-
"start": "node dist/index.js",
|
|
15
|
-
"validate": "node dist/scripts/validate-catalog.js",
|
|
16
|
-
"test": "pnpm build && pnpm validate && vitest run",
|
|
17
|
-
"changelog": "auto-changelog --tag-prefix helius-mcp@",
|
|
18
|
-
"prepublishOnly": "pnpm build"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"helius",
|
|
22
|
-
"solana",
|
|
23
|
-
"blockchain",
|
|
24
|
-
"mcp",
|
|
25
|
-
"model-context-protocol",
|
|
26
|
-
"ai",
|
|
27
|
-
"llm",
|
|
28
|
-
"claude",
|
|
29
|
-
"web3",
|
|
30
|
-
"nft",
|
|
31
|
-
"defi",
|
|
32
|
-
"websocket",
|
|
33
|
-
"laserstream"
|
|
34
|
-
],
|
|
35
|
-
"author": "Helius Labs",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/helius-labs/core-ai"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/helius-labs/core-ai#readme",
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
44
|
-
"@solana-program/system": "^0.10.0",
|
|
45
|
-
"@solana-program/token": "^0.9.0",
|
|
46
|
-
"@solana/kit": "^5.0.0",
|
|
47
|
-
"bs58": "^6.0.0",
|
|
48
|
-
"helius-sdk": "^
|
|
49
|
-
"zod": "^3.23.0"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@types/node": "^20.0.0",
|
|
53
|
-
"auto-changelog": "^2.5.0",
|
|
54
|
-
"typescript": "^5.4.0",
|
|
55
|
-
"vitest": "^2.0.0"
|
|
56
|
-
},
|
|
57
|
-
"files": [
|
|
58
|
-
"dist",
|
|
59
|
-
"system-prompts",
|
|
60
|
-
"CHANGELOG.md",
|
|
61
|
-
"README.md",
|
|
62
|
-
"LICENSE"
|
|
63
|
-
]
|
|
64
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "helius-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Official Helius MCP Server - Complete Solana blockchain data access for AI assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"helius-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prebuild": "node -p \"'export const version = \\'' + require('./package.json').version + '\\';'\" > src/version.ts",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"validate": "node dist/scripts/validate-catalog.js",
|
|
16
|
+
"test": "pnpm build && pnpm validate && vitest run",
|
|
17
|
+
"changelog": "auto-changelog --tag-prefix helius-mcp@",
|
|
18
|
+
"prepublishOnly": "pnpm build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"helius",
|
|
22
|
+
"solana",
|
|
23
|
+
"blockchain",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"claude",
|
|
29
|
+
"web3",
|
|
30
|
+
"nft",
|
|
31
|
+
"defi",
|
|
32
|
+
"websocket",
|
|
33
|
+
"laserstream"
|
|
34
|
+
],
|
|
35
|
+
"author": "Helius Labs",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/helius-labs/core-ai"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/helius-labs/core-ai#readme",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
44
|
+
"@solana-program/system": "^0.10.0",
|
|
45
|
+
"@solana-program/token": "^0.9.0",
|
|
46
|
+
"@solana/kit": "^5.0.0",
|
|
47
|
+
"bs58": "^6.0.0",
|
|
48
|
+
"helius-sdk": "^3.0.0",
|
|
49
|
+
"zod": "^3.23.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^20.0.0",
|
|
53
|
+
"auto-changelog": "^2.5.0",
|
|
54
|
+
"typescript": "^5.4.0",
|
|
55
|
+
"vitest": "^2.0.0"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist",
|
|
59
|
+
"system-prompts",
|
|
60
|
+
"CHANGELOG.md",
|
|
61
|
+
"README.md",
|
|
62
|
+
"LICENSE"
|
|
63
|
+
]
|
|
64
|
+
}
|