| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- import z from "zod"
- import { Tool } from "./tool"
- import DESCRIPTION from "./websearch.txt"
- import { Config } from "../config/config"
- import { Permission } from "../permission"
- const API_CONFIG = {
- BASE_URL: "https://mcp.exa.ai",
- ENDPOINTS: {
- SEARCH: "/mcp",
- },
- DEFAULT_NUM_RESULTS: 8,
- } as const
- interface McpSearchRequest {
- jsonrpc: string
- id: number
- method: string
- params: {
- name: string
- arguments: {
- query: string
- numResults?: number
- livecrawl?: "fallback" | "preferred"
- type?: "auto" | "fast" | "deep"
- contextMaxCharacters?: number
- }
- }
- }
- interface McpSearchResponse {
- jsonrpc: string
- result: {
- content: Array<{
- type: string
- text: string
- }>
- }
- }
- export const WebSearchTool = Tool.define("websearch", {
- description: DESCRIPTION,
- parameters: z.object({
- query: z.string().describe("Websearch query"),
- numResults: z.number().optional().describe("Number of search results to return (default: 8)"),
- livecrawl: z
- .enum(["fallback", "preferred"])
- .optional()
- .describe(
- "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')",
- ),
- type: z
- .enum(["auto", "fast", "deep"])
- .optional()
- .describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"),
- contextMaxCharacters: z
- .number()
- .optional()
- .describe("Maximum characters for context string optimized for LLMs (default: 10000)"),
- }),
- async execute(params, ctx) {
- const cfg = await Config.get()
- if (cfg.permission?.webfetch === "ask")
- await Permission.ask({
- type: "websearch",
- sessionID: ctx.sessionID,
- messageID: ctx.messageID,
- callID: ctx.callID,
- title: "Search web for: " + params.query,
- metadata: {
- query: params.query,
- numResults: params.numResults,
- livecrawl: params.livecrawl,
- type: params.type,
- contextMaxCharacters: params.contextMaxCharacters,
- },
- })
- const searchRequest: McpSearchRequest = {
- jsonrpc: "2.0",
- id: 1,
- method: "tools/call",
- params: {
- name: "web_search_exa",
- arguments: {
- query: params.query,
- type: params.type || "auto",
- numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
- livecrawl: params.livecrawl || "fallback",
- contextMaxCharacters: params.contextMaxCharacters,
- },
- },
- }
- const controller = new AbortController()
- const timeoutId = setTimeout(() => controller.abort(), 25000)
- try {
- const headers: Record<string, string> = {
- accept: "application/json, text/event-stream",
- "content-type": "application/json",
- }
- const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SEARCH}`, {
- method: "POST",
- headers,
- body: JSON.stringify(searchRequest),
- signal: AbortSignal.any([controller.signal, ctx.abort]),
- })
- clearTimeout(timeoutId)
- if (!response.ok) {
- const errorText = await response.text()
- throw new Error(`Search error (${response.status}): ${errorText}`)
- }
- const responseText = await response.text()
- // Parse SSE response
- const lines = responseText.split("\n")
- for (const line of lines) {
- if (line.startsWith("data: ")) {
- const data: McpSearchResponse = JSON.parse(line.substring(6))
- if (data.result && data.result.content && data.result.content.length > 0) {
- return {
- output: data.result.content[0].text,
- title: `Web search: ${params.query}`,
- metadata: {},
- }
- }
- }
- }
- return {
- output: "No search results found. Please try a different query.",
- title: `Web search: ${params.query}`,
- metadata: {},
- }
- } catch (error) {
- clearTimeout(timeoutId)
- if (error instanceof Error && error.name === "AbortError") {
- throw new Error("Search request timed out")
- }
- throw error
- }
- },
- })
|