Skip to content
Open
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
36 changes: 18 additions & 18 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 19 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
],
}
})

Expand All @@ -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}` }],
Expand Down
15 changes: 14 additions & 1 deletion src/tools/error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Shared error handling for MCP tools
*/

import { LuziaError } from '@luziadev/sdk'
import { InsufficientBalanceError, LuziaError } from '@luziadev/sdk'

Check failure on line 5 in src/tools/error-handler.ts

View workflow job for this annotation

GitHub Actions / test

Module '"@luziadev/sdk"' has no exported member 'InsufficientBalanceError'.
import { z } from 'zod'

export type ToolResult = {
Expand All @@ -26,6 +26,19 @@
}
}

// 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}`,

Check failure on line 35 in src/tools/error-handler.ts

View workflow job for this annotation

GitHub Actions / test

'error' is of type 'unknown'.

Check failure on line 35 in src/tools/error-handler.ts

View workflow job for this annotation

GitHub Actions / test

'error' is of type 'unknown'.
},
],
isError: true,
}
}

if (error instanceof LuziaError) {
return handleLuziaError(error)
}
Expand Down
82 changes: 82 additions & 0 deletions src/tools/get-balance.ts
Original file line number Diff line number Diff line change
@@ -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<ToolResult> {
try {
log.debug({}, 'Fetching balance')

const luzia = getLuziaClient()
const balance = await luzia.billing.getBalance()

Check failure on line 34 in src/tools/get-balance.ts

View workflow job for this annotation

GitHub Actions / test

Property 'billing' does not exist on type 'Luzia'.

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')
}
3 changes: 2 additions & 1 deletion src/tools/get-exchanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand Down
2 changes: 1 addition & 1 deletion src/tools/get-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/get-markets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
97 changes: 97 additions & 0 deletions src/tools/get-pricing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* get_pricing Tool
*
* Get the full pricing table for all Luzia API endpoints.
*/

import type { PricingResponse } from '@luziadev/sdk'

Check failure on line 7 in src/tools/get-pricing.ts

View workflow job for this annotation

GitHub Actions / test

Module '"@luziadev/sdk"' has no exported member 'PricingResponse'.
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<ToolResult> {
try {
log.debug({}, 'Fetching pricing')

const luzia = getLuziaClient()
const pricing = await luzia.billing.getPricing()

Check failure on line 35 in src/tools/get-pricing.ts

View workflow job for this annotation

GitHub Actions / test

Property 'billing' does not exist on type 'Luzia'.

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(' | ')

Check failure on line 69 in src/tools/get-pricing.ts

View workflow job for this annotation

GitHub Actions / test

Parameter 't' implicitly has an 'any' type.
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')
}
2 changes: 1 addition & 1 deletion src/tools/get-ticker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/get-tickers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 4 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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']
Loading