helius-mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +29 -0
  5. package/dist/tools/accounts.d.ts +2 -0
  6. package/dist/tools/accounts.js +197 -0
  7. package/dist/tools/assets-extended.d.ts +2 -0
  8. package/dist/tools/assets-extended.js +54 -0
  9. package/dist/tools/assets.d.ts +2 -0
  10. package/dist/tools/assets.js +156 -0
  11. package/dist/tools/balance.d.ts +2 -0
  12. package/dist/tools/balance.js +169 -0
  13. package/dist/tools/config.d.ts +2 -0
  14. package/dist/tools/config.js +33 -0
  15. package/dist/tools/das.d.ts +2 -0
  16. package/dist/tools/das.js +428 -0
  17. package/dist/tools/enhanced-transactions.d.ts +2 -0
  18. package/dist/tools/enhanced-transactions.js +51 -0
  19. package/dist/tools/enhanced-websockets.d.ts +12 -0
  20. package/dist/tools/enhanced-websockets.js +329 -0
  21. package/dist/tools/index.d.ts +2 -0
  22. package/dist/tools/index.js +18 -0
  23. package/dist/tools/laserstream.d.ts +16 -0
  24. package/dist/tools/laserstream.js +348 -0
  25. package/dist/tools/priority-fees.d.ts +2 -0
  26. package/dist/tools/priority-fees.js +61 -0
  27. package/dist/tools/rpc.d.ts +2 -0
  28. package/dist/tools/rpc.js +130 -0
  29. package/dist/tools/shared.d.ts +6 -0
  30. package/dist/tools/shared.js +13 -0
  31. package/dist/tools/transactions-enhanced.d.ts +2 -0
  32. package/dist/tools/transactions-enhanced.js +165 -0
  33. package/dist/tools/transactions.d.ts +2 -0
  34. package/dist/tools/transactions.js +336 -0
  35. package/dist/tools/webhooks.d.ts +2 -0
  36. package/dist/tools/webhooks.js +156 -0
  37. package/dist/utils/formatters.d.ts +4 -0
  38. package/dist/utils/formatters.js +12 -0
  39. package/dist/utils/helius.d.ts +33 -0
  40. package/dist/utils/helius.js +134 -0
  41. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Helius Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # Helius MCP Server
