server.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  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
  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 .utils.date_parser import DateParser
  15. from .utils.errors import MCPError
  16. # 创建 FastMCP 2.0 应用
  17. mcp = FastMCP('trendradar-news')
  18. # 全局工具实例(在第一次请求时初始化)
  19. _tools_instances = {}
  20. def _get_tools(project_root: Optional[str] = None):
  21. """获取或创建工具实例(单例模式)"""
  22. if not _tools_instances:
  23. _tools_instances['data'] = DataQueryTools(project_root)
  24. _tools_instances['analytics'] = AnalyticsTools(project_root)
  25. _tools_instances['search'] = SearchTools(project_root)
  26. _tools_instances['config'] = ConfigManagementTools(project_root)
  27. _tools_instances['system'] = SystemManagementTools(project_root)
  28. return _tools_instances
  29. # ==================== 日期解析工具(优先调用)====================
  30. @mcp.tool
  31. async def resolve_date_range(
  32. expression: str
  33. ) -> str:
  34. """
  35. 【推荐优先调用】将自然语言日期表达式解析为标准日期范围
  36. **为什么需要这个工具?**
  37. 用户经常使用"本周"、"最近7天"等自然语言表达日期,但 AI 模型自己计算日期
  38. 可能导致不一致的结果。此工具在服务器端使用精确的当前时间计算,确保所有
  39. AI 模型获得一致的日期范围。
  40. **推荐使用流程:**
  41. 1. 用户说"分析AI本周的情感倾向"
  42. 2. AI 调用 resolve_date_range("本周") → 获取精确日期范围
  43. 3. AI 调用 analyze_sentiment(topic="ai", date_range=上一步返回的date_range)
  44. Args:
  45. expression: 自然语言日期表达式,支持:
  46. - 单日: "今天", "昨天", "today", "yesterday"
  47. - 周: "本周", "上周", "this week", "last week"
  48. - 月: "本月", "上月", "this month", "last month"
  49. - 最近N天: "最近7天", "最近30天", "last 7 days", "last 30 days"
  50. - 动态: "最近5天", "last 10 days"(任意天数)
  51. Returns:
  52. JSON格式的日期范围,可直接用于其他工具的 date_range 参数:
  53. {
  54. "success": true,
  55. "expression": "本周",
  56. "date_range": {
  57. "start": "2025-11-18",
  58. "end": "2025-11-26"
  59. },
  60. "current_date": "2025-11-26",
  61. "description": "本周(周一到周日,11-18 至 11-26)"
  62. }
  63. Examples:
  64. 用户:"分析AI本周的情感倾向"
  65. AI调用步骤:
  66. 1. resolve_date_range("本周")
  67. → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}, ...}
  68. 2. analyze_sentiment(topic="ai", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  69. 用户:"看看最近7天的特斯拉新闻"
  70. AI调用步骤:
  71. 1. resolve_date_range("最近7天")
  72. → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}, ...}
  73. 2. search_news(query="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  74. """
  75. try:
  76. result = DateParser.resolve_date_range_expression(expression)
  77. return json.dumps(result, ensure_ascii=False, indent=2)
  78. except MCPError as e:
  79. return json.dumps({
  80. "success": False,
  81. "error": e.to_dict()
  82. }, ensure_ascii=False, indent=2)
  83. except Exception as e:
  84. return json.dumps({
  85. "success": False,
  86. "error": {
  87. "code": "INTERNAL_ERROR",
  88. "message": str(e)
  89. }
  90. }, ensure_ascii=False, indent=2)
  91. # ==================== 数据查询工具 ====================
  92. @mcp.tool
  93. async def get_latest_news(
  94. platforms: Optional[List[str]] = None,
  95. limit: int = 50,
  96. include_url: bool = False
  97. ) -> str:
  98. """
  99. 获取最新一批爬取的新闻数据,快速了解当前热点
  100. Args:
  101. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  102. - 不指定时:使用 config.yaml 中配置的所有平台
  103. - 支持的平台来自 config/config.yaml 的 platforms 配置
  104. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  105. limit: 返回条数限制,默认50,最大1000
  106. 注意:实际返回数量可能少于请求值,取决于当前可用的新闻总数
  107. include_url: 是否包含URL链接,默认False(节省token)
  108. Returns:
  109. JSON格式的新闻列表
  110. **重要:数据展示建议**
  111. 本工具会返回完整的新闻列表(通常50条)给你。但请注意:
  112. - **工具返回**:完整的50条数据 ✅
  113. - **建议展示**:向用户展示全部数据,除非用户明确要求总结
  114. - **用户期望**:用户可能需要完整数据,请谨慎总结
  115. **何时可以总结**:
  116. - 用户明确说"给我总结一下"或"挑重点说"
  117. - 数据量超过100条时,可先展示部分并询问是否查看全部
  118. **注意**:如果用户询问"为什么只显示了部分",说明他们需要完整数据
  119. """
  120. tools = _get_tools()
  121. result = tools['data'].get_latest_news(platforms=platforms, limit=limit, include_url=include_url)
  122. return json.dumps(result, ensure_ascii=False, indent=2)
  123. @mcp.tool
  124. async def get_trending_topics(
  125. top_n: int = 10,
  126. mode: str = 'current'
  127. ) -> str:
  128. """
  129. 获取个人关注词的新闻出现频率统计(基于 config/frequency_words.txt)
  130. 注意:本工具不是自动提取新闻热点,而是统计你在 config/frequency_words.txt 中
  131. 设置的个人关注词在新闻中出现的频率。你可以自定义这个关注词列表。
  132. Args:
  133. top_n: 返回TOP N关注词,默认10
  134. mode: 模式选择
  135. - daily: 当日累计数据统计
  136. - current: 最新一批数据统计(默认)
  137. Returns:
  138. JSON格式的关注词频率统计列表
  139. """
  140. tools = _get_tools()
  141. result = tools['data'].get_trending_topics(top_n=top_n, mode=mode)
  142. return json.dumps(result, ensure_ascii=False, indent=2)
  143. @mcp.tool
  144. async def get_news_by_date(
  145. date_query: Optional[str] = None,
  146. platforms: Optional[List[str]] = None,
  147. limit: int = 50,
  148. include_url: bool = False
  149. ) -> str:
  150. """
  151. 获取指定日期的新闻数据,用于历史数据分析和对比
  152. Args:
  153. date_query: 日期查询,可选格式:
  154. - 自然语言: "今天", "昨天", "前天", "3天前"
  155. - 标准日期: "2024-01-15", "2024/01/15"
  156. - 默认值: "今天"(节省token)
  157. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  158. - 不指定时:使用 config.yaml 中配置的所有平台
  159. - 支持的平台来自 config/config.yaml 的 platforms 配置
  160. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  161. limit: 返回条数限制,默认50,最大1000
  162. 注意:实际返回数量可能少于请求值,取决于指定日期的新闻总数
  163. include_url: 是否包含URL链接,默认False(节省token)
  164. Returns:
  165. JSON格式的新闻列表,包含标题、平台、排名等信息
  166. **重要:数据展示建议**
  167. 本工具会返回完整的新闻列表(通常50条)给你。但请注意:
  168. - **工具返回**:完整的50条数据 ✅
  169. - **建议展示**:向用户展示全部数据,除非用户明确要求总结
  170. - **用户期望**:用户可能需要完整数据,请谨慎总结
  171. **何时可以总结**:
  172. - 用户明确说"给我总结一下"或"挑重点说"
  173. - 数据量超过100条时,可先展示部分并询问是否查看全部
  174. **注意**:如果用户询问"为什么只显示了部分",说明他们需要完整数据
  175. """
  176. tools = _get_tools()
  177. result = tools['data'].get_news_by_date(
  178. date_query=date_query,
  179. platforms=platforms,
  180. limit=limit,
  181. include_url=include_url
  182. )
  183. return json.dumps(result, ensure_ascii=False, indent=2)
  184. # ==================== 高级数据分析工具 ====================
  185. @mcp.tool
  186. async def analyze_topic_trend(
  187. topic: str,
  188. analysis_type: str = "trend",
  189. date_range: Optional[Dict[str, str]] = None,
  190. granularity: str = "day",
  191. threshold: float = 3.0,
  192. time_window: int = 24,
  193. lookahead_hours: int = 6,
  194. confidence_threshold: float = 0.7
  195. ) -> str:
  196. """
  197. 统一话题趋势分析工具 - 整合多种趋势分析模式
  198. **重要:日期范围处理**
  199. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  200. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  201. 2. 将返回的 date_range 传入本工具
  202. Args:
  203. topic: 话题关键词(必需)
  204. analysis_type: 分析类型,可选值:
  205. - "trend": 热度趋势分析(追踪话题的热度变化)
  206. - "lifecycle": 生命周期分析(从出现到消失的完整周期)
  207. - "viral": 异常热度检测(识别突然爆火的话题)
  208. - "predict": 话题预测(预测未来可能的热点)
  209. date_range: 日期范围(trend和lifecycle模式),可选
  210. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  211. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  212. - **默认**: 不指定时默认分析最近7天
  213. granularity: 时间粒度(trend模式),默认"day"(仅支持 day,因为底层数据按天聚合)
  214. threshold: 热度突增倍数阈值(viral模式),默认3.0
  215. time_window: 检测时间窗口小时数(viral模式),默认24
  216. lookahead_hours: 预测未来小时数(predict模式),默认6
  217. confidence_threshold: 置信度阈值(predict模式),默认0.7
  218. Returns:
  219. JSON格式的趋势分析结果
  220. Examples:
  221. 用户:"分析AI本周的趋势"
  222. 推荐调用流程:
  223. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  224. 2. analyze_topic_trend(topic="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  225. 用户:"看看特斯拉最近30天的热度"
  226. 推荐调用流程:
  227. 1. resolve_date_range("最近30天") → {"date_range": {"start": "2025-10-28", "end": "2025-11-26"}}
  228. 2. analyze_topic_trend(topic="特斯拉", analysis_type="lifecycle", date_range=...)
  229. """
  230. tools = _get_tools()
  231. result = tools['analytics'].analyze_topic_trend_unified(
  232. topic=topic,
  233. analysis_type=analysis_type,
  234. date_range=date_range,
  235. granularity=granularity,
  236. threshold=threshold,
  237. time_window=time_window,
  238. lookahead_hours=lookahead_hours,
  239. confidence_threshold=confidence_threshold
  240. )
  241. return json.dumps(result, ensure_ascii=False, indent=2)
  242. @mcp.tool
  243. async def analyze_data_insights(
  244. insight_type: str = "platform_compare",
  245. topic: Optional[str] = None,
  246. date_range: Optional[Dict[str, str]] = None,
  247. min_frequency: int = 3,
  248. top_n: int = 20
  249. ) -> str:
  250. """
  251. 统一数据洞察分析工具 - 整合多种数据分析模式
  252. Args:
  253. insight_type: 洞察类型,可选值:
  254. - "platform_compare": 平台对比分析(对比不同平台对话题的关注度)
  255. - "platform_activity": 平台活跃度统计(统计各平台发布频率和活跃时间)
  256. - "keyword_cooccur": 关键词共现分析(分析关键词同时出现的模式)
  257. topic: 话题关键词(可选,platform_compare模式适用)
  258. date_range: **【对象类型】** 日期范围(可选)
  259. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  260. - **示例**: {"start": "2025-01-01", "end": "2025-01-07"}
  261. - **重要**: 必须是对象格式,不能传递整数
  262. min_frequency: 最小共现频次(keyword_cooccur模式),默认3
  263. top_n: 返回TOP N结果(keyword_cooccur模式),默认20
  264. Returns:
  265. JSON格式的数据洞察分析结果
  266. Examples:
  267. - analyze_data_insights(insight_type="platform_compare", topic="人工智能")
  268. - analyze_data_insights(insight_type="platform_activity", date_range={"start": "2025-01-01", "end": "2025-01-07"})
  269. - analyze_data_insights(insight_type="keyword_cooccur", min_frequency=5, top_n=15)
  270. """
  271. tools = _get_tools()
  272. result = tools['analytics'].analyze_data_insights_unified(
  273. insight_type=insight_type,
  274. topic=topic,
  275. date_range=date_range,
  276. min_frequency=min_frequency,
  277. top_n=top_n
  278. )
  279. return json.dumps(result, ensure_ascii=False, indent=2)
  280. @mcp.tool
  281. async def analyze_sentiment(
  282. topic: Optional[str] = None,
  283. platforms: Optional[List[str]] = None,
  284. date_range: Optional[Dict[str, str]] = None,
  285. limit: int = 50,
  286. sort_by_weight: bool = True,
  287. include_url: bool = False
  288. ) -> str:
  289. """
  290. 分析新闻的情感倾向和热度趋势
  291. **重要:日期范围处理**
  292. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  293. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  294. 2. 将返回的 date_range 传入本工具
  295. Args:
  296. topic: 话题关键词(可选)
  297. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  298. - 不指定时:使用 config.yaml 中配置的所有平台
  299. - 支持的平台来自 config/config.yaml 的 platforms 配置
  300. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  301. date_range: 日期范围(可选)
  302. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  303. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  304. - **默认**: 不指定则默认查询今天的数据
  305. limit: 返回新闻数量,默认50,最大100
  306. 注意:本工具会对新闻标题进行去重(同一标题在不同平台只保留一次),
  307. 因此实际返回数量可能少于请求的 limit 值
  308. sort_by_weight: 是否按热度权重排序,默认True
  309. include_url: 是否包含URL链接,默认False(节省token)
  310. Returns:
  311. JSON格式的分析结果,包含情感分布、热度趋势和相关新闻
  312. Examples:
  313. 用户:"分析AI本周的情感倾向"
  314. 推荐调用流程:
  315. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  316. 2. analyze_sentiment(topic="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  317. 用户:"分析特斯拉最近7天的新闻情感"
  318. 推荐调用流程:
  319. 1. resolve_date_range("最近7天") → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}}
  320. 2. analyze_sentiment(topic="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  321. **重要:数据展示策略**
  322. - 本工具返回完整的分析结果和新闻列表
  323. - **默认展示方式**:展示完整的分析结果(包括所有新闻)
  324. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  325. """
  326. tools = _get_tools()
  327. result = tools['analytics'].analyze_sentiment(
  328. topic=topic,
  329. platforms=platforms,
  330. date_range=date_range,
  331. limit=limit,
  332. sort_by_weight=sort_by_weight,
  333. include_url=include_url
  334. )
  335. return json.dumps(result, ensure_ascii=False, indent=2)
  336. @mcp.tool
  337. async def find_similar_news(
  338. reference_title: str,
  339. threshold: float = 0.6,
  340. limit: int = 50,
  341. include_url: bool = False
  342. ) -> str:
  343. """
  344. 查找与指定新闻标题相似的其他新闻
  345. Args:
  346. reference_title: 新闻标题(完整或部分)
  347. threshold: 相似度阈值,0-1之间,默认0.6
  348. 注意:阈值越高匹配越严格,返回结果越少
  349. limit: 返回条数限制,默认50,最大100
  350. 注意:实际返回数量取决于相似度匹配结果,可能少于请求值
  351. include_url: 是否包含URL链接,默认False(节省token)
  352. Returns:
  353. JSON格式的相似新闻列表,包含相似度分数
  354. **重要:数据展示策略**
  355. - 本工具返回完整的相似新闻列表
  356. - **默认展示方式**:展示全部返回的新闻(包括相似度分数)
  357. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  358. """
  359. tools = _get_tools()
  360. result = tools['analytics'].find_similar_news(
  361. reference_title=reference_title,
  362. threshold=threshold,
  363. limit=limit,
  364. include_url=include_url
  365. )
  366. return json.dumps(result, ensure_ascii=False, indent=2)
  367. @mcp.tool
  368. async def generate_summary_report(
  369. report_type: str = "daily",
  370. date_range: Optional[Dict[str, str]] = None
  371. ) -> str:
  372. """
  373. 每日/每周摘要生成器 - 自动生成热点摘要报告
  374. Args:
  375. report_type: 报告类型(daily/weekly)
  376. date_range: **【对象类型】** 自定义日期范围(可选)
  377. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  378. - **示例**: {"start": "2025-01-01", "end": "2025-01-07"}
  379. - **重要**: 必须是对象格式,不能传递整数
  380. Returns:
  381. JSON格式的摘要报告,包含Markdown格式内容
  382. """
  383. tools = _get_tools()
  384. result = tools['analytics'].generate_summary_report(
  385. report_type=report_type,
  386. date_range=date_range
  387. )
  388. return json.dumps(result, ensure_ascii=False, indent=2)
  389. # ==================== 智能检索工具 ====================
  390. @mcp.tool
  391. async def search_news(
  392. query: str,
  393. search_mode: str = "keyword",
  394. date_range: Optional[Dict[str, str]] = None,
  395. platforms: Optional[List[str]] = None,
  396. limit: int = 50,
  397. sort_by: str = "relevance",
  398. threshold: float = 0.6,
  399. include_url: bool = False
  400. ) -> str:
  401. """
  402. 统一搜索接口,支持多种搜索模式
  403. **重要:日期范围处理**
  404. 当用户使用"本周"、"最近7天"等自然语言时,请先调用 resolve_date_range 工具获取精确日期:
  405. 1. 调用 resolve_date_range("本周") → 获取 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  406. 2. 将返回的 date_range 传入本工具
  407. Args:
  408. query: 搜索关键词或内容片段
  409. search_mode: 搜索模式,可选值:
  410. - "keyword": 精确关键词匹配(默认,适合搜索特定话题)
  411. - "fuzzy": 模糊内容匹配(适合搜索内容片段,会过滤相似度低于阈值的结果)
  412. - "entity": 实体名称搜索(适合搜索人物/地点/机构)
  413. date_range: 日期范围(可选)
  414. - **格式**: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
  415. - **获取方式**: 调用 resolve_date_range 工具解析自然语言日期
  416. - **默认**: 不指定时默认查询今天的新闻
  417. platforms: 平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  418. - 不指定时:使用 config.yaml 中配置的所有平台
  419. - 支持的平台来自 config/config.yaml 的 platforms 配置
  420. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  421. limit: 返回条数限制,默认50,最大1000
  422. 注意:实际返回数量取决于搜索匹配结果(特别是 fuzzy 模式下会过滤低相似度结果)
  423. sort_by: 排序方式,可选值:
  424. - "relevance": 按相关度排序(默认)
  425. - "weight": 按新闻权重排序
  426. - "date": 按日期排序
  427. threshold: 相似度阈值(仅fuzzy模式有效),0-1之间,默认0.6
  428. 注意:阈值越高匹配越严格,返回结果越少
  429. include_url: 是否包含URL链接,默认False(节省token)
  430. Returns:
  431. JSON格式的搜索结果,包含标题、平台、排名等信息
  432. Examples:
  433. 用户:"搜索本周的AI新闻"
  434. 推荐调用流程:
  435. 1. resolve_date_range("本周") → {"date_range": {"start": "2025-11-18", "end": "2025-11-26"}}
  436. 2. search_news(query="AI", date_range={"start": "2025-11-18", "end": "2025-11-26"})
  437. 用户:"最近7天的特斯拉新闻"
  438. 推荐调用流程:
  439. 1. resolve_date_range("最近7天") → {"date_range": {"start": "2025-11-20", "end": "2025-11-26"}}
  440. 2. search_news(query="特斯拉", date_range={"start": "2025-11-20", "end": "2025-11-26"})
  441. 用户:"今天的AI新闻"(默认今天,无需解析)
  442. → search_news(query="AI")
  443. **重要:数据展示策略**
  444. - 本工具返回完整的搜索结果列表
  445. - **默认展示方式**:展示全部返回的新闻,无需总结或筛选
  446. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  447. """
  448. tools = _get_tools()
  449. result = tools['search'].search_news_unified(
  450. query=query,
  451. search_mode=search_mode,
  452. date_range=date_range,
  453. platforms=platforms,
  454. limit=limit,
  455. sort_by=sort_by,
  456. threshold=threshold,
  457. include_url=include_url
  458. )
  459. return json.dumps(result, ensure_ascii=False, indent=2)
  460. @mcp.tool
  461. async def search_related_news_history(
  462. reference_text: str,
  463. time_preset: str = "yesterday",
  464. threshold: float = 0.4,
  465. limit: int = 50,
  466. include_url: bool = False
  467. ) -> str:
  468. """
  469. 基于种子新闻,在历史数据中搜索相关新闻
  470. Args:
  471. reference_text: 参考新闻标题(完整或部分)
  472. time_preset: 时间范围预设值,可选:
  473. - "yesterday": 昨天
  474. - "last_week": 上周 (7天)
  475. - "last_month": 上个月 (30天)
  476. - "custom": 自定义日期范围(需要提供 start_date 和 end_date)
  477. threshold: 相关性阈值,0-1之间,默认0.4
  478. 注意:综合相似度计算(70%关键词重合 + 30%文本相似度)
  479. 阈值越高匹配越严格,返回结果越少
  480. limit: 返回条数限制,默认50,最大100
  481. 注意:实际返回数量取决于相关性匹配结果,可能少于请求值
  482. include_url: 是否包含URL链接,默认False(节省token)
  483. Returns:
  484. JSON格式的相关新闻列表,包含相关性分数和时间分布
  485. **重要:数据展示策略**
  486. - 本工具返回完整的相关新闻列表
  487. - **默认展示方式**:展示全部返回的新闻(包括相关性分数)
  488. - 仅在用户明确要求"总结"或"挑重点"时才进行筛选
  489. """
  490. tools = _get_tools()
  491. result = tools['search'].search_related_news_history(
  492. reference_text=reference_text,
  493. time_preset=time_preset,
  494. threshold=threshold,
  495. limit=limit,
  496. include_url=include_url
  497. )
  498. return json.dumps(result, ensure_ascii=False, indent=2)
  499. # ==================== 配置与系统管理工具 ====================
  500. @mcp.tool
  501. async def get_current_config(
  502. section: str = "all"
  503. ) -> str:
  504. """
  505. 获取当前系统配置
  506. Args:
  507. section: 配置节,可选值:
  508. - "all": 所有配置(默认)
  509. - "crawler": 爬虫配置
  510. - "push": 推送配置
  511. - "keywords": 关键词配置
  512. - "weights": 权重配置
  513. Returns:
  514. JSON格式的配置信息
  515. """
  516. tools = _get_tools()
  517. result = tools['config'].get_current_config(section=section)
  518. return json.dumps(result, ensure_ascii=False, indent=2)
  519. @mcp.tool
  520. async def get_system_status() -> str:
  521. """
  522. 获取系统运行状态和健康检查信息
  523. 返回系统版本、数据统计、缓存状态等信息
  524. Returns:
  525. JSON格式的系统状态信息
  526. """
  527. tools = _get_tools()
  528. result = tools['system'].get_system_status()
  529. return json.dumps(result, ensure_ascii=False, indent=2)
  530. @mcp.tool
  531. async def trigger_crawl(
  532. platforms: Optional[List[str]] = None,
  533. save_to_local: bool = False,
  534. include_url: bool = False
  535. ) -> str:
  536. """
  537. 手动触发一次爬取任务(可选持久化)
  538. Args:
  539. platforms: 指定平台ID列表,如 ['zhihu', 'weibo', 'douyin']
  540. - 不指定时:使用 config.yaml 中配置的所有平台
  541. - 支持的平台来自 config/config.yaml 的 platforms 配置
  542. - 每个平台都有对应的name字段(如"知乎"、"微博"),方便AI识别
  543. - 注意:失败的平台会在返回结果的 failed_platforms 字段中列出
  544. save_to_local: 是否保存到本地 output 目录,默认 False
  545. include_url: 是否包含URL链接,默认False(节省token)
  546. Returns:
  547. JSON格式的任务状态信息,包含:
  548. - platforms: 成功爬取的平台列表
  549. - failed_platforms: 失败的平台列表(如有)
  550. - total_news: 爬取的新闻总数
  551. - data: 新闻数据
  552. Examples:
  553. - 临时爬取: trigger_crawl(platforms=['zhihu'])
  554. - 爬取并保存: trigger_crawl(platforms=['weibo'], save_to_local=True)
  555. - 使用默认平台: trigger_crawl() # 爬取config.yaml中配置的所有平台
  556. """
  557. tools = _get_tools()
  558. result = tools['system'].trigger_crawl(platforms=platforms, save_to_local=save_to_local, include_url=include_url)
  559. return json.dumps(result, ensure_ascii=False, indent=2)
  560. # ==================== 启动入口 ====================
  561. def run_server(
  562. project_root: Optional[str] = None,
  563. transport: str = 'stdio',
  564. host: str = '0.0.0.0',
  565. port: int = 3333
  566. ):
  567. """
  568. 启动 MCP 服务器
  569. Args:
  570. project_root: 项目根目录路径
  571. transport: 传输模式,'stdio' 或 'http'
  572. host: HTTP模式的监听地址,默认 0.0.0.0
  573. port: HTTP模式的监听端口,默认 3333
  574. """
  575. # 初始化工具实例
  576. _get_tools(project_root)
  577. # 打印启动信息
  578. print()
  579. print("=" * 60)
  580. print(" TrendRadar MCP Server - FastMCP 2.0")
  581. print("=" * 60)
  582. print(f" 传输模式: {transport.upper()}")
  583. if transport == 'stdio':
  584. print(" 协议: MCP over stdio (标准输入输出)")
  585. print(" 说明: 通过标准输入输出与 MCP 客户端通信")
  586. elif transport == 'http':
  587. print(f" 协议: MCP over HTTP (生产环境)")
  588. print(f" 服务器监听: {host}:{port}")
  589. if project_root:
  590. print(f" 项目目录: {project_root}")
  591. else:
  592. print(" 项目目录: 当前目录")
  593. print()
  594. print(" 已注册的工具:")
  595. print(" === 日期解析工具(推荐优先调用)===")
  596. print(" 0. resolve_date_range - 解析自然语言日期为标准格式")
  597. print()
  598. print(" === 基础数据查询(P0核心)===")
  599. print(" 1. get_latest_news - 获取最新新闻")
  600. print(" 2. get_news_by_date - 按日期查询新闻(支持自然语言)")
  601. print(" 3. get_trending_topics - 获取趋势话题")
  602. print()
  603. print(" === 智能检索工具 ===")
  604. print(" 4. search_news - 统一新闻搜索(关键词/模糊/实体)")
  605. print(" 5. search_related_news_history - 历史相关新闻检索")
  606. print()
  607. print(" === 高级数据分析 ===")
  608. print(" 6. analyze_topic_trend - 统一话题趋势分析(热度/生命周期/爆火/预测)")
  609. print(" 7. analyze_data_insights - 统一数据洞察分析(平台对比/活跃度/关键词共现)")
  610. print(" 8. analyze_sentiment - 情感倾向分析")
  611. print(" 9. find_similar_news - 相似新闻查找")
  612. print(" 10. generate_summary_report - 每日/每周摘要生成")
  613. print()
  614. print(" === 配置与系统管理 ===")
  615. print(" 11. get_current_config - 获取当前系统配置")
  616. print(" 12. get_system_status - 获取系统运行状态")
  617. print(" 13. trigger_crawl - 手动触发爬取任务")
  618. print("=" * 60)
  619. print()
  620. # 根据传输模式运行服务器
  621. if transport == 'stdio':
  622. mcp.run(transport='stdio')
  623. elif transport == 'http':
  624. # HTTP 模式(生产推荐)
  625. mcp.run(
  626. transport='http',
  627. host=host,
  628. port=port,
  629. path='/mcp' # HTTP 端点路径
  630. )
  631. else:
  632. raise ValueError(f"不支持的传输模式: {transport}")
  633. if __name__ == '__main__':
  634. import argparse
  635. parser = argparse.ArgumentParser(
  636. description='TrendRadar MCP Server - 新闻热点聚合 MCP 工具服务器',
  637. formatter_class=argparse.RawDescriptionHelpFormatter,
  638. epilog="""
  639. 详细配置教程请查看: README-Cherry-Studio.md
  640. """
  641. )
  642. parser.add_argument(
  643. '--transport',
  644. choices=['stdio', 'http'],
  645. default='stdio',
  646. help='传输模式:stdio (默认) 或 http (生产环境)'
  647. )
  648. parser.add_argument(
  649. '--host',
  650. default='0.0.0.0',
  651. help='HTTP模式的监听地址,默认 0.0.0.0'
  652. )
  653. parser.add_argument(
  654. '--port',
  655. type=int,
  656. default=3333,
  657. help='HTTP模式的监听端口,默认 3333'
  658. )
  659. parser.add_argument(
  660. '--project-root',
  661. help='项目根目录路径'
  662. )
  663. args = parser.parse_args()
  664. run_server(
  665. project_root=args.project_root,
  666. transport=args.transport,
  667. host=args.host,
  668. port=args.port
  669. )