|
|
@@ -36,109 +36,115 @@ interface McpSearchResponse {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-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) {
|
|
|
- await ctx.ask({
|
|
|
- permission: "websearch",
|
|
|
- patterns: [params.query],
|
|
|
- always: ["*"],
|
|
|
- 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: {
|
|
|
+export const WebSearchTool = Tool.define("websearch", async () => {
|
|
|
+ return {
|
|
|
+ get description() {
|
|
|
+ return DESCRIPTION.replace("{{date}}", new Date().toISOString().slice(0, 10))
|
|
|
+ },
|
|
|
+ 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) {
|
|
|
+ await ctx.ask({
|
|
|
+ permission: "websearch",
|
|
|
+ patterns: [params.query],
|
|
|
+ always: ["*"],
|
|
|
+ metadata: {
|
|
|
query: params.query,
|
|
|
- type: params.type || "auto",
|
|
|
- numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
|
|
|
- livecrawl: params.livecrawl || "fallback",
|
|
|
+ numResults: params.numResults,
|
|
|
+ livecrawl: params.livecrawl,
|
|
|
+ type: params.type,
|
|
|
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 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 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]),
|
|
|
- })
|
|
|
+ const controller = new AbortController()
|
|
|
+ const timeoutId = setTimeout(() => controller.abort(), 25000)
|
|
|
|
|
|
- clearTimeout(timeoutId)
|
|
|
+ try {
|
|
|
+ const headers: Record<string, string> = {
|
|
|
+ accept: "application/json, text/event-stream",
|
|
|
+ "content-type": "application/json",
|
|
|
+ }
|
|
|
|
|
|
- if (!response.ok) {
|
|
|
- const errorText = await response.text()
|
|
|
- throw new Error(`Search error (${response.status}): ${errorText}`)
|
|
|
- }
|
|
|
+ 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()
|
|
|
+ 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: {},
|
|
|
+ // 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)
|
|
|
+ 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")
|
|
|
- }
|
|
|
+ if (error instanceof Error && error.name === "AbortError") {
|
|
|
+ throw new Error("Search request timed out")
|
|
|
+ }
|
|
|
|
|
|
- throw error
|
|
|
- }
|
|
|
- },
|
|
|
+ throw error
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
})
|