search_news
Search news articles across multiple sources with automatic API failover to ensure reliable results when querying topics in various languages.
Instructions
Search for news articles using multiple news APIs with automatic failover. The system intelligently switches between different news APIs when one reaches its limit or fails.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query for news articles (keywords, topics, etc.) | |
| language | No | Language code for news articles (e.g., 'en', 'zh', 'es') | en |
| limit | No | Maximum number of articles to return (1-10) |
Implementation Reference
- src/tools/newsSearch.ts:193-281 (handler)The 'run' method of the newsSearch tool, containing the core logic for searching news articles across multiple APIs with failover, rate limiting, and formatted output.async run(args: { query: string; language?: string; limit?: number }) { try { // Validate input if (!args.query || args.query.trim().length === 0) { throw new Error("Search query cannot be empty"); } const query = args.query.trim(); const language = args.language || 'en'; const limit = Math.min(Math.max(args.limit || 5, 1), 10); // Get available APIs const activeAPIs = getActiveAPIs(); if (activeAPIs.length === 0) { return { content: [{ type: "text", text: "❌ **No News APIs Configured**\n\nPlease configure at least one news API key in your .env file. Available options:\n\n" + "- **NewsAPI.org**: Get free key at https://newsapi.org/register\n" + "- **GNews API**: Get free key at https://gnews.io/\n" + "- **TheNewsAPI**: Get free key at https://www.thenewsapi.com/\n" + "- **NewsData.io**: Get free key at https://newsdata.io/\n" + "- **Twingly**: Get trial key at https://www.twingly.com/news-api/\n\n" + "Add your API keys to the .env file and restart the service." }], isError: true }; } let lastError = ''; let attemptedAPIs: string[] = []; // Try each API in priority order for (const apiConfig of activeAPIs) { // Skip APIs that have reached their daily limit if (!hasRemainingQuota(apiConfig)) { attemptedAPIs.push(`${apiConfig.name} (quota exceeded)`); continue; } attemptedAPIs.push(apiConfig.name); const result = await searchWithAPI(apiConfig, query, language, limit); if (result.success && result.data) { const formattedResponse = formatNewsResponse(result.data, result.apiUsed!); const quotaInfo = `\n\n---\n\n**API Used:** ${result.apiUsed}\n**Remaining Quota:** ${result.remainingQuota} requests\n**Attempted APIs:** ${attemptedAPIs.join(', ')}`; return { content: [{ type: "text", text: formattedResponse + quotaInfo }] }; } else { lastError = result.error || 'Unknown error'; console.warn(`API ${apiConfig.name} failed: ${lastError}`); } } // All APIs failed return { content: [{ type: "text", text: `❌ **All News APIs Failed**\n\n` + `**Query:** "${query}"\n` + `**Attempted APIs:** ${attemptedAPIs.join(', ')}\n` + `**Last Error:** ${lastError}\n\n` + `**Suggestions:**\n` + `- Check your API keys in the .env file\n` + `- Verify your internet connection\n` + `- Try a different search query\n` + `- Some APIs may have temporary outages` }], isError: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [{ type: "text", text: `❌ **Search Failed:** ${errorMessage}` }], isError: true }; } }
- src/tools/newsSearch.ts:170-191 (schema)JSON Schema defining the input parameters for the 'search_news' tool, including query (required), language, and limit.parameters: { type: "object", properties: { query: { type: "string", description: "Search query for news articles (keywords, topics, etc.)" }, language: { type: "string", description: "Language code for news articles (e.g., 'en', 'zh', 'es')", default: "en" }, limit: { type: "number", description: "Maximum number of articles to return (1-10)", minimum: 1, maximum: 10, default: 5 } }, required: ["query"] },
- src/index.ts:19-29 (registration)MCP server registration for listing tools, exposing the 'search_news' tool with its name, description, and input schema.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: newsSearch.name, description: newsSearch.description, inputSchema: newsSearch.parameters } ] }; });
- src/index.ts:32-39 (registration)MCP server handler for tool calls, dispatching 'search_news' requests to the newsSearch.run implementation.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case "search_news": return await newsSearch.run(request.params.arguments as { query: string; language?: string; limit?: number }); default: throw new Error(`Unknown tool: ${request.params.name}`); } });
- src/tools/newsSearch.ts:102-164 (helper)Helper function to perform API-specific news searches, handling different endpoints and error conditions for multiple news providers.async function searchWithAPI(apiConfig: NewsAPIConfig, query: string, language: string = 'en', pageSize: number = 10): Promise<NewsAPIResponse> { try { let url = ''; let headers: any = {}; // Build API-specific request switch (apiConfig.name) { case 'NewsAPI.org': url = `${apiConfig.baseUrl}?q=${encodeURIComponent(query)}&language=${language}&pageSize=${pageSize}&apiKey=${apiConfig.apiKey}`; break; case 'GNews': url = `${apiConfig.baseUrl}?q=${encodeURIComponent(query)}&lang=${language}&max=${pageSize}&apikey=${apiConfig.apiKey}`; break; case 'TheNewsAPI': url = `${apiConfig.baseUrl}/all?api_token=${apiConfig.apiKey}&search=${encodeURIComponent(query)}&language=${language}&limit=${pageSize}`; break; case 'NewsData.io': url = `${apiConfig.baseUrl}?apikey=${apiConfig.apiKey}&q=${encodeURIComponent(query)}&language=${language}&size=${pageSize}`; break; case 'Twingly': url = `${apiConfig.baseUrl}?q=${encodeURIComponent(query)}&format=json&apikey=${apiConfig.apiKey}`; break; default: throw new Error(`Unsupported API: ${apiConfig.name}`); } const response = await fetch(url, { headers }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } const data = await response.json(); // Check for API-specific error responses if ((data as any).status === 'error' || (data as any).error) { throw new Error((data as any).message || (data as any).error || 'API returned an error'); } incrementUsage(apiConfig.name); return { success: true, data, apiUsed: apiConfig.name, remainingQuota: getRemainingQuota(apiConfig) }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, error: errorMessage, apiUsed: apiConfig.name }; } }