ソースを参照

feat: mcp 可读取整篇新闻内容

sansan 3 ヶ月 前
コミット
ccc13a983a

+ 2 - 7
README-EN.md

@@ -11,8 +11,8 @@ Deploy in <strong>30 seconds</strong> — Say goodbye to endless scrolling, only
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v5.5.0-blue.svg)](https://github.com/sansan0/TrendRadar)
-[![MCP](https://img.shields.io/badge/MCP-v3.1.7-green.svg)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v5.5.3-blue.svg)](https://github.com/sansan0/TrendRadar)
+[![MCP](https://img.shields.io/badge/MCP-v3.2.0-green.svg)](https://github.com/sansan0/TrendRadar)
 [![RSS](https://img.shields.io/badge/RSS-Feed_Support-orange.svg?style=flat-square&logo=rss&logoColor=white)](https://github.com/sansan0/TrendRadar)
 [![AI Translation](https://img.shields.io/badge/AI-Multi--Language-purple.svg?style=flat-square)](https://github.com/sansan0/TrendRadar)
 
@@ -182,11 +182,6 @@ If you utilize the core code or draw inspiration from the logic of this project,
 
 This contributes to the sustainable maintenance of the project and the growth of the community. Thank you for your respect and support! ❤️
 
-**Reference Example** (e.g., my other project):
-
-> [https://github.com/sansan0/bilibili-comment-analyzer](https://github.com/sansan0/bilibili-comment-analyzer)
-
-
 ---
 
 ### 💬 Feedback & Community

+ 83 - 1
README-MCP-FAQ-EN.md

@@ -6,7 +6,7 @@
 
 # TrendRadar MCP Tool Usage Q&A
 
-> AI Query Guide - How to Use News Trend Analysis Tools Through Natural Conversation (v3.1.6)
+> AI Query Guide - How to Use News Trend Analysis Tools Through Natural Conversation (v3.1.7)
 
 ---
 
@@ -36,6 +36,8 @@
 | **Storage** | `sync_from_remote` | Pull data from remote storage to local |
 | | `get_storage_status` | Get storage config and status |
 | | `list_available_dates` | List available dates (local/remote) |
+| **Article** | `read_article` | Read single article content (Markdown format) |
+| | `read_articles_batch` | Batch read multiple articles (max 5) |
 
 ---
 
@@ -722,6 +724,86 @@ Users often use natural language like "this week", "last 7 days" to express date
 
 ---
 
+## Article Content Reading
+
+### Q19: How to read the full content of a news article?
+
+**You can ask like this:**
+
+- "Help me read the content of this news: https://example.com/news/123"
+- "Get the article body from this link"
+- "Read the detailed content of this report"
+
+**Tool functionality:**
+
+- Converts web pages to clean Markdown format via Jina AI Reader
+- Automatically removes ads, navigation bars, sidebars, and other noise
+- Returns LLM-friendly structured content
+
+**Typical workflow:**
+
+1. First use `search_news(include_url=True)` to search news and get links
+2. Then use `read_article(url=link)` to read the article body
+3. AI analyzes, summarizes, translates the Markdown content
+
+**Return information:**
+
+| Field | Description |
+|-------|-------------|
+| **content** | Article body in Markdown format |
+| **url** | Original link |
+| **content_length** | Content length (characters) |
+
+**Can be adjusted:**
+
+- Timeout: like "set timeout to 60 seconds" (default 30 seconds, max 60 seconds)
+
+**Notes:**
+
+- 5-second interval between requests (built-in rate control)
+- Uses Jina AI Reader free service (100 RPM limit)
+- Some paywalled/login-required pages may not be fully accessible
+
+---
+
+### Q20: How to batch read multiple articles?
+
+**You can ask like this:**
+
+- "Help me read the content of these news articles"
+- "Batch get the article bodies from these links"
+- "Read the detailed content of the first 3 search results"
+
+**Typical workflow:**
+
+1. First use `search_news(include_url=True)` to search news and get multiple links
+2. Then use `read_articles_batch(urls=[...])` to batch read article bodies
+3. AI performs comparative analysis, comprehensive reports on multiple articles
+
+**Tool limits:**
+
+| Limit | Value |
+|-------|-------|
+| Max articles per batch | **5** |
+| Request interval | **5 seconds** |
+| Estimated time (5 articles) | **25-30 seconds** |
+
+**Return information:**
+
+| Field | Description |
+|-------|-------------|
+| **summary** | Statistics of batch reading |
+| **articles** | Content and status of each article |
+| **note** | If any articles were skipped, explains why |
+
+**Notes:**
+
+- Articles beyond 5 will be automatically skipped
+- Single article failure doesn't affect other articles
+- More articles mean longer wait time, please be patient
+
+---
+
 ## 💡 Usage Tips
 
 ### 1. How to make AI display all data instead of auto-summarizing?

+ 83 - 1
README-MCP-FAQ.md

@@ -6,7 +6,7 @@
 
 # TrendRadar MCP 工具使用问答
 
-> AI 提问指南 - 如何通过自然对话使用新闻热点分析工具(v3.1.6
+> AI 提问指南 - 如何通过自然对话使用新闻热点分析工具(v3.1.7
 
 ---
 
@@ -36,6 +36,8 @@
 | **存储** | `sync_from_remote` | 从远程存储拉取数据到本地 |
 | | `get_storage_status` | 获取存储配置和状态 |
 | | `list_available_dates` | 列出本地/远程可用的日期 |
+| **文章** | `read_article` | 读取单篇文章内容(Markdown 格式) |
+| | `read_articles_batch` | 批量读取多篇文章(最多 5 篇) |
 
 ---
 
@@ -722,6 +724,86 @@
 
 ---
 
+## 文章内容读取
+
+### Q19: 如何读取新闻文章的正文内容?
+
+**你可以这样问:**
+
+- "帮我读取这篇新闻的内容:https://example.com/news/123"
+- "获取这个链接的文章正文"
+- "读取这篇报道的详细内容"
+
+**工具功能:**
+
+- 通过 Jina AI Reader 将网页转换为干净的 Markdown 格式
+- 自动去除广告、导航栏、侧边栏等噪音内容
+- 返回 LLM 友好的结构化内容
+
+**典型使用流程:**
+
+1. 先用 `search_news(include_url=True)` 搜索新闻获取链接
+2. 再用 `read_article(url=链接)` 读取正文内容
+3. AI 对 Markdown 正文进行分析、摘要、翻译等
+
+**返回信息:**
+
+| 字段 | 说明 |
+|------|------|
+| **content** | Markdown 格式的文章正文 |
+| **url** | 原始链接 |
+| **content_length** | 内容长度(字符数) |
+
+**可以调整:**
+
+- 超时时间:如"超时设为 60 秒"(默认 30 秒,最大 60 秒)
+
+**注意事项:**
+
+- 每次请求间隔 5 秒(内置速率控制)
+- 使用 Jina AI Reader 免费服务(100 RPM 限制)
+- 部分付费墙/登录墙页面可能无法完整获取
+
+---
+
+### Q20: 如何批量读取多篇文章?
+
+**你可以这样问:**
+
+- "帮我读取这几篇新闻的内容"
+- "批量获取这些链接的文章正文"
+- "读取搜索结果中前 3 篇的详细内容"
+
+**典型使用流程:**
+
+1. 先用 `search_news(include_url=True)` 搜索新闻获取多个链接
+2. 再用 `read_articles_batch(urls=[...])` 批量读取正文
+3. AI 对多篇文章进行对比分析、综合报告
+
+**工具限制:**
+
+| 限制 | 值 |
+|------|------|
+| 单次最多篇数 | **5 篇** |
+| 请求间隔 | **5 秒** |
+| 预计耗时(5篇) | **25-30 秒** |
+
+**返回信息:**
+
+| 字段 | 说明 |
+|------|------|
+| **summary** | 批量读取的统计信息 |
+| **articles** | 每篇文章的内容和状态 |
+| **note** | 如有跳过的文章,会说明原因 |
+
+**注意事项:**
+
+- 超出 5 篇的部分会被自动跳过
+- 单篇失败不影响其他篇的读取
+- 篇数越多耗时越长,请耐心等待
+
+---
+
 ## 💡 使用技巧
 
 ### 1. 如何让 AI 展示全部数据而不是自动总结?

+ 16 - 10
README.md

@@ -12,8 +12,8 @@
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v5.5.0-blue.svg)](https://github.com/sansan0/TrendRadar)
-[![MCP](https://img.shields.io/badge/MCP-v3.1.7-green.svg)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v5.5.3-blue.svg)](https://github.com/sansan0/TrendRadar)
+[![MCP](https://img.shields.io/badge/MCP-v3.2.0-green.svg)](https://github.com/sansan0/TrendRadar)
 [![RSS](https://img.shields.io/badge/RSS-订阅源支持-orange.svg?style=flat-square&logo=rss&logoColor=white)](https://github.com/sansan0/TrendRadar)
 [![AI翻译](https://img.shields.io/badge/AI-多语言推送-purple.svg?style=flat-square)](https://github.com/sansan0/TrendRadar)
 
@@ -229,9 +229,6 @@ TrendRadar 是完全开源免费的项目,持续更新需要你的动力支持
 
 这将有助于项目的持续维护和社区发展,感谢你的尊重与支持!❤️
 
-**示范参考**(比如我的这个项目):
-> [https://github.com/sansan0/bilibili-comment-analyzer](https://github.com/sansan0/bilibili-comment-analyzer)
-
 ---
 
 ### 💬 交流与反馈
@@ -263,7 +260,20 @@ TrendRadar 是完全开源免费的项目,持续更新需要你的动力支持
 
 > 和 mcp 功能一样, 这个小工具我也不新开一个仓库维护了, 反正纯前端, 都搁一起吧
 
-- 增加 trendradar 的可视化配置编辑器 
+- 增加 trendradar 的可视化配置编辑器
+
+
+### 2026/02/02 - mcp-v3.2.0
+
+- **新增 read_article 工具**:通过 Jina AI Reader 读取单篇文章正文(Markdown 格式)
+- **新增 read_articles_batch 工具**:批量读取多篇文章(最多 5 篇,自动限速)
+- **推荐工作流**:`search_news(query="关键词", include_url=True)` → `read_article(url=...)` 读取正文
+- **文档更新**:README-MCP-FAQ.md 和 README-MCP-FAQ-EN.md 新增 Q19-Q20 文章读取相关说明
+
+
+
+<details>
+<summary>👉 点击展开:<strong>历史更新</strong></summary>
 
 
 ### 2026/01/10 - mcp-v3.0.0~v3.1.5
@@ -277,10 +287,6 @@ TrendRadar 是完全开源免费的项目,持续更新需要你的动力支持
 - **新增 check_version 工具**:支持同时检查 TrendRadar 和 MCP Server 版本更新
 
 
-<details>
-<summary>👉 点击展开:<strong>历史更新</strong></summary>
-
-
 ### 2026/01/23 - v5.4.0
 
 - 增加 AI 分析模式的独立控制功能,可选 follow_report | daily | current | incremental 

+ 1 - 1
mcp_server/__init__.py

@@ -5,4 +5,4 @@ TrendRadar MCP Server
 
 """
 
-__version__ = "3.1.7"
+__version__ = "3.2.0"

+ 87 - 0
mcp_server/server.py

@@ -17,6 +17,7 @@ from .tools.search_tools import SearchTools
 from .tools.config_mgmt import ConfigManagementTools
 from .tools.system import SystemManagementTools
 from .tools.storage_sync import StorageSyncTools
+from .tools.article_reader import ArticleReaderTools
 from .utils.date_parser import DateParser
 from .utils.errors import MCPError
 
@@ -37,6 +38,7 @@ def _get_tools(project_root: Optional[str] = None):
         _tools_instances['config'] = ConfigManagementTools(project_root)
         _tools_instances['system'] = SystemManagementTools(project_root)
         _tools_instances['storage'] = StorageSyncTools(project_root)
+        _tools_instances['article'] = ArticleReaderTools(project_root)
     return _tools_instances
 
 
@@ -921,6 +923,87 @@ async def list_available_dates(
     return json.dumps(result, ensure_ascii=False, indent=2)
 
 
+# ==================== 文章内容读取工具 ====================
+
+@mcp.tool
+async def read_article(
+    url: str,
+    timeout: int = 30
+) -> str:
+    """
+    读取指定 URL 的文章内容,返回 LLM 友好的 Markdown 格式
+
+    通过 Jina AI Reader 将网页转换为干净的 Markdown,自动去除广告、导航栏等噪音内容。
+    适合用于:阅读新闻正文、获取文章详情、分析文章内容。
+
+    **典型使用流程:**
+    1. 先用 search_news(include_url=True) 搜索新闻获取链接
+    2. 再用 read_article(url=链接) 读取正文内容
+    3. AI 对 Markdown 正文进行分析、摘要、翻译等
+
+    Args:
+        url: 文章链接(必需),以 http:// 或 https:// 开头
+        timeout: 请求超时时间(秒),默认 30,最大 60
+
+    Returns:
+        JSON格式的文章内容,包含完整 Markdown 正文
+
+    Examples:
+        - read_article(url="https://example.com/news/123")
+
+    Note:
+        - 使用 Jina AI Reader 免费服务(100 RPM 限制)
+        - 每次请求间隔 5 秒(内置速率控制)
+        - 部分付费墙/登录墙页面可能无法完整获取
+    """
+    tools = _get_tools()
+    timeout = min(max(timeout, 10), 60)
+    result = await asyncio.to_thread(
+        tools['article'].read_article,
+        url=url, timeout=timeout
+    )
+    return json.dumps(result, ensure_ascii=False, indent=2)
+
+
+@mcp.tool
+async def read_articles_batch(
+    urls: List[str],
+    timeout: int = 30
+) -> str:
+    """
+    批量读取多篇文章内容(最多 5 篇,间隔 5 秒)
+
+    逐篇请求文章内容,每篇之间自动间隔 5 秒以遵守速率限制。
+
+    **典型使用流程:**
+    1. 先用 search_news(include_url=True) 搜索新闻获取多个链接
+    2. 再用 read_articles_batch(urls=[...]) 批量读取正文
+    3. AI 对多篇文章进行对比分析、综合报告
+
+    Args:
+        urls: 文章链接列表(必需),最多处理 5 篇
+        timeout: 每篇的请求超时时间(秒),默认 30
+
+    Returns:
+        JSON格式的批量读取结果,包含每篇的完整内容和状态
+
+    Examples:
+        - read_articles_batch(urls=["https://a.com/1", "https://b.com/2"])
+
+    Note:
+        - 单次最多读取 5 篇,超出部分会被跳过
+        - 5 篇约需 25-30 秒(每篇间隔 5 秒)
+        - 单篇失败不影响其他篇的读取
+    """
+    tools = _get_tools()
+    timeout = min(max(timeout, 10), 60)
+    result = await asyncio.to_thread(
+        tools['article'].read_articles_batch,
+        urls=urls, timeout=timeout
+    )
+    return json.dumps(result, ensure_ascii=False, indent=2)
+
+
 # ==================== 启动入口 ====================
 
 def run_server(
@@ -997,6 +1080,10 @@ def run_server(
     print("    19. sync_from_remote        - 从远程存储拉取数据到本地")
     print("    20. get_storage_status      - 获取存储配置和状态")
     print("    21. list_available_dates    - 列出本地/远程可用日期")
+    print()
+    print("    === 文章内容读取 ===")
+    print("    22. read_article            - 读取单篇文章内容(Markdown格式)")
+    print("    23. read_articles_batch     - 批量读取多篇文章(自动限速)")
     print("=" * 60)
     print()
 

+ 209 - 0
mcp_server/tools/article_reader.py

@@ -0,0 +1,209 @@
+"""
+文章内容读取工具
+
+通过 Jina AI Reader API 将 URL 转换为 LLM 友好的 Markdown 格式。
+支持单篇和批量读取,内置速率限制和并发控制。
+
+"""
+
+import time
+from typing import Dict, List
+
+import requests
+
+from ..utils.errors import MCPError, InvalidParameterError
+
+
+# Jina Reader 配置
+JINA_READER_BASE = "https://r.jina.ai"
+DEFAULT_TIMEOUT = 30  # 秒
+MAX_BATCH_SIZE = 5  # 单次批量最大篇数
+BATCH_INTERVAL = 5.0  # 批量请求间隔(秒)
+
+
+class ArticleReaderTools:
+    """文章内容读取工具类"""
+
+    def __init__(self, project_root: str = None, jina_api_key: str = None):
+        """
+        初始化文章读取工具
+
+        Args:
+            project_root: 项目根目录
+            jina_api_key: Jina API Key(可选,有 Key 可提升速率限制)
+        """
+        self.project_root = project_root
+        self.jina_api_key = jina_api_key
+        self._last_request_time = 0.0
+
+    def _build_headers(self) -> Dict[str, str]:
+        """构建请求头"""
+        headers = {
+            "Accept": "text/markdown",
+            "X-Return-Format": "markdown",
+            "X-No-Cache": "true",
+        }
+        if self.jina_api_key:
+            headers["Authorization"] = f"Bearer {self.jina_api_key}"
+        return headers
+
+    def _throttle(self):
+        """速率控制:确保请求间隔 5 秒"""
+        now = time.time()
+        elapsed = now - self._last_request_time
+        if elapsed < BATCH_INTERVAL:
+            time.sleep(BATCH_INTERVAL - elapsed)
+        self._last_request_time = time.time()
+
+    def read_article(
+        self,
+        url: str,
+        timeout: int = DEFAULT_TIMEOUT
+    ) -> Dict:
+        """
+        读取单篇文章内容(Markdown 格式)
+
+        Args:
+            url: 文章链接
+            timeout: 请求超时时间(秒),默认 30
+
+        Returns:
+            文章内容字典
+        """
+        try:
+            if not url or not url.startswith(("http://", "https://")):
+                raise InvalidParameterError(
+                    f"无效的 URL: {url}",
+                    suggestion="URL 必须以 http:// 或 https:// 开头"
+                )
+
+            self._throttle()
+
+            response = requests.get(
+                f"{JINA_READER_BASE}/{url}",
+                headers=self._build_headers(),
+                timeout=timeout
+            )
+
+            if response.status_code == 200:
+                return {
+                    "success": True,
+                    "data": {
+                        "url": url,
+                        "content": response.text,
+                        "format": "markdown",
+                        "content_length": len(response.text)
+                    }
+                }
+            elif response.status_code == 429:
+                return {
+                    "success": False,
+                    "error": {
+                        "code": "RATE_LIMITED",
+                        "message": "Jina Reader 速率限制,请稍后重试",
+                        "suggestion": "免费限制: 100 RPM / 2 并发,可配置 API Key 提升限额"
+                    }
+                }
+            else:
+                return {
+                    "success": False,
+                    "error": {
+                        "code": "FETCH_FAILED",
+                        "message": f"HTTP {response.status_code}: {response.reason}",
+                        "url": url
+                    }
+                }
+
+        except requests.Timeout:
+            return {
+                "success": False,
+                "error": {
+                    "code": "TIMEOUT",
+                    "message": f"请求超时({timeout}秒)",
+                    "url": url,
+                    "suggestion": "可尝试增加 timeout 参数"
+                }
+            }
+        except MCPError as e:
+            return {"success": False, "error": e.to_dict()}
+        except Exception as e:
+            return {
+                "success": False,
+                "error": {
+                    "code": "REQUEST_ERROR",
+                    "message": str(e),
+                    "url": url
+                }
+            }
+
+    def read_articles_batch(
+        self,
+        urls: List[str],
+        timeout: int = DEFAULT_TIMEOUT
+    ) -> Dict:
+        """
+        批量读取多篇文章内容(最多 5 篇,间隔 5 秒)
+
+        Args:
+            urls: 文章链接列表
+            timeout: 每篇的请求超时时间(秒)
+
+        Returns:
+            批量读取结果
+        """
+        try:
+            if not urls:
+                raise InvalidParameterError(
+                    "URL 列表不能为空",
+                    suggestion="请提供至少一个 URL"
+                )
+
+            # 限制最多 5 篇
+            actual_urls = urls[:MAX_BATCH_SIZE]
+            skipped = len(urls) - len(actual_urls)
+
+            results = []
+            succeeded = 0
+            failed = 0
+
+            for i, url in enumerate(actual_urls):
+                result = self.read_article(url=url, timeout=timeout)
+
+                results.append({
+                    "index": i + 1,
+                    "url": url,
+                    "success": result["success"],
+                    "data": result.get("data"),
+                    "error": result.get("error")
+                })
+
+                if result["success"]:
+                    succeeded += 1
+                else:
+                    failed += 1
+
+            return {
+                "success": True,
+                "summary": {
+                    "description": "批量文章读取结果",
+                    "requested": len(urls),
+                    "processed": len(actual_urls),
+                    "succeeded": succeeded,
+                    "failed": failed,
+                    "skipped": skipped,
+                    "interval_seconds": BATCH_INTERVAL,
+                },
+                "articles": results,
+                "note": f"已跳过 {skipped} 篇(单次上限 {MAX_BATCH_SIZE} 篇)" if skipped > 0 else None
+            }
+
+        except MCPError as e:
+            return {"success": False, "error": e.to_dict()}
+        except Exception as e:
+            return {
+                "success": False,
+                "error": {
+                    "code": "BATCH_ERROR",
+                    "message": str(e)
+                }
+            }

+ 50 - 11
mcp_server/utils/validators.py

@@ -349,7 +349,11 @@ def validate_date_range(date_range: Optional[Union[dict, str]]) -> Optional[tupl
     验证日期范围
 
     Args:
-        date_range: 日期范围字典或JSON字符串 {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
+        date_range: 日期范围,支持多种格式:
+            - dict: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
+            - JSON 字符串: '{"start": "2025-01-01", "end": "2025-01-07"}'
+            - 单日字符串: "2025-01-01"(自动转为同一天的范围)
+            - 自然语言: "今天", "昨天", "本周", "最近7天" 等
 
     Returns:
         (start_date, end_date) 元组,或 None
@@ -360,20 +364,55 @@ def validate_date_range(date_range: Optional[Union[dict, str]]) -> Optional[tupl
     if date_range is None:
         return None
 
-    # 支持字符串形式的JSON输入(某些MCP客户端会将JSON对象序列化为字符串)
+    # 支持字符串形式的输入
     if isinstance(date_range, str):
-        try:
-            date_range = json.loads(date_range)
-        except json.JSONDecodeError as e:
-            raise InvalidParameterError(
-                f"date_range JSON 解析失败: {e}",
-                suggestion='请使用正确的JSON格式: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}'
-            )
+        stripped = date_range.strip()
+
+        # 1. 检查是否是 JSON 对象格式
+        if stripped.startswith('{') and stripped.endswith('}'):
+            try:
+                date_range = json.loads(stripped)
+            except json.JSONDecodeError as e:
+                raise InvalidParameterError(
+                    f"date_range JSON 解析失败: {e}",
+                    suggestion='请使用正确的JSON格式: {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}'
+                )
+        # 2. 检查是否是单日字符串格式 YYYY-MM-DD
+        elif len(stripped) == 10 and stripped[4] == '-' and stripped[7] == '-':
+            try:
+                single_date = datetime.strptime(stripped, "%Y-%m-%d")
+                return (single_date, single_date)
+            except ValueError:
+                raise InvalidParameterError(
+                    f"日期格式错误: {stripped}",
+                    suggestion="请使用 YYYY-MM-DD 格式,例如: 2025-10-11"
+                )
+        # 3. 尝试自然语言解析
+        else:
+            try:
+                result = DateParser.resolve_date_range_expression(stripped)
+                if result.get("success"):
+                    dr = result["date_range"]
+                    start_date = datetime.strptime(dr["start"], "%Y-%m-%d")
+                    end_date = datetime.strptime(dr["end"], "%Y-%m-%d")
+                    return (start_date, end_date)
+                else:
+                    raise InvalidParameterError(
+                        f"无法识别的日期表达式: {stripped}",
+                        suggestion="支持格式: YYYY-MM-DD, {\"start\": \"...\", \"end\": \"...\"}, 或自然语言(今天、本周、最近7天等)"
+                    )
+            except InvalidParameterError:
+                raise
+            except Exception:
+                raise InvalidParameterError(
+                    f"日期解析失败: {stripped}",
+                    suggestion="支持格式: YYYY-MM-DD, {\"start\": \"...\", \"end\": \"...\"}, 或自然语言(今天、本周、最近7天等)"
+                )
 
     if not isinstance(date_range, dict):
         raise InvalidParameterError(
-            "date_range 必须是字典类型或有效的JSON字符串",
-            suggestion='例如: {"start": "2025-10-01", "end": "2025-10-11"}'
+            "date_range 必须是字典类型、日期字符串或有效的JSON字符串",
+            suggestion='例如: {"start": "2025-10-01", "end": "2025-10-11"} 或 "2025-10-01"'
         )
 
     start_str = date_range.get("start")

+ 1 - 1
version_mcp

@@ -1 +1 @@
-3.1.7
+3.2.0