server.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. """
  2. TrendRadar MCP Server - FastMCP 2.0 实现
  3. 使用 FastMCP 2.0 提供生产级 MCP 工具服务器。
  4. 支持 stdio 和 HTTP 两种传输模式。
  5. """
  6. import json
  7. from typing import List, Optional, Dict, Union
  8. from fastmcp import FastMCP
  9. from .tools.data_query import DataQueryTools
  10. from .tools.analytics import AnalyticsTools
  11. from .tools.search_tools import SearchTools
  12. from .tools.config_mgmt import ConfigManagementTools
  13. from .tools.system import SystemManagementTools
  14. from .tools.storage_sync import StorageSyncTools
  15. from .utils.date_parser import DateParser
  16. from .utils.errors import MCPError
  17. # 创建 FastMCP 2.0 应用
  18. mcp = FastMCP('trendradar-news')
  19. # 全局工具实例(在第一次请求时初始化)
  20. _tools_instances = {}
  21. def _get_tools(project_root: Optional[str] = None):
  22. """获取或创建工具实例(单例模式)"""
  23. if not _tools_instances:
  24. _tools_instances['data'] = DataQueryTools(project_root)
  25. _tools_instances['analytics'] = AnalyticsTools(project_root)
  26. _tools_instances['search'] = SearchTools(project_root)
  27. _tools_instances['config'] = ConfigManagementTools(project_root)
  28. _tools_instances['system'] = SystemManagementTools(project_root)
  29. _tools_instances['storage'] = StorageSyncTools(project_root)
  30. return _tools_instances
  31. # ==================== 日期解析工具(优先调用)====================
  32. @mcp.tool
  33. async def resolve_date_range(
  34. expression: str
  35. ) -> str:
  36. """
  37. 【推荐优先调用】将自然语言日期表达式解析为标准日期范围
  38. **为什么需要这个工具?**
  39. 用户经常使用"本周"、"最近7天"等自然语言表达日期,但 AI 模型自己计算日期
  40. 可能导致不一致的结果。此工具在服务器端使用精确的当前时间计算,确保所有
  41. AI 模型获得一致的日期范围。
  42. **推荐使用流程:**
  43. 1. 用户说"分析AI本周的情感倾向"
  44. 2. AI 调用 resolve_date_range("本周") → 获取精确日期范围
  45. 3. AI 调用 analyze_sentiment(topic="ai", date_range=上一步返回的date_range)
  46. Args:
  47. expression: 自然语言日期表达式,支持:
  48. - 单日: "今天", "昨天", "today", "yesterday"
  49. - 周: "本周", "上周", "this week", "last week"
  50. - 月: "本月", "上月", "this month", "last month"
  51. - 最近N天: "最近7天", "最近30天", "last 7 days", "last 30 days"
  52. - 动态: "最近5天", "last 10 days"(任意天数)
  53. Returns:
  54. JSON格式的日期范围,可直接用于其他工具的 date_range 参数:
  55. {
  56. "success": true,
  57. "expression": "本周",
  58. "date_range": {
  59. "start": "2025-11-18",
  60. "end": "2025-11-26"
  61. },
  62. "current_date": "2025-11-26",
  63. "description": "本周(周一到周日,11-18 至 11-26)"
  64. }
  65. Examples:
  66. 用户:"分析AI本周的情感倾向"
  67. AI调用步骤:
  68. 1. resolve_date_range("本周")
  69. → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}, ...}
  70. 2. analyze_sentiment(topic="ai", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  71. 用户:"看看最近7天的特斯拉新闻"
  72. AI调用步骤:
  73. 1. resolve_date_range("最近7天")
  74. → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}, ...}
  75. 2. search_news(query="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  76. """
  77. try:
  78. result = DateParser.resolve_date_range_expression(expression)
  79. return json.dumps(result, ensure_ascii=False, indent=2)
  80. except MCPError as e:
  81. return json.dumps({
  82. "success": False,
  83. "error": e.to_dict()
  84. }, ensure_ascii=False, indent=2)
  85. except Exception as e:
  86. return json.dumps({
  87. "success": False,
  88. "error": {
  89. "code": "INTERNAL_ERROR",
  90. "message": str(e)
  91. }
  92. }, ensure_ascii=False, indent=2)
  93. # ==================== 数据查询工具 ====================
  94. @mcp.tool
  95. async def get_latest_news(
  96. platforms: Optional[List[str]] = None,
  97. limit: int = 50,
  98. include_url: bool = False
  99. ) -> str:
  100. """
  101. 获取最新一批爬取的新闻数据,快速了解当前热点
  102. Args:
  103. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  104. - 不指定时:使用 config.yaml 中配置的所有平台
  105. - 支持的平台来自 config/config.yaml 的 platforms 配置
  106. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  107. limit: 返回条数限制,默认50,最大1000
  108. 注意:实际返回数量可能少于请求值,取决于当前可用的新闻总数
  109. include_url: 是否包含URL链接,默认False(节省token)
  110. Returns:
  111. JSON格式的新闻列表
  112. **重要:数据展示建议**
  113. 本工具会返回完整的新闻列表(通常50条)给你。但请注意:
  114. - **工具返回**:完整的50条数据 ✅
  115. - **建议展示**:向用户展示全部数据,除非用户明确要求总结
  116. - **用户期望**:用户可能需要完整数据,请谨慎总结
  117. **何时可以总结**:
  118. - 用户明确说"给我总结一下"或"挑重点说"
  119. - 数据量超过100条时,可先展示部分并询问是否查看全部
  120. **注意**:如果用户询问"为什么只显示了部分",说明他们需要完整数据
  121. """
  122. tools = _get_tools()
  123. result = tools['data'].get_latest_news(platforms=platforms, limit=limit, include_url=include_url)
  124. return json.dumps(result, ensure_ascii=False, indent=2)
  125. @mcp.tool
  126. async def get_trending_topics(
  127. top_n: int = 10,
  128. mode: str = 'current',
  129. extract_mode: str = 'keywords'
  130. ) -> str:
  131. """
  132. 获取热点话题统计
  133. Args:
  134. top_n: 返回TOP N话题,默认10
  135. mode: 时间模式
  136. - "daily": 当日累计数据统计
  137. - "current": 最新一批数据统计(默认)
  138. extract_mode: 提取模式
  139. - "keywords": 统计预设关注词(基于 config/frequency_words.txt,默认)
  140. - "auto_extract": 自动从新闻标题提取高频词(无需预设,自动发现热点)
  141. Returns:
  142. JSON格式的话题频率统计列表
  143. Examples:
  144. - 使用预设关注词: get_trending_topics(mode="current")
  145. - 自动提取热点: get_trending_topics(extract_mode="auto_extract", top_n=20)
  146. """
  147. tools = _get_tools()
  148. result = tools['data'].get_trending_topics(top_n=top_n, mode=mode, extract_mode=extract_mode)
  149. return json.dumps(result, ensure_ascii=False, indent=2)
  150. @mcp.tool
  151. async def get_news_by_date(
  152. date_range: Optional[Union[Dict[str, str], str]] = None,
  153. platforms: Optional[List[str]] = None,
  154. limit: int = 50,
  155. include_url: bool = False
  156. ) -> str:
  157. """
  158. 获取指定日期的新闻数据,用于历史数据分析和对比
  159. Args:
  160. date_range: 日期范围,支持多种格式:
  161. - 范围对象: {"start": "2025-01-01", "end": "2025-01-07"}
  162. - 自然语言: "今天", "昨天", "本周", "最近7天"
  163. - 单日字符串: "2025-01-15"
  164. - 默认值: "今天"
  165. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  166. - 不指定时:使用 config.yaml 中配置的所有平台
  167. - 支持的平台来自 config/config.yaml 的 platforms 配置
  168. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  169. limit: 返回条数限制,默认50,最大1000
  170. 注意:实际返回数量可能少于请求值,取决于指定日期的新闻总数
  171. include_url: 是否包含URL链接,默认False(节省token)
  172. Returns:
  173. JSON格式的新闻列表,包含标题、平台、排名等信息
  174. **重要:数据展示建议**
  175. 本工具会返回完整的新闻列表(通常50条)给你。但请注意:
  176. - **工具返回**:完整的50条数据 ✅
  177. - **建议展示**:向用户展示全部数据,除非用户明确要求总结
  178. - **用户期望**:用户可能需要完整数据,请谨慎总结
  179. **何时可以总结**:
  180. - 用户明确说"给我总结一下"或"挑重点说"
  181. - 数据量超过100条时,可先展示部分并询问是否查看全部
  182. **注意**:如果用户询问"为什么只显示了部分",说明他们需要完整数据
  183. """
  184. tools = _get_tools()
  185. result = tools['data'].get_news_by_date(
  186. date_range=date_range,
  187. platforms=platforms,
  188. limit=limit,
  189. include_url=include_url
  190. )
  191. return json.dumps(result, ensure_ascii=False, indent=2)
  192. # ==================== 高级数据分析工具 ====================
  193. @mcp.tool
  194. async def analyze_topic_trend(
  195. topic: str,
  196. analysis_type: str = "trend",
  197. date_range: Optional[Union[Dict[str, str], str]] = None,
  198. granularity: str = "day",
  199. spike_threshold: float = 3.0,
  200. time_window: int = 24,
  201. lookahead_hours: int = 6,
  202. confidence_threshold: float = 0.7
  203. ) -> str:
  204. """
  205. 统一话题趋势分析工具 - 整合多种趋势分析模式
  206. **重要:日期范围处理**
  207. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  208. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  209. 2. 将返回的 date_range 传入本工具
  210. Args:
  211. topic: 话题关键词(必需)
  212. analysis_type: 分析类型,可选值:
  213. - "trend": 热度趋势分析(追踪话题的热度变化)
  214. - "lifecycle": 生命周期分析(从出现到消失的完整周期)
  215. - "viral": 异常热度检测(识别突然爆火的话题)
  216. - "predict": 话题预测(预测未来可能的热点)
  217. date_range: 日期范围(trend和lifecycle模式),可选
  218. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  219. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  220. - **默认**: 不指定时默认分析最近7天
  221. granularity: 时间粒度(trend模式),默认"day"(仅支持 day,因为底层数据按天聚合)
  222. spike_threshold: 热度突增倍数阈值(viral模式),默认3.0
  223. time_window: 检测时间窗口小时数(viral模式),默认24
  224. lookahead_hours: 预测未来小时数(predict模式),默认6
  225. confidence_threshold: 置信度阈值(predict模式),默认0.7
  226. Returns:
  227. JSON格式的趋势分析结果
  228. Examples:
  229. 用户:"分析AI本周的趋势"
  230. 推荐调用流程:
  231. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  232. 2. analyze_topic_trend(topic="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  233. 用户:"看看特斯拉最近30天的热度"
  234. 推荐调用流程:
  235. 1. resolve_date_range("最近30天") → {"date_range": {"start": "2025-10-28", "end": "2025-11-26"}}
  236. 2. analyze_topic_trend(topic="特斯拉", analysis_type="lifecycle", date_range=...)
  237. """
  238. tools = _get_tools()
  239. result = tools['analytics'].analyze_topic_trend_unified(
  240. topic=topic,
  241. analysis_type=analysis_type,
  242. date_range=date_range,
  243. granularity=granularity,
  244. threshold=spike_threshold,
  245. time_window=time_window,
  246. lookahead_hours=lookahead_hours,
  247. confidence_threshold=confidence_threshold
  248. )
  249. return json.dumps(result, ensure_ascii=False, indent=2)
  250. @mcp.tool
  251. async def analyze_data_insights(
  252. insight_type: str = "platform_compare",
  253. topic: Optional[str] = None,
  254. date_range: Optional[Union[Dict[str, str], str]] = None,
  255. min_frequency: int = 3,
  256. top_n: int = 20
  257. ) -> str:
  258. """
  259. 统一数据洞察分析工具 - 整合多种数据分析模式
  260. Args:
  261. insight_type: 洞察类型,可选值:
  262. - "platform_compare": 平台对比分析(对比不同平台对话题的关注度)
  263. - "platform_activity": 平台活跃度统计(统计各平台发布频率和活跃时间)
  264. - "keyword_cooccur": 关键词共现分析(分析关键词同时出现的模式)
  265. topic: 话题关键词(可选,platform_compare模式适用)
  266. date_range: **【对象类型】** 日期范围(可选)
  267. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  268. - **示例**: {"start": "2025-01-01", "end": "2025-01-07"}
  269. - **重要**: 必须是对象格式,不能传递整数
  270. min_frequency: 最小共现频次(keyword_cooccur模式),默认3
  271. top_n: 返回TOP N结果(keyword_cooccur模式),默认20
  272. Returns:
  273. JSON格式的数据洞察分析结果
  274. Examples:
  275. - analyze_data_insights(insight_type="platform_compare", topic="人工智能")
  276. - analyze_data_insights(insight_type="platform_activity", date_range={"start": "2025-01-01", "end": "2025-01-07"})
  277. - analyze_data_insights(insight_type="keyword_cooccur", min_frequency=5, top_n=15)
  278. """
  279. tools = _get_tools()
  280. result = tools['analytics'].analyze_data_insights_unified(
  281. insight_type=insight_type,
  282. topic=topic,
  283. date_range=date_range,
  284. min_frequency=min_frequency,
  285. top_n=top_n
  286. )
  287. return json.dumps(result, ensure_ascii=False, indent=2)
  288. @mcp.tool
  289. async def analyze_sentiment(
  290. topic: Optional[str] = None,
  291. platforms: Optional[List[str]] = None,
  292. date_range: Optional[Union[Dict[str, str], str]] = None,
  293. limit: int = 50,
  294. sort_by_weight: bool = True,
  295. include_url: bool = False
  296. ) -> str:
  297. """
  298. 分析新闻的情感倾向和热度趋势
  299. **重要:日期范围处理**
  300. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  301. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  302. 2. 将返回的 date_range 传入本工具
  303. Args:
  304. topic: 话题关键词(可选)
  305. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  306. - 不指定时:使用 config.yaml 中配置的所有平台
  307. - 支持的平台来自 config/config.yaml 的 platforms 配置
  308. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  309. date_range: 日期范围(可选)
  310. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  311. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  312. - **默认**: 不指定则默认查询今天的数据
  313. limit: 返回新闻数量,默认50,最大100
  314. 注意:本工具会对新闻标题进行去重(同一标题在不同平台只保留一次),
  315. 因此实际返回数量可能少于请求的 limit 值
  316. sort_by_weight: 是否按热度权重排序,默认True
  317. include_url: 是否包含URL链接,默认False(节省token)
  318. Returns:
  319. JSON格式的分析结果,包含情感分布、热度趋势和相关新闻
  320. Examples:
  321. 用户:"分析AI本周的情感倾向"
  322. 推荐调用流程:
  323. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  324. 2. analyze_sentiment(topic="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  325. 用户:"分析特斯拉最近7天的新闻情感"
  326. 推荐调用流程:
  327. 1. resolve_date_range("最近7天") → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}}
  328. 2. analyze_sentiment(topic="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  329. **重要:数据展示策略**
  330. - 本工具返回完整的分析结果和新闻列表
  331. - **默认展示方式**:展示完整的分析结果(包括所有新闻)
  332. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  333. """
  334. tools = _get_tools()
  335. result = tools['analytics'].analyze_sentiment(
  336. topic=topic,
  337. platforms=platforms,
  338. date_range=date_range,
  339. limit=limit,
  340. sort_by_weight=sort_by_weight,
  341. include_url=include_url
  342. )
  343. return json.dumps(result, ensure_ascii=False, indent=2)
  344. @mcp.tool
  345. async def find_related_news(
  346. reference_title: str,
  347. date_range: Optional[Union[Dict[str, str], str]] = None,
  348. threshold: float = 0.5,
  349. limit: int = 50,
  350. include_url: bool = False
  351. ) -> str:
  352. """
  353. 查找与指定新闻标题相关的其他新闻(支持当天和历史数据)
  354. Args:
  355. reference_title: 参考新闻标题(完整或部分)
  356. date_range: 日期范围(可选)
  357. - 不指定: 只查询今天的数据
  358. - "today": 今天
  359. - "yesterday": 昨天
  360. - "last_week": 最近7天
  361. - "last_month": 最近30天
  362. - {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}: 自定义范围
  363. threshold: 相似度阈值,0-1之间,默认0.5
  364. 注意:阈值越高匹配越严格,返回结果越少
  365. limit: 返回条数限制,默认50
  366. include_url: 是否包含URL链接,默认False(节省token)
  367. Returns:
  368. JSON格式的相关新闻列表,按相似度排序
  369. Examples:
  370. - 查找今天的相似新闻: find_related_news(reference_title="特斯拉降价")
  371. - 查找历史相关新闻: find_related_news(reference_title="特斯拉降价", date_range="last_week")
  372. - 自定义日期范围: find_related_news(reference_title="AI突破", date_range={"start": "2025-01-01", "end": "2025-01-15"})
  373. **重要:数据展示策略**
  374. - 本工具返回完整的相关新闻列表(包括相似度分数)
  375. - 仅在用户明确要求"总结"时才进行筛选
  376. """
  377. tools = _get_tools()
  378. result = tools['search'].find_related_news_unified(
  379. reference_title=reference_title,
  380. date_range=date_range,
  381. threshold=threshold,
  382. limit=limit,
  383. include_url=include_url
  384. )
  385. return json.dumps(result, ensure_ascii=False, indent=2)
  386. @mcp.tool
  387. async def generate_summary_report(
  388. report_type: str = "daily",
  389. date_range: Optional[Union[Dict[str, str], str]] = None
  390. ) -> str:
  391. """
  392. 每日/每周摘要生成器 - 自动生成热点摘要报告
  393. Args:
  394. report_type: 报告类型(daily/weekly)
  395. date_range: **【对象类型】** 自定义日期范围(可选)
  396. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  397. - **示例**: {"start": "2025-01-01", "end": "2025-01-07"}
  398. - **重要**: 必须是对象格式,不能传递整数
  399. Returns:
  400. JSON格式的摘要报告,包含Markdown格式内容
  401. """
  402. tools = _get_tools()
  403. result = tools['analytics'].generate_summary_report(
  404. report_type=report_type,
  405. date_range=date_range
  406. )
  407. return json.dumps(result, ensure_ascii=False, indent=2)
  408. @mcp.tool
  409. async def aggregate_news(
  410. date_range: Optional[Union[Dict[str, str], str]] = None,
  411. platforms: Optional[List[str]] = None,
  412. similarity_threshold: float = 0.7,
  413. limit: int = 50,
  414. include_url: bool = False
  415. ) -> str:
  416. """
  417. 跨平台新闻聚合 - 对相似新闻进行去重合并
  418. 将不同平台报道的同一事件合并为一条聚合新闻,
  419. 显示该新闻在各平台的覆盖情况和综合热度。
  420. **使用场景:**
  421. - 想要看到去重后的热点新闻(避免同一事件在不同平台重复展示)
  422. - 分析某个话题在多个平台的覆盖情况
  423. - 获取跨平台的综合热度排名
  424. Args:
  425. date_range: 日期范围(可选)
  426. - 不指定: 查询今天
  427. - {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}: 日期范围
  428. platforms: 平台过滤列表,如 ['zhihu', 'weibo']
  429. similarity_threshold: 相似度阈值,0.3-1.0之间,默认0.7
  430. 越高越严格(仅合并非常相似的标题)
  431. limit: 返回聚合新闻数量,默认50
  432. include_url: 是否包含URL链接,默认False
  433. Returns:
  434. JSON格式的聚合结果,包含:
  435. - summary: 聚合统计(原始数量、去重后数量、去重率)
  436. - aggregated_news: 聚合后的新闻列表
  437. - representative_title: 代表标题
  438. - platforms: 覆盖的平台列表
  439. - platform_count: 覆盖平台数
  440. - is_cross_platform: 是否跨平台新闻
  441. - best_rank: 最佳排名
  442. - aggregate_weight: 综合权重
  443. - sources: 各平台来源详情
  444. - statistics: 平台覆盖统计
  445. Examples:
  446. - aggregate_news() # 聚合今天所有平台的新闻
  447. - aggregate_news(similarity_threshold=0.8) # 更严格的相似度匹配
  448. - aggregate_news(date_range={"start": "2025-01-01", "end": "2025-01-07"})
  449. **重要:数据展示策略**
  450. - 本工具返回去重聚合后的新闻列表
  451. - 跨平台新闻(is_cross_platform=true)通常更具新闻价值
  452. - 可优先展示 platform_count > 1 的新闻
  453. """
  454. tools = _get_tools()
  455. result = tools['analytics'].aggregate_news(
  456. date_range=date_range,
  457. platforms=platforms,
  458. similarity_threshold=similarity_threshold,
  459. limit=limit,
  460. include_url=include_url
  461. )
  462. return json.dumps(result, ensure_ascii=False, indent=2)
  463. @mcp.tool
  464. async def compare_periods(
  465. period1: Union[Dict[str, str], str],
  466. period2: Union[Dict[str, str], str],
  467. topic: Optional[str] = None,
  468. compare_type: str = "overview",
  469. platforms: Optional[List[str]] = None,
  470. top_n: int = 10
  471. ) -> str:
  472. """
  473. 时期对比分析 - 比较两个时间段的新闻数据
  474. 对比不同时期的热点话题、平台活跃度、新闻数量等维度。
  475. **使用场景:**
  476. - 对比本周和上周的热点变化
  477. - 分析某个话题在两个时期的热度差异
  478. - 查看各平台活跃度的周期性变化
  479. Args:
  480. period1: 第一个时间段(基准期)
  481. - {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}: 日期范围
  482. - "today", "yesterday", "this_week", "last_week", "this_month", "last_month": 预设值
  483. period2: 第二个时间段(对比期,格式同 period1)
  484. topic: 可选的话题关键词(聚焦特定话题的对比)
  485. compare_type: 对比类型
  486. - "overview": 总体概览(默认)- 新闻数量、关键词变化、TOP新闻
  487. - "topic_shift": 话题变化分析 - 上升话题、下降话题、新出现话题
  488. - "platform_activity": 平台活跃度对比 - 各平台新闻数量变化
  489. platforms: 平台过滤列表,如 ['zhihu', 'weibo']
  490. top_n: 返回 TOP N 结果,默认10
  491. Returns:
  492. JSON格式的对比分析结果,包含:
  493. - periods: 两个时期的日期范围
  494. - compare_type: 对比类型
  495. - overview/topic_shift/platform_comparison: 具体对比结果(根据类型)
  496. Examples:
  497. - compare_periods(period1="last_week", period2="this_week") # 周环比
  498. - compare_periods(period1="last_month", period2="this_month", compare_type="topic_shift")
  499. - compare_periods(
  500. period1={"start": "2025-01-01", "end": "2025-01-07"},
  501. period2={"start": "2025-01-08", "end": "2025-01-14"},
  502. topic="人工智能"
  503. )
  504. """
  505. tools = _get_tools()
  506. result = tools['analytics'].compare_periods(
  507. period1=period1,
  508. period2=period2,
  509. topic=topic,
  510. compare_type=compare_type,
  511. platforms=platforms,
  512. top_n=top_n
  513. )
  514. return json.dumps(result, ensure_ascii=False, indent=2)
  515. # ==================== 智能检索工具 ====================
  516. @mcp.tool
  517. async def search_news(
  518. query: str,
  519. search_mode: str = "keyword",
  520. date_range: Optional[Union[Dict[str, str], str]] = None,
  521. platforms: Optional[List[str]] = None,
  522. limit: int = 50,
  523. sort_by: str = "relevance",
  524. threshold: float = 0.6,
  525. include_url: bool = False
  526. ) -> str:
  527. """
  528. 统一搜索接口,支持多种搜索模式
  529. **重要:日期范围处理**
  530. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  531. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  532. 2. 将返回的 date_range 传入本工具
  533. Args:
  534. query: 搜索关键词或内容片段
  535. search_mode: 搜索模式,可选值:
  536. - "keyword": 精确关键词匹配(默认,适合搜索特定话题)
  537. - "fuzzy": 模糊内容匹配(适合搜索内容片段,会过滤相似度低于阈值的结果)
  538. - "entity": 实体名称搜索(适合搜索人物/地点/机构)
  539. date_range: 日期范围(可选)
  540. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  541. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  542. - **默认**: 不指定时默认查询今天的新闻
  543. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  544. - 不指定时:使用 config.yaml 中配置的所有平台
  545. - 支持的平台来自 config/config.yaml 的 platforms 配置
  546. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  547. limit: 返回条数限制,默认50,最大1000
  548. 注意:实际返回数量取决于搜索匹配结果(特别是 fuzzy 模式下会过滤低相似度结果)
  549. sort_by: 排序方式,可选值:
  550. - "relevance": 按相关度排序(默认)
  551. - "weight": 按新闻权重排序
  552. - "date": 按日期排序
  553. threshold: 相似度阈值(仅fuzzy模式有效),0-1之间,默认0.6
  554. 注意:阈值越高匹配越严格,返回结果越少
  555. include_url: 是否包含URL链接,默认False(节省token)
  556. Returns:
  557. JSON格式的搜索结果,包含标题、平台、排名等信息
  558. Examples:
  559. 用户:"搜索本周的AI新闻"
  560. 推荐调用流程:
  561. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  562. 2. search_news(query="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  563. 用户:"最近7天的特斯拉新闻"
  564. 推荐调用流程:
  565. 1. resolve_date_range("最近7天") → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}}
  566. 2. search_news(query="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  567. 用户:"今天的AI新闻"(默认今天,无需解析)
  568. → search_news(query="AI")
  569. **重要:数据展示策略**
  570. - 本工具返回完整的搜索结果列表
  571. - **默认展示方式**:展示全部返回的新闻,无需总结或筛选
  572. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  573. """
  574. tools = _get_tools()
  575. result = tools['search'].search_news_unified(
  576. query=query,
  577. search_mode=search_mode,
  578. date_range=date_range,
  579. platforms=platforms,
  580. limit=limit,
  581. sort_by=sort_by,
  582. threshold=threshold,
  583. include_url=include_url
  584. )
  585. return json.dumps(result, ensure_ascii=False, indent=2)
  586. # ==================== 配置与系统管理工具 ====================
  587. @mcp.tool
  588. async def get_current_config(
  589. section: str = "all"
  590. ) -> str:
  591. """
  592. 获取当前系统配置
  593. Args:
  594. section: 配置节,可选值:
  595. - "all": 所有配置(默认)
  596. - "crawler": 爬虫配置
  597. - "push": 推送配置
  598. - "keywords": 关键词配置
  599. - "weights": 权重配置
  600. Returns:
  601. JSON格式的配置信息
  602. """
  603. tools = _get_tools()
  604. result = tools['config'].get_current_config(section=section)
  605. return json.dumps(result, ensure_ascii=False, indent=2)
  606. @mcp.tool
  607. async def get_system_status() -> str:
  608. """
  609. 获取系统运行状态和健康检查信息
  610. 返回系统版本、数据统计、缓存状态等信息
  611. Returns:
  612. JSON格式的系统状态信息
  613. """
  614. tools = _get_tools()
  615. result = tools['system'].get_system_status()
  616. return json.dumps(result, ensure_ascii=False, indent=2)
  617. @mcp.tool
  618. async def trigger_crawl(
  619. platforms: Optional[List[str]] = None,
  620. save_to_local: bool = False,
  621. include_url: bool = False
  622. ) -> str:
  623. """
  624. 手动触发一次爬取任务(可选持久化)
  625. Args:
  626. platforms: 指定平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  627. - 不指定时:使用 config.yaml 中配置的所有平台
  628. - 支持的平台来自 config/config.yaml 的 platforms 配置
  629. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  630. - 注意:失败的平台会在返回结果的 failed_platforms 字段中列出
  631. save_to_local: 是否保存到本地 output 目录,默认 False
  632. include_url: 是否包含URL链接,默认False(节省token)
  633. Returns:
  634. JSON格式的任务状态信息,包含:
  635. - platforms: 成功爬取的平台列表
  636. - failed_platforms: 失败的平台列表(如有)
  637. - total_news: 爬取的新闻总数
  638. - data: 新闻数据
  639. Examples:
  640. - 临时爬取: trigger_crawl(platforms=['zhihu'])
  641. - 爬取并保存: trigger_crawl(platforms=['weibo'], save_to_local=True)
  642. - 使用默认平台: trigger_crawl() # 爬取config.yaml中配置的所有平台
  643. """
  644. tools = _get_tools()
  645. result = tools['system'].trigger_crawl(platforms=platforms, save_to_local=save_to_local, include_url=include_url)
  646. return json.dumps(result, ensure_ascii=False, indent=2)
  647. # ==================== 存储同步工具 ====================
  648. @mcp.tool
  649. async def sync_from_remote(
  650. days: int = 7
  651. ) -> str:
  652. """
  653. 从远程存储拉取数据到本地
  654. 用于 MCP Server 等场景:爬虫存到远程云存储(如 Cloudflare R2),
  655. MCP Server 拉取到本地进行分析查询。
  656. Args:
  657. days: 拉取最近 N 天的数据,默认 7 天
  658. - 0: 不拉取
  659. - 7: 拉取最近一周的数据
  660. - 30: 拉取最近一个月的数据
  661. Returns:
  662. JSON格式的同步结果,包含:
  663. - success: 是否成功
  664. - synced_files: 成功同步的文件数量
  665. - synced_dates: 成功同步的日期列表
  666. - skipped_dates: 跳过的日期(本地已存在)
  667. - failed_dates: 失败的日期及错误信息
  668. - message: 操作结果描述
  669. Examples:
  670. - sync_from_remote() # 拉取最近7天
  671. - sync_from_remote(days=30) # 拉取最近30天
  672. Note:
  673. 需要在 config/config.yaml 中配置远程存储(storage.remote)或设置环境变量:
  674. - S3_ENDPOINT_URL: 服务端点
  675. - S3_BUCKET_NAME: 存储桶名称
  676. - S3_ACCESS_KEY_ID: 访问密钥 ID
  677. - S3_SECRET_ACCESS_KEY: 访问密钥
  678. """
  679. tools = _get_tools()
  680. result = tools['storage'].sync_from_remote(days=days)
  681. return json.dumps(result, ensure_ascii=False, indent=2)
  682. @mcp.tool
  683. async def get_storage_status() -> str:
  684. """
  685. 获取存储配置和状态
  686. 查看当前存储后端配置、本地和远程存储的状态信息。
  687. Returns:
  688. JSON格式的存储状态信息,包含:
  689. - backend: 当前使用的后端类型(local/remote/auto)
  690. - local: 本地存储状态
  691. - data_dir: 数据目录
  692. - retention_days: 保留天数
  693. - total_size: 总大小
  694. - date_count: 日期数量
  695. - earliest_date: 最早日期
  696. - latest_date: 最新日期
  697. - remote: 远程存储状态
  698. - configured: 是否已配置
  699. - endpoint_url: 服务端点
  700. - bucket_name: 存储桶名称
  701. - date_count: 远程日期数量
  702. - pull: 拉取配置
  703. - enabled: 是否启用自动拉取
  704. - days: 自动拉取天数
  705. Examples:
  706. - get_storage_status() # 查看所有存储状态
  707. """
  708. tools = _get_tools()
  709. result = tools['storage'].get_storage_status()
  710. return json.dumps(result, ensure_ascii=False, indent=2)
  711. @mcp.tool
  712. async def list_available_dates(
  713. source: str = "both"
  714. ) -> str:
  715. """
  716. 列出本地/远程可用的日期范围
  717. 查看本地和远程存储中有哪些日期的数据可用,
  718. 帮助了解数据覆盖范围和同步状态。
  719. Args:
  720. source: 数据来源,可选值:
  721. - "local": 仅列出本地可用日期
  722. - "remote": 仅列出远程可用日期
  723. - "both": 同时列出两者并进行对比(默认)
  724. Returns:
  725. JSON格式的日期列表,包含:
  726. - local: 本地日期信息(如果 source 包含 local)
  727. - dates: 日期列表(按时间倒序)
  728. - count: 日期数量
  729. - earliest: 最早日期
  730. - latest: 最新日期
  731. - remote: 远程日期信息(如果 source 包含 remote)
  732. - configured: 是否已配置远程存储
  733. - dates: 日期列表
  734. - count: 日期数量
  735. - earliest: 最早日期
  736. - latest: 最新日期
  737. - comparison: 对比结果(仅当 source="both" 时)
  738. - only_local: 仅本地存在的日期
  739. - only_remote: 仅远程存在的日期
  740. - both: 两边都存在的日期
  741. Examples:
  742. - list_available_dates() # 查看本地和远程的对比
  743. - list_available_dates(source="local") # 仅查看本地
  744. - list_available_dates(source="remote") # 仅查看远程
  745. """
  746. tools = _get_tools()
  747. result = tools['storage'].list_available_dates(source=source)
  748. return json.dumps(result, ensure_ascii=False, indent=2)
  749. # ==================== 启动入口 ====================
  750. def run_server(
  751. project_root: Optional[str] = None,
  752. transport: str = 'stdio',
  753. host: str = '0.0.0.0',
  754. port: int = 3333
  755. ):
  756. """
  757. 启动 MCP 服务器
  758. Args:
  759. project_root: 项目根目录路径
  760. transport: 传输模式,'stdio' 或 'http'
  761. host: HTTP模式的监听地址,默认 0.0.0.0
  762. port: HTTP模式的监听端口,默认 3333
  763. """
  764. # 初始化工具实例
  765. _get_tools(project_root)
  766. # 打印启动信息
  767. print()
  768. print("=" * 60)
  769. print(" TrendRadar MCP Server - FastMCP 2.0")
  770. print("=" * 60)
  771. print(f" 传输模式: {transport.upper()}")
  772. if transport == 'stdio':
  773. print(" 协议: MCP over stdio (标准输入输出)")
  774. print(" 说明: 通过标准输入输出与 MCP 客户端通信")
  775. elif transport == 'http':
  776. print(f" 协议: MCP over HTTP (生产环境)")
  777. print(f" 服务器监听: {host}:{port}")
  778. if project_root:
  779. print(f" 项目目录: {project_root}")
  780. else:
  781. print(" 项目目录: 当前目录")
  782. print()
  783. print(" 已注册的工具:")
  784. print(" === 日期解析工具(推荐优先调用)===")
  785. print(" 0. resolve_date_range - 解析自然语言日期为标准格式")
  786. print()
  787. print(" === 基础数据查询(P0核心)===")
  788. print(" 1. get_latest_news - 获取最新新闻")
  789. print(" 2. get_news_by_date - 按日期查询新闻(支持自然语言)")
  790. print(" 3. get_trending_topics - 获取趋势话题(支持自动提取)")
  791. print()
  792. print(" === 智能检索工具 ===")
  793. print(" 4. search_news - 统一新闻搜索(关键词/模糊/实体)")
  794. print(" 5. find_related_news - 相关新闻查找(支持历史数据)")
  795. print()
  796. print(" === 高级数据分析 ===")
  797. print(" 6. analyze_topic_trend - 统一话题趋势分析(热度/生命周期/爆火/预测)")
  798. print(" 7. analyze_data_insights - 统一数据洞察分析(平台对比/活跃度/关键词共现)")
  799. print(" 8. analyze_sentiment - 情感倾向分析")
  800. print(" 9. aggregate_news - 跨平台新闻聚合去重")
  801. print(" 10. compare_periods - 时期对比分析(周环比/月环比)")
  802. print(" 11. generate_summary_report - 每日/每周摘要生成")
  803. print()
  804. print(" === 配置与系统管理 ===")
  805. print(" 12. get_current_config - 获取当前系统配置")
  806. print(" 13. get_system_status - 获取系统运行状态")
  807. print(" 14. trigger_crawl - 手动触发爬取任务")
  808. print()
  809. print(" === 存储同步工具 ===")
  810. print(" 15. sync_from_remote - 从远程存储拉取数据到本地")
  811. print(" 16. get_storage_status - 获取存储配置和状态")
  812. print(" 17. list_available_dates - 列出本地/远程可用日期")
  813. print("=" * 60)
  814. print()
  815. # 根据传输模式运行服务器
  816. if transport == 'stdio':
  817. mcp.run(transport='stdio')
  818. elif transport == 'http':
  819. # HTTP 模式(生产推荐)
  820. mcp.run(
  821. transport='http',
  822. host=host,
  823. port=port,
  824. path='/mcp' # HTTP 端点路径
  825. )
  826. else:
  827. raise ValueError(f"不支持的传输模式: {transport}")
  828. if __name__ == '__main__':
  829. import argparse
  830. parser = argparse.ArgumentParser(
  831. description='TrendRadar MCP Server - 新闻热点聚合 MCP 工具服务器',
  832. formatter_class=argparse.RawDescriptionHelpFormatter,
  833. epilog="""
  834. 详细配置教程请查看: README-Cherry-Studio.md
  835. """
  836. )
  837. parser.add_argument(
  838. '--transport',
  839. choices=['stdio', 'http'],
  840. default='stdio',
  841. help='传输模式:stdio (默认) 或 http (生产环境)'
  842. )
  843. parser.add_argument(
  844. '--host',
  845. default='0.0.0.0',
  846. help='HTTP模式的监听地址,默认 0.0.0.0'
  847. )
  848. parser.add_argument(
  849. '--port',
  850. type=int,
  851. default=3333,
  852. help='HTTP模式的监听端口,默认 3333'
  853. )
  854. parser.add_argument(
  855. '--project-root',
  856. help='项目根目录路径'
  857. )
  858. args = parser.parse_args()
  859. run_server(
  860. project_root=args.project_root,
  861. transport=args.transport,
  862. host=args.host,
  863. port=args.port
  864. )