Skip to main content
Glama
ricleedo

Google Services MCP Server

by ricleedo

finance-search

Search Google Finance for stocks, ETFs, indices, cryptocurrencies, and futures using symbol:exchange format to get price data, charts, and financial news.

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

TableJSON Schema
NameRequiredDescriptionDefault
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.
windowNoTime range for graph data
asyncNoSubmit search asynchronously
include_marketsNoInclude market overview data
include_discoverNoInclude discover more section
include_newsNoInclude top news
max_futuresNoMaximum number of futures chain items to return
summary_onlyNoReturn only essential information (price, movement, news)
max_newsNoMaximum number of news articles to return (1 = only top news from Google Finance, >1 = additional news from Brave Search)

Implementation Reference

  • Main handler function that queries SerpAPI for Google Finance data, filters response, optionally fetches additional news from Brave, and formats output to 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 input parameters for the finance-search tool, including query, options for markets, news, etc.
    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)
    Registration of the finance-search tool on the MCP server, providing description, schema, and wrapper handler that calls searchFinance.
    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, changes, news, etc.
    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;
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes the search functionality and format requirements, and notes a specific behavior: 'Query 'SPY' (no exchange) returns market overview with S&P 500 related ETFs.' This adds useful context beyond basic functionality. However, it doesn't cover other behavioral aspects like rate limits, authentication needs, error conditions, or what the output looks like (since there's no output schema). The description provides some behavioral insight but leaves gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded: it starts with the core purpose, then provides format guidance and examples, and ends with a behavioral note. Every sentence earns its place by conveying essential information. It could be slightly more structured (e.g., bullet points for examples), but it's efficient and avoids redundancy. There's no wasted text, making it concise yet informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (9 parameters, no annotations, no output schema), the description is moderately complete. It covers the tool's purpose, usage format, and a key behavioral note, which helps an agent understand when to invoke it. However, without annotations or output schema, it lacks details on behavioral traits (e.g., side effects, performance) and return values. For a search tool with many parameters, the description provides a good foundation but leaves room for more context, especially regarding output.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 9 parameters thoroughly. The description adds minimal parameter semantics: it explains the format for the 'q' parameter with examples and notes the special case for 'SPY'. However, it doesn't provide additional meaning for other parameters like 'window', 'async', or the boolean flags. Given the high schema coverage, the baseline is 3, and the description adds only marginal value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search for stocks, indices, mutual funds, currencies, and futures using Google Finance.' It specifies the verb ('Search') and resources (multiple financial instruments), and distinguishes itself from sibling tools like airports-search or calendar tools by focusing on financial data. The description goes beyond the name 'finance-search' by detailing what types of assets can be searched.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context on when to use this tool: for searching financial instruments via Google Finance. It includes specific format guidance ('SYMBOL:EXCHANGE') and examples for different asset types. However, it doesn't explicitly state when NOT to use it or mention alternatives (though sibling tools are unrelated to finance, so this is less critical). The guidance is practical but lacks explicit exclusions or comparison to other finance tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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