Skip to main content
Glama
by ricleedo

finance-search

Search for real-time financial data on stocks, ETFs, cryptocurrencies, and indices using Google Finance. Get current prices, market movements, and relevant news by entering symbols with exchange codes.

Instructions

Search for stocks, indices, mutual funds, currencies, and futures using Google Finance. Use format 'SYMBOL:EXCHANGE'. Stocks: 'AAPL:NASDAQ', 'TSLA:NASDAQ'. ETFs/Index funds: 'SPY:NYSEARCA', 'VTI:NYSEARCA', 'QQQ:NASDAQ', 'VOO:NYSEARCA'. Crypto: 'BTC-USD', 'ETH-USD'. Note: Query 'SPY' (no exchange) returns market overview with S&P 500 related ETFs.

Input Schema

NameRequiredDescriptionDefault
asyncNoSubmit search asynchronously
include_discoverNoInclude discover more section
include_marketsNoInclude market overview data
include_newsNoInclude top news
max_futuresNoMaximum number of futures chain items to return
max_newsNoMaximum number of news articles to return (1 = only top news from Google Finance, >1 = additional news from Brave Search)
qYesStock symbol with exchange in format 'SYMBOL:EXCHANGE'. Examples: 'AAPL:NASDAQ', 'TSLA:NASDAQ', 'MSFT:NASDAQ', 'GOOGL:NASDAQ', 'NVDA:NASDAQ' for stocks. For ETFs/Index funds: 'SPY:NYSEARCA', 'VTI:NYSEARCA', 'QQQ:NASDAQ', 'VOO:NYSEARCA'. For crypto: 'BTC-USD', 'ETH-USD'. Note: Just 'SPY' returns market overview with related ETFs in futures_chain.
summary_onlyNoReturn only essential information (price, movement, news)
windowNoTime range for graph data

Input Schema (JSON Schema)

{ "properties": { "async": { "description": "Submit search asynchronously", "type": "boolean" }, "include_discover": { "default": false, "description": "Include discover more section", "type": "boolean" }, "include_markets": { "default": false, "description": "Include market overview data", "type": "boolean" }, "include_news": { "default": true, "description": "Include top news", "type": "boolean" }, "max_futures": { "default": 3, "description": "Maximum number of futures chain items to return", "type": "number" }, "max_news": { "default": 1, "description": "Maximum number of news articles to return (1 = only top news from Google Finance, >1 = additional news from Brave Search)", "type": "number" }, "q": { "description": "Stock symbol with exchange in format 'SYMBOL:EXCHANGE'. Examples: 'AAPL:NASDAQ', 'TSLA:NASDAQ', 'MSFT:NASDAQ', 'GOOGL:NASDAQ', 'NVDA:NASDAQ' for stocks. For ETFs/Index funds: 'SPY:NYSEARCA', 'VTI:NYSEARCA', 'QQQ:NASDAQ', 'VOO:NYSEARCA'. For crypto: 'BTC-USD', 'ETH-USD'. Note: Just 'SPY' returns market overview with related ETFs in futures_chain.", "type": "string" }, "summary_only": { "default": true, "description": "Return only essential information (price, movement, news)", "type": "boolean" }, "window": { "description": "Time range for graph data", "enum": [ "1D", "5D", "1M", "6M", "YTD", "1Y", "5Y", "MAX" ], "type": "string" } }, "required": [ "q" ], "type": "object" }

