Skip to main content
Glama

Open Search MCP

by flyanima
MIT License
2
  • Apple
  • Linux
newsapi-search-client.ts9.37 kB
import axios, { AxiosInstance } from 'axios'; import { Logger } from '../../utils/logger.js'; /** * NewsAPI 搜索客户端 - 专注于新闻搜索场景 * 支持新闻搜索、头条新闻、新闻来源查询 */ interface EverythingParams { q?: string; sources?: string; domains?: string; from?: string; to?: string; language?: string; sortBy?: 'relevancy' | 'popularity' | 'publishedAt'; pageSize?: number; page?: number; } interface HeadlinesParams { country?: string; category?: string; sources?: string; q?: string; pageSize?: number; page?: number; } interface SourcesParams { category?: string; language?: string; country?: string; } export class NewsAPISearchClient { private httpClient: AxiosInstance; private logger: Logger; private apiKey: string; private requestCount = 0; private lastResetTime = Date.now(); constructor(apiKey: string) { this.apiKey = apiKey; this.logger = new Logger('NewsAPISearch'); this.httpClient = axios.create({ baseURL: 'https://newsapi.org/v2', timeout: 15000, headers: { 'User-Agent': 'Open-Search-MCP/2.0', 'X-Api-Key': this.apiKey } }); } /** * 速率限制检查 (每天1000次) */ private async checkRateLimit(): Promise<void> { const now = Date.now(); // 重置计数器 (每天) if (now - this.lastResetTime >= 86400000) { // 24小时 this.requestCount = 0; this.lastResetTime = now; } // 检查限制 if (this.requestCount >= 1000) { const waitTime = 86400000 - (now - this.lastResetTime); this.logger.warn(`Daily rate limit reached, waiting ${Math.round(waitTime / 3600000)}h`); throw new Error('Daily rate limit exceeded. Please try again tomorrow.'); } this.requestCount++; } /** * 通用API请求方法 */ private async makeRequest(endpoint: string, params: Record<string, any>): Promise<any> { await this.checkRateLimit(); try { const response = await this.httpClient.get(endpoint, { params }); const data = response.data; // 检查API错误 if (data.status === 'error') { throw new Error(data.message || 'NewsAPI request failed'); } return data; } catch (error) { this.logger.error('NewsAPI request failed:', error); throw error; } } /** * 搜索所有新闻 (Everything端点) */ async searchEverything(params: EverythingParams): Promise<any> { this.logger.info(`Searching news: ${params.q || 'all'}`); // 设置默认参数 const searchParams = { pageSize: 20, sortBy: 'publishedAt', language: 'en', ...params }; const data = await this.makeRequest('/everything', searchParams); return { query: params.q || 'all news', totalResults: data.totalResults, articles: data.articles.map((article: any) => ({ title: article.title, description: article.description, url: article.url, urlToImage: article.urlToImage, publishedAt: article.publishedAt, source: { id: article.source.id, name: article.source.name }, author: article.author, content: article.content })), source: 'NewsAPI', timestamp: new Date().toISOString() }; } /** * 获取头条新闻 (Top Headlines端点) */ async getTopHeadlines(params: HeadlinesParams): Promise<any> { this.logger.info(`Getting headlines: ${params.country || params.category || 'general'}`); // 设置默认参数 const searchParams = { pageSize: 20, ...params }; const data = await this.makeRequest('/top-headlines', searchParams); return { query: params.q || `${params.country || 'global'} headlines`, totalResults: data.totalResults, articles: data.articles.map((article: any) => ({ title: article.title, description: article.description, url: article.url, urlToImage: article.urlToImage, publishedAt: article.publishedAt, source: { id: article.source.id, name: article.source.name }, author: article.author, content: article.content })), source: 'NewsAPI', timestamp: new Date().toISOString() }; } /** * 获取新闻来源 (Sources端点) */ async getSources(params: SourcesParams = {}): Promise<any> { this.logger.info(`Getting sources: ${params.category || params.language || 'all'}`); const data = await this.makeRequest('/sources', params); return { query: `sources ${params.category || params.language || 'all'}`, totalResults: data.sources.length, sources: data.sources.map((source: any) => ({ id: source.id, name: source.name, description: source.description, url: source.url, category: source.category, language: source.language, country: source.country })), source: 'NewsAPI', timestamp: new Date().toISOString() }; } /** * 智能新闻搜索 - 根据查询自动选择最佳端点 */ async smartSearch(query: string, options: any = {}): Promise<any> { this.logger.info(`Smart search: ${query}`); // 分析查询意图 const intent = this.analyzeSearchIntent(query); switch (intent.type) { case 'headlines': return await this.getTopHeadlines({ q: intent.keywords, country: intent.country, category: intent.category, ...options }); case 'sources': return await this.getSources({ category: intent.category, language: intent.language, country: intent.country, ...options }); default: // 'everything' return await this.searchEverything({ q: intent.keywords, language: intent.language, from: intent.dateFrom, to: intent.dateTo, sortBy: intent.sortBy, ...options }); } } /** * 分析搜索意图 */ private analyzeSearchIntent(query: string): any { const normalizedQuery = query.toLowerCase().trim(); // 头条新闻关键词 const headlineKeywords = ['头条', 'headlines', '今日', 'today', '最新', 'latest', 'breaking']; const isHeadlines = headlineKeywords.some(kw => normalizedQuery.includes(kw)); // 来源查询关键词 const sourceKeywords = ['来源', 'sources', '媒体', 'media', '新闻源']; const isSources = sourceKeywords.some(kw => normalizedQuery.includes(kw)); // 提取国家 const countryMap: Record<string, string> = { '中国': 'cn', '美国': 'us', '英国': 'gb', '日本': 'jp', 'china': 'cn', 'usa': 'us', 'america': 'us', 'uk': 'gb', 'japan': 'jp' }; let country = undefined; for (const [name, code] of Object.entries(countryMap)) { if (normalizedQuery.includes(name)) { country = code; break; } } // 提取类别 const categoryMap: Record<string, string> = { '科技': 'technology', '商业': 'business', '体育': 'sports', '娱乐': 'entertainment', '健康': 'health', '科学': 'science', 'tech': 'technology', 'business': 'business', 'sports': 'sports', 'entertainment': 'entertainment', 'health': 'health', 'science': 'science' }; let category = undefined; for (const [name, cat] of Object.entries(categoryMap)) { if (normalizedQuery.includes(name)) { category = cat; break; } } // 提取语言 const language = normalizedQuery.includes('中文') || /[\u4e00-\u9fff]/.test(query) ? 'zh' : 'en'; // 提取日期范围 let dateFrom = undefined; let dateTo = undefined; if (normalizedQuery.includes('今日') || normalizedQuery.includes('today')) { dateFrom = new Date().toISOString().split('T')[0]; } else if (normalizedQuery.includes('本周') || normalizedQuery.includes('this week')) { const weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7); dateFrom = weekAgo.toISOString().split('T')[0]; } // 确定搜索类型 let type = 'everything'; if (isSources) { type = 'sources'; } else if (isHeadlines) { type = 'headlines'; } // 提取关键词 (移除意图词汇) let keywords = query; const removeWords = [...headlineKeywords, ...sourceKeywords, ...Object.keys(countryMap), ...Object.keys(categoryMap)]; removeWords.forEach(word => { keywords = keywords.replace(new RegExp(word, 'gi'), '').trim(); }); return { type, keywords: keywords || undefined, country, category, language, dateFrom, dateTo, sortBy: isHeadlines ? 'publishedAt' : 'relevancy' }; } /** * 获取使用统计 */ getUsageStats(): any { return { requestsToday: this.requestCount, dailyLimit: 1000, remainingRequests: Math.max(0, 1000 - this.requestCount), usagePercentage: (this.requestCount / 1000) * 100, resetTime: new Date(this.lastResetTime + 86400000).toISOString() }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flyanima/open-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server