diff --git a/server.json b/server.json index 1c52a64..54d7e59 100644 --- a/server.json +++ b/server.json @@ -157,24 +157,24 @@ } ], "packages": [ - { - "registryType": "npm", - "identifier": "@luziadev/mcp-server", - "version": "1.2.1", - "transport": { - "type": "stdio" - }, - "environmentVariables": [ - { - "name": "LUZIA_API_KEY", - "description": "Your Luzia API key (lz_ prefix)", - "isRequired": true, - "format": "string", - "isSecret": true - } - ] - } - ], + { + "registryType": "npm", + "identifier": "@luziadev/mcp-server", + "version": "1.2.1", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "name": "LUZIA_API_KEY", + "description": "Your Luzia API key (lz_ prefix)", + "isRequired": true, + "format": "string", + "isSecret": true + } + ] + } + ], "transports": ["stdio", "http"], "runtimeRequirements": { "node": ">=20.0.0" diff --git a/src/server.ts b/src/server.ts index dbd051c..ad6b050 100644 --- a/src/server.ts +++ b/src/server.ts @@ -26,14 +26,18 @@ import { } from './prompts/index.js' import { getLuziaClient } from './sdk.js' import { + executeGetBalance, executeGetExchanges, executeGetHistory, executeGetMarkets, + executeGetPricing, executeGetTicker, executeGetTickers, + getBalanceTool, getExchangesTool, getHistoryTool, getMarketsTool, + getPricingTool, getTickersTool, getTickerTool, } from './tools/index.js' @@ -81,7 +85,15 @@ function registerToolHandlers(server: Server): void { log.debug({}, 'Listing tools') return { - tools: [getTickerTool, getTickersTool, getHistoryTool, getExchangesTool, getMarketsTool], + tools: [ + getTickerTool, + getTickersTool, + getHistoryTool, + getExchangesTool, + getMarketsTool, + getBalanceTool, + getPricingTool, + ], } }) @@ -107,6 +119,12 @@ function registerToolHandlers(server: Server): void { case 'get_history': return executeGetHistory(args) + case 'get_balance': + return executeGetBalance() + + case 'get_pricing': + return executeGetPricing() + default: return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], diff --git a/src/tools/error-handler.ts b/src/tools/error-handler.ts index b4e9bf4..8479c33 100644 --- a/src/tools/error-handler.ts +++ b/src/tools/error-handler.ts @@ -2,7 +2,7 @@ * Shared error handling for MCP tools */ -import { LuziaError } from '@luziadev/sdk' +import { InsufficientBalanceError, LuziaError } from '@luziadev/sdk' import { z } from 'zod' export type ToolResult = { @@ -26,6 +26,19 @@ export function handleToolError(error: unknown, toolName: string): ToolResult { } } + // Handle insufficient balance before generic LuziaError (since it's a subclass) + if (error instanceof InsufficientBalanceError) { + return { + content: [ + { + type: 'text', + text: `Insufficient balance ($${error.balance} remaining). Top up your account to continue making requests: ${error.topUpUrl}`, + }, + ], + isError: true, + } + } + if (error instanceof LuziaError) { return handleLuziaError(error) } diff --git a/src/tools/get-balance.ts b/src/tools/get-balance.ts new file mode 100644 index 0000000..6f2bb01 --- /dev/null +++ b/src/tools/get-balance.ts @@ -0,0 +1,82 @@ +/** + * get_balance Tool + * + * Get the current user's balance and lifetime spending summary. + */ + +import { createLogger } from '../logging.js' +import { getLuziaClient } from '../sdk.js' +import { handleToolError, type ToolResult } from './error-handler.js' + +const log = createLogger({ module: 'tool:get-balance' }) + +/** + * Tool definition for MCP + */ +export const getBalanceTool = { + name: 'get_balance', + description: + 'Get your current Luzia account balance, lifetime spending, and a link to top up. Use this to check how much credit you have remaining before making requests.', + inputSchema: { + type: 'object' as const, + properties: {}, + }, +} + +/** + * Execute the get_balance tool + */ +export async function executeGetBalance(): Promise { + try { + log.debug({}, 'Fetching balance') + + const luzia = getLuziaClient() + const balance = await luzia.billing.getBalance() + + const response = formatBalanceResponse(balance) + + log.debug({ balance_units: balance.balance_units }, 'Balance fetched successfully') + + return { + content: [{ type: 'text', text: response }], + } + } catch (error) { + log.error({ error }, 'Failed to execute get_balance') + return handleToolError(error, 'get_balance') + } +} + +/** + * Format balance data for AI-friendly response + */ +function formatBalanceResponse(balance: { + balance_usd: string + balance_units: number + lifetime_spent_usd: string + lifetime_spent_units: number + free_credit_usd: string + top_up_url: string +}): string { + const isLow = balance.balance_units < 10_000 + const isDepleted = balance.balance_units <= 0 + const statusIcon = isDepleted ? '🔴' : isLow ? '🟠' : '🟢' + + const lines: string[] = [ + '## Account Balance', + '', + `${statusIcon} **Current Balance:** $${balance.balance_usd}`, + `- **Lifetime Spent:** $${balance.lifetime_spent_usd}`, + `- **Free Credit:** $${balance.free_credit_usd}`, + '', + ] + + if (isDepleted) { + lines.push('⚠️ **Your balance is depleted.** API requests will return 402 errors.') + lines.push(`Top up your balance at: ${balance.top_up_url}`) + } else if (isLow) { + lines.push('⚠️ **Your balance is low.** Consider topping up to avoid service interruption.') + lines.push(`Top up your balance at: ${balance.top_up_url}`) + } + + return lines.join('\n') +} diff --git a/src/tools/get-exchanges.ts b/src/tools/get-exchanges.ts index 691e080..9c2b79d 100644 --- a/src/tools/get-exchanges.ts +++ b/src/tools/get-exchanges.ts @@ -15,7 +15,8 @@ const log = createLogger({ module: 'tool:get-exchanges' }) */ export const getExchangesTool = { name: 'get_exchanges', - description: 'List all supported cryptocurrency exchanges with their current status.', + description: + 'List all supported cryptocurrency exchanges with their current status. Cost: $0.0001 per call.', inputSchema: { type: 'object' as const, properties: {}, diff --git a/src/tools/get-history.ts b/src/tools/get-history.ts index 74a7ff7..cf66a14 100644 --- a/src/tools/get-history.ts +++ b/src/tools/get-history.ts @@ -18,7 +18,7 @@ const log = createLogger({ module: 'tool:get-history' }) export const getHistoryTool = { name: 'get_history', description: - 'Get historical OHLCV (Open, High, Low, Close, Volume) candlestick data for a cryptocurrency trading pair. Returns time-series candle data useful for technical analysis, charting, and trend detection.', + 'Get historical OHLCV (Open, High, Low, Close, Volume) candlestick data for a cryptocurrency trading pair. Returns time-series candle data useful for technical analysis, charting, and trend detection. Cost: $0.001-$0.005 per call depending on limit.', inputSchema: { type: 'object' as const, properties: { diff --git a/src/tools/get-markets.ts b/src/tools/get-markets.ts index 75908fb..4e41c78 100644 --- a/src/tools/get-markets.ts +++ b/src/tools/get-markets.ts @@ -17,7 +17,7 @@ const log = createLogger({ module: 'tool:get-markets' }) export const getMarketsTool = { name: 'get_markets', description: - 'List available trading pairs (markets) for a specific exchange. Can filter by quote currency.', + 'List available trading pairs (markets) for a specific exchange. Can filter by quote currency. Cost: $0.0002 per call.', inputSchema: { type: 'object' as const, properties: { diff --git a/src/tools/get-pricing.ts b/src/tools/get-pricing.ts new file mode 100644 index 0000000..6e4a084 --- /dev/null +++ b/src/tools/get-pricing.ts @@ -0,0 +1,97 @@ +/** + * get_pricing Tool + * + * Get the full pricing table for all Luzia API endpoints. + */ + +import type { PricingResponse } from '@luziadev/sdk' +import { createLogger } from '../logging.js' +import { getLuziaClient } from '../sdk.js' +import { handleToolError, type ToolResult } from './error-handler.js' + +const log = createLogger({ module: 'tool:get-pricing' }) + +/** + * Tool definition for MCP + */ +export const getPricingTool = { + name: 'get_pricing', + description: + 'Get the full Luzia API pricing table. Shows the cost per request for each endpoint. Useful for understanding costs before making requests or estimating usage costs.', + inputSchema: { + type: 'object' as const, + properties: {}, + }, +} + +/** + * Execute the get_pricing tool + */ +export async function executeGetPricing(): Promise { + try { + log.debug({}, 'Fetching pricing') + + const luzia = getLuziaClient() + const pricing = await luzia.billing.getPricing() + + const response = formatPricingResponse(pricing) + + log.debug({}, 'Pricing fetched successfully') + + return { + content: [{ type: 'text', text: response }], + } + } catch (error) { + log.error({ error }, 'Failed to execute get_pricing') + return handleToolError(error, 'get_pricing') + } +} + +/** + * Format pricing data for AI-friendly response + */ +function formatPricingResponse(pricing: PricingResponse): string { + const lines: string[] = [ + '## Luzia API Pricing', + '', + `**Currency:** ${pricing.currency}`, + `**Free Credit:** $${pricing.free_credit_usd.toFixed(2)}`, + '', + '### REST API Endpoints', + '', + '| Endpoint | Cost | Unit |', + '|----------|------|------|', + ] + + for (const entry of pricing.rest) { + if (entry.tiers) { + // Tiered pricing (e.g., history endpoint) + const tierStr = entry.tiers.map((t) => `${t.limit}: $${t.cost_usd}`).join(' | ') + lines.push(`| ${entry.endpoint} | ${tierStr} | ${entry.unit} |`) + } else { + lines.push(`| ${entry.endpoint} | $${entry.cost_usd} | ${entry.unit} |`) + } + } + + lines.push('') + lines.push('### WebSocket') + lines.push('') + lines.push('| Description | Cost | Unit |') + lines.push('|-------------|------|------|') + + for (const entry of pricing.websocket) { + lines.push(`| ${entry.description} | $${entry.cost_usd} | ${entry.unit} |`) + } + + lines.push('') + lines.push('### Free Endpoints') + lines.push('') + for (const endpoint of pricing.free_endpoints) { + lines.push(`- ${endpoint}`) + } + + lines.push('') + lines.push('*All billing tools (get_balance, get_pricing) are free to use.*') + + return lines.join('\n') +} diff --git a/src/tools/get-ticker.ts b/src/tools/get-ticker.ts index e9f8bbd..959662b 100644 --- a/src/tools/get-ticker.ts +++ b/src/tools/get-ticker.ts @@ -18,7 +18,7 @@ const log = createLogger({ module: 'tool:get-ticker' }) export const getTickerTool = { name: 'get_ticker', description: - 'Get real-time price data for a specific cryptocurrency trading pair on a given exchange. Returns current price, bid/ask, 24h high/low, volume, and percentage change.', + 'Get real-time price data for a specific cryptocurrency trading pair on a given exchange. Returns current price, bid/ask, 24h high/low, volume, and percentage change. Cost: $0.0005 per call.', inputSchema: { type: 'object' as const, properties: { diff --git a/src/tools/get-tickers.ts b/src/tools/get-tickers.ts index bbc60a4..da7fd62 100644 --- a/src/tools/get-tickers.ts +++ b/src/tools/get-tickers.ts @@ -17,7 +17,7 @@ const log = createLogger({ module: 'tool:get-tickers' }) export const getTickersTool = { name: 'get_tickers', description: - 'Get real-time price data for multiple cryptocurrency pairs, optionally filtered by exchange or specific symbols. Returns a summary of prices with 24h changes.', + 'Get real-time price data for multiple cryptocurrency pairs, optionally filtered by exchange or specific symbols. Returns a summary of prices with 24h changes. Cost: $0.0002 per symbol.', inputSchema: { type: 'object' as const, properties: { diff --git a/src/tools/index.ts b/src/tools/index.ts index fbb4fa0..6f74570 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -4,9 +4,11 @@ * Exports all tool definitions and handlers. */ +export { executeGetBalance, getBalanceTool } from './get-balance.js' export { executeGetExchanges, getExchangesTool } from './get-exchanges.js' export { executeGetHistory, getHistoryTool } from './get-history.js' export { executeGetMarkets, getMarketsTool } from './get-markets.js' +export { executeGetPricing, getPricingTool } from './get-pricing.js' export { executeGetTicker, getTickerTool } from './get-ticker.js' export { executeGetTickers, getTickersTool } from './get-tickers.js' @@ -19,6 +21,8 @@ export const tools = [ { name: 'get_history', module: 'get-history' }, { name: 'get_exchanges', module: 'get-exchanges' }, { name: 'get_markets', module: 'get-markets' }, + { name: 'get_balance', module: 'get-balance' }, + { name: 'get_pricing', module: 'get-pricing' }, ] as const export type ToolName = (typeof tools)[number]['name']