Skip to main content
Glama
privateTools.js39.7 kB
/** * Private tools - Require authentication * These tools need valid API credentials configured in .env */ import { getExchange } from "../utils/exchangeManager.js"; import { getExchangeCredentials, SUPPORTED_EXCHANGES, DEFAULT_EXCHANGE, } from "../config/config.js"; import { withTimeout, DEFAULT_CCXT_TIMEOUT } from "../utils/withTimeout.js"; /** * Private tools definitions for MCP */ export const privateToolsDefinitions = [ { name: "account_balance", description: "Retrieve your complete account balance across all assets. Shows available, locked, and total balances for each currency you hold.", inputSchema: { type: "object", properties: { exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: [], }, }, { name: "list_accounts", description: "List all individual wallets/accounts in your exchange profile. Particularly useful for Coinbase which maintains separate wallets per currency.", inputSchema: { type: "object", properties: { exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: [], }, }, { name: "place_market_order", description: "Execute a market order immediately at the best available price. WARNING: Market orders have slippage risk. For Coinbase BUY orders, 'amount' specifies how much quote currency to spend (e.g., amount=10 means spend 10 USDC). For SELL orders, 'amount' is the quantity of base currency to sell. Minimum order size varies by exchange (typically ~$1-10 USD equivalent).", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Trading pair in format BASE/QUOTE (e.g., 'BTC/USDT')", }, side: { type: "string", description: "'buy' to purchase the base currency, 'sell' to sell it", enum: ["buy", "sell"], }, amount: { type: "number", description: "For Coinbase BUY: amount of quote currency to spend (min ~$1). For SELL or other exchanges: quantity of base currency to trade.", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "side", "amount"], }, }, { name: "place_limit_order", description: "Place a limit order that will only execute at your specified price or better. Use this to avoid slippage and control exact entry/exit prices. Order remains open until filled, canceled, or expired.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Trading pair in format BASE/QUOTE (e.g., 'BTC/USDT')", }, side: { type: "string", description: "'buy' to purchase at or below limit price, 'sell' to sell at or above limit price", enum: ["buy", "sell"], }, amount: { type: "number", description: "Quantity of base currency to buy or sell", }, price: { type: "number", description: "Maximum price for buy orders, minimum price for sell orders", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "side", "amount", "price"], }, }, { name: "cancel_order", description: "Cancel a specific pending order using its unique order ID. Note: Cannot cancel already filled or expired orders.", inputSchema: { type: "object", properties: { orderId: { type: "string", description: "Unique identifier of the order to cancel (obtained from order placement or order history)", }, symbol: { type: "string", description: "Trading pair (required by some exchanges, e.g., 'BTC/USDT')", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["orderId"], }, }, { name: "cancel_all_orders", description: "Cancel all pending orders for a specific trading pair or across your entire account. Use with caution - this action cannot be undone.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Trading pair to cancel orders for (e.g., 'BTC/USDT'). Omit to cancel ALL orders across all pairs.", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: [], }, }, { name: "set_leverage", description: "Configure leverage multiplier for futures trading. Higher leverage increases both potential profits and losses. WARNING: Use leverage responsibly - liquidation risk increases with leverage.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Futures trading pair (e.g., 'BTC/USDT')", }, leverage: { type: "number", description: "Leverage multiplier (common values: 1, 2, 5, 10, 20, 50, 100). Maximum leverage varies by exchange and symbol.", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "leverage"], }, }, { name: "set_margin_mode", description: "Switch between isolated and cross margin modes for futures trading. ISOLATED: Each position has separate margin. CROSS: All positions share account margin. WARNING: Cross margin risks your entire account balance.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Futures trading pair (e.g., 'BTC/USDT')", }, marginMode: { type: "string", description: "'isolated' = separate margin per position (recommended for risk management), 'cross' = shared margin across all positions", enum: ["isolated", "cross"], }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "marginMode"], }, }, { name: "place_futures_market_order", description: "Execute a futures market order immediately at current market price. Use 'reduceOnly' to close existing positions without opening new ones.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Futures trading pair (e.g., 'BTC/USDT')", }, side: { type: "string", description: "'buy' to go long (profit from price increases), 'sell' to go short (profit from price decreases)", enum: ["buy", "sell"], }, amount: { type: "number", description: "Contract quantity to trade (in base currency units)", }, reduceOnly: { type: "boolean", description: "If true, order will only reduce existing position size (cannot open new positions or increase size)", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "side", "amount"], }, }, { name: "place_futures_limit_order", description: "Place a futures limit order at specified price. Use 'postOnly' for maker-only orders (guaranteed no taker fees). Use 'reduceOnly' to close positions only.", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Futures trading pair (e.g., 'BTC/USDT')", }, side: { type: "string", description: "'buy' for long entry, 'sell' for short entry", enum: ["buy", "sell"], }, amount: { type: "number", description: "Contract quantity to trade (in base currency units)", }, price: { type: "number", description: "Limit price for order execution", }, reduceOnly: { type: "boolean", description: "If true, can only reduce position size (useful for take-profit/stop-loss)", }, postOnly: { type: "boolean", description: "If true, order will only act as maker (cancelled if would take liquidity). Typically pays lower fees.", }, exchange: { type: "string", description: `Exchange name (defaults to '${DEFAULT_EXCHANGE}' if not specified)`, enum: SUPPORTED_EXCHANGES, }, }, required: ["symbol", "side", "amount", "price"], }, }, { name: "transfer_funds", description: "Transfer funds between different account types (e.g., spot wallet to futures wallet, or vice versa). Required before trading in different markets.", inputSchema: { type: "object", properties: { currency: { type: "string", description: "Currency code to transfer (e.g., 'USDT', 'BTC', 'ETH')", }, amount: { type: "number", description: "Amount of currency to transfer", }, fromAccount: { type: "string", description: "Source account", enum: ["spot", "futures", "margin", "swap"], }, toAccount: { type: "string", description: "Destination account", enum: ["spot", "futures", "margin", "swap"], }, exchange: { type: "string", description: `Exchange to use (optional, defaults to ${DEFAULT_EXCHANGE})`, enum: SUPPORTED_EXCHANGES, }, }, required: ["currency", "amount", "fromAccount", "toAccount"], }, }, { name: "get_portfolios", description: "List all portfolios in your Coinbase account. Coinbase-specific feature for managing multiple portfolios. Each portfolio can contain different assets and trading strategies.", inputSchema: { type: "object", properties: { exchange: { type: "string", description: `Exchange to use (optional, defaults to ${DEFAULT_EXCHANGE})`, enum: SUPPORTED_EXCHANGES, }, }, required: [], }, }, { name: "get_portfolio_details", description: "Get detailed information about a specific portfolio including breakdown of holdings, allocations, and performance. Coinbase-specific feature.", inputSchema: { type: "object", properties: { portfolioId: { type: "string", description: "Portfolio UUID to fetch details for", }, exchange: { type: "string", description: `Exchange to use (optional, defaults to ${DEFAULT_EXCHANGE})`, enum: SUPPORTED_EXCHANGES, }, }, required: ["portfolioId"], }, }, { name: "edit_order", description: "Modify an existing open order (typically changes price and/or quantity). Not all exchanges support this - some require cancel and recreate. Use this to adjust limit orders without losing queue position.", inputSchema: { type: "object", properties: { orderId: { type: "string", description: "Order ID to modify", }, symbol: { type: "string", description: "Trading pair (e.g., 'BTC/USDT')", }, side: { type: "string", description: "'buy' or 'sell'", enum: ["buy", "sell"], }, amount: { type: "number", description: "New order quantity (optional - keep existing if not provided)", }, price: { type: "number", description: "New limit price (optional - keep existing if not provided)", }, exchange: { type: "string", description: `Exchange to use (optional, defaults to ${DEFAULT_EXCHANGE})`, enum: SUPPORTED_EXCHANGES, }, }, required: ["orderId", "symbol"], }, }, ]; /** * Private tools handlers */ export const privateToolsHandlers = { /** * Handler for account_balance */ account_balance: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); const balance = await withTimeout( exchange.fetchBalance(), DEFAULT_CCXT_TIMEOUT, 'fetchBalance' ); // Filter out zero balances const nonZeroBalances = {}; for (const [currency, data] of Object.entries(balance)) { if ( typeof data === "object" && data.total !== undefined && data.total > 0 ) { nonZeroBalances[currency] = { free: data.free, used: data.used, total: data.total, }; } } return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, timestamp: Date.now(), balances: nonZeroBalances, }, null, 2 ), }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error fetching account balance: ${error.message}`, }, ], }; } }, /** * Handler for list_accounts */ list_accounts: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); let accounts = []; // Try to fetch accounts if the exchange supports it if (exchange.has["fetchAccounts"]) { accounts = await withTimeout( exchange.fetchAccounts(), DEFAULT_CCXT_TIMEOUT, 'fetchAccounts' ); } else if (exchange.has["fetchBalance"]) { // Fallback: get balance and try to extract account information const balance = await withTimeout( exchange.fetchBalance(), DEFAULT_CCXT_TIMEOUT, 'fetchBalance' ); // For some exchanges, balance.info might contain account details if (balance.info && Array.isArray(balance.info)) { accounts = balance.info; } else if (balance.info && typeof balance.info === "object") { // Try to extract account/wallet structure from info accounts = [balance.info]; } else { // Create a single "default" account entry accounts = [ { id: "default", type: "spot", currency: null, info: balance, }, ]; } } else { return { isError: true, content: [ { type: "text", text: `Exchange ${exchangeName} does not support account listing`, }, ], }; } return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, timestamp: Date.now(), accounts: accounts, count: accounts.length, }, null, 2 ), }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error fetching accounts: ${error.message}`, }, ], }; } }, /** * Handler for place_market_order */ place_market_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); let order; // Special handling for Coinbase market buy orders if (exchangeName === 'coinbase' && args.side === 'buy') { // Coinbase requires "quoteOrderQty" parameter for market buy orders (spend X USDC to buy BTC) // The amount represents how much quote currency (e.g. USDC) to spend order = await exchange.createMarketBuyOrderWithCost( args.symbol, args.amount ); } else { // Standard market order for other exchanges or sell orders order = await exchange.createMarketOrder( args.symbol, args.side, args.amount ); } return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, order: order, }, null, 2 ), }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error placing market order: ${error.message}`, }, ], }; } }, /** * Handler for place_limit_order */ place_limit_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); const order = await exchange.createLimitOrder( args.symbol, args.side, args.amount, args.price ); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, order: order, }, null, 2 ), }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error placing limit order: ${error.message}`, }, ], }; } }, /** * Handler for cancel_order */ cancel_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); const result = await exchange.cancelOrder(args.orderId, args.symbol); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, result: result, }, null, 2 ), }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error canceling order: ${error.message}`, }, ], }; } }, /** * Handler for cancel_all_orders */ cancel_all_orders: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); if (!exchange.has.cancelAllOrders) { // Fallback: fetch open orders and cancel individually const openOrders = await withTimeout( exchange.fetchOpenOrders(args.symbol), DEFAULT_CCXT_TIMEOUT, 'fetchOpenOrders' ); const results = []; for (const order of openOrders) { try { const result = await exchange.cancelOrder(order.id, order.symbol); results.push(result); } catch (error) { results.push({ error: error.message, orderId: order.id }); } } return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, symbol: args.symbol || "all", canceled: results.length, results: results, }, null, 2 ), }, ], }; } const result = await exchange.cancelAllOrders(args.symbol); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, symbol: args.symbol || "all", result: result, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in cancel_all_orders:`, error.message); return { isError: true, content: [ { type: "text", text: `Error canceling all orders: ${error.message}` } ] }; } }, /** * Handler for set_leverage */ set_leverage: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); if (!exchange.has.setLeverage) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support setLeverage`, }, ], }; } const result = await exchange.setLeverage(args.leverage, args.symbol); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, symbol: args.symbol, leverage: args.leverage, result: result, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in set_leverage:`, error.message); return { isError: true, content: [ { type: "text", text: `Error setting leverage: ${error.message}` } ] }; } }, /** * Handler for set_margin_mode */ set_margin_mode: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); if (!exchange.has.setMarginMode) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support setMarginMode`, }, ], }; } const result = await exchange.setMarginMode(args.marginMode, args.symbol); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, symbol: args.symbol, marginMode: args.marginMode, result: result, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in set_margin_mode:`, error.message); return { isError: true, content: [ { type: "text", text: `Error setting margin mode: ${error.message}` } ] }; } }, /** * Handler for place_futures_market_order */ place_futures_market_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); const params = {}; if (args.reduceOnly !== undefined) { params.reduceOnly = args.reduceOnly; } const order = await exchange.createMarketOrder( args.symbol, args.side, args.amount, params ); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, type: "futures", order: order, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in place_futures_market_order:`, error.message); return { isError: true, content: [ { type: "text", text: `Error placing futures market order: ${error.message}` } ] }; } }, /** * Handler for place_futures_limit_order */ place_futures_limit_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); const params = {}; if (args.reduceOnly !== undefined) { params.reduceOnly = args.reduceOnly; } if (args.postOnly !== undefined) { params.postOnly = args.postOnly; } const order = await exchange.createLimitOrder( args.symbol, args.side, args.amount, args.price, params ); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, type: "futures", order: order, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in place_futures_limit_order:`, error.message); return { isError: true, content: [ { type: "text", text: `Error placing futures limit order: ${error.message}` } ] }; } }, /** * Handler for transfer_funds */ transfer_funds: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); if (!exchange.has.transfer) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support fund transfers`, }, ], }; } const result = await exchange.transfer( args.currency, args.amount, args.fromAccount, args.toAccount ); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, transfer: { currency: args.currency, amount: args.amount, from: args.fromAccount, to: args.toAccount, }, result: result, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in transfer_funds:`, error.message); return { isError: true, content: [ { type: "text", text: `Error transferring funds: ${error.message}` } ] }; } }, /** * Handler for get_portfolios */ get_portfolios: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } if (exchangeName !== 'coinbase') { return { isError: true, content: [ { type: "text", text: 'Portfolios are a Coinbase-specific feature. This tool only works with Coinbase exchange.', }, ], }; } const exchange = getExchange(exchangeName, credentials); if (!exchange.has.fetchPortfolios) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support fetchPortfolios`, }, ], }; } const portfolios = await exchange.fetchPortfolios(); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, count: portfolios ? portfolios.length : 0, portfolios: portfolios, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in get_portfolios:`, error.message); return { isError: true, content: [ { type: "text", text: `Error fetching portfolios: ${error.message}` } ] }; } }, /** * Handler for get_portfolio_details */ get_portfolio_details: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } if (exchangeName !== 'coinbase') { return { isError: true, content: [ { type: "text", text: 'Portfolio details are a Coinbase-specific feature. This tool only works with Coinbase exchange.', }, ], }; } const exchange = getExchange(exchangeName, credentials); // Coinbase uses a custom method for portfolio breakdown // This may require direct API calls or custom implementation if (!exchange.has.fetchPortfolio && !exchange.has.fetchPortfolios) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support portfolio details`, }, ], }; } // Try to fetch specific portfolio details // Note: This might need adjustment based on actual Coinbase API structure let portfolio; if (exchange.has.fetchPortfolio) { portfolio = await exchange.fetchPortfolio(args.portfolioId); } else { // Fallback: fetch all portfolios and filter const portfolios = await exchange.fetchPortfolios(); portfolio = portfolios.find(p => p.id === args.portfolioId); if (!portfolio) { return { isError: true, content: [ { type: "text", text: `Portfolio ${args.portfolioId} not found`, }, ], }; } } return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, portfolioId: args.portfolioId, portfolio: portfolio, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in get_portfolio_details:`, error.message); return { isError: true, content: [ { type: "text", text: `Error fetching portfolio details: ${error.message}` } ] }; } }, /** * Handler for edit_order */ edit_order: async (args) => { try { const exchangeName = args.exchange || DEFAULT_EXCHANGE; const credentials = getExchangeCredentials(exchangeName); if (!credentials) { return { isError: true, content: [ { type: "text", text: `No credentials configured for ${exchangeName}. Please set ${exchangeName.toUpperCase()}_API_KEY and ${exchangeName.toUpperCase()}_SECRET in .env file.`, }, ], }; } const exchange = getExchange(exchangeName, credentials); await exchange.loadMarkets(); if (!exchange.has.editOrder) { return { isError: true, content: [ { type: "text", text: `${exchangeName} does not support editOrder. Consider using cancel_order + place_limit_order instead.`, }, ], }; } // Build params object with only provided values const params = {}; if (args.amount !== undefined) { params.amount = args.amount; } if (args.price !== undefined) { params.price = args.price; } const order = await exchange.editOrder( args.orderId, args.symbol, undefined, // type (keep existing) args.side, args.amount, args.price, params ); return { content: [ { type: "text", text: JSON.stringify( { exchange: exchangeName, orderId: args.orderId, editedOrder: order, }, null, 2 ), }, ], }; } catch (error) { console.error(`❌ Error in edit_order:`, error.message); return { isError: true, content: [ { type: "text", text: `Error editing order: ${error.message}` } ] }; } }, };

Latest Blog Posts

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/carlosatta/mcp-server-ccxt'

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