Skip to main content
Glama

search

Perform Google searches to find current information, bypass anti-bot mechanisms, and extract structured results for AI assistants.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes搜索查询字符串
limitNo返回的搜索结果数量,默认为10
timeoutNo搜索操作的超时时间(毫秒),默认为60000
languageNo搜索结果的语言,例如 zh-CN, en-US 等,默认为 zh-CN
regionNo搜索结果的地区,例如 cn, com, co.jp 等,默认为 cn

Implementation Reference

  • The main handler function 'googleSearch' that implements the Google search logic using Playwright browser automation, including anti-bot evasion techniques, result extraction, and state persistence.
    export async function googleSearch(
      query: string,
      options: CommandOptions = {},
      existingBrowser?: Browser
    ): Promise<SearchResponse> {
      // 设置默认选项
      const {
        limit = 10,
        timeout = 60000,
        stateFile = path.join(os.homedir(), ".google-search-browser-state.json"),
        noSaveState = false,
        locale = "zh-CN", // 默认使用中文
        region = "cn", // 默认使用中国区域
      } = options;
    
      // 状态文件路径
      const stateFilePath = path.resolve(stateFile);
      const fingerprintFilePath = stateFilePath.replace(
        ".json",
        "-fingerprint.json"
      );
    
      // 加载保存的状态
      let savedState: SavedState = {};
      let fingerprint: FingerprintConfig = getHostMachineConfig(locale);
    
      // 尝试加载指纹配置
      try {
        if (fs.existsSync(fingerprintFilePath)) {
          const fingerprintData = fs.readFileSync(fingerprintFilePath, "utf-8");
          fingerprint = JSON.parse(fingerprintData);
          logger.info("已加载浏览器指纹配置");
        } else {
          // 保存新生成的指纹配置
          fs.writeFileSync(
            fingerprintFilePath,
            JSON.stringify(fingerprint, null, 2)
          );
          logger.info("已生成并保存新的浏览器指纹配置");
        }
      } catch (error) {
        logger.warn("加载或保存浏览器指纹配置时出错,使用默认配置");
      }
    
      // 尝试加载保存的状态
      try {
        if (fs.existsSync(stateFilePath)) {
          const stateData = fs.readFileSync(stateFilePath, "utf-8");
          savedState = JSON.parse(stateData);
          logger.info("已加载保存的状态");
        }
      } catch (error) {
        logger.warn("加载保存的状态时出错,将使用新会话");
      }
    
      // 获取 Google 域名
      const googleDomain = savedState.googleDomain || `www.google.${region}`;
    
      // 忽略传入的headless参数,总是以无头模式启动
      let useHeadless = true;
    
      logger.info({ options }, "正在初始化浏览器...");
    
      // 检查是否存在状态文件
      let storageState: string | undefined = undefined;
    
      if (fs.existsSync(stateFilePath)) {
        logger.info(
          { stateFile },
          "发现浏览器状态文件,将使用保存的浏览器状态以避免反机器人检测"
        );
        storageState = stateFilePath;
      } else {
        logger.info(
          { stateFile },
          "未找到浏览器状态文件,将创建新的浏览器会话和指纹"
        );
      }
    
      // 获取随机延迟时间
      const getRandomDelay = (min: number, max: number) => {
        return Math.floor(Math.random() * (max - min + 1)) + min;
      };
    
      // 定义一个函数来执行搜索,可以重用于无头和有头模式
      async function performSearch(headless: boolean): Promise<SearchResponse> {
        let browser: Browser;
        let browserWasProvided = false;
    
        if (existingBrowser) {
          browser = existingBrowser;
          browserWasProvided = true;
          logger.info("使用已存在的浏览器实例");
        } else {
          logger.info(
            { headless },
            `准备以${headless ? "无头" : "有头"}模式启动浏览器...`
          );
    
          // 初始化浏览器,添加更多参数以避免检测
          browser = await chromium.launch({
            headless,
            timeout: timeout * 2, // 增加浏览器启动超时时间
            args: [
              "--disable-blink-features=AutomationControlled",
              "--disable-features=IsolateOrigins,site-per-process",
              "--disable-site-isolation-trials",
              "--disable-web-security",
              "--no-sandbox",
              "--disable-setuid-sandbox",
              "--disable-dev-shm-usage",
              "--disable-accelerated-2d-canvas",
              "--no-first-run",
              "--no-zygote",
              "--disable-gpu",
              "--hide-scrollbars",
              "--mute-audio",
              "--disable-background-networking",
              "--disable-background-timer-throttling",
              "--disable-backgrounding-occluded-windows",
              "--disable-breakpad",
              "--disable-component-extensions-with-background-pages",
              "--disable-extensions",
              "--disable-features=TranslateUI",
              "--disable-ipc-flooding-protection",
              "--disable-renderer-backgrounding",
              "--enable-features=NetworkService,NetworkServiceInProcess",
              "--force-color-profile=srgb",
              "--metrics-recording-only",
            ],
            ignoreDefaultArgs: ["--enable-automation"],
          });
    
          logger.info("浏览器已成功启动!");
        }
    
        // 使用统一的 Chrome 设备配置
        const deviceConfig = devices["Desktop Chrome"];
    
        // 创建浏览器上下文选项
        let contextOptions: BrowserContextOptions = {
          ...deviceConfig,
        };
    
        // 如果有保存的指纹配置,使用它;否则使用宿主机器的实际设置
        if (savedState.fingerprint) {
          contextOptions = {
            ...contextOptions,
            locale: savedState.fingerprint.locale,
            timezoneId: savedState.fingerprint.timezoneId,
            colorScheme: savedState.fingerprint.colorScheme,
            reducedMotion: savedState.fingerprint.reducedMotion,
            forcedColors: savedState.fingerprint.forcedColors,
          };
          logger.info("使用保存的浏览器指纹配置");
        } else {
          // 获取宿主机器的实际设置
          const hostConfig = getHostMachineConfig(locale);
    
          contextOptions = {
            ...contextOptions,
            locale: hostConfig.locale,
            timezoneId: hostConfig.timezoneId,
            colorScheme: hostConfig.colorScheme,
            reducedMotion: hostConfig.reducedMotion,
            forcedColors: hostConfig.forcedColors,
          };
    
          // 保存新生成的指纹配置
          savedState.fingerprint = hostConfig;
          logger.info(
            {
              locale: hostConfig.locale,
              timezone: hostConfig.timezoneId,
              colorScheme: hostConfig.colorScheme,
              deviceType: hostConfig.deviceName,
            },
            "已根据宿主机器生成新的浏览器指纹配置"
          );
        }
    
        // 添加通用选项 - 确保使用桌面配置
        contextOptions = {
          ...contextOptions,
          permissions: ["geolocation", "notifications"],
          acceptDownloads: true,
          isMobile: false, // 强制使用桌面模式
          hasTouch: false, // 禁用触摸功能
          javaScriptEnabled: true,
        };
    
        if (storageState) {
          logger.info("正在加载保存的浏览器状态...");
        }
    
        const context = await browser.newContext(
          storageState ? { ...contextOptions, storageState } : contextOptions
        );
    
        // 设置额外的浏览器属性以避免检测
        await context.addInitScript(() => {
          // 覆盖 navigator 属性
          Object.defineProperty(navigator, "webdriver", { get: () => false });
          Object.defineProperty(navigator, "plugins", {
            get: () => [1, 2, 3, 4, 5],
          });
          Object.defineProperty(navigator, "languages", {
            get: () => ["en-US", "en", "zh-CN"],
          });
    
          // 覆盖 window 属性
          // @ts-ignore - 忽略 chrome 属性不存在的错误
          window.chrome = {
            runtime: {},
            loadTimes: function () {},
            csi: function () {},
            app: {},
          };
    
          // 添加 WebGL 指纹随机化
          if (typeof WebGLRenderingContext !== "undefined") {
            const getParameter = WebGLRenderingContext.prototype.getParameter;
            WebGLRenderingContext.prototype.getParameter = function (
              parameter: number
            ) {
              // 随机化 UNMASKED_VENDOR_WEBGL 和 UNMASKED_RENDERER_WEBGL
              if (parameter === 37445) {
                return "Intel Inc.";
              }
              if (parameter === 37446) {
                return "Intel Iris OpenGL Engine";
              }
              return getParameter.call(this, parameter);
            };
          }
        });
    
        const page = await context.newPage();
    
        // 设置页面额外属性
        await page.addInitScript(() => {
          // 模拟真实的屏幕尺寸和颜色深度
          Object.defineProperty(window.screen, "width", { get: () => 1920 });
          Object.defineProperty(window.screen, "height", { get: () => 1080 });
          Object.defineProperty(window.screen, "colorDepth", { get: () => 24 });
          Object.defineProperty(window.screen, "pixelDepth", { get: () => 24 });
        });
    
        try {
          logger.info("正在访问Google搜索页面...");
    
          // 统一使用 www.google.com 作为域名
          const selectedDomain = "www.google.com";
          // 保存选择的域名
          savedState.googleDomain = selectedDomain;
    
          // 构建搜索URL
          const searchUrl = `https://${selectedDomain}/search?q=${encodeURIComponent(
            query
          )}&hl=${locale}`;
    
          logger.info({ url: searchUrl, query, locale }, "正在访问Google搜索页面");
    
          // 尝试访问Google搜索页面,带重试机制
          let response: Response | null = null;
          let retryCount = 0;
          const maxRetries = 3;
          
          while (retryCount < maxRetries) {
            try {
              // 访问Google搜索页面
              response = await page.goto(searchUrl, {
                timeout: timeout * 2, // 增加超时时间
                waitUntil: "domcontentloaded", // 改用 domcontentloaded 而不是 networkidle
              });
              
              // 如果成功加载页面,跳出循环
              if (response && response.ok()) {
                logger.info("页面加载成功");
                break;
              }
              
              logger.warn({ 
                status: response?.status(), 
                url: response?.url(),
                retry: retryCount + 1
              }, "页面加载不成功,准备重试");
              
              // 等待一段时间后重试
              await page.waitForTimeout(2000);
              retryCount++;
            } catch (error) {
              logger.error({ error: error instanceof Error ? error.message : String(error), retry: retryCount + 1 }, "页面加载出错");
              
              // 等待一段时间后重试
              await page.waitForTimeout(2000);
              retryCount++;
            }
          }
    
          // 如果所有重试都失败,抛出错误
          if (retryCount >= maxRetries && (!response || !response.ok())) {
            throw new Error(`无法加载Google搜索页面,已重试${maxRetries}次`);
          }
    
          // 检查是否被重定向到人机验证页面
          const currentUrl = page.url();
          logger.info({ currentUrl }, "当前页面URL");
    
          const sorryPatterns = [
            "google.com/sorry/index",
            "google.com/sorry",
            "recaptcha",
            "captcha",
            "unusual traffic",
          ];
    
          const isBlockedPage = sorryPatterns.some(
            (pattern) =>
              currentUrl.includes(pattern) ||
              (response && response.url().includes(pattern))
          );
    
          if (isBlockedPage) {
            logger.warn("检测到人机验证页面");
            if (headless) {
              // 在无头模式下,转为有头模式重试
              await page.close();
              await context.close();
              if (!browserWasProvided) {
                await browser.close();
                return performSearch(false); // 以有头模式重新执行搜索
              }
              throw new Error("检测到人机验证页面,请尝试有头模式或手动验证");
            } else {
              logger.warn("请在浏览器中完成验证...");
              throw new Error("检测到人机验证页面,需要手动完成验证");
            }
          }
    
          // 检查URL是否已经包含搜索查询
          const isSearchResultPage = currentUrl.includes("/search") && currentUrl.includes("q=");
          
          // 如果已经是搜索结果页面,跳过输入搜索关键词的步骤
          if (isSearchResultPage) {
            logger.info({ currentUrl }, "已经在搜索结果页面,跳过输入搜索关键词的步骤");
          } else {
            logger.info({ query }, "正在输入搜索关键词");
    
            // 等待搜索框出现 - 尝试多个可能的选择器
            const searchInputSelectors = [
              "textarea[name='q']",
              "input[name='q']",
              "textarea[title='Search']",
              "input[title='Search']",
              "textarea[aria-label='Search']",
              "input[aria-label='Search']",
              "textarea[aria-label='搜索']",
              "input[aria-label='搜索']",
              "#search-box",
              "#searchform input",
              "#searchbox",
              ".gLFyf",
              "textarea",
              "input[type='text']"
            ];
    
            // 尝试等待搜索框出现
            try {
              const selector = searchInputSelectors.join(',');
              logger.debug({ selector }, "等待搜索框选择器");
              // 使用更短的超时时间,避免长时间等待
              await page.waitForSelector(selector, { timeout: 10000 });
              logger.info({ selector }, "搜索框已出现");
            } catch (error) {
              // 处理 error 为 unknown 类型的情况
              const errorMessage = error instanceof Error ? error.message : String(error);
              logger.warn({ error: errorMessage }, "等待搜索框出现超时,将尝试直接查找");
            }
    
            let searchInput = null;
            for (const selector of searchInputSelectors) {
              logger.debug({ selector }, "尝试查找搜索框");
              searchInput = await page.$(selector);
              if (searchInput) {
                logger.info({ selector }, "找到搜索框");
                break;
              }
              logger.debug({ selector }, "未找到搜索框");
            }
    
            if (!searchInput) {
              // 分析页面内容
              logger.info("分析页面内容以查找问题...");
              
              // 获取页面标题
              const title = await page.title();
              logger.info({ title }, "页面标题");
              
              // 检查页面是否包含特定文本
              const pageContent = await page.content();
              const containsRecaptcha = pageContent.includes("recaptcha") || pageContent.includes("captcha");
              const containsRobot = pageContent.includes("robot") || pageContent.includes("automated");
              const containsError = pageContent.includes("error") || pageContent.includes("sorry");
              
              logger.info({ 
                containsRecaptcha, 
                containsRobot, 
                containsError,
                url: page.url()
              }, "页面内容分析");
              
              // 获取所有可见的输入元素
              const inputElements = await page.$$eval('input, textarea', elements => {
                return elements.map(el => ({
                  type: el.tagName,
                  id: el.id,
                  name: (el as HTMLInputElement | HTMLTextAreaElement).name || '',
                  class: el.className,
                  placeholder: (el as HTMLInputElement | HTMLTextAreaElement).placeholder || '',
                  visible: (el as HTMLElement).offsetWidth > 0 && (el as HTMLElement).offsetHeight > 0
                }));
              });
              
              logger.info({ inputElements }, "页面上的输入元素");
    
              // 保存页面截图以便调试
              const screenshotPath = path.join(os.tmpdir(), `google-search-error-${Date.now()}.png`);
              try {
                await page.screenshot({ path: screenshotPath, fullPage: true });
                logger.error({ screenshotPath }, "已保存页面截图");
              } catch (screenshotError) {
                logger.error({ error: screenshotError }, "保存截图失败");
              }
              
              // 保存页面HTML以便调试
              const htmlPath = path.join(os.tmpdir(), `google-search-error-${Date.now()}.html`);
              try {
                const html = await page.content();
                fs.writeFileSync(htmlPath, html);
                logger.error({ htmlPath }, "已保存页面HTML");
              } catch (htmlError) {
                logger.error({ error: htmlError }, "保存HTML失败");
              }
              
              logger.error("无法找到搜索框");
              throw new Error("无法找到搜索框");
            }
    
            // 直接点击搜索框,减少延迟
            await searchInput.click();
    
            // 直接输入整个查询字符串,而不是逐个字符输入
            await page.keyboard.type(query, { delay: getRandomDelay(10, 30) });
    
            // 减少按回车前的延迟
            await page.waitForTimeout(getRandomDelay(100, 300));
            await page.keyboard.press("Enter");
    
            logger.info("正在等待页面加载完成...");
    
            // 等待页面加载完成
            await page.waitForLoadState("domcontentloaded", { timeout });
          }
    
          logger.info({ url: page.url() }, "正在等待搜索结果加载...");
    
          // 等待搜索结果加载
          try {
            await page.waitForSelector("#search, #rso, .g, [data-sokoban-container], div[role='main']", { 
              timeout: timeout / 2 
            });
            logger.info("搜索结果已加载");
          } catch (error) {
            logger.error("无法找到搜索结果元素");
            throw new Error("无法找到搜索结果元素");
          }
    
          // 减少等待时间
          await page.waitForTimeout(500);
    
          logger.info("正在提取搜索结果...");
    
          // 提取搜索结果
          const results = await page.$$eval(
            ".g, [data-sokoban-container] > div",
            (elements, maxResults) => {
              return elements
                .slice(0, maxResults)
                .map((el) => {
                  const titleElement = el.querySelector("h3");
                  const linkElement = el.querySelector("a");
                  const snippetElement = el.querySelector(".VwiC3b, [data-sncf='1']");
    
                  return {
                    title: titleElement ? titleElement.textContent || "" : "",
                    link: linkElement && linkElement instanceof HTMLAnchorElement
                      ? linkElement.href
                      : "",
                    snippet: snippetElement ? snippetElement.textContent || "" : "",
                  };
                })
                .filter((item) => item.title && item.link); // 过滤掉空结果
            },
            limit
          );
    
          logger.info({ count: results.length }, "成功获取到搜索结果");
    
          try {
            // 保存浏览器状态(除非用户指定了不保存)
            if (!noSaveState) {
              logger.info({ stateFile }, "正在保存浏览器状态...");
    
              // 确保目录存在
              const stateDir = path.dirname(stateFilePath);
              if (!fs.existsSync(stateDir)) {
                fs.mkdirSync(stateDir, { recursive: true });
              }
    
              // 保存状态
              await context.storageState({ path: stateFilePath });
              
              // 保存指纹配置
              fs.writeFileSync(
                fingerprintFilePath,
                JSON.stringify(savedState, null, 2),
                "utf8"
              );
              
              logger.info("浏览器状态和指纹配置已保存");
            }
          } catch (error) {
            logger.error({ error: error instanceof Error ? error.message : String(error) }, "保存状态时发生错误");
          }
    
          // 关闭浏览器(如果不是外部提供的)
          if (!browserWasProvided) {
            await browser.close();
          }
    
          // 返回搜索结果
          return {
            query,
            results,
            language: locale,
            region
          };
        } catch (error) {
          logger.error({ error: error instanceof Error ? error.message : String(error) }, "搜索过程中发生错误");
    
          // 尝试关闭资源
          try {
            if (!browserWasProvided && browser) {
              await browser.close();
            }
          } catch (closeError) {
            logger.error({ error: closeError instanceof Error ? closeError.message : String(closeError) }, "关闭浏览器时发生错误");
          }
    
          // 返回错误结果
          return {
            query,
            results: [
              {
                title: "搜索失败",
                link: "",
                snippet: `无法完成搜索,错误信息: ${error instanceof Error ? error.message : String(error)}`,
              },
            ],
            language: locale,
            region,
          };
        }
      }
    
      // 执行搜索,返回结果
      return performSearch(useHeadless);
    }
  • src/index.ts:32-80 (registration)
    Registers the 'search' tool with the MCP server, providing input schema via Zod and a handler that delegates to googleSearch.
    server.tool(
      "search",
      { 
        query: z.string().describe("搜索查询字符串"),
        limit: z.number().optional().describe("返回的搜索结果数量,默认为10"),
        timeout: z.number().optional().describe("搜索操作的超时时间(毫秒),默认为60000"),
        language: z.string().optional().describe("搜索结果的语言,例如 zh-CN, en-US 等,默认为 zh-CN"),
        region: z.string().optional().describe("搜索结果的地区,例如 cn, com, co.jp 等,默认为 cn")
      },
      async ({ 
        query, 
        limit = DEFAULT_LIMIT, 
        timeout = DEFAULT_TIMEOUT,
        language = DEFAULT_LANGUAGE,
        region = DEFAULT_REGION
      }: { 
        query: string; 
        limit?: number; 
        timeout?: number;
        language?: string;
        region?: string;
      }) => {
        try {
          // 构建搜索选项
          const options: CommandOptions = {
            limit,
            timeout,
            stateFile: STATE_FILE_PATH,
            locale: language,
            region
          }
          
          // 执行搜索
          const results = await googleSearch(query, options)
          
          return {
            content: [{ 
              type: "text", 
              text: JSON.stringify(results, null, 2)
            }]
          }
        } catch (error: any) {
          return {
            content: [{ type: "text", text: `执行 Google 搜索时出错: ${error.message}` }],
            isError: true
          }
        }
      }
    )
  • Zod input schema for the 'search' tool parameters.
    { 
      query: z.string().describe("搜索查询字符串"),
      limit: z.number().optional().describe("返回的搜索结果数量,默认为10"),
      timeout: z.number().optional().describe("搜索操作的超时时间(毫秒),默认为60000"),
      language: z.string().optional().describe("搜索结果的语言,例如 zh-CN, en-US 等,默认为 zh-CN"),
      region: z.string().optional().describe("搜索结果的地区,例如 cn, com, co.jp 等,默认为 cn")
    },
  • TypeScript interfaces defining SearchResult, SearchResponse (output), and CommandOptions (internal).
    export interface SearchResult {
      title: string;
      link: string;
      snippet: string;
    }
    
    /**
     * 搜索响应接口
     */
    export interface SearchResponse {
      query: string;
      results: SearchResult[];
      language?: string;
      region?: string;
    }
    
    /**
     * 命令行选项接口
     */
    export interface CommandOptions {
      limit?: number;
      timeout?: number;
      headless?: boolean; // 已废弃,但保留以兼容现有代码
      stateFile?: string;
      noSaveState?: boolean;
      locale?: string; // 搜索结果语言,默认为中文(zh-CN)
      region?: string; // 搜索结果地区,默认为中国(cn)
    }
  • Helper function to generate realistic browser fingerprint config to evade detection.
    function getHostMachineConfig(userLocale?: string): FingerprintConfig {
      // 获取系统区域设置
      const systemLocale = userLocale || process.env.LANG || "zh-CN";
    
      // 获取系统时区
      // Node.js 不直接提供时区信息,但可以通过时区偏移量推断
      const timezoneOffset = new Date().getTimezoneOffset();
      let timezoneId = "Asia/Shanghai"; // 默认使用上海时区
    
      // 根据时区偏移量粗略推断时区
      // 时区偏移量是以分钟为单位,与UTC的差值,负值表示东区
      if (timezoneOffset <= -480 && timezoneOffset > -600) {
        // UTC+8 (中国、新加坡、香港等)
        timezoneId = "Asia/Shanghai";
      } else if (timezoneOffset <= -540) {
        // UTC+9 (日本、韩国等)
        timezoneId = "Asia/Tokyo";
      } else if (timezoneOffset <= -420 && timezoneOffset > -480) {
        // UTC+7 (泰国、越南等)
        timezoneId = "Asia/Bangkok";
      } else if (timezoneOffset <= 0 && timezoneOffset > -60) {
        // UTC+0 (英国等)
        timezoneId = "Europe/London";
      } else if (timezoneOffset <= 60 && timezoneOffset > 0) {
        // UTC-1 (欧洲部分地区)
        timezoneId = "Europe/Berlin";
      } else if (timezoneOffset <= 300 && timezoneOffset > 240) {
        // UTC-5 (美国东部)
        timezoneId = "America/New_York";
      }
    
      // 检测系统颜色方案
      const hour = new Date().getHours();
      const colorScheme =
        hour >= 19 || hour < 7 ? ("dark" as const) : ("light" as const);
    
      // 其他设置使用合理的默认值
      const reducedMotion = "no-preference" as const;
      const forcedColors = "none" as const;
    
      // 直接使用 Chrome 作为设备名称
      const deviceName = "Desktop Chrome";
    
      return {
        deviceName,
        locale: systemLocale,
        timezoneId,
        colorScheme,
        reducedMotion,
        forcedColors,
      };
    }
Behavior1/5

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

Tool has no description.

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

Conciseness1/5

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

Tool has no description.

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

Completeness1/5

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

Tool has no description.

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

Parameters1/5

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

Tool has no description.

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

Purpose1/5

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

Tool has no description.

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

Usage Guidelines1/5

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

Tool has no description.

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/modelcontextprotocol-servers/google-search-mcp'

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