UNPKG

unusualwhales-mcp

Version:

MCP server for Unusual Whales financial data and analytics

928 lines (927 loc) 41.7 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; import dotenv from "dotenv"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; dotenv.config(); const API_KEY = process.env.UNUSUAL_WHALES_API_KEY; if (!API_KEY) { throw new Error("UNUSUAL_WHALES_API_KEY environment variable is required"); } const BASE_URL = "https://api.unusualwhales.com"; class UnusualWhalesAPI { async makeRequest(endpoint, params = {}) { try { const cleanParams = Object.fromEntries(Object.entries(params).filter(([_, value]) => value !== undefined && value !== null)); const response = await axios.get(`${BASE_URL}${endpoint}`, { headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, params: cleanParams, timeout: 30000 }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { const status = error.response?.status; const message = error.response?.data?.message || error.message; throw new Error(`API Error (${status}): ${message}`); } throw error; } } // Alerts async getAlerts(params = {}) { return this.makeRequest('/api/alerts', params); } async getAlertsConfiguration() { return this.makeRequest('/api/alerts/configuration'); } // Congress async getCongressTrader(params = {}) { return this.makeRequest('/api/congress/congress-trader', params); } async getCongressLateReports(params = {}) { return this.makeRequest('/api/congress/late-reports', params); } async getCongressRecentTrades(params = {}) { return this.makeRequest('/api/congress/recent-trades', params); } // Darkpool async getDarkpoolRecent(params = {}) { return this.makeRequest('/api/darkpool/recent', params); } async getDarkpoolTicker(ticker, params = {}) { return this.makeRequest(`/api/darkpool/${ticker}`, params); } // Earnings async getEarningsAfterhours(params = {}) { return this.makeRequest('/api/earnings/afterhours', params); } async getEarningsPremarket(params = {}) { return this.makeRequest('/api/earnings/premarket', params); } async getEarningsTicker(ticker) { return this.makeRequest(`/api/earnings/${ticker}`); } // ETFs async getETFExposure(ticker) { return this.makeRequest(`/api/etfs/${ticker}/exposure`); } async getETFHoldings(ticker) { return this.makeRequest(`/api/etfs/${ticker}/holdings`); } async getETFInOutflow(ticker) { return this.makeRequest(`/api/etfs/${ticker}/in-outflow`); } async getETFInfo(ticker) { return this.makeRequest(`/api/etfs/${ticker}/info`); } async getETFWeights(ticker) { return this.makeRequest(`/api/etfs/${ticker}/weights`); } // Group Flow async getGroupFlowGreekFlow(flowGroup) { return this.makeRequest(`/api/group-flow/${flowGroup}/greek-flow`); } async getGroupFlowGreekFlowExpiry(flowGroup, expiry) { return this.makeRequest(`/api/group-flow/${flowGroup}/greek-flow/${expiry}`); } // Insider async getInsiderTransactions(params = {}) { return this.makeRequest('/api/insider/transactions', params); } async getInsiderSectorFlow(sector) { return this.makeRequest(`/api/insider/${sector}/sector-flow`); } async getInsiderTicker(ticker) { return this.makeRequest(`/api/insider/${ticker}`); } async getInsiderTickerFlow(ticker) { return this.makeRequest(`/api/insider/${ticker}/ticker-flow`); } // Institutions async getInstitutionActivity(name) { return this.makeRequest(`/api/institution/${name}/activity`); } async getInstitutionHoldings(name) { return this.makeRequest(`/api/institution/${name}/holdings`); } async getInstitutionSectors(name) { return this.makeRequest(`/api/institution/${name}/sectors`); } async getInstitutionOwnership(ticker) { return this.makeRequest(`/api/institution/${ticker}/ownership`); } async getInstitutions(params = {}) { return this.makeRequest('/api/institutions', params); } async getInstitutionsLatestFilings(params = {}) { return this.makeRequest('/api/institutions/latest_filings', params); } // Market async getMarketCorrelations(params = {}) { return this.makeRequest('/api/market/correlations', params); } async getMarketEconomicCalendar(params = {}) { return this.makeRequest('/api/market/economic-calendar', params); } async getMarketFDACalendar(params = {}) { return this.makeRequest('/api/market/fda-calendar', params); } async getMarketInsiderBuySells(params = {}) { return this.makeRequest('/api/market/insider-buy-sells', params); } async getMarketTide(params = {}) { return this.makeRequest('/api/market/market-tide', params); } async getMarketOIChange(params = {}) { return this.makeRequest('/api/market/oi-change', params); } async getMarketSectorETFs(params = {}) { return this.makeRequest('/api/market/sector-etfs', params); } async getMarketSpike(params = {}) { return this.makeRequest('/api/market/spike', params); } async getMarketTotalOptionsVolume(params = {}) { return this.makeRequest('/api/market/total-options-volume', params); } async getMarketSectorTide(sector) { return this.makeRequest(`/api/market/${sector}/sector-tide`); } async getMarketETFTide(ticker) { return this.makeRequest(`/api/market/${ticker}/etf-tide`); } // Net Flow async getNetFlowExpiry(params = {}) { return this.makeRequest('/api/net-flow/expiry', params); } // News async getNewsHeadlines(params = {}) { return this.makeRequest('/api/news/headlines', params); } // Option Contract async getOptionContractFlow(id) { return this.makeRequest(`/api/option-contract/${id}/flow`); } async getOptionContractHistoric(id) { return this.makeRequest(`/api/option-contract/${id}/historic`); } async getOptionContractIntraday(id) { return this.makeRequest(`/api/option-contract/${id}/intraday`); } async getOptionContractVolumeProfile(id) { return this.makeRequest(`/api/option-contract/${id}/volume-profile`); } // Option Trades async getOptionTradesFlowAlerts(params = {}) { return this.makeRequest('/api/option-trades/flow-alerts', params); } async getOptionTradesFullTape(date) { return this.makeRequest(`/api/option-trades/full-tape/${date}`); } // Screeners async getScreenerAnalysts(params = {}) { return this.makeRequest('/api/screener/analysts', params); } async getScreenerOptionContracts(params = {}) { return this.makeRequest('/api/screener/option-contracts', params); } async getScreenerStocks(params = {}) { return this.makeRequest('/api/screener/stocks', params); } // Seasonality async getSeasonalityMarket(params = {}) { return this.makeRequest('/api/seasonality/market', params); } async getSeasonalityPerformers(month) { return this.makeRequest(`/api/seasonality/${month}/performers`); } async getSeasonalityTickerMonthly(ticker) { return this.makeRequest(`/api/seasonality/${ticker}/monthly`); } async getSeasonalityTickerYearMonth(ticker) { return this.makeRequest(`/api/seasonality/${ticker}/year-month`); } // Shorts async getShortsData(ticker) { return this.makeRequest(`/api/shorts/${ticker}/data`); } async getShortsFTDs(ticker) { return this.makeRequest(`/api/shorts/${ticker}/ftds`); } async getShortsInterestFloat(ticker) { return this.makeRequest(`/api/shorts/${ticker}/interest-float`); } async getShortsVolumeAndRatio(ticker) { return this.makeRequest(`/api/shorts/${ticker}/volume-and-ratio`); } async getShortsVolumesByExchange(ticker) { return this.makeRequest(`/api/shorts/${ticker}/volumes-by-exchange`); } // Stock async getStockSectorTickers(sector) { return this.makeRequest(`/api/stock/${sector}/tickers`); } async getStockATMChains(ticker) { return this.makeRequest(`/api/stock/${ticker}/atm-chains`); } async getStockExpiryBreakdown(ticker) { return this.makeRequest(`/api/stock/${ticker}/expiry-breakdown`); } async getStockFlowAlerts(ticker) { return this.makeRequest(`/api/stock/${ticker}/flow-alerts`); } async getStockFlowPerExpiry(ticker) { return this.makeRequest(`/api/stock/${ticker}/flow-per-expiry`); } async getStockFlowPerStrike(ticker) { return this.makeRequest(`/api/stock/${ticker}/flow-per-strike`); } async getStockFlowPerStrikeIntraday(ticker) { return this.makeRequest(`/api/stock/${ticker}/flow-per-strike-intraday`); } async getStockFlowRecent(ticker) { return this.makeRequest(`/api/stock/${ticker}/flow-recent`); } async getStockGreekExposure(ticker) { return this.makeRequest(`/api/stock/${ticker}/greek-exposure`); } async getStockGreekExposureExpiry(ticker) { return this.makeRequest(`/api/stock/${ticker}/greek-exposure/expiry`); } async getStockGreekExposureStrike(ticker) { return this.makeRequest(`/api/stock/${ticker}/greek-exposure/strike`); } async getStockGreekExposureStrikeExpiry(ticker) { return this.makeRequest(`/api/stock/${ticker}/greek-exposure/strike-expiry`); } async getStockGreekFlow(ticker) { return this.makeRequest(`/api/stock/${ticker}/greek-flow`); } async getStockGreekFlowExpiry(ticker, expiry) { return this.makeRequest(`/api/stock/${ticker}/greek-flow/${expiry}`); } async getStockGreeks(ticker) { return this.makeRequest(`/api/stock/${ticker}/greeks`); } async getStockHistoricalRiskReversalSkew(ticker) { return this.makeRequest(`/api/stock/${ticker}/historical-risk-reversal-skew`); } async getStockInfo(ticker) { return this.makeRequest(`/api/stock/${ticker}/info`); } async getStockInsiderBuySells(ticker) { return this.makeRequest(`/api/stock/${ticker}/insider-buy-sells`); } async getStockInterpolatedIV(ticker) { return this.makeRequest(`/api/stock/${ticker}/interpolated-iv`); } async getStockIVRank(ticker) { return this.makeRequest(`/api/stock/${ticker}/iv-rank`); } async getStockMaxPain(ticker) { return this.makeRequest(`/api/stock/${ticker}/max-pain`); } async getStockNetPremTicks(ticker) { return this.makeRequest(`/api/stock/${ticker}/net-prem-ticks`); } async getStockNOPE(ticker) { return this.makeRequest(`/api/stock/${ticker}/nope`); } async getStockOHLC(ticker, candleSize) { return this.makeRequest(`/api/stock/${ticker}/ohlc/${candleSize}`); } async getStockOIChange(ticker) { return this.makeRequest(`/api/stock/${ticker}/oi-change`); } async getStockOIPerExpiry(ticker) { return this.makeRequest(`/api/stock/${ticker}/oi-per-expiry`); } async getStockOIPerStrike(ticker) { return this.makeRequest(`/api/stock/${ticker}/oi-per-strike`); } async getStockOptionChains(ticker) { return this.makeRequest(`/api/stock/${ticker}/option-chains`); } async getStockOptionContracts(ticker) { return this.makeRequest(`/api/stock/${ticker}/option-contracts`); } async getStockOptionStockPriceLevels(ticker) { return this.makeRequest(`/api/stock/${ticker}/option/stock-price-levels`); } async getStockOptionVolumeOIExpiry(ticker) { return this.makeRequest(`/api/stock/${ticker}/option/volume-oi-expiry`); } async getStockOptionsVolume(ticker) { return this.makeRequest(`/api/stock/${ticker}/options-volume`); } async getStockSpotExposures(ticker) { return this.makeRequest(`/api/stock/${ticker}/spot-exposures`); } async getStockSpotExposuresExpiryStrike(ticker) { return this.makeRequest(`/api/stock/${ticker}/spot-exposures/expiry-strike`); } async getStockSpotExposuresStrike(ticker) { return this.makeRequest(`/api/stock/${ticker}/spot-exposures/strike`); } async getStockState(ticker) { return this.makeRequest(`/api/stock/${ticker}/stock-state`); } async getStockVolumePriceLevels(ticker) { return this.makeRequest(`/api/stock/${ticker}/stock-volume-price-levels`); } async getStockVolatilityRealized(ticker) { return this.makeRequest(`/api/stock/${ticker}/volatility/realized`); } async getStockVolatilityStats(ticker) { return this.makeRequest(`/api/stock/${ticker}/volatility/stats`); } async getStockVolatilityTermStructure(ticker) { return this.makeRequest(`/api/stock/${ticker}/volatility/term-structure`); } } class UnusualWhalesMCP { server; api; constructor() { this.api = new UnusualWhalesAPI(); this.server = new Server({ name: "unusualwhales-mcp", version: "0.1.0", }, { capabilities: { tools: {}, }, }); this.setupHandlers(); this.setupErrorHandling(); } setupErrorHandling() { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupHandlers() { this.setupToolHandlers(); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Alerts { name: "get_alerts", description: "Get triggered alerts for the user", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of results to return" }, page: { type: "number", description: "Page number" }, intraday_only: { type: "boolean", description: "Only intraday alerts" }, config_ids: { type: "array", items: { type: "string" }, description: "Alert configuration IDs" }, ticker_symbols: { type: "string", description: "Ticker symbols" }, noti_types: { type: "array", items: { type: "string" }, description: "Notification types" } } } }, { name: "get_alerts_configuration", description: "Get alert configurations for the user", inputSchema: { type: "object", properties: {} } }, // Congress { name: "get_congress_trader", description: "Get recent reports by congress member", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of results to return" }, date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, ticker: { type: "string", description: "Ticker symbol" }, name: { type: "string", description: "Congress member name" } } } }, { name: "get_congress_late_reports", description: "Get recent late reports by congress members", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of results to return" }, date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, ticker: { type: "string", description: "Ticker symbol" } } } }, { name: "get_congress_recent_trades", description: "Get latest trades by congress members", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of results to return" }, date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, ticker: { type: "string", description: "Ticker symbol" } } } }, // Darkpool { name: "get_darkpool_recent", description: "Get latest darkpool trades", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of results to return" }, date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, min_premium: { type: "number", description: "Minimum premium" }, max_premium: { type: "number", description: "Maximum premium" }, min_size: { type: "number", description: "Minimum size" }, max_size: { type: "number", description: "Maximum size" }, min_volume: { type: "number", description: "Minimum volume" }, max_volume: { type: "number", description: "Maximum volume" } } } }, { name: "get_darkpool_ticker", description: "Get darkpool trades for a specific ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Ticker symbol" }, date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, newer_than: { type: "string", description: "Newer than timestamp" }, older_than: { type: "string", description: "Older than timestamp" }, min_premium: { type: "number", description: "Minimum premium" }, max_premium: { type: "number", description: "Maximum premium" }, min_size: { type: "number", description: "Minimum size" }, max_size: { type: "number", description: "Maximum size" }, min_volume: { type: "number", description: "Minimum volume" }, max_volume: { type: "number", description: "Maximum volume" }, limit: { type: "number", description: "Number of results to return" } }, required: ["ticker"] } }, // Earnings { name: "get_earnings_afterhours", description: "Get afterhours earnings for a date", inputSchema: { type: "object", properties: { date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, limit: { type: "number", description: "Number of results to return" }, page: { type: "number", description: "Page number" } } } }, { name: "get_earnings_premarket", description: "Get premarket earnings for a date", inputSchema: { type: "object", properties: { date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, limit: { type: "number", description: "Number of results to return" }, page: { type: "number", description: "Page number" } } } }, { name: "get_earnings_ticker", description: "Get historical earnings data for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Ticker symbol" } }, required: ["ticker"] } }, // ETFs { name: "get_etf_exposure", description: "Get ETF exposure data", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "ETF ticker symbol" } }, required: ["ticker"] } }, { name: "get_etf_holdings", description: "Get ETF holdings information", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "ETF ticker symbol" } }, required: ["ticker"] } }, { name: "get_etf_in_outflow", description: "Get ETF inflow & outflow data", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "ETF ticker symbol" } }, required: ["ticker"] } }, { name: "get_etf_info", description: "Get ETF information", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "ETF ticker symbol" } }, required: ["ticker"] } }, { name: "get_etf_weights", description: "Get ETF sector & country weights", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "ETF ticker symbol" } }, required: ["ticker"] } }, // Market { name: "get_market_tide", description: "Get market tide data", inputSchema: { type: "object", properties: {} } }, { name: "get_market_economic_calendar", description: "Get economic calendar events", inputSchema: { type: "object", properties: { date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, limit: { type: "number", description: "Number of results to return" } } } }, { name: "get_market_fda_calendar", description: "Get FDA calendar events", inputSchema: { type: "object", properties: { date: { type: "string", description: "Date filter (YYYY-MM-DD)" }, limit: { type: "number", description: "Number of results to return" } } } }, { name: "get_market_spike", description: "Get SPIKE data (volatility indicator)", inputSchema: { type: "object", properties: {} } }, { name: "get_market_total_options_volume", description: "Get total options volume across the market", inputSchema: { type: "object", properties: {} } }, // News { name: "get_news_headlines", description: "Get news headlines", inputSchema: { type: "object", properties: {} } }, // Option Trades { name: "get_option_trades_flow_alerts", description: "Get option flow alerts", inputSchema: { type: "object", properties: {} } }, // Screeners { name: "get_screener_analysts", description: "Get analyst rating screener", inputSchema: { type: "object", properties: {} } }, { name: "get_screener_option_contracts", description: "Get hottest chains screener (option contracts)", inputSchema: { type: "object", properties: {} } }, { name: "get_screener_stocks", description: "Get stock screener", inputSchema: { type: "object", properties: {} } }, // Stock { name: "get_stock_info", description: "Get stock information for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_flow_alerts", description: "Get flow alerts for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_flow_recent", description: "Get recent flows for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_option_chains", description: "Get option chains for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_greek_exposure", description: "Get Greek exposure for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_max_pain", description: "Get max pain data for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_iv_rank", description: "Get IV rank for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } }, { name: "get_stock_volatility_stats", description: "Get volatility statistics for a ticker", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Stock ticker symbol" } }, required: ["ticker"] } } ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { // Alerts case "get_alerts": result = await this.api.getAlerts(args); break; case "get_alerts_configuration": result = await this.api.getAlertsConfiguration(); break; // Congress case "get_congress_trader": result = await this.api.getCongressTrader(args); break; case "get_congress_late_reports": result = await this.api.getCongressLateReports(args); break; case "get_congress_recent_trades": result = await this.api.getCongressRecentTrades(args); break; // Darkpool case "get_darkpool_recent": result = await this.api.getDarkpoolRecent(args); break; case "get_darkpool_ticker": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } const { ticker, ...otherArgs } = args; result = await this.api.getDarkpoolTicker(ticker, otherArgs); break; // Earnings case "get_earnings_afterhours": result = await this.api.getEarningsAfterhours(args); break; case "get_earnings_premarket": result = await this.api.getEarningsPremarket(args); break; case "get_earnings_ticker": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getEarningsTicker(args.ticker); break; // ETFs case "get_etf_exposure": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getETFExposure(args.ticker); break; case "get_etf_holdings": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getETFHoldings(args.ticker); break; case "get_etf_in_outflow": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getETFInOutflow(args.ticker); break; case "get_etf_info": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getETFInfo(args.ticker); break; case "get_etf_weights": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getETFWeights(args.ticker); break; // Market case "get_market_tide": result = await this.api.getMarketTide(args); break; case "get_market_economic_calendar": result = await this.api.getMarketEconomicCalendar(args); break; case "get_market_fda_calendar": result = await this.api.getMarketFDACalendar(args); break; case "get_market_spike": result = await this.api.getMarketSpike(args); break; case "get_market_total_options_volume": result = await this.api.getMarketTotalOptionsVolume(args); break; // News case "get_news_headlines": result = await this.api.getNewsHeadlines(args); break; // Option Trades case "get_option_trades_flow_alerts": result = await this.api.getOptionTradesFlowAlerts(args); break; // Screeners case "get_screener_analysts": result = await this.api.getScreenerAnalysts(args); break; case "get_screener_option_contracts": result = await this.api.getScreenerOptionContracts(args); break; case "get_screener_stocks": result = await this.api.getScreenerStocks(args); break; // Stock case "get_stock_info": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockInfo(args.ticker); break; case "get_stock_flow_alerts": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockFlowAlerts(args.ticker); break; case "get_stock_flow_recent": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockFlowRecent(args.ticker); break; case "get_stock_option_chains": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockOptionChains(args.ticker); break; case "get_stock_greek_exposure": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockGreekExposure(args.ticker); break; case "get_stock_max_pain": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockMaxPain(args.ticker); break; case "get_stock_iv_rank": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockIVRank(args.ticker); break; case "get_stock_volatility_stats": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker parameter is required"); } result = await this.api.getStockVolatilityStats(args.ticker); break; default: throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`); } return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { if (error instanceof McpError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); throw new McpError(ErrorCode.InternalError, `Error calling tool ${name}: ${errorMessage}`); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Unusual Whales MCP server running on stdio"); } } const argv = yargs(hideBin(process.argv)) .option('stdio', { type: 'boolean', default: true, description: 'Use stdio transport' }) .help() .parse(); const mcpServer = new UnusualWhalesMCP(); mcpServer.run().catch(console.error);