kagi_search_fetch
Execute web searches using Kagi's API to retrieve and organize results for one or more queries. Enables users to reference specific results by number for efficient information retrieval.
Instructions
Fetch web results based on one or more queries using the Kagi Search API. Use for general search and when the user explicitly tells you to 'fetch' results/information. Results are from all queries given. They are numbered continuously, so that a user may be able to refer to a result by a specific number.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| queries | Yes | One or more concise, keyword-focused search queries. Include essential context within each query for standalone use. |
Input Schema (JSON Schema)
{
"properties": {
"queries": {
"description": "One or more concise, keyword-focused search queries. Include essential context within each query for standalone use.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"queries"
],
"type": "object"
}
Implementation Reference
- src/index.ts:126-161 (handler)Main handler logic for 'kagi_search_fetch' tool: validates input queries, executes parallel searches using KagiClient, formats results with formatSearchResults, and returns formatted text content or error.if (name === 'kagi_search_fetch') { const queries = args?.queries as string[] | undefined; if (!queries || queries.length === 0) { throw new McpError( ErrorCode.InvalidParams, 'Search called with no queries.' ); } try { // Execute searches in parallel const searchPromises = queries.map(query => kagiClient.search(query)); const results = await Promise.all(searchPromises); const formattedResults = formatSearchResults(queries, results); return { content: [ { type: 'text', text: formattedResults, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }
- src/index.ts:26-42 (schema)Input schema and metadata definition for the 'kagi_search_fetch' tool, including name, description, and JSON schema for queries array.const SEARCH_TOOL: Tool = { name: 'kagi_search_fetch', description: 'Fetch web results based on one or more queries using the Kagi Search API. Use for general search and when the user explicitly tells you to \'fetch\' results/information. Results are from all queries given. They are numbered continuously, so that a user may be able to refer to a result by a specific number.', inputSchema: { type: 'object', properties: { queries: { type: 'array', items: { type: 'string', }, description: 'One or more concise, keyword-focused search queries. Include essential context within each query for standalone use.', }, }, required: ['queries'], }, };
- src/index.ts:69-73 (registration)Registration of the 'kagi_search_fetch' tool (as SEARCH_TOOL) in the MCP server's listTools handler.server.setRequestHandler(ListToolsRequestSchema, async (): Promise<ListToolsResult> => { return { tools: [SEARCH_TOOL, SUMMARIZER_TOOL], }; });
- src/index.ts:75-120 (helper)Helper function to format search results from multiple queries into a continuous numbered list grouped by query.function formatSearchResults(queries: string[], responses: SearchResponse[]): string { const resultTemplate = (result: { result_number: number; title: string; url: string; published: string; snippet: string; }) => `${result.result_number}: ${result.title} ${result.url} Published Date: ${result.published} ${result.snippet}`; const queryResponseTemplate = (query: string, formattedResults: string) => `----- Results for search query "${query}": ----- ${formattedResults}`; const perQueryResponseStrs: string[] = []; let startIndex = 1; for (let i = 0; i < queries.length; i++) { const query = queries[i]; const response = responses[i]; // t == 0 is search result, t == 1 is related searches const results = response.data.filter(result => result.t === 0); const formattedResultsList = results.map((result, idx) => resultTemplate({ result_number: startIndex + idx, title: result.title, url: result.url, published: result.published || 'Not Available', snippet: result.snippet, }) ); startIndex += results.length; const formattedResultsStr = formattedResultsList.join('\n\n'); const queryResponseStr = queryResponseTemplate(query, formattedResultsStr); perQueryResponseStrs.push(queryResponseStr); } return perQueryResponseStrs.join('\n\n'); }
- src/kagi-client.ts:49-82 (helper)KagiClient.search method: performs HTTP GET to Kagi search API with query, handles errors, returns SearchResponse.async search(query: string): Promise<SearchResponse> { try { const response = await this.axios.get('/search', { params: { q: query }, }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { let errorMessage = 'Unknown error'; if (error.response?.data) { if (typeof error.response.data === 'string') { errorMessage = error.response.data; } else if (error.response.data.error) { // Kagi API returns error as an array if (Array.isArray(error.response.data.error)) { errorMessage = error.response.data.error.map((e: any) => e.msg).join('; '); } else { errorMessage = error.response.data.error; } } else if (error.response.data.message) { errorMessage = error.response.data.message; } else { errorMessage = JSON.stringify(error.response.data); } } else { errorMessage = error.response?.statusText || error.message; } const statusCode = error.response?.status; throw new Error(`Kagi search error (${statusCode}): ${errorMessage}`); } throw error; } }