Implementation Reference

  • Core implementation of the finance search tool handler. Queries SerpAPI Google Finance endpoint, handles parameters for filtering and additional features like news from Brave Search, formats response as Markdown.
    export async function searchFinance( params: z.infer<typeof financeSearchSchema> ): Promise<string> { const apiKey = process.env.SERP_API_KEY; if (!apiKey) { throw new Error("SERP_API_KEY environment variable is required"); } const { include_markets, include_discover, include_news, max_futures, summary_only, max_news, ...apiParams } = params; const searchParams = new URLSearchParams({ engine: "google_finance", api_key: apiKey, q: apiParams.q, hl: "en", ...(apiParams.window && { window: apiParams.window }), ...(apiParams.async !== undefined && { async: apiParams.async.toString() }), }); try { const response = await axios.get<SerpApiFinanceResponse>( `${SERPAPI_BASE_URL}?${searchParams.toString()}` ); // Check for API errors first if (response.data.error) { throw new Error(`SerpAPI Error: ${response.data.error}`); } const filteredData = filterFinanceResponse(response.data, params); // Fetch additional news if requested let braveNews: BraveNewsResult[] = []; if (max_news && max_news > 1 && include_news !== false) { braveNews = await fetchAdditionalNews(params.q, max_news); } return formatFinanceToMarkdown(filteredData, params, braveNews); } catch (error) { if (axios.isAxiosError(error)) { throw new Error( `Finance API request failed: ${ error.response?.data?.error || error.message }` ); } throw error; } }
  • Zod schema defining the input parameters and validation for the finance-search tool.
    const financeSearchSchema = z.object({ q: z .string() .describe( "Stock symbol with exchange in format 'SYMBOL:EXCHANGE'. Examples: 'AAPL:NASDAQ', 'TSLA:NASDAQ', 'MSFT:NASDAQ', 'GOOGL:NASDAQ', 'NVDA:NASDAQ' for stocks. For ETFs/Index funds: 'SPY:NYSEARCA', 'VTI:NYSEARCA', 'QQQ:NASDAQ', 'VOO:NYSEARCA'. For crypto: 'BTC-USD', 'ETH-USD'. Note: Just 'SPY' returns market overview with related ETFs in futures_chain." ), window: z .enum(["1D", "5D", "1M", "6M", "YTD", "1Y", "5Y", "MAX"]) .optional() .describe("Time range for graph data"), async: z.boolean().optional().describe("Submit search asynchronously"), include_markets: z .boolean() .optional() .default(false) .describe("Include market overview data"), include_discover: z .boolean() .optional() .default(false) .describe("Include discover more section"), include_news: z .boolean() .optional() .default(true) .describe("Include top news"), max_futures: z .number() .optional() .default(3) .describe("Maximum number of futures chain items to return"), summary_only: z .boolean() .optional() .default(true) .describe("Return only essential information (price, movement, news)"), max_news: z .number() .optional() .default(1) .describe( "Maximum number of news articles to return (1 = only top news from Google Finance, >1 = additional news from Brave Search)" ), });
  • src/index.ts:120-148 (registration)
    MCP server tool registration for 'finance-search', providing tool description, input schema, and wrapper handler that calls searchFinance and formats response as MCP content.
    server.tool( "finance-search", "Search for stocks, indices, mutual funds, currencies, and futures using Google Finance. Use format 'SYMBOL:EXCHANGE'. Stocks: 'AAPL:NASDAQ', 'TSLA:NASDAQ'. ETFs/Index funds: 'SPY:NYSEARCA', 'VTI:NYSEARCA', 'QQQ:NASDAQ', 'VOO:NYSEARCA'. Crypto: 'BTC-USD', 'ETH-USD'. Note: Query 'SPY' (no exchange) returns market overview with S&P 500 related ETFs.", financeSearchSchema.shape, async (params) => { try { const result = await searchFinance(params); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error searching finance data: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } );
  • Helper function that formats the raw finance API data into a structured Markdown response, including price, change, insights, news, and market data based on parameters.
    function formatFinanceToMarkdown( data: any, params: z.infer<typeof financeSearchSchema>, braveNews: BraveNewsResult[] = [] ): string { if (!data) return "No financial data available."; let markdown = `# ${params.q}\n\n`; // Main stock/security info if (data.summary) { const summary = data.summary; const price = summary.price !== undefined ? summary.price : "N/A"; markdown += `Current Price: ${summary.currency || "$"}${price} \n`; if (summary.price_movement) { const movement = summary.price_movement; const arrow = movement.movement === "up" ? "πŸ“ˆ" : movement.movement === "down" ? "πŸ“‰" : "➑️"; markdown += `Change: ${arrow} ${movement.percentage}% (${ movement.value >= 0 ? "+" : "" }${movement.value}) \n`; } if (summary.name && summary.name !== summary.symbol) { markdown += `Name: ${summary.name} \n`; } markdown += `\n`; } // Price insights if (data.price_insights) { const insights = data.price_insights; markdown += `## Price Analysis\n\n`; if (insights.previous_close) markdown += `Previous Close: $${insights.previous_close} \n`; if (insights.day_range) markdown += `Day Range: $${insights.day_range} \n`; if (insights.year_range) markdown += `52-Week Range: $${insights.year_range} \n`; if (insights.market_cap) markdown += `Market Cap: ${insights.market_cap} \n`; if (insights.pe_ratio) markdown += `P/E Ratio: ${insights.pe_ratio} \n`; markdown += `\n`; } // Futures chain (for summary mode) if ( data.futures_chain && params.summary_only && data.futures_chain.length > 0 ) { const futures = data.futures_chain.slice(0, params.max_futures || 3); if (futures.length > 1) { markdown += `## Futures Contracts\n\n`; futures.forEach((future: any) => { markdown += `${future.date || future.stock}: $${ future.price || future.extracted_price }`; if (future.change) markdown += ` (${future.change})`; markdown += ` \n`; }); markdown += `\n`; } } // Top news - safely handle different data structures if (data.top_news && params.include_news !== false) { try { let newsItems: any[] = []; if (Array.isArray(data.top_news)) { newsItems = data.top_news; } else if ( data.top_news.results && Array.isArray(data.top_news.results) ) { newsItems = data.top_news.results; } else if (typeof data.top_news === "object") { // If it's a single news object, put it in an array if ( data.top_news.title || data.top_news.headline || data.top_news.snippet ) { newsItems = [data.top_news]; } else { // Try to convert object values to array newsItems = Object.values(data.top_news).filter( (item: any) => item && typeof item === "object" && (item.title || item.headline || item.snippet) ); } } if (newsItems.length > 0) { markdown += `## Latest News\n\n`; newsItems.slice(0, 5).forEach((news: any, index: number) => { if (news && (news.title || news.headline || news.snippet)) { markdown += `### ${index + 1}. ${ news.title || news.headline || "News Update" }\n`; if (news.source) markdown += `Source: ${news.source} \n`; if (news.date || news.published_date) markdown += `Date: ${news.date || news.published_date} \n`; if (news.snippet || news.description) markdown += `${news.snippet || news.description} \n`; if (news.link || news.url) markdown += `[Read More](${news.link || news.url}) \n`; markdown += `\n`; } }); } } catch (error) { // Silently skip news section if there's an error } } // Additional news from Brave Search if (braveNews.length > 0 && params.include_news !== false) { if (!data.top_news || Object.keys(data.top_news || {}).length === 0) { markdown += `## Latest News\n\n`; } braveNews.forEach((news, index) => { const newsIndex = data.top_news ? index + 2 : index + 1; // Start after Google Finance news markdown += `### ${newsIndex}. ${news.title}\n`; if (news.source?.name) markdown += `Source: ${news.source.name} \n`; if (news.published_datetime) markdown += `Date: ${news.published_datetime} \n`; if (news.description) markdown += `${news.description} \n`; if (news.url) markdown += `[Read More](${news.url}) \n`; markdown += `\n`; }); } // Market overview (if included) if (data.markets && params.include_markets) { markdown += `## Market Overview\n\n`; if (data.markets.top_news) { markdown += `Market News Available: ${data.markets.top_news.length} articles \n`; } markdown += `\n`; } // Discover more (if included) if (data.discover_more && params.include_discover) { markdown += `## Related\n\n`; if (data.discover_more.similar_stocks) { markdown += `Similar Stocks: ${data.discover_more.similar_stocks .map((s: any) => s.stock || s) .join(", ")} \n`; } markdown += `\n`; } return markdown; }

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/ricleedo/Google-Service-MCP'

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