server.py 49 KB

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