Skip to main content
Glama

finance-search

Search for financial instruments including stocks, ETFs, cryptocurrencies, and futures using Google Finance data with symbol:exchange format queries.

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 performs the finance search using SerpAPI Google Finance, optionally Brave Search for news, filters response, and returns formatted 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 for input validation of the finance-search tool parameters.
    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 description, schema, and handler wrapper.
    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 to format the financial data and news into a structured Markdown response.
    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 mentions the query format and a note about 'SPY' returning market overview, which adds some context. However, it lacks details on permissions, rate limits, error handling, or the response structure, leaving gaps for a tool with 9 parameters.

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, starting with the core purpose and usage. It uses examples efficiently but could be slightly more streamlined by avoiding repetition of schema details, such as the query format examples.

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 of 9 parameters and no output schema, the description is moderately complete. It covers the main use case and query format but lacks information on return values, error cases, or advanced usage scenarios, which could hinder an agent's ability to invoke it correctly without trial and error.

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?

The schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds minimal value beyond the schema by reiterating the query format and examples, but does not provide additional semantic insights or clarify interactions between parameters.

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 explicitly states the action ('Search for') and the resources ('stocks, indices, mutual funds, currencies, and futures using Google Finance'), making the purpose clear and specific. It distinguishes this tool from siblings by focusing on financial data search, unlike other tools for calendars, emails, or geography.

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 by specifying the types of financial instruments it searches and the required query format. However, it does not explicitly state when not to use it or name alternatives, such as for non-financial searches or other data sources.

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/CaptainCrouton89/maps-mcp'

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