secrets are now secure
This commit is contained in:
13
.env.example
Normal file
13
.env.example
Normal file
@@ -0,0 +1,13 @@
|
||||
# Network RPC URLs
|
||||
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
||||
ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-api-key
|
||||
ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc
|
||||
BASE_RPC_URL=https://mainnet.base.org
|
||||
OPTIMISM_RPC_URL=https://mainnet.optimism.io
|
||||
BSC_RPC_URL=https://bsc-dataseed.binance.org
|
||||
POLYGON_RPC_URL=https://polygon-rpc.com
|
||||
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
|
||||
|
||||
# Private keys (replace with your own)
|
||||
ETH_PRIVATE_KEY=your-ethereum-private-key
|
||||
SOLANA_PRIVATE_KEY=your-solana-private-key
|
||||
103
README.md
103
README.md
@@ -1,6 +1,6 @@
|
||||
# Web3 MCP
|
||||
|
||||
A Model-Context-Protocol server for interacting with multiple blockchains including Solana and Ethereum. This server provides simple RPC endpoints for common blockchain operations.
|
||||
A Model-Context-Protocol server for interacting with multiple blockchains including Solana and Ethereum. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -9,52 +9,119 @@ Solana Operations:
|
||||
- Check SOL account balances
|
||||
- Get detailed account information
|
||||
- Display keypair information
|
||||
- Transfer SOL between accounts
|
||||
- Transfer SOL between accounts (using private key from .env)
|
||||
- View SPL token balances
|
||||
- Get detailed SPL token account information
|
||||
|
||||
Ethereum Operations:
|
||||
- Check ETH account balances
|
||||
Ethereum & EVM Chain Operations:
|
||||
- Check native token balances across multiple networks
|
||||
- Check ERC-20 token balances
|
||||
- Send native tokens (using private key from .env)
|
||||
- Send ERC-20 tokens (using private key from .env)
|
||||
- Approve ERC-20 token spending (using private key from .env)
|
||||
|
||||
## Quickstart
|
||||
Supported EVM Networks:
|
||||
- Ethereum
|
||||
- Base
|
||||
- Arbitrum
|
||||
- Optimism
|
||||
- BSC (Binance Smart Chain)
|
||||
- Polygon
|
||||
- Avalanche
|
||||
|
||||
clone and install dependencies:
|
||||
## Setup
|
||||
|
||||
1. Clone and install dependencies:
|
||||
```bash
|
||||
git clone https://github.com/strangelove-ventures/web3-mcp.git
|
||||
cd web3-mcp
|
||||
npm install
|
||||
```
|
||||
|
||||
build the tool
|
||||
2. Create a .env file in the root directory:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
3. Configure your environment variables in .env:
|
||||
```env
|
||||
# Network RPC URLs (optional - will use public endpoints if not specified)
|
||||
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
||||
ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-api-key
|
||||
ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc
|
||||
BASE_RPC_URL=https://mainnet.base.org
|
||||
OPTIMISM_RPC_URL=https://mainnet.optimism.io
|
||||
BSC_RPC_URL=https://bsc-dataseed.binance.org
|
||||
POLYGON_RPC_URL=https://polygon-rpc.com
|
||||
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
|
||||
|
||||
# Private keys (required for transactions)
|
||||
ETH_PRIVATE_KEY=your-ethereum-private-key
|
||||
SOLANA_PRIVATE_KEY=your-base58-encoded-solana-private-key
|
||||
```
|
||||
|
||||
4. Build the tool:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
add the tool to your claude_desktop_config.json
|
||||
```
|
||||
5. Add the tool to your claude_desktop_config.json:
|
||||
```json
|
||||
{
|
||||
"web3-rpc": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/PATH/TO/web3-mcp/build/index.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Usage Examples
|
||||
|
||||
Ask Claude:
|
||||
- whats the latest slot on solana?
|
||||
- whats the balance of 62QXuWZ3WT6ws1ZFxJobVDVXn6bEsiYpLo5yG612U6u3?
|
||||
- Here's my test key [REPLACE WITH SECRET KEY]. let's transfer 0.001 SOL to [REPLACE WITH PUBLIC ADDRESS]
|
||||
- what's the balance of 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933?
|
||||
|
||||
## Security Note
|
||||
### Solana Operations
|
||||
- "What's the latest slot on Solana?"
|
||||
- "What's the balance of 62QXuWZ3WT6ws1ZFxJobVDVXn6bEsiYpLo5yG612U6u3?"
|
||||
- "Transfer 0.001 SOL to Cg6cVS4tjkxHthm3K9BHhmvqF7kSz8GnXqqYXnHBzGXd"
|
||||
- "Show me my SPL token balances for address 62QXuWZ3WT6ws1ZFxJobVDVXn6bEsiYpLo5yG612U6u3"
|
||||
|
||||
Only use this with a test wallet with a small amount of funds.
|
||||
### EVM Operations
|
||||
- "What's the ETH balance of 0x6b96f801ae7f7112e659181a84d2e22ebd0bc72a?"
|
||||
- "Check the USDC balance for 0x6b96f801ae7f7112e659181a84d2e22ebd0bc72a on Ethereum"
|
||||
- "Send 0.001 ETH to 0x6b96f801ae7f7112e659181a84d2e22ebd0bc72a"
|
||||
- "What's the current gas price on Arbitrum?"
|
||||
- "Send 100 USDC to 0x6b96f801ae7f7112e659181a84d2e22ebd0bc72a on Polygon"
|
||||
|
||||
## RPC Endpoint
|
||||
## Security Notes
|
||||
|
||||
The server connects to Solana's mainnet at `https://api.mainnet-beta.solana.com`. To use a different network (like devnet or testnet), modify the `SOLANA_RPC` constant in `src/index.ts`.
|
||||
1. **Environment Variables**: All private keys are stored in the .env file and never exposed in the conversation history
|
||||
2. **Private Keys**: Only use this with test wallets containing small amounts of funds
|
||||
3. **RPC Endpoints**: Custom RPC endpoints can be configured in the .env file for better reliability and rate limits
|
||||
4. **.env Security**: The .env file is automatically ignored by git to prevent accidental exposure of private keys
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom RPC Endpoints
|
||||
You can configure custom RPC endpoints in your .env file for better reliability and higher rate limits. If not specified, the tool will fall back to public RPC endpoints.
|
||||
|
||||
### Network Selection
|
||||
For EVM operations, you can specify the network by name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche). The tool will automatically use the appropriate RPC endpoint and network configuration.
|
||||
|
||||
## Development
|
||||
|
||||
To modify or extend the tool:
|
||||
|
||||
1. Source code is in the `src` directory
|
||||
2. Chain-specific code is in `src/chains`
|
||||
3. Run `npm run build` after making changes
|
||||
4. Use TypeScript for all new code
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please submit pull requests with any improvements or bug fixes.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 License
|
||||
@@ -50,6 +50,7 @@
|
||||
"@solana/spl-token": "^0.4.1",
|
||||
"@solana/web3.js": "^1.87.6",
|
||||
"bs58": "^5.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ethers": "^6.11.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
|
||||
@@ -14,49 +14,49 @@ interface NetworkConfig {
|
||||
const NETWORKS: { [key: string]: NetworkConfig } = {
|
||||
ethereum: {
|
||||
name: "Ethereum",
|
||||
rpc: "https://eth-mainnet.g.alchemy.com/v2/demo",
|
||||
rpc: process.env.ETH_RPC_URL || "https://eth-mainnet.g.alchemy.com/v2/demo",
|
||||
chainId: 1,
|
||||
currencySymbol: "ETH",
|
||||
explorer: "https://etherscan.io"
|
||||
},
|
||||
base: {
|
||||
name: "Base",
|
||||
rpc: "https://mainnet.base.org",
|
||||
rpc: process.env.BASE_RPC_URL || "https://mainnet.base.org",
|
||||
chainId: 8453,
|
||||
currencySymbol: "ETH",
|
||||
explorer: "https://basescan.org"
|
||||
},
|
||||
arbitrum: {
|
||||
name: "Arbitrum",
|
||||
rpc: "https://arb1.arbitrum.io/rpc",
|
||||
rpc: process.env.ARBITRUM_RPC_URL || "https://arb1.arbitrum.io/rpc",
|
||||
chainId: 42161,
|
||||
currencySymbol: "ETH",
|
||||
explorer: "https://arbiscan.io"
|
||||
},
|
||||
optimism: {
|
||||
name: "Optimism",
|
||||
rpc: "https://mainnet.optimism.io",
|
||||
rpc: process.env.OPTIMISM_RPC_URL || "https://mainnet.optimism.io",
|
||||
chainId: 10,
|
||||
currencySymbol: "ETH",
|
||||
explorer: "https://optimistic.etherscan.io"
|
||||
},
|
||||
bsc: {
|
||||
name: "BNB Smart Chain",
|
||||
rpc: "https://bsc-dataseed.binance.org",
|
||||
rpc: process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org",
|
||||
chainId: 56,
|
||||
currencySymbol: "BNB",
|
||||
explorer: "https://bscscan.com"
|
||||
},
|
||||
polygon: {
|
||||
name: "Polygon",
|
||||
rpc: "https://polygon-rpc.com",
|
||||
rpc: process.env.POLYGON_RPC_URL || "https://polygon-rpc.com",
|
||||
chainId: 137,
|
||||
currencySymbol: "MATIC",
|
||||
explorer: "https://polygonscan.com"
|
||||
},
|
||||
avalanche: {
|
||||
name: "Avalanche",
|
||||
rpc: "https://api.avax.network/ext/bc/C/rpc",
|
||||
rpc: process.env.AVALANCHE_RPC_URL || "https://api.avax.network/ext/bc/C/rpc",
|
||||
chainId: 43114,
|
||||
currencySymbol: "AVAX",
|
||||
explorer: "https://snowtrace.io"
|
||||
@@ -243,14 +243,13 @@ export function registerEthereumTools(server: McpServer) {
|
||||
// Send native tokens on any EVM network
|
||||
server.tool(
|
||||
"sendEvmTransaction",
|
||||
"Send native tokens on any supported EVM network",
|
||||
"Send native tokens on any supported EVM network (using private key from .env)",
|
||||
{
|
||||
privateKey: z.string().describe("Sender's private key"),
|
||||
toAddress: z.string().describe("Recipient's address"),
|
||||
amount: z.string().describe("Amount to send (in native tokens)"),
|
||||
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||
},
|
||||
async ({ privateKey, toAddress, amount, network }) => {
|
||||
async ({ toAddress, amount, network }) => {
|
||||
try {
|
||||
if (!NETWORKS[network]) {
|
||||
return {
|
||||
@@ -266,8 +265,13 @@ export function registerEthereumTools(server: McpServer) {
|
||||
const provider = providers[network];
|
||||
const networkConfig = NETWORKS[network];
|
||||
|
||||
// Get private key from environment variables
|
||||
if (!process.env.ETH_PRIVATE_KEY) {
|
||||
throw new Error('ETH_PRIVATE_KEY not found in environment variables');
|
||||
}
|
||||
|
||||
// Create wallet from private key
|
||||
const wallet = new Wallet(privateKey, provider);
|
||||
const wallet = new Wallet(process.env.ETH_PRIVATE_KEY, provider);
|
||||
const fromAddress = wallet.address;
|
||||
|
||||
// Get current gas price and nonce
|
||||
@@ -315,15 +319,14 @@ export function registerEthereumTools(server: McpServer) {
|
||||
// Send ERC-20 tokens on any EVM network
|
||||
server.tool(
|
||||
"sendEvmToken",
|
||||
"Send ERC-20 tokens on any supported EVM network",
|
||||
"Send ERC-20 tokens on any supported EVM network (using private key from .env)",
|
||||
{
|
||||
privateKey: z.string().describe("Sender's private key"),
|
||||
toAddress: z.string().describe("Recipient's address"),
|
||||
tokenAddress: z.string().describe("Token contract address"),
|
||||
amount: z.string().describe("Amount to send (in token units)"),
|
||||
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||
},
|
||||
async ({ privateKey, toAddress, tokenAddress, amount, network }) => {
|
||||
async ({ toAddress, tokenAddress, amount, network }) => {
|
||||
try {
|
||||
if (!NETWORKS[network]) {
|
||||
return {
|
||||
@@ -339,8 +342,13 @@ export function registerEthereumTools(server: McpServer) {
|
||||
const provider = providers[network];
|
||||
const networkConfig = NETWORKS[network];
|
||||
|
||||
// Get private key from environment variables
|
||||
if (!process.env.ETH_PRIVATE_KEY) {
|
||||
throw new Error('ETH_PRIVATE_KEY not found in environment variables');
|
||||
}
|
||||
|
||||
// Create wallet from private key
|
||||
const wallet = new Wallet(privateKey, provider);
|
||||
const wallet = new Wallet(process.env.ETH_PRIVATE_KEY, provider);
|
||||
const fromAddress = wallet.address;
|
||||
|
||||
// Create contract instance
|
||||
@@ -385,15 +393,14 @@ export function registerEthereumTools(server: McpServer) {
|
||||
// Approve ERC-20 token spending
|
||||
server.tool(
|
||||
"approveEvmToken",
|
||||
"Approve ERC-20 token spending on any supported EVM network",
|
||||
"Approve ERC-20 token spending on any supported EVM network (using private key from .env)",
|
||||
{
|
||||
privateKey: z.string().describe("Token holder's private key"),
|
||||
spenderAddress: z.string().describe("Address to approve for spending"),
|
||||
tokenAddress: z.string().describe("Token contract address"),
|
||||
amount: z.string().optional().describe("Amount to approve (in token units, defaults to unlimited)"),
|
||||
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||
},
|
||||
async ({ privateKey, spenderAddress, tokenAddress, amount, network }) => {
|
||||
async ({ spenderAddress, tokenAddress, amount, network }) => {
|
||||
try {
|
||||
if (!NETWORKS[network]) {
|
||||
return {
|
||||
@@ -409,8 +416,13 @@ export function registerEthereumTools(server: McpServer) {
|
||||
const provider = providers[network];
|
||||
const networkConfig = NETWORKS[network];
|
||||
|
||||
// Get private key from environment variables
|
||||
if (!process.env.ETH_PRIVATE_KEY) {
|
||||
throw new Error('ETH_PRIVATE_KEY not found in environment variables');
|
||||
}
|
||||
|
||||
// Create wallet from private key
|
||||
const wallet = new Wallet(privateKey, provider);
|
||||
const wallet = new Wallet(process.env.ETH_PRIVATE_KEY, provider);
|
||||
|
||||
// Create contract instance
|
||||
const contract = new Contract(tokenAddress, ERC20_ABI, wallet);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
|
||||
import {
|
||||
Connection,
|
||||
PublicKey,
|
||||
@@ -15,7 +18,7 @@ import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import bs58 from 'bs58';
|
||||
|
||||
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
||||
const SOLANA_RPC = process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
|
||||
|
||||
// Initialize Solana connection
|
||||
const solanaConnection = new Connection(SOLANA_RPC, 'confirmed');
|
||||
@@ -47,6 +50,13 @@ async function getTokenAccounts(walletAddress: string) {
|
||||
}
|
||||
|
||||
export function registerSolanaTools(server: McpServer) {
|
||||
// Debug: Check environment variables
|
||||
console.error('Debug - ENV vars available:', Object.keys(process.env));
|
||||
console.error('Debug - SOLANA_PRIVATE_KEY exists:', !!process.env.SOLANA_PRIVATE_KEY);
|
||||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||
console.error('Warning: SOLANA_PRIVATE_KEY not found in environment');
|
||||
}
|
||||
|
||||
server.tool(
|
||||
"getSlot",
|
||||
"Get the current slot",
|
||||
@@ -223,38 +233,75 @@ Data (${encoding}): ${formattedData}`,
|
||||
|
||||
server.tool(
|
||||
"transfer",
|
||||
"Transfer SOL from your keypair to another address",
|
||||
"Transfer SOL from your keypair (using private key from .env) to another address",
|
||||
{
|
||||
secretKey: z.string().describe("Your keypair's secret key (as comma-separated numbers or JSON array)"),
|
||||
toAddress: z.string().describe("Destination wallet address"),
|
||||
amount: z.number().positive().describe("Amount of SOL to send"),
|
||||
},
|
||||
async ({ secretKey, toAddress, amount }) => {
|
||||
async ({ toAddress, amount }) => {
|
||||
try {
|
||||
let fromKeypair: Keypair;
|
||||
try {
|
||||
const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim())));
|
||||
fromKeypair = Keypair.fromSecretKey(decoded);
|
||||
} catch {
|
||||
fromKeypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey)));
|
||||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||
throw new Error('SOLANA_PRIVATE_KEY not found in environment variables');
|
||||
}
|
||||
|
||||
let fromKeypair: Keypair;
|
||||
try {
|
||||
// Try to decode the private key from base58
|
||||
const privateKeyBytes = bs58.decode(process.env.SOLANA_PRIVATE_KEY);
|
||||
fromKeypair = Keypair.fromSecretKey(new Uint8Array(privateKeyBytes));
|
||||
|
||||
// Verify the keypair is valid
|
||||
if (!fromKeypair.publicKey) {
|
||||
throw new Error('Invalid keypair generated');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error decoding private key:', error);
|
||||
throw new Error('Failed to create keypair from private key. Please ensure the private key is a valid base58-encoded string.');
|
||||
}
|
||||
|
||||
// Convert SOL to lamports
|
||||
const lamports = amount * LAMPORTS_PER_SOL;
|
||||
|
||||
const transaction = new Transaction().add(
|
||||
// Validate destination address
|
||||
let destinationPubkey: PublicKey;
|
||||
try {
|
||||
destinationPubkey = new PublicKey(toAddress);
|
||||
} catch (error) {
|
||||
throw new Error('Invalid destination address');
|
||||
}
|
||||
|
||||
// Build transaction
|
||||
const transaction = new Transaction();
|
||||
|
||||
// Get recent blockhash
|
||||
const { blockhash } = await solanaConnection.getLatestBlockhash('finalized');
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = fromKeypair.publicKey;
|
||||
|
||||
// Add transfer instruction
|
||||
transaction.add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: fromKeypair.publicKey,
|
||||
toPubkey: new PublicKey(toAddress),
|
||||
toPubkey: destinationPubkey,
|
||||
lamports,
|
||||
})
|
||||
);
|
||||
|
||||
// Sign and send transaction
|
||||
try {
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
solanaConnection,
|
||||
transaction,
|
||||
[fromKeypair]
|
||||
[fromKeypair],
|
||||
{ commitment: 'confirmed' }
|
||||
);
|
||||
|
||||
// Verify the transaction was successful
|
||||
const confirmedTransaction = await solanaConnection.getTransaction(signature, {
|
||||
commitment: 'confirmed',
|
||||
});
|
||||
|
||||
if (!confirmedTransaction?.meta?.err) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@@ -268,13 +315,29 @@ Explorer URL: https://explorer.solana.com/tx/${signature}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
throw new Error('Transaction failed on-chain');
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
console.error('Transaction error:', err);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to transfer SOL: ${error.message}`,
|
||||
text: `Failed to transfer SOL: ${errorMessage}. Please check your wallet has sufficient SOL for the transfer and transaction fees.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Setup error:', err);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to setup transfer: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
17
src/env.d.ts
vendored
Normal file
17
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
// Solana
|
||||
SOLANA_RPC_URL: string;
|
||||
SOLANA_PRIVATE_KEY: string;
|
||||
|
||||
// Ethereum and other EVM chains
|
||||
ETH_RPC_URL: string;
|
||||
ETH_PRIVATE_KEY: string;
|
||||
BASE_RPC_URL: string;
|
||||
ARBITRUM_RPC_URL: string;
|
||||
OPTIMISM_RPC_URL: string;
|
||||
BSC_RPC_URL: string;
|
||||
POLYGON_RPC_URL: string;
|
||||
AVALANCHE_RPC_URL: string;
|
||||
}
|
||||
}
|
||||
22
src/index.ts
22
src/index.ts
@@ -1,3 +1,25 @@
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
// Get directory name for ES module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Check for .env file
|
||||
const envPath = resolve(__dirname, '../.env');
|
||||
console.error('Looking for .env file at:', envPath);
|
||||
console.error('.env file exists:', existsSync(envPath));
|
||||
|
||||
// Load environment variables
|
||||
const result = config({ path: envPath });
|
||||
if (result.error) {
|
||||
console.error('Error loading .env file:', result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { registerSolanaTools } from "./chains/solana.js";
|
||||
|
||||
Reference in New Issue
Block a user