Skip to main content
Glama

Weather & Stock MCP Server

by Jeetinida
tools.ts22.3 kB
// ─────────────────────────────────────────────────────────────────────────────── // src/tools.ts // Centralised registration of all MCP tools for the Weather + Stock server // ─────────────────────────────────────────────────────────────────────────────── import axios from "axios"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import yahooFinance from "yahoo-finance2"; import NewsAPI from "newsapi"; import { z } from "zod"; import { SMA, EMA, RSI, MACD, BollingerBands } from "../functions/technicalIndicator.js"; const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; const NEWS_API_KEY = process.env.NEWS_API_KEY ?? ""; const newsapi = new NewsAPI(NEWS_API_KEY); /* -------------------------------------------------------------------------- */ /* Helper utilities */ /* -------------------------------------------------------------------------- */ async function fetchData<T = any>(url: string, headers: Record<string,string> = {}): Promise<T | null> { try { const { data } = await axios.get<T>(url, { headers }); return data; } catch (err: any) { console.error(`Error fetching ${url}:`, err.message); return null; } } function formatAlert({ properties }: any): string { return [ `🚨 ${properties.event ?? "Unknown"}`, `📍 ${properties.areaDesc ?? "Unknown"}`, `⚠️ Severity: ${properties.severity ?? "Unknown"}`, `📰 ${properties.headline ?? "No headline"}`, "---" ].join("\n"); } /* -------------------------------------------------------------------------- */ /* Main export: registerTools */ /* -------------------------------------------------------------------------- */ /** * Attaches every MCP tool to the provided server instance. * Keep all your tool definitions in this file so the entry‑point stays tiny. */ export function registerTools(server: McpServer) { /* ───── Stock: current price ─────────────────────────────────────────── */ server.tool( "get-stock-price", "Fetch the current price for a ticker", { symbol: z.string().describe("Ticker, e.g. AAPL") }, async ({ symbol }) => { try { const quote = await yahooFinance.quote(symbol); return { content: [{ type: "text", text: `💹 ${symbol}: $${quote?.regularMarketPrice}` }] }; } catch (err) { console.error(err); return { content: [{ type: "text", text: `🚫 Could not fetch price for ${symbol}.` }] }; } }, ); /* ───── Stock: historical data ───────────────────────────────────────── */ server.tool( "get-historical-data", "Fetch historical OHLC data", { symbol: z.string(), startDate: z.string(), endDate: z.string(), interval: z.enum(["1d", "1wk", "1mo"]), }, async ({ symbol, startDate, endDate, interval }) => { try { const rows = await yahooFinance.historical(symbol, { period1: startDate, period2: endDate, interval }); if (!rows.length) return { content: [{ type: "text", text: `🚫 No data for ${symbol} in that range.` }] }; const txt = rows.map(r => `📅 ${r.date}: close $${r.close}`).join("\n"); return { content: [{ type: "text", text: `📊 Historical for ${symbol}:\n\n${txt}` }] }; } catch { return { content: [{ type: "text", text: "🚫 Failed to fetch historical data." }] }; } }, ); /* ───── Stock: news ──────────────────────────────────────────────────── */ server.tool( "get-news", "Get news articles for a company/stock", { stockName: z.string(), startDate: z.string(), endDate: z.string(), }, async ({ stockName, startDate, endDate }) => { try { const news = await newsapi.v2.everything({ q: stockName, from: startDate, to: endDate, language: "en", sortBy: "relevancy", }); if (!news.articles?.length) return { content: [{ type: "text", text: `📰 No news for ${stockName}.` }] }; const text = news.articles.slice(0, 5) .map((a: { title: any; url: any; }) => `📌 ${a.title}\n🔗 ${a.url}`) .join("\n\n"); return { content: [{ type: "text", text: `📰 Top news for ${stockName}:\n\n${text}` }] }; } catch { return { content: [{ type: "text", text: "🚫 Failed to fetch news." }] }; } }, ); /* ───── Stock: single technical indicator ────────────────────────────── */ server.tool( "get-technical-indicators", "Calculate a single technical indicator", { symbol: z.string(), indicator: z.enum(["sma", "ema", "rsi", "macd", "bollinger"]), period: z.number().min(1).max(200).default(14), startDate: z.string(), endDate: z.string(), }, async ({ symbol, indicator, period, startDate, endDate }) => { try { // Fetch historical data const historicalData = await yahooFinance.historical(symbol, { period1: startDate, period2: endDate, interval: "1d" }); if (!historicalData.length) { return { content: [{ type: "text", text: `🚫 No historical data available for ${symbol} in the specified range.` }] }; } // Extract closing prices const closingPrices = historicalData.map(day => day.close); const dates = historicalData.map(day => typeof day.date === 'object' ? day.date.toISOString().split('T')[0] : day.date); // OHLC data for indicators that need it const ohlcData = historicalData.map(day => ({ open: day.open, high: day.high, low: day.low, close: day.close, })); let result = ""; let indicatorValues = []; // Calculate the requested indicator switch (indicator) { case "sma": { const smaValues = SMA(closingPrices, period); // Align dates with SMA values (SMA values start after period-1 days) const smaResults = dates.slice(period - 1).map((date, i) => ({ date, sma: smaValues[i] })); indicatorValues = smaResults; result = `📈 SMA(${period}) for ${symbol}:\n\n${smaResults.slice(-10).map(day => `📅 ${day.date}: SMA = $${day.sma.toFixed(2)}`).join('\n')}`; break; } case "ema": { const emaValues = EMA(closingPrices, period); // Align dates with EMA values const emaResults = dates.slice(period - 1).map((date, i) => ({ date, ema: emaValues[i] })); indicatorValues = emaResults; result = `📉 EMA(${period}) for ${symbol}:\n\n${emaResults.slice(-10).map(day => `📅 ${day.date}: EMA = $${day.ema.toFixed(2)}`).join('\n')}`; break; } case "rsi": { const rsiValues = RSI(closingPrices, period); // Align dates with RSI values const rsiResults = dates.slice(period).map((date, i) => ({ date, rsi: rsiValues[i] })); indicatorValues = rsiResults; result = `🔍 RSI(${period}) for ${symbol}:\n\n${rsiResults.slice(-10).map(day => { let rsiLevel = ""; if (day.rsi > 70) rsiLevel = "⚠️ Potentially Overbought"; else if (day.rsi < 30) rsiLevel = "⚠️ Potentially Oversold"; return `📅 ${day.date}: RSI = ${day.rsi.toFixed(2)} ${rsiLevel}`; }).join('\n')}`; break; } case "macd": { // Standard MACD parameters const fastPeriod = 12; const slowPeriod = 26; const signalPeriod = 9; const macdValues = MACD(closingPrices, fastPeriod, slowPeriod, signalPeriod); // Align dates with MACD values const macdResults = dates.slice(slowPeriod + signalPeriod - 2).map((date, i) => ({ date, macd: macdValues.macd[i], signal: macdValues.signal[i], histogram: macdValues.histogram[i] })); indicatorValues = macdResults; result = `📊 MACD(${fastPeriod},${slowPeriod},${signalPeriod}) for ${symbol}:\n\n${macdResults.slice(-10).map(day => { let signal = ""; if (day.histogram! > 0 && day.histogram! > macdResults[macdResults.indexOf(day) - 1]?.histogram!) signal = "📈 Bullish"; else if (day.histogram! < 0 && day.histogram! < macdResults[macdResults.indexOf(day) - 1]?.histogram!) signal = "📉 Bearish"; return `📅 ${day.date}: MACD = ${day.macd!.toFixed(2)}, Signal = ${day.signal!.toFixed(2)}, Histogram = ${day.histogram!.toFixed(2)} ${signal}`; }).join('\n')}`; break; } case "bollinger": { const standardDeviation = 2; const bbandsValues = BollingerBands(closingPrices, period, standardDeviation); // Align dates with Bollinger Bands values const bbandsResults = dates.slice(period - 1).map((date, i) => ({ date, upper: bbandsValues.upper[i], middle: bbandsValues.middle[i], lower: bbandsValues.lower[i], price: closingPrices[period - 1 + i] })); indicatorValues = bbandsResults; result = `🎯 Bollinger Bands(${period}, ${standardDeviation}σ) for ${symbol}:\n\n${bbandsResults.slice(-10).map(day => { let position = ""; if (day.price > day.upper) position = "⚠️ Above Upper Band"; else if (day.price < day.lower) position = "⚠️ Below Lower Band"; return `📅 ${day.date}: Upper = $${day.upper.toFixed(2)}, Middle = $${day.middle.toFixed(2)}, Lower = $${day.lower.toFixed(2)}, Price = $${day.price.toFixed(2)} ${position}`; }).join('\n')}`; break; } } return { content: [{ type: "text", text: `${result}\n\n(Showing the last 10 data points. Request covered ${dates.length} trading days.)` }] }; } catch (error) { console.error(`Error calculating technical indicators for ${symbol}:`, error); return { content: [{ type: "text", text: `🚫 Failed to calculate ${indicator} for ${symbol}.` }] }; } }, ); /* ───── Stock: comprehensive analysis ───────────────────────────────── */ server.tool( "get-technical-analysis", "Full multi‑indicator technical analysis", { symbol: z.string(), startDate: z.string(), endDate: z.string(), }, async ({ symbol, startDate, endDate }) => { try { // Fetch historical data const historicalData = await yahooFinance.historical(symbol, { period1: startDate, period2: endDate, interval: "1d" }); if (!historicalData.length) { return { content: [{ type: "text", text: `🚫 No historical data available for ${symbol} in the specified range.` }] }; } // Extract price data const closingPrices = historicalData.map(day => day.close); const latestPrice = closingPrices[closingPrices.length - 1]; const previousPrice = closingPrices[closingPrices.length - 2]; // Calculate various indicators const sma20 = SMA(closingPrices, 20).pop(); const sma50 = SMA(closingPrices, 50).pop(); const sma200 = SMA(closingPrices, 200).pop(); const ema12 = EMA(closingPrices, 12).pop(); const ema26 = EMA(closingPrices, 26).pop(); const rsi14 = RSI(closingPrices, 14).pop(); const macdResult = MACD(closingPrices, 12, 26, 9); const macd = macdResult.macd.pop(); const macdSignal = macdResult.signal?.pop(); const macdHistogram = macdResult.histogram?.pop(); const bbands = BollingerBands(closingPrices, 20, 2); const bbandsUpper = bbands.upper.pop(); const bbandsMiddle = bbands.middle.pop(); const bbandsLower = bbands.lower.pop(); // Analyze the trends let trendAnalysis = []; // Moving Average Analysis if (sma20 !== undefined && sma50 !== undefined && latestPrice > sma20 && sma20 > sma50) { trendAnalysis.push("📈 Price is above SMA(20) and SMA(50), suggesting a positive trend."); } else if (sma20 !== undefined && sma50 !== undefined && latestPrice < sma20 && sma20 < sma50) { trendAnalysis.push("📉 Price is below SMA(20) and SMA(50), suggesting a negative trend."); } if ((sma50 ?? 0) > (sma200 ?? 0)) { trendAnalysis.push("📈 SMA(50) is above SMA(200), indicating a long-term uptrend (Golden Cross pattern)."); } else if ((sma50 ?? 0) < (sma200 ?? 0)) { trendAnalysis.push("📉 SMA(50) is below SMA(200), indicating a long-term downtrend (Death Cross pattern)."); } // RSI Analysis if ((rsi14 ?? 0) > 70) { trendAnalysis.push("⚠️ RSI(14) is above 70, suggesting the stock may be overbought."); } else if ((rsi14 ?? 0) < 30) { trendAnalysis.push("⚠️ RSI(14) is below 30, suggesting the stock may be oversold."); } else { trendAnalysis.push(`✅ RSI(14) is at ${(rsi14 ?? 0).toFixed(2)}, indicating neutral momentum.`); } // MACD Analysis if (macdResult && macdResult.macd! > (macdResult.signal ?? 0)) { trendAnalysis.push("📈 MACD is above signal line, suggesting bullish momentum."); } else { trendAnalysis.push("📉 MACD is below signal line, suggesting bearish momentum."); } // Bollinger Bands Analysis if (latestPrice > bbandsUpper!) { trendAnalysis.push("⚠️ Price is above the upper Bollinger Band, potentially indicating overbought conditions."); } else if (latestPrice < bbandsLower!) { trendAnalysis.push("⚠️ Price is below the lower Bollinger Band, potentially indicating oversold conditions."); } else { const bandWidth = bbandsUpper! - bbandsLower!; if (bandWidth < 10) { trendAnalysis.push("📊 Bollinger Bands are contracting, suggesting a potential upcoming volatility increase."); } } // Support/Resistance Analysis const recentPrices = closingPrices.slice(-30); const max = Math.max(...recentPrices); const min = Math.min(...recentPrices); const resistanceLevels: number[] = []; const supportLevels: number[] = []; // Simple algorithm to identify potential support/resistance levels // This is a simplified approach - in a real application you'd want a more sophisticated algorithm for (let i = 10; i < recentPrices.length - 1; i++) { if (recentPrices[i] > recentPrices[i-1] && recentPrices[i] > recentPrices[i+1]) { const potentialResistance = recentPrices[i]; // Check if we already have a similar level if (!resistanceLevels.some(level => Math.abs(level - potentialResistance) / potentialResistance < 0.01)) { resistanceLevels.push(potentialResistance); } } if (recentPrices[i] < recentPrices[i-1] && recentPrices[i] < recentPrices[i+1]) { const potentialSupport = recentPrices[i]; // Check if we already have a similar level if (!supportLevels.some(level => Math.abs(level - potentialSupport) / potentialSupport < 0.01)) { supportLevels.push(potentialSupport); } } } // Sort and take the most significant levels resistanceLevels.sort((a, b) => b - a); supportLevels.sort((a, b) => b - a); const significantResistance = resistanceLevels.length > 0 ? resistanceLevels.slice(0, Math.min(2, resistanceLevels.length)) : []; const significantSupport = supportLevels.length > 0 ? supportLevels.slice(-Math.min(2, supportLevels.length)) : []; // Volume Analysis const volumes = historicalData.map(day => day.volume); const avgVolume = volumes.slice(-10).reduce((sum, vol) => sum + vol, 0) / 10; const latestVolume = volumes[volumes.length - 1]; if (latestVolume > avgVolume * 1.5) { trendAnalysis.push("📊 Trading volume is significantly higher than average, suggesting strong market interest."); } else if (latestVolume < avgVolume * 0.5) { trendAnalysis.push("📊 Trading volume is significantly lower than average, suggesting weak market interest."); } // Generate the analysis text let analysisText = `🔍 Technical Analysis for ${symbol}\n\n`; // Price information analysisText += `Current Price: $${latestPrice.toFixed(2)}\n`; analysisText += `Daily Change: ${((latestPrice - previousPrice) / previousPrice * 100).toFixed(2)}%\n\n`; // Key Indicators analysisText += `Key Indicators:\n`; analysisText += `• SMA(20): $${(sma20 ?? 0).toFixed(2)}\n`; analysisText += `• SMA(50): $${(sma50 ?? 0).toFixed(2)}\n`; analysisText += `• SMA(200): $${(sma200 ?? 0).toFixed(2)}\n`; analysisText += `• RSI(14): ${(rsi14 ?? 0).toFixed(2)}\n`; analysisText += `• MACD: ${(macd ?? 0).toFixed(2)}\n`; analysisText += `• MACD Signal: ${macdSignal ?.toFixed(2) ?? "N/A"}\n`; analysisText += `• MACD Histogram: ${(macdHistogram ?? 0).toFixed(2)}\n`; if (bbands) { analysisText += `• Bollinger Upper: $${(bbandsUpper ?? 0).toFixed(2)}\n`; analysisText += `• Bollinger Middle: $${(bbandsMiddle ?? 0).toFixed(2)}\n`; analysisText += `• Bollinger Lower: $${(bbandsLower ?? 0).toFixed(2)}\n\n`; } else { analysisText += `• Bollinger Bands: Data unavailable\n\n`; } // Support & Resistance if (significantResistance.length > 0 || significantSupport.length > 0) { analysisText += `Support & Resistance:\n`; if (significantResistance.length > 0) { analysisText += `• Resistance: ${significantResistance.map(level => `$${level.toFixed(2)}`).join(', ')}\n`; } if (significantSupport.length > 0) { analysisText += `• Support: ${significantSupport.map(level => `$${level.toFixed(2)}`).join(', ')}\n`; } analysisText += `\n`; } // Analysis summary analysisText += `Analysis Summary:\n`; trendAnalysis.forEach(trend => { analysisText += `• ${trend}\n`; }); return { content: [{ type: "text", text: analysisText }] }; } catch (error) { console.error(`Error generating technical analysis for ${symbol}:`, error); return { content: [{ type: "text", text: `🚫 Failed to generate technical analysis for ${symbol}.` }] }; } }, ); } function indicatorSwitch( indicator: string, closes: number[], dates: string[], period: number, symbol: string, ): string { // Insert your long switch/case from earlier (SMA, EMA, RSI, MACD, Bollinger) // and return the final formatted string. return `🚧 ${indicator} not yet implemented for ${symbol}`; }

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/Jeetinida/stocknews-mcp'

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