vn6295337 Claude Opus 4.5 commited on
Commit
210e257
·
1 Parent(s): 1707aaf

Add NewsAPI as third news source

Browse files

- Add newsapi_search function for NewsAPI.org
- Update search_company_news to fetch from Tavily + NYT + NewsAPI in parallel
- 150,000+ sources coverage (24hr delay on free tier)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. mcp-servers/news-basket/server.py +111 -5
mcp-servers/news-basket/server.py CHANGED
@@ -11,6 +11,7 @@ Use cases:
11
  Data Sources:
12
  - Tavily API: https://docs.tavily.com/ (1,000 credits/month free)
13
  - NYT Article Search API: https://developer.nytimes.com/ (500 req/day free)
 
14
  """
15
 
16
  import asyncio
@@ -55,6 +56,10 @@ TAVILY_BASE_URL = "https://api.tavily.com"
55
  NYT_API_KEY = os.getenv("NYT_API_KEY")
56
  NYT_BASE_URL = "https://api.nytimes.com/svc/search/v2/articlesearch.json"
57
 
 
 
 
 
58
 
59
  # ============================================================
60
  # SEARCH FUNCTIONS
@@ -229,20 +234,107 @@ async def nyt_search(
229
  return {"error": str(e)}
230
 
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  async def search_company_news(ticker: str, company_name: str = None) -> dict:
233
  """
234
- Search for recent news about a company using Tavily and NYT APIs.
235
- Combines results from both sources for comprehensive coverage.
236
  """
237
  query = f"{ticker} stock news"
238
  if company_name:
239
  query = f"{company_name} ({ticker}) stock news"
240
 
241
- # Fetch from both sources in parallel
242
  tavily_task = tavily_search(
243
  query=query,
244
  search_depth="basic",
245
- max_results=5,
246
  exclude_domains=["reddit.com", "twitter.com", "x.com"],
247
  )
248
 
@@ -253,7 +345,16 @@ async def search_company_news(ticker: str, company_name: str = None) -> dict:
253
  sort="newest",
254
  )
255
 
256
- tavily_result, nyt_result = await asyncio.gather(tavily_task, nyt_task)
 
 
 
 
 
 
 
 
 
257
 
258
  # Combine results
259
  all_results = []
@@ -269,6 +370,11 @@ async def search_company_news(ticker: str, company_name: str = None) -> dict:
269
  all_results.extend(nyt_result["results"])
270
  sources_used.append("NYT")
271
 
 
 
 
 
 
272
  # Build combined result
273
  result = {
274
  "query": query,
 
11
  Data Sources:
12
  - Tavily API: https://docs.tavily.com/ (1,000 credits/month free)
13
  - NYT Article Search API: https://developer.nytimes.com/ (500 req/day free)
14
+ - NewsAPI: https://newsapi.org/ (100 req/day free, 24hr delay)
15
  """
16
 
17
  import asyncio
 
56
  NYT_API_KEY = os.getenv("NYT_API_KEY")
57
  NYT_BASE_URL = "https://api.nytimes.com/svc/search/v2/articlesearch.json"
58
 
59
+ # NewsAPI configuration (24hr lag on free tier)
60
+ NEWSAPI_API_KEY = os.getenv("NEWSAPI_API_KEY")
61
+ NEWSAPI_BASE_URL = "https://newsapi.org/v2/everything"
62
+
63
 
64
  # ============================================================
65
  # SEARCH FUNCTIONS
 
234
  return {"error": str(e)}
235
 
236
 
