Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/some-areas-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sei-js/mcp-server": patch
---

Disable wallet based tools by default, add ability to add more wallet providers
Binary file modified .yarn/install-state.gz
Binary file not shown.
11 changes: 10 additions & 1 deletion packages/mcp-server/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Sei MCP Server Environment Variables

# Wallet Configuration
# Wallet mode: private-key | dynamic | porto | disabled
# - disabled: Disable all wallet functionality (default - safest for production)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, how will this work for dynamic and porto modes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It extends that same WalletProvider interface and adds a new wallet mode "dynamic"

# - private-key: Use PRIVATE_KEY environment variable
WALLET_MODE=disabled

@dssei dssei Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assume we are in readonly mode if no private key is set?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does default to disabled if no private key. We could make it not required for the wallet mode, but with dynamic added we will still need a wallet mode for that.


# Private key for blockchain transactions (without 0x prefix)
# This is used when no private key is provided in the request
# Used when WALLET_MODE=private-key
# SECURITY: Never commit your actual private key to version control
PRIVATE_KEY=your_private_key_here

# Wallet API Key (future use with wallet providers)
# WALLET_API_KEY=your_wallet_api_key_here
22 changes: 22 additions & 0 deletions packages/mcp-server/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ const require = createRequire(import.meta.url);
const args = process.argv.slice(2);
const httpMode = args.includes('--http') || args.includes('-h');

// Parse wallet mode argument
let walletMode = null;
const walletModeIndex = args.findIndex(arg => arg === '--wallet-mode');
if (walletModeIndex !== -1 && walletModeIndex + 1 < args.length) {
walletMode = args[walletModeIndex + 1];
}

// Set environment variables based on CLI arguments
if (walletMode) {
const validModes = ['private-key', 'disabled'];
if (validModes.includes(walletMode)) {
process.env.WALLET_MODE = walletMode;
} else {
console.error(`Invalid wallet mode: ${walletMode}. Valid modes are: ${validModes.join(', ')}`);
process.exit(1);
}
}

// Determine which file to execute
const scriptPath = resolve(__dirname, '../dist/esm', httpMode ? '/server/http-server.js' : 'index.js');

Expand Down Expand Up @@ -45,6 +63,10 @@ try {
} catch (error) {
console.error('Error: Server files not found. The package may not be built correctly.');
console.error('Please try reinstalling the package or contact the maintainers.');
console.error('\nUsage:');
console.error(' mcp-server # Start in STDIO mode (default)');
console.error(' mcp-server --http # Start in HTTP mode');
console.error(' mcp-server --wallet-mode MODE # Set wallet mode (private-key|dynamic|porto|disabled)');
console.error(error);
process.exit(1);
}
2 changes: 2 additions & 0 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.21.2",
"jest": "^30.0.3",
"tsx": "^4.20.3",
"viem": "^2.30.5",
"zod": "^3.24.2"
},
Expand Down
27 changes: 25 additions & 2 deletions packages/mcp-server/src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import type { Hex } from 'viem';
// Load environment variables from .env file
dotenv.config();

// Wallet mode types
export type WalletMode = 'private-key' | 'disabled';

// Define environment variable schema
const envSchema = z.object({
PRIVATE_KEY: z.string().optional()
PRIVATE_KEY: z.string().optional(),
WALLET_MODE: z.enum(['private-key', 'disabled']).default('disabled'),
WALLET_API_KEY: z.string().optional() // Used for wallet providers
});

// Parse and validate environment variables
Expand All @@ -23,7 +28,9 @@ export const formatPrivateKey = (key?: string): string | undefined => {

// Export validated environment variables with formatted private key
export const config = {
privateKey: env.success ? formatPrivateKey(env.data.PRIVATE_KEY) : undefined
privateKey: env.success ? formatPrivateKey(env.data.PRIVATE_KEY) : undefined,
walletMode: (env.success ? env.data.WALLET_MODE : 'disabled') as WalletMode,
walletApiKey: env.success ? env.data.WALLET_API_KEY : undefined
};

/**
Expand All @@ -34,3 +41,19 @@ export const config = {
export function getPrivateKeyAsHex(): Hex | undefined {
return config.privateKey as Hex | undefined;
}

/**
* Check if wallet functionality is enabled based on configuration
* @returns True if wallet functionality should be available
*/
export function isWalletEnabled(): boolean {
return config.walletMode !== 'disabled';
}

/**
* Get the current wallet mode
* @returns The configured wallet mode
*/
export function getWalletMode(): WalletMode {
return config.walletMode;
}
94 changes: 82 additions & 12 deletions packages/mcp-server/src/core/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { DEFAULT_NETWORK } from './chains.js';
import { isWalletEnabled } from './config.js';

/**
* Register all EVM-related prompts with the MCP server
* @param server The MCP server instance
*/
export function registerEVMPrompts(server: McpServer) {
// Register read-only prompts (always available)
registerReadOnlyPrompts(server);

// Register wallet-dependent prompts (only if wallet is enabled)
if (isWalletEnabled()) {
registerWalletPrompts(server);
} else {
console.error('Wallet functionality is disabled. Wallet-dependent prompts will not be available.');
}
}

/**
* Register read-only prompts that don't require wallet functionality
* @param server The MCP server instance
*/
function registerReadOnlyPrompts(server: McpServer) {
// Basic block explorer prompt
server.prompt(
'explore_block',
Expand Down Expand Up @@ -57,18 +74,7 @@ export function registerEVMPrompts(server: McpServer) {
})
);

// Get wallet address from private key prompt
server.prompt('my_wallet_address', 'What is my wallet EVM address', {}, () => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: 'Please retrieve my wallet EVM address using tools get_address_from_private_key via MCP server.'
}
}
]
}));


