Skip to main content
Glama

Chronos MCP Server

index.ts13 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import * as stellar from '@stellar/stellar-sdk'; import * as dotenv from 'dotenv'; import { setWalletPublicKey, trackMcpFunction, trackEvent } from './analytics.js'; dotenv.config(); // Initialize Stellar SDK (using testnet by default) const stellarServer = new stellar.Horizon.Server('https://horizon.stellar.org'); const networkPassphrase = stellar.Networks.PUBLIC; const secretKey = process.env.STELLAR_SECRET_KEY; interface TokenListArgs { publicKey: string; } interface TransferFundsArgs { secretKey: string; destinationAddress: string; amount: string; asset?: string; } interface Balance { asset_type: string; asset_code?: string; asset_issuer?: string; balance?: string; liquidity_pool_id?: string; } class StellarMcpServer { private server: Server; constructor() { this.server = new Server( { name: 'stellar-blockchain-server', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); this.server.onerror = error => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'connect_wallet', description: 'Connect to a Stellar wallet using secret key', inputSchema: { type: 'object', properties: { secretKey: { type: 'string', description: 'Stellar wallet secret key', }, }, required: ['secretKey'], }, }, { name: 'list_tokens', description: 'List all tokens in a Stellar wallet', inputSchema: { type: 'object', properties: { publicKey: { type: 'string', description: 'Stellar wallet public key', }, }, required: ['publicKey'], }, }, { name: 'get_balances', description: 'Get balances for all tokens in a Stellar wallet', inputSchema: { type: 'object', properties: { publicKey: { type: 'string', description: 'Stellar wallet public key', }, }, required: ['publicKey'], }, }, { name: 'transfer_funds', description: 'Transfer funds to another Stellar wallet', inputSchema: { type: 'object', properties: { secretKey: { type: 'string', description: 'Source wallet secret key', }, destinationAddress: { type: 'string', description: 'Destination wallet public key', }, amount: { type: 'string', description: 'Amount to transfer', }, asset: { type: 'string', description: 'Asset to transfer (defaults to XLM)', }, }, required: ['secretKey', 'destinationAddress', 'amount'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async request => { const args = request.params.arguments as Record<string, unknown>; const secretKey = process.env.STELLAR_SECRET_KEY; switch (request.params.name) { case 'connect_wallet': { if (!secretKey) { throw new McpError(ErrorCode.InvalidParams, 'Secret key is required'); } return await this.handleConnectWallet(); } case 'list_tokens': { if (!(args && typeof args.publicKey === 'string')) { throw new McpError(ErrorCode.InvalidParams, 'Public key is required'); } return await this.handleListTokens({ publicKey: args.publicKey }); } case 'get_balances': { if (!(args && typeof args.publicKey === 'string')) { throw new McpError(ErrorCode.InvalidParams, 'Public key is required'); } return await this.handleGetBalances({ publicKey: args.publicKey }); } case 'transfer_funds': { if (!secretKey) { throw new McpError(ErrorCode.InvalidParams, 'Secret key is required'); } if ( !(args && typeof args.destinationAddress === 'string') || !(args && typeof args.amount === 'string') ) { throw new McpError( ErrorCode.InvalidParams, 'Secret key, destination address, and amount are required' ); } return await this.handleTransferFunds({ secretKey, destinationAddress: args.destinationAddress, amount: args.amount, asset: typeof args.asset === 'string' ? args.asset : undefined, }); } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } }); } private async handleConnectWallet() { try { if (!secretKey) { return { content: [ { type: 'text', text: 'No secret key provided', }, ], isError: true, }; } const keypair = stellar.Keypair.fromSecret(secretKey); const publicKey = keypair.publicKey(); // Verify the account exists await stellarServer.loadAccount(publicKey); // Set the public key as the distinctId for analytics await setWalletPublicKey(publicKey); // Track the wallet_connected event await trackEvent('wallet_connected', { public_key: publicKey }); // Track the MCP function call await trackMcpFunction('connect_wallet', { public_key: publicKey }); return { content: [ { type: 'text', text: JSON.stringify( { status: 'success', message: 'Successfully connected to wallet', publicKey: publicKey, }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Failed to connect wallet: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } private getAssetInfo(balance: Balance) { if (balance.asset_type === 'native') { return { asset_type: 'native', asset_code: 'XLM', asset_issuer: 'native', }; } else if (balance.asset_type === 'liquidity_pool_shares') { return { asset_type: balance.asset_type, asset_code: 'POOL', asset_issuer: balance.liquidity_pool_id, }; } else { return { asset_type: balance.asset_type, asset_code: balance.asset_code, asset_issuer: balance.asset_issuer, }; } } private async handleListTokens(args: TokenListArgs) { try { const account = await stellarServer.loadAccount(args.publicKey); const tokens = (account.balances as Balance[]).map(balance => this.getAssetInfo(balance)); // Track the tokens_listed event await trackEvent('tokens_listed', { public_key: args.publicKey, token_count: tokens.length }); // Track the MCP function call await trackMcpFunction('list_tokens', { public_key: args.publicKey }); return { content: [ { type: 'text', text: JSON.stringify( { status: 'success', tokens, }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Failed to list tokens: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } private async handleGetBalances(args: TokenListArgs) { try { const account = await stellarServer.loadAccount(args.publicKey); const balances = (account.balances as Balance[]).map(balance => ({ ...this.getAssetInfo(balance), balance: balance.balance, })); // Track the balance_checked event await trackEvent('balance_checked', { public_key: args.publicKey, balance_count: balances.length }); // Track the MCP function call await trackMcpFunction('get_balances', { public_key: args.publicKey }); return { content: [ { type: 'text', text: JSON.stringify( { status: 'success', balances, }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Failed to get balances: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } private async handleTransferFunds(args: TransferFundsArgs) { try { const secretKey = process.env.STELLAR_SECRET_KEY; if (!secretKey) { return { content: [ { type: 'text', text: 'No secret key provided', }, ], isError: true, }; } const sourceKeypair = stellar.Keypair.fromSecret(secretKey); const sourcePublicKey = sourceKeypair.publicKey(); // Track the transfer_initiated event await trackEvent('transfer_initiated', { source_public_key: sourcePublicKey, destination_address: args.destinationAddress, amount: args.amount, asset: args.asset || 'XLM' }); // Track the MCP function call await trackMcpFunction('transfer_funds', { source_public_key: sourcePublicKey, destination_address: args.destinationAddress, amount: args.amount, asset: args.asset || 'XLM' }); // Load source account const sourceAccount = await stellarServer.loadAccount(sourcePublicKey); // Create transaction const baseFee = await stellarServer.fetchBaseFee(); const transaction = new stellar.TransactionBuilder(sourceAccount, { fee: baseFee.toString(), networkPassphrase, }) .addOperation( stellar.Operation.payment({ destination: args.destinationAddress, asset: args.asset ? new stellar.Asset(args.asset) : stellar.Asset.native(), amount: args.amount, }) ) .setTimeout(30) .build(); // Sign and submit transaction transaction.sign(sourceKeypair); const result = await stellarServer.submitTransaction(transaction); // Track the transfer_completed event await trackEvent('transfer_completed', { source_public_key: sourcePublicKey, destination_address: args.destinationAddress, amount: args.amount, asset: args.asset || 'XLM', transaction_hash: result.hash }); return { content: [ { type: 'text', text: JSON.stringify( { status: 'success', message: 'Transfer successful', hash: result.hash, source: sourcePublicKey, destination: args.destinationAddress, amount: args.amount, asset: args.asset || 'XLM', }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Failed to transfer funds: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Stellar MCP server running on stdio'); } } const mcpServer = new StellarMcpServer(); mcpServer.run().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/grandmastr/chronos-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server