237
+ async def newsapi_search(
238
+ query: str,
239
+ max_results: int = 5,
240
+ sort_by: str = "publishedAt",
241
+ language: str = "en",
242
+ ) -> dict:
243
+ """
244
+ Search NewsAPI.org for articles.
245
+
246
+ Args:
247
+ query: Search query
248
+ max_results: Number of results (max 100)
249
+ sort_by: "publishedAt", "relevancy", or "popularity"
250
+ language: Language code (e.g., "en")
251
+
252
+ Returns:
253
+ Dict with articles from 150,000+ sources
254
+
255
+ Note: Free tier has 24-hour delay on articles
256
+ """
257
+ if not NEWSAPI_API_KEY:
258
+ return {
259
+ "error": "NEWSAPI_API_KEY not configured",
260
+ "message": "Add NEWSAPI_API_KEY to ~/.env file. Get free key at https://newsapi.org/"
261
+ }
262
+
263
+ try:
264
+ async with httpx.AsyncClient() as client:
265
+ params = {
266
+ "apiKey": NEWSAPI_API_KEY,
267
+ "q": query,
268
+ "sortBy": sort_by,
269
+ "language": language,
270
+ "pageSize": min(max_results, 100),
271
+ }
272
+
273
+ response = await client.get(
274
+ NEWSAPI_BASE_URL,
275
+ params=params,
276
+ timeout=30
277
+ )
278
+
279
+ if response.status_code == 426:
280
+ return {
281
+ "error": "NewsAPI requires paid plan for this request",
282
+ "message": "Free tier limited to 24hr old articles"
283
+ }
284
+
285
+ if response.status_code != 200:
286
+ return {
287
+ "error": f"NewsAPI error: {response.status_code}",
288
+ "message": response.text
289
+ }
290
+
291
+ data = response.json()
292
+
293
+ if data.get("status") != "ok":
294
+ return {
295
+ "error": data.get("code", "Unknown error"),
296
+ "message": data.get("message", "")
297
+ }
298
+
299
+ # Format results
300
+ results = []
301
+ for art in data.get("articles", [])[:max_results]:
302
+ results.append({
303
+ "title": art.get("title", ""),
304
+ "url": art.get("url", ""),
305
+ "content": art.get("description", "") or art.get("content", ""),
306
+ "published_date": art.get("publishedAt", ""),
307
+ "source": art.get("source", {}).get("name", "NewsAPI"),
308
+ })
309
+
310
+ return {
311
+ "query": query,
312
+ "results": results,
313
+ "result_count": len(results),
314
+ "total_hits": data.get("totalResults", 0),
315
+ "source": "NewsAPI",
316
+ "as_of": datetime.now().isoformat()
317
+ }
318
+
319
+ except Exception as e:
320
+ logger.error(f"NewsAPI search error: {e}")
321
+ return {"error": str(e)}
322
+
323
+
324
  async def search_company_news(ticker: str, company_name: str = None) -> dict:
325
  """
326
+ Search for recent news about a company using Tavily, NYT, and NewsAPI.
327
+ Combines results from all sources for comprehensive coverage.
328
  """
329
  query = f"{ticker} stock news"
330
  if company_name:
331
  query = f"{company_name} ({ticker}) stock news"
332
 
333
+ # Fetch from all sources in parallel
334
  tavily_task = tavily_search(
335
  query=query,
336
  search_depth="basic",
337
+ max_results=4,
338
  exclude_domains=["reddit.com", "twitter.com", "x.com"],
339
  )
340
 
 
345
  sort="newest",
346
  )
347
 
348
+ newsapi_query = company_name or ticker
349
+ newsapi_task = newsapi_search(
350
+ query=newsapi_query,
351
+ max_results=3,
352
+ sort_by="publishedAt",
353
+ )
354
+
355
+ tavily_result, nyt_result, newsapi_result = await asyncio.gather(
356
+ tavily_task, nyt_task, newsapi_task
357
+ )
358
 
359
  # Combine results
360
  all_results = []
 
370
  all_results.extend(nyt_result["results"])
371
  sources_used.append("NYT")
372
 
373
+ # Add NewsAPI results
374
+ if "results" in newsapi_result and newsapi_result["results"]:
375
+ all_results.extend(newsapi_result["results"])
376
+ sources_used.append("NewsAPI")
377
+
378
  # Build combined result
379
  result = {
380
  "query": query,