// Address analysis prompt
server.prompt(
Expand Down Expand Up @@ -199,3 +205,67 @@ export function registerEVMPrompts(server: McpServer) {
}
);
}

/**
* Register wallet-dependent prompts that require wallet functionality
* @param server The MCP server instance
*/
function registerWalletPrompts(server: McpServer) {
// Get wallet address from private key prompt
server.prompt('my_wallet_address', 'What is my wallet EVM address', {}, () => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: 'Please retrieve my wallet EVM address using tools get_address_from_private_key via MCP server.'
}
}
]
}));

// Send transaction prompt
server.prompt(
'send_transaction_guidance',
'Get guidance on sending a transaction',
{
toAddress: z.string().describe('The recipient address'),
amount: z.string().describe('The amount to send (in SEI)'),
network: z.string().optional().describe('Network name or chain ID. Defaults to Sei mainnet.')
},
({ toAddress, amount, network = DEFAULT_NETWORK }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I want to send ${amount} SEI to ${toAddress} on the ${network} network. Please guide me through this process, including checking my balance first, estimating gas, and executing the transaction safely.`
}
}
]
})
);

// Token transfer guidance
server.prompt(
'token_transfer_guidance',
'Get guidance on transferring tokens',
{
tokenAddress: z.string().describe('The token contract address'),
toAddress: z.string().describe('The recipient address'),
amount: z.string().describe('The amount to transfer'),
network: z.string().optional().describe('Network name or chain ID. Defaults to Sei mainnet.')
},
({ tokenAddress, toAddress, amount, network = DEFAULT_NETWORK }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I want to transfer ${amount} tokens from contract ${tokenAddress} to ${toAddress} on the ${network} network. Please guide me through this process, including checking my balance first, approving the token if needed, and executing the transfer safely.`
}
}
]
})
);
}
26 changes: 9 additions & 17 deletions packages/mcp-server/src/core/services/clients.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { http, type Address, type Hex, type PublicClient, type WalletClient, createPublicClient, createWalletClient } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { DEFAULT_NETWORK, getChain, getRpcUrl } from '../chains.js';
import { getWalletProvider } from '../wallet/index.js';

// Cache for clients to avoid recreating them for each request
const clientCache = new Map<string, PublicClient>();
Expand Down Expand Up @@ -37,26 +38,17 @@ export function getPublicClient(network = DEFAULT_NETWORK): PublicClient {
}

/**
* Create a wallet client for a specific network and private key
* Get a wallet client using the configured wallet provider
*/
export function getWalletClient(privateKey: Hex, network = DEFAULT_NETWORK): WalletClient {
const chain = getChain(network);
const rpcUrl = getRpcUrl(network);
const account = privateKeyToAccount(privateKey);

return createWalletClient({
account,
chain,
transport: http(rpcUrl)
});
export async function getWalletClientFromProvider(network = DEFAULT_NETWORK): Promise<WalletClient> {
const walletProvider = getWalletProvider();
return walletProvider.getWalletClient(network);
}

/**
* Get an EVM address from a private key
* @param privateKey The private key in hex format (with or without 0x prefix)
* @returns The EVM address derived from the private key
* Get an EVM address from the configured wallet provider
*/
export function getAddressFromPrivateKey(privateKey: Hex): Address {
const account = privateKeyToAccount(privateKey);
return account.address;
export async function getAddressFromProvider(): Promise<Address> {
const walletProvider = getWalletProvider();
return walletProvider.getAddress();
}
6 changes: 3 additions & 3 deletions packages/mcp-server/src/core/services/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { GetLogsParameters, Hash, Hex, Log, ReadContractParameters, WriteContractParameters } from 'viem';
import { DEFAULT_NETWORK } from '../chains.js';
import { getPrivateKeyAsHex } from '../config.js';
import { getPublicClient, getWalletClient } from './clients.js';
import { getPublicClient, getWalletClientFromProvider } from './clients.js';
import * as services from './index.js';

/**
Expand All @@ -27,7 +27,7 @@ export async function writeContract(params: Record<string, any>, network = DEFAU
throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.');
}

const client = getWalletClient(key, network);
const client = await getWalletClientFromProvider(network);
return await client.writeContract(params as any);
}

Expand Down Expand Up @@ -75,7 +75,7 @@ export async function deployContract(
throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.');
}

const client = getWalletClient(key, network);
const client = await getWalletClientFromProvider(network);

if (!client.account) {
throw new Error('Wallet client account not available for contract deployment.');
Expand Down
Loading