2
+
3
+ Official Model Context Protocol (MCP) server for Helius - Complete Solana blockchain data access for Claude.
4
+
5
+ ## Features
6
+
7
+ **29 Tools Covering:**
8
+ - ✅ Digital Asset Standard (DAS) API - NFTs, tokens, compressed NFTs
9
+ - ✅ RPC Methods - Balances, accounts, signatures
10
+ - ✅ Enhanced Transactions - Human-readable parsing
11
+ - ✅ Priority Fees - Real-time fee estimation
12
+ - ✅ Webhooks - Event monitoring and notifications
13
+ - ✅ Enhanced WebSockets - Real-time streaming (1.5-2× faster)
14
+ - ✅ Laserstream gRPC - Lowest latency streaming with 24h replay
15
+
16
+ **Network Support:**
17
+ - Mainnet Beta
18
+ - Devnet
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install -g @helius-labs/mcp-server
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### 1. Get Helius API Key
29
+
30
+ Sign up at [helius.dev](https://www.helius.dev) and create an API key.
31
+
32
+ ### 2. Configure Claude Desktop
33
+
34
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "helius": {
40
+ "command": "helius-mcp",
41
+ "env": {
42
+ "HELIUS_API_KEY": "your-api-key-here",
43
+ "HELIUS_NETWORK": "mainnet-beta"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### 3. Restart Claude Desktop
51
+
52
+ The Helius tools will now be available in Claude.
53
+
54
+ ## Usage Examples
55
+
56
+ **Check wallet balance:**
57
+ ```
58
+ What's the SOL balance for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg?
59
+ ```
60
+
61
+ **Get NFT information:**
62
+ ```
63
+ Show me details for NFT FKZJkqv2YbXmcGWbp2RJfhVmpE6PnSaq6dMj1DKXgj1m
64
+ ```
65
+
66
+ **Monitor transactions:**
67
+ ```
68
+ Set up a webhook to track transactions for wallet ABC...XYZ
69
+ ```
70
+
71
+ **Real-time streaming:**
72
+ ```
73
+ Show me how to subscribe to Raydium swap transactions
74
+ ```
75
+
76
+ ## Available Tools
77
+
78
+ ### Config (1 tool)
79
+ - `setHeliusApiKey` - Configure API key and network
80
+
81
+ ### DAS API (12 tools)
82
+ - `getAsset` - NFT/token details
83
+ - `getAssetBatch` - Batch fetch assets
84
+ - `getAssetsByOwner` - Wallet's assets
85
+ - `getAssetsByGroup` - Collection NFTs
86
+ - `getAssetsByCreator` - Creator's assets
87
+ - `getAssetsByAuthority` - Authority's assets
88
+ - `searchAssets` - Advanced search
89
+ - `getAssetProof` - cNFT Merkle proof
90
+ - `getAssetProofBatch` - Batch proofs
91
+ - `getSignaturesForAsset` - cNFT transaction history
92
+ - `getNftEditions` - Edition NFTs
93
+ - `getTokenAccounts` - Token account queries
94
+
95
+ ### RPC Methods (4 tools)
96
+ - `getBalance` - SOL balance
97
+ - `getAccountInfo` - Account details
98
+ - `getMultipleAccounts` - Batch account lookups
99
+ - `getSignaturesForAddress` - Transaction signatures
100
+
101
+ ### Enhanced Transactions (1 tool)
102
+ - `parseTransactions` - Human-readable parsing
103
+
104
+ ### Priority Fees (1 tool)
105
+ - `getPriorityFeeEstimate` - Optimal fees
106
+
107
+ ### Webhooks (5 tools)
108
+ - `getAllWebhooks` - List webhooks
109
+ - `getWebhookByID` - Webhook details
110
+ - `createWebhook` - Create webhook
111
+ - `updateWebhook` - Update webhook
112
+ - `deleteWebhook` - Delete webhook
113
+
114
+ ### Enhanced WebSockets (3 tools)
115
+ - `transactionSubscribe` - Real-time transaction streaming
116
+ - `accountSubscribe` - Real-time account monitoring
117
+ - `getEnhancedWebSocketInfo` - WebSocket service info
118
+
119
+ ### Laserstream gRPC (2 tools)
120
+ - `laserstreamSubscribe` - High-performance gRPC streaming
121
+ - `getLaserstreamInfo` - Laserstream service info
122
+
123
+ ## Plan Requirements
124
+
125
+ ### Enhanced WebSockets
126
+ - Free/Developer: ❌ Not available
127
+ - Business: ✅ 250 connections
128
+ - Professional: ✅ 250 connections
129
+
130
+ ### Laserstream gRPC
131
+ - Free: ❌ Not available
132
+ - Developer: ✅ Devnet only
133
+ - Business: ✅ Devnet only
134
+ - Professional: ✅ Mainnet + Devnet
135
+
136
+ ## Documentation
137
+
138
+ - [Helius API Documentation](https://docs.helius.dev)
139
+ - [Enhanced WebSockets](https://docs.helius.dev/enhanced-websockets)
140
+ - [Laserstream gRPC](https://docs.helius.dev/laserstream)
141
+ - [MCP Protocol](https://modelcontextprotocol.io)
142
+
143
+ ## Support
144
+
145
+ - GitHub Issues: [helius-labs/helius-mcp-server](https://github.com/helius-labs/helius-mcp-server/issues)
146
+ - Discord: [Helius Discord](https://discord.gg/aYYStRz3YX)
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { registerTools } from './tools/index.js';
5
+ import { setApiKey } from './utils/helius.js';
6
+ const server = new McpServer({
7
+ name: 'helius-claude-plugin',
8
+ version: '0.3.0'
9
+ });
10
+ // Register all tools
11
+ registerTools(server);
12
+ // Start the server
13
+ async function main() {
14
+ // Auto-configure API key from environment if available
15
+ if (process.env.HELIUS_API_KEY) {
16
+ setApiKey(process.env.HELIUS_API_KEY);
17
+ console.error('✅ Helius API key loaded from environment');
18
+ }
19
+ else {
20
+ console.error('ℹ️ No API key found. Use setHeliusApiKey tool or set HELIUS_API_KEY environment variable');
21
+ }
22
+ const transport = new StdioServerTransport();
23
+ await server.connect(transport);
24
+ console.error('🚀 Helius MCP Server v0.3.0 running (20 tools)');
25
+ }
26
+ main().catch((error) => {
27
+ console.error('Failed to start server:', error);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAccountTools(server: McpServer): void;
@@ -0,0 +1,197 @@
1
+ import { z } from 'zod';
2
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
+ import { formatAddress, formatSol } from '../utils/formatters.js';
4
+ import { noApiKeyResponse } from './shared.js';
5
+ export function registerAccountTools(server) {
6
+ // Get multiple account balances at once
7
+ server.tool('getMultipleAccounts', 'Get account info for multiple addresses in a single call (up to 100 addresses). More efficient than calling getBalance repeatedly. Returns SOL balance, owner, executable status, and rent epoch for each account.', {
8
+ addresses: z.array(z.string()).describe('Array of Solana addresses (max 100)'),
9
+ commitment: z.enum(['processed', 'confirmed', 'finalized']).optional().default('confirmed')
10
+ }, async ({ addresses, commitment }) => {
11
+ if (!hasApiKey())
12
+ return noApiKeyResponse();
13
+ if (addresses.length > 100) {
14
+ return {
15
+ content: [{
16
+ type: 'text',
17
+ text: '**Error**\n\nMaximum 100 addresses allowed per request'
18
+ }],
19
+ isError: true
20
+ };
21
+ }
22
+ try {
23
+ const helius = getHeliusClient();
24
+ const connection = helius.connection;
25
+ if (!connection || !connection.getMultipleAccountsInfo) {
26
+ return {
27
+ content: [{
28
+ type: 'text',
29
+ text: '**Error**\n\nMultiple account lookup not available'
30
+ }],
31
+ isError: true
32
+ };
33
+ }
34
+ const accountsInfo = await connection.getMultipleAccountsInfo(addresses, { commitment });
35
+ const lines = [
36
+ `**Account Info (${addresses.length} addresses)**`,
37
+ ``
38
+ ];
39
+ addresses.forEach((addr, i) => {
40
+ const info = accountsInfo[i];
41
+ if (!info) {
42
+ lines.push(`${i + 1}. ${formatAddress(addr)} - Account does not exist`);
43
+ return;
44
+ }
45
+ lines.push(`${i + 1}. ${formatAddress(addr)}`);
46
+ lines.push(` Balance: ${formatSol(info.lamports)}`);
47
+ lines.push(` Owner: ${formatAddress(info.owner.toBase58())}`);
48
+ if (info.executable) {
49
+ lines.push(` ⚡ Executable program`);
50
+ }
51
+ lines.push(``);
52
+ });
53
+ return {
54
+ content: [{ type: 'text', text: lines.join('\n') }]
55
+ };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
60
+ isError: true
61
+ };
62
+ }
63
+ });
64
+ // Get program accounts
65
+ server.tool('getProgramAccounts', 'Query all accounts owned by a program. Use filters to narrow results (e.g., token accounts for a specific mint, PDAs with certain data). Returns account addresses and data. Warning: Can be slow for programs with many accounts.', {
66
+ programId: z.string().describe('Program address to query'),
67
+ filters: z.array(z.object({
68
+ memcmp: z.object({
69
+ offset: z.number(),
70
+ bytes: z.string()
71
+ }).optional(),
72
+ dataSize: z.number().optional()
73
+ })).optional().describe('Optional filters to narrow results'),
74
+ limit: z.number().optional().default(10).describe('Max accounts to return')
75
+ }, async ({ programId, filters, limit }) => {
76
+ if (!hasApiKey())
77
+ return noApiKeyResponse();
78
+ try {
79
+ const helius = getHeliusClient();
80
+ const connection = helius.connection;
81
+ if (!connection || !connection.getProgramAccounts) {
82
+ return {
83
+ content: [{
84
+ type: 'text',
85
+ text: '**Error**\n\nProgram account lookup not available'
86
+ }],
87
+ isError: true
88
+ };
89
+ }
90
+ const config = {};
91
+ if (filters && filters.length > 0) {
92
+ config.filters = filters;
93
+ }
94
+ const accounts = await connection.getProgramAccounts(programId, config);
95
+ if (!accounts || accounts.length === 0) {
96
+ return {
97
+ content: [{
98
+ type: 'text',
99
+ text: `**No Accounts Found**\n\nProgram: ${formatAddress(programId)}`
100
+ }]
101
+ };
102
+ }
103
+ const lines = [
104
+ `**Program Accounts**`,
105
+ ``,
106
+ `Program: ${formatAddress(programId)}`,
107
+ `Total: ${accounts.length} accounts`,
108
+ ``
109
+ ];
110
+ const displayAccounts = accounts.slice(0, limit);
111
+ displayAccounts.forEach((account, i) => {
112
+ const pubkey = account.pubkey.toBase58();
113
+ const lamports = account.account.lamports;
114
+ const dataLength = account.account.data.length;
115
+ lines.push(`${i + 1}. ${formatAddress(pubkey)}`);
116
+ lines.push(` Balance: ${formatSol(lamports)}`);
117
+ lines.push(` Data: ${dataLength} bytes`);
118
+ });
119
+ if (accounts.length > limit) {
120
+ lines.push(``);
121
+ lines.push(`💡 Showing first ${limit} of ${accounts.length} accounts`);
122
+ }
123
+ return {
124
+ content: [{ type: 'text', text: lines.join('\n') }]
125
+ };
126
+ }
127
+ catch (error) {
128
+ return {
129
+ content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
130
+ isError: true
131
+ };
132
+ }
133
+ });
134
+ // Get account info
135
+ server.tool('getAccountInfo', 'Get detailed information about a single Solana account including balance, owner, data, executable status, and rent epoch. Use this for inspecting program accounts, PDAs, or any on-chain account.', {
136
+ address: z.string().describe('Solana address to query'),
137
+ encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('base64')
138
+ }, async ({ address, encoding }) => {
139
+ if (!hasApiKey())
140
+ return noApiKeyResponse();
141
+ try {
142
+ const helius = getHeliusClient();
143
+ const connection = helius.connection;
144
+ if (!connection || !connection.getAccountInfo) {
145
+ return {
146
+ content: [{
147
+ type: 'text',
148
+ text: '**Error**\n\nAccount info lookup not available'
149
+ }],
150
+ isError: true
151
+ };
152
+ }
153
+ const accountInfo = await connection.getAccountInfo(address, { encoding });
154
+ if (!accountInfo) {
155
+ return {
156
+ content: [{
157
+ type: 'text',
158
+ text: `**Account Not Found**\n\n${formatAddress(address)}\n\nThis account does not exist or has been closed.`
159
+ }]
160
+ };
161
+ }
162
+ const lines = [
163
+ `**Account Info**`,
164
+ ``,
165
+ `Address: ${formatAddress(address)}`,
166
+ `Balance: ${formatSol(accountInfo.lamports)}`,
167
+ `Owner: ${formatAddress(accountInfo.owner.toBase58())}`,
168
+ `Data Length: ${accountInfo.data.length} bytes`,
169
+ `Rent Epoch: ${accountInfo.rentEpoch}`,
170
+ ``
171
+ ];
172
+ if (accountInfo.executable) {
173
+ lines.push(`⚡ **Executable Program**`);
174
+ lines.push(``);
175
+ }
176
+ if (encoding === 'jsonParsed' && accountInfo.data.parsed) {
177
+ lines.push(`**Parsed Data:**`);
178
+ lines.push('```json');
179
+ lines.push(JSON.stringify(accountInfo.data.parsed, null, 2));
180
+ lines.push('```');
181
+ }
182
+ else if (accountInfo.data.length > 0 && accountInfo.data.length <= 100) {
183
+ lines.push(`**Data (first 100 bytes):**`);
184
+ lines.push(`${accountInfo.data.toString('hex').slice(0, 200)}...`);
185
+ }
186
+ return {
187
+ content: [{ type: 'text', text: lines.join('\n') }]
188
+ };
189
+ }
190
+ catch (error) {
191
+ return {
192
+ content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
193
+ isError: true
194
+ };
195
+ }
196
+ });
197
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerExtendedAssetTools(server: McpServer): void;
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
+ import { formatAddress } from '../utils/formatters.js';
4
+ import { noApiKeyResponse } from './shared.js';
5
+ export function registerExtendedAssetTools(server) {
6
+ server.tool('getAssetsByGroup', 'Get NFTs in a collection', { groupKey: z.string(), groupValue: z.string(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async ({ groupKey, groupValue, page, limit }) => {
7
+ if (!hasApiKey())
8
+ return noApiKeyResponse();
9
+ const response = await getHeliusClient().getAssetsByGroup({ groupKey, groupValue, page, limit });
10
+ const items = response.items || [];
11
+ const total = response.total || 0;
12
+ if (items.length === 0)
13
+ return { content: [{ type: 'text', text: 'No assets found' }] };
14
+ const lines = [`**Collection: ${total} total**`, ``];
15
+ items.slice(0, 15).forEach((a, i) => {
16
+ const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
17
+ lines.push(`${i + 1}. ${name}`);
18
+ lines.push(` ${formatAddress(a.id)}`);
19
+ });
20
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
21
+ });
22
+ server.tool('searchAssets', 'Search NFTs with filters', { creatorAddress: z.string().optional(), ownerAddress: z.string().optional(), burnt: z.boolean().optional(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async (params) => {
23
+ if (!hasApiKey())
24
+ return noApiKeyResponse();
25
+ const response = await getHeliusClient().searchAssets(params);
26
+ const items = response.items || [];
27
+ if (items.length === 0)
28
+ return { content: [{ type: 'text', text: 'No results' }] };
29
+ const lines = [`**Found ${response.total || 0} assets**`, ``];
30
+ items.slice(0, 10).forEach((a, i) => {
31
+ const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
32
+ lines.push(`${i + 1}. ${name}`);
33
+ });
34
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
35
+ });
36
+ server.tool('getAssetProof', 'Get Merkle proof for cNFT', { id: z.string() }, async ({ id }) => {
37
+ if (!hasApiKey())
38
+ return noApiKeyResponse();
39
+ const proof = await getHeliusClient().getAssetProof({ id });
40
+ return { content: [{ type: 'text', text: `**Proof for ${formatAddress(id)}**\n\nLength: ${proof.proof ? proof.proof.length : 0} nodes` }] };
41
+ });
42
+ server.tool('getAssetsByCreator', 'Get NFTs by creator', { creatorAddress: z.string(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async (params) => {
43
+ if (!hasApiKey())
44
+ return noApiKeyResponse();
45
+ const response = await getHeliusClient().getAssetsByCreator(params);
46
+ const items = response.items || [];
47
+ const lines = [`**${response.total || 0} assets by creator**`, ``];
48
+ items.slice(0, 10).forEach((a, i) => {
49
+ const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
50
+ lines.push(`${i + 1}. ${name}`);
51
+ });
52
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
53
+ });
54
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAssetTools(server: McpServer): void;
@@ -0,0 +1,156 @@
1
+ import { z } from 'zod';
2
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
+ import { formatAddress } from '../utils/formatters.js';
4
+ import { noApiKeyResponse } from './shared.js';
5
+ export function registerAssetTools(server) {
6
+ // Get Assets by Owner (NFTs and tokens via DAS)
7
+ server.tool('getAssetsByOwner', 'Get all NFTs and digital assets owned by a Solana wallet using the DAS (Digital Asset Standard) API. Returns asset names, types (NFT, cNFT, Fungible, etc.), and mint addresses. Supports both regular NFTs and compressed NFTs (cNFTs). Use this to see what NFTs/collectibles a wallet owns. For fungible token balances, use getTokenBalances instead.', {
8
+ address: z.string().describe('Solana wallet address (base58 encoded)'),
9
+ limit: z.number().optional().default(20).describe('Number of assets to return (default 20). Increase for wallets with many NFTs.')
10
+ }, async ({ address, limit }) => {
11
+ if (!hasApiKey())
12
+ return noApiKeyResponse();
13
+ const helius = getHeliusClient();
14
+ const response = await helius.getAssetsByOwner({
15
+ ownerAddress: address,
16
+ page: 1,
17
+ limit
18
+ });
19
+ if (!response.items || response.items.length === 0) {
20
+ return {
21
+ content: [{
22
+ type: 'text',
23
+ text: `**Assets for ${formatAddress(address)}**\n\nNo assets found.`
24
+ }]
25
+ };
26
+ }
27
+ const items = response.items;
28
+ // Enrich assets missing name/symbol
29
+ const unknownAssets = items.filter((asset) => !asset.content?.metadata?.name && !asset.content?.metadata?.symbol && !asset.token_info?.symbol);
30
+ if (unknownAssets.length > 0 && unknownAssets.length <= 10) {
31
+ const enrichedData = await Promise.allSettled(unknownAssets.map((asset) => helius.getAsset({ id: asset.id })));
32
+ enrichedData.forEach((result, idx) => {
33
+ if (result.status === 'fulfilled' && result.value) {
34
+ const enriched = result.value;
35
+ const original = unknownAssets[idx];
36
+ if (enriched.content?.metadata) {
37
+ original.content = original.content || {};
38
+ original.content.metadata = enriched.content.metadata;
39
+ }
40
+ if (enriched.token_info?.symbol) {
41
+ original.token_info = original.token_info || {};
42
+ original.token_info.symbol = enriched.token_info.symbol;
43
+ }
44
+ }
45
+ });
46
+ }
47
+ const lines = [`**Assets for ${formatAddress(address)}** (${response.total} total)`, ''];
48
+ items.forEach((asset) => {
49
+ const name = asset.content?.metadata?.name || asset.content?.metadata?.symbol || asset.token_info?.symbol || 'Unnamed';
50
+ lines.push(`- **${name}** (${asset.interface})`);
51
+ lines.push(` ID: ${formatAddress(asset.id)}`);
52
+ });
53
+ return {
54
+ content: [{
55
+ type: 'text',
56
+ text: lines.join('\n')
57
+ }]
58
+ };
59
+ });
60
+ // Get Single Asset Details
61
+ server.tool('getAsset', 'Get detailed information about a specific NFT, token, or digital asset by its mint address. Returns name, symbol, description, image URL, current owner, creators/deployer addresses, authorities, token supply, decimals, royalties, and whether the token is mutable. Use this to find who created/deployed a token, verify token details, or get full NFT metadata.', {
62
+ id: z.string().describe('Asset mint address / ID (base58 encoded). This is the unique identifier for the NFT or token.')
63
+ }, async ({ id }) => {
64
+ if (!hasApiKey())
65
+ return noApiKeyResponse();
66
+ const helius = getHeliusClient();
67
+ const asset = await helius.getAsset({ id });
68
+ if (!asset) {
69
+ return {
70
+ content: [{
71
+ type: 'text',
72
+ text: `Asset ${formatAddress(id)} not found.`
73
+ }]
74
+ };
75
+ }
76
+ const metadata = asset.content?.metadata;
77
+ const tokenInfo = asset.token_info;
78
+ const lines = [
79
+ `**Asset: ${metadata?.name || tokenInfo?.symbol || 'Unnamed'}**`,
80
+ '',
81
+ `**Mint Address:** ${asset.id}`,
82
+ `**Type:** ${asset.interface}`,
83
+ ];
84
+ if (metadata?.symbol || tokenInfo?.symbol) {
85
+ lines.push(`**Symbol:** ${metadata?.symbol || tokenInfo?.symbol}`);
86
+ }
87
+ if (asset.ownership?.owner) {
88
+ lines.push(`**Owner:** ${formatAddress(asset.ownership.owner)}`);
89
+ }
90
+ if (metadata?.description) {
91
+ lines.push(`**Description:** ${metadata.description}`);
92
+ }
93
+ // Creators/Deployer info
94
+ if (asset.creators && asset.creators.length > 0) {
95
+ lines.push('', '**Creators:**');
96
+ asset.creators.forEach((creator) => {
97
+ const verified = creator.verified ? '✓' : '✗';
98
+ lines.push(`- ${formatAddress(creator.address)} (${creator.share}% share) ${verified}`);
99
+ });
100
+ }
101
+ // Authorities
102
+ if (asset.authorities && asset.authorities.length > 0) {
103
+ lines.push('', '**Authorities:**');
104
+ asset.authorities.forEach((auth) => {
105
+ lines.push(`- ${formatAddress(auth.address)} [${auth.scopes.join(', ')}]`);
106
+ });
107
+ }
108
+ // Token info (for fungible tokens)
109
+ if (tokenInfo) {
110
+ lines.push('', '**Token Info:**');
111
+ if (tokenInfo.decimals !== undefined) {
112
+ lines.push(`- Decimals: ${tokenInfo.decimals}`);
113
+ }
114
+ if (tokenInfo.supply !== undefined) {
115
+ const formattedSupply = tokenInfo.decimals
116
+ ? (tokenInfo.supply / Math.pow(10, tokenInfo.decimals)).toLocaleString()
117
+ : tokenInfo.supply.toLocaleString();
118
+ lines.push(`- Supply: ${formattedSupply}`);
119
+ }
120
+ if (tokenInfo.token_program) {
121
+ const programName = tokenInfo.token_program === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
122
+ ? 'SPL Token'
123
+ : tokenInfo.token_program === 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
124
+ ? 'Token-2022'
125
+ : formatAddress(tokenInfo.token_program);
126
+ lines.push(`- Program: ${programName}`);
127
+ }
128
+ if (tokenInfo.price_info?.price_per_token) {
129
+ lines.push(`- Price: $${tokenInfo.price_info.price_per_token.toFixed(6)} ${tokenInfo.price_info.currency || 'USD'}`);
130
+ }
131
+ }
132
+ if (asset.content?.links?.image) {
133
+ lines.push(`**Image:** ${asset.content.links.image}`);
134
+ }
135
+ if (asset.royalty && asset.royalty.percent > 0) {
136
+ lines.push(`**Royalty:** ${asset.royalty.percent}%`);
137
+ }
138
+ // Mutable/burnt status
139
+ const flags = [];
140
+ if (asset.mutable === false)
141
+ flags.push('Immutable');
142
+ if (asset.mutable === true)
143
+ flags.push('Mutable');
144
+ if (asset.burnt)
145
+ flags.push('Burnt');
146
+ if (flags.length > 0) {
147
+ lines.push(`**Status:** ${flags.join(', ')}`);
148
+ }
149
+ return {
150
+ content: [{
151
+ type: 'text',
152
+ text: lines.join('\n')
153
+ }]
154
+ };
155
+ });
156
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerBalanceTools(server: McpServer): void;