server.py 45 KB

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