報錯信息如下:
[unsupported] Python 3.1 (mcp-calculator-main) (3) C:\Users(xsshu\AppData\Local\Programs\Python\Python313\python.exe
[unsupported] Python 3.1 C:\Users\xsshu\AppData\Local\Programs\Python\Python311\python.exe
這條輸出顯示了兩個 Python 環境,但都被標記為 [unsupported]
,并且顯示內容存在一些混亂(可能是某個 IDE 或工具的環境列表),讓我們理一下目前的狀況并給出明確的解決步驟。
🔍 當前情況分析
你可能有以下環境:
Python 3.13
(Python313
)—— ?? 這是未來開發版,目前很多庫(包括 mcp)可能不支持。Python 3.11
(Python311
)—— ? 支持match-case
,推薦使用這個版本。- 報錯中曾使用
Python 3.9
—— ? 不支持match-case
,不能運行mcp
庫。 [unsupported]
標簽可能來自你的 IDE(例如 PyCharm)表示環境配置不完整或不兼容。
? 解決方案(標準步驟)
? 步驟 1:確認 Python 3.11
是可用的
打開命令行(Win+R 輸入 cmd
),輸入:
C:\Users\xsshu\AppData\Local\Programs\Python\Python311\python.exe --version
應輸出:
Python 3.11.x
如果有輸出說明這個 Python 可正常使用。
? 步驟 2:用 Python 3.11 創建虛擬環境(推薦)
你可以這樣創建虛擬環境:
cd C:\Users\xsshu\Desktop\mcp-calculator-main
C:\Users\xsshu\AppData\Local\Programs\Python\Python311\python.exe -m venv venv
激活虛擬環境(Windows 命令行):
venv\Scripts\activate
激活后你會看到命令提示符變成:
(venv) C:\Users\xsshu\Desktop\mcp-calculator-main>
? 步驟 3:重新安裝依賴
確保你在虛擬環境中,執行:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
? 步驟 4:運行腳本
仍在虛擬環境中執行你的腳本:
python juhe_news_mcp.py
🔄 如果你使用的是 PyCharm 或 VS Code:
🔄 PyCharm 設置解釋器步驟:
-
進入:File > Settings > Project: xxx > Python Interpreter
-
點擊右上角齒輪 > Add…
-
選擇 “Existing Environment”,指定路徑為:
C:\Users\xsshu\Desktop\mcp-calculator-main\venv\Scripts\python.exe
-
點擊 OK
? 總結
問題 | 原因 | 解決方式 |
---|---|---|
match-case 報錯 | Python 版本太低(??.10) | 用 Python 3.11 運行 |
[unsupported] | IDE識別不到有效環境 | 用 Python 3.11 創建虛擬環境 |
mcp 無法導入 | pip 安裝環境錯亂或模塊路徑錯 | 用正確 Python 和虛擬環境裝依賴 |
實例1:聚合數據查詢實時新聞
from mcp.server.fastmcp import FastMCP
import sys
import logging
import math
import random
import requests
import os
import time# ---------------------- 日志配置 ----------------------
logger = logging.getLogger('MCP_Server')
logging.basicConfig(level=logging.INFO)# ---------------------- 修復 Windows 控制臺中文亂碼問題 ----------------------
if sys.platform == 'win32':sys.stderr.reconfigure(encoding='utf-8')sys.stdout.reconfigure(encoding='utf-8')# ---------------------- 聚合數據新聞API配置 ----------------------
JUHE_NEWS_API_KEY = 'xxxxxxxxxxx' # 請替換為您的密鑰
JUHE_NEWS_BASE_URL = 'http://v.juhe.cn/toutiao/index'# 新聞類型映射
NEWS_TYPE_MAPPING = {'top': '頭條','guonei': '國內','guoji': '國際','yule': '娛樂','tiyu': '體育','junshi': '軍事','keji': '科技','caijing': '財經','shishang': '時尚'
}# ---------------------- 創建 MCP Server 對象 ----------------------
mcp = FastMCP("MyTools")# 🔧 工具 1??:計算器工具
@mcp.tool()
def calculator(python_expression: str) -> dict:"""數學表達式計算器功能說明:計算任意合法 Python 表達式的結果,適合用于數學推理。內置 math 和 random 模塊,可以使用如 math.pi、random.randint 等。使用場景:當用戶詢問如"直徑為 8 的圓面積是多少"、"√2+5是多少"等數學問題時,自動調用此工具。參數說明:python_expression (str): 要計算的 Python 表達式,例如 "math.pi * 4 ** 2"返回值:dict: {"success": True, "result": 計算結果}"""result = eval(python_expression)logger.info(f"計算表達式: {python_expression} = {result}")return {"success": True, "result": result}# 🔧 工具 2??:中文新聞搜索工具
@mcp.tool()
def search_chinese_news(news_category: str = "top", max_articles: int = 3, page_number: int = 1, include_content: bool = False) -> dict:"""中文新聞搜索工具功能說明:獲取指定分類的中文新聞,支持多種新聞分類和分頁,數據來源于聚合數據API。支持獲取新聞標題、內容、作者、日期等完整信息。使用場景:當用戶詢問"查看今天的頭條新聞"、"獲取科技新聞"、"查看國內新聞"等需要中文新聞時,自動調用此工具。參數說明:news_category (str): 新聞分類,可選值: top(頭條), guonei(國內), guoji(國際), yule(娛樂), tiyu(體育), junshi(軍事), keji(科技), caijing(財經), shishang(時尚)max_articles (int): 最大返回文章數量,默認為3篇,最大不超過5篇page_number (int): 頁碼,默認第1頁include_content (bool): 是否包含新聞內容,默認False(僅標題),True時返回完整內容返回值:dict: {"success": True, "news": [{"title": "標題", "content": "內容", "author": "作者", "date": "日期", "category": "分類", "url": "鏈接", "uniquekey": "唯一標識"}]}"""# 檢查API密鑰if JUHE_NEWS_API_KEY == 'YOUR_API_KEY_HERE' or not JUHE_NEWS_API_KEY:return {"success": False, "error": "請先配置聚合數據API密鑰"}# 驗證分類參數if news_category not in NEWS_TYPE_MAPPING:news_category = "top"# 限制文章數量和頁碼max_articles = min(max_articles, 5)page_number = max(1, page_number)try:# 構造API請求 - 根據官方文檔完善參數url = JUHE_NEWS_BASE_URLparams = {'type': news_category,'key': JUHE_NEWS_API_KEY,'page': page_number,'page_size': max_articles,'is_filter': 1 # 過濾垃圾信息}logger.info(f"請求新聞API: {url}, 參數: {params}")# 發送請求start_time = time.time()response = requests.get(url, params=params, timeout=15)response_time = time.time() - start_timeif response.status_code != 200:logger.error(f"聚合數據API錯誤: {response.status_code}")return {"success": False, "error": f"API請求失敗,狀態碼: {response.status_code}"}data = response.json()logger.info(f"API返回狀態: error_code={data.get('error_code')}, reason={data.get('reason')}")if data.get('error_code') != 0:error_msg = data.get('reason', '未知錯誤')logger.error(f"聚合數據API返回錯誤: {error_msg}")return {"success": False, "error": f"API錯誤: {error_msg}"}result = data.get('result', {})articles = result.get('data', [])if not articles:category_name = NEWS_TYPE_MAPPING.get(news_category, news_category)return {"success": True, "news": [], "message": f"未找到 {category_name} 新聞"}# 調試:記錄API返回的數據結構if articles:sample_article = articles[0]available_fields = list(sample_article.keys())logger.info(f"API返回數據字段: {available_fields}")# 處理文章數據processed_news = []total_length = 0for i, article in enumerate(articles):# 獲取基本信息title = article.get('title', '無標題')[:120]author = article.get('author_name', '未知作者')date = article.get('date', '未知日期')category_name = NEWS_TYPE_MAPPING.get(news_category, news_category)url = article.get('url', '')uniquekey = article.get('uniquekey', '')thumbnail = article.get('thumbnail_pic_s', '')# 獲取新聞內容content = ""if include_content:# 根據官方文檔,content字段包含新聞內容raw_content = article.get('content', '')if raw_content:content = str(raw_content)[:300] # 限制內容長度else:content = "暫無詳細內容"else:content = "如需查看詳細內容,請設置include_content=True"news_data = {"title": title,"content": content,"author": author,"date": date,"category": category_name,"url": url,"uniquekey": uniquekey,"thumbnail": thumbnail}# 檢查總長度是否超過MCP限制news_str = str(news_data)if total_length + len(news_str) > 1200:logger.info(f"達到內容長度限制,停止添加更多新聞")breakprocessed_news.append(news_data)total_length += len(news_str)logger.info(f"中文新聞: {category_name}, 頁碼: {page_number}, 返回 {len(processed_news)} 篇, 響應時間: {response_time:.2f}秒")return {"success": True,"news": processed_news,"category": category_name,"page": page_number,"total_found": len(articles),"include_content": include_content,"api_response_time": f"{response_time:.2f}秒"}except requests.exceptions.Timeout:logger.error(f"聚合數據API請求超時: {news_category}")return {"success": False, "error": "請求超時,請稍后重試"}except requests.exceptions.RequestException as e:logger.error(f"聚合數據API網絡錯誤: {str(e)}")return {"success": False, "error": "網絡連接錯誤"}except Exception as e:logger.error(f"獲取中文新聞出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 3??:新聞詳情查詢工具(基于uniquekey)
@mcp.tool()
def get_news_detail_by_uniquekey(news_uniquekey: str) -> dict:"""根據新聞唯一標識獲取詳細內容功能說明:通過新聞的uniquekey獲取該新聞的完整詳細信息,包括標題、內容、作者等。這是獲取特定新聞詳細內容的推薦方式。使用場景:當用戶想要查看某條特定新聞的詳細內容時,可以使用此工具。通常配合新聞搜索工具使用,先搜索新聞獲取uniquekey,再查詢詳情。參數說明:news_uniquekey (str): 新聞的唯一標識符,從新聞搜索結果中獲取返回值:dict: {"success": True, "title": "標題", "content": "詳細內容", "author": "作者", "date": "日期", "url": "鏈接"}"""# 檢查API密鑰if JUHE_NEWS_API_KEY == 'YOUR_API_KEY_HERE' or not JUHE_NEWS_API_KEY:return {"success": False, "error": "請先配置聚合數據API密鑰"}# 驗證參數if not news_uniquekey or not isinstance(news_uniquekey, str):return {"success": False, "error": "請提供有效的新聞uniquekey"}try:# 構造API請求 - 使用uniquekey查詢特定新聞詳情url = "http://v.juhe.cn/toutiao/content" # 新聞詳情查詢接口params = {'key': JUHE_NEWS_API_KEY,'uniquekey': news_uniquekey}logger.info(f"查詢新聞詳情: uniquekey={news_uniquekey}")# 發送請求start_time = time.time()response = requests.get(url, params=params, timeout=15)response_time = time.time() - start_timeif response.status_code != 200:logger.error(f"新聞詳情API錯誤: {response.status_code}")return {"success": False, "error": f"API請求失敗,狀態碼: {response.status_code}"}data = response.json()if data.get('error_code') != 0:error_msg = data.get('reason', '未知錯誤')logger.error(f"新聞詳情API返回錯誤: {error_msg}")return {"success": False, "error": f"API錯誤: {error_msg}"}result = data.get('result', {})if not result:return {"success": False, "error": "未找到該新聞詳情"}# 提取詳細信息title = result.get('title', '無標題')content = result.get('content', '暫無內容')detail = result.get('detail', '暫無詳細信息')author = result.get('author_name', '未知作者')date = result.get('date', '未知日期')category = result.get('category', '未知分類')url = result.get('url', '')# 由于MCP返回值長度限制,適當截取內容if len(content) > 800:content = content[:800] + "...[內容過長已截取]"logger.info(f"成功獲取新聞詳情: {title[:30]}..., 響應時間: {response_time:.2f}秒")return {"success": True,"uniquekey": news_uniquekey,"title": title,"content": content,"detail": detail,"author": author,"date": date,"category": category,"url": url,"api_response_time": f"{response_time:.2f}秒"}except requests.exceptions.Timeout:logger.error(f"新聞詳情API請求超時: {news_uniquekey}")return {"success": False, "error": "請求超時,請稍后重試"}except requests.exceptions.RequestException as e:logger.error(f"新聞詳情API網絡錯誤: {str(e)}")return {"success": False, "error": "網絡連接錯誤"}except Exception as e:logger.error(f"獲取新聞詳情出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 4??:新聞URL內容提取工具(保留作為備用)
@mcp.tool()
def extract_news_content_from_url(news_url: str) -> dict:"""從新聞鏈接提取內容(備用方案)功能說明:通過新聞URL直接抓取網頁內容,作為獲取新聞詳情的備用方案。注意:由于網站反爬蟲機制,成功率可能較低,推薦優先使用uniquekey查詢方式。使用場景:當無法通過uniquekey獲取詳情時的備用方案。參數說明:news_url (str): 新聞的完整URL鏈接返回值:dict: {"success": True, "content": "提取的內容", "url": "原鏈接"}"""if not news_url or not news_url.startswith('http'):return {"success": False, "error": "無效的新聞鏈接"}try:# 設置請求頭,模擬瀏覽器訪問headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3','Connection': 'keep-alive',}response = requests.get(news_url, headers=headers, timeout=10)if response.status_code != 200:return {"success": False, "error": f"無法訪問新聞鏈接,狀態碼: {response.status_code}"}# 簡單的內容提取content = response.text# 由于MCP返回值長度限制,只返回前600字符if len(content) > 600:content = content[:600] + "...[內容已截取]"logger.info(f"成功提取URL內容: {news_url[:50]}...")return {"success": True,"content": content,"url": news_url,"method": "URL抓取","note": "此方法為備用方案,推薦使用uniquekey查詢獲取更準確的內容"}except requests.exceptions.Timeout:return {"success": False, "error": "請求超時"}except requests.exceptions.RequestException as e:return {"success": False, "error": f"網絡錯誤: {str(e)}"}except Exception as e:logger.error(f"URL內容提取出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🚀 啟動 MCP Server 主程序
if __name__ == "__main__":mcp.run(transport="stdio")# 🧪 測試函數(開發調試用)
def test_news_api():"""測試新聞API功能的驗證函數僅在開發階段使用,用于驗證API改進效果"""print("=" * 50)print("📰 新聞API功能測試")print("=" * 50)# 測試1:基礎新聞搜索(不包含內容)print("\n🔸 測試1:基礎新聞搜索")result1 = search_chinese_news(news_category="keji", max_articles=2, include_content=False)print(f"成功: {result1.get('success')}")if result1.get('success'):print(f"新聞數量: {len(result1.get('news', []))}")if result1.get('news'):first_news = result1['news'][0]print(f"標題示例: {first_news.get('title', '')[:50]}...")print(f"uniquekey: {first_news.get('uniquekey', 'N/A')}")# 測試2:包含內容的新聞搜索print("\n🔸 測試2:包含內容的新聞搜索")result2 = search_chinese_news(news_category="keji", max_articles=1, include_content=True)print(f"成功: {result2.get('success')}")if result2.get('success') and result2.get('news'):news_with_content = result2['news'][0]content = news_with_content.get('content', '')print(f"內容長度: {len(content)} 字符")print(f"內容預覽: {content[:100]}..." if len(content) > 100 else f"完整內容: {content}")# 測試3:基于uniquekey的詳情查詢if result1.get('success') and result1.get('news') and result1['news'][0].get('uniquekey'):print("\n🔸 測試3:uniquekey詳情查詢")uniquekey = result1['news'][0]['uniquekey']result3 = get_news_detail_by_uniquekey(uniquekey)print(f"成功: {result3.get('success')}")if result3.get('success'):print(f"詳情內容長度: {len(result3.get('content', ''))} 字符")print("\n" + "=" * 50)print("? 測試完成")print("=" * 50)# 如果需要測試,取消下面的注釋
# test_news_api()
實例2:NBA賽事查詢
from mcp.server.fastmcp import FastMCP
import sys
import logging
import math
import random
import requests
import os
import time# ---------------------- 日志配置 ----------------------
logger = logging.getLogger('MCP_Server')
logging.basicConfig(level=logging.INFO)# ---------------------- 修復 Windows 控制臺中文亂碼問題 ----------------------
if sys.platform == 'win32':sys.stderr.reconfigure(encoding='utf-8')sys.stdout.reconfigure(encoding='utf-8')# ---------------------- 聚合數據新聞API配置 ----------------------
JUHE_NEWS_API_KEY = '6688b0200d8fd96381fb0d6ae4e03cc5' # 請替換為您的密鑰
JUHE_NEWS_BASE_URL = 'http://v.juhe.cn/toutiao/index'# 新聞類型映射
NEWS_TYPE_MAPPING = {'top': '頭條','guonei': '國內','guoji': '國際','yule': '娛樂','tiyu': '體育','junshi': '軍事','keji': '科技','caijing': '財經','shishang': '時尚'
}# ---------------------- 聚合數據NBA API配置 ----------------------
JUHE_NBA_API_KEY = 'xxxxxxxxxxxxxxxxxxxxxx' # NBA賽事API密鑰
JUHE_NBA_BASE_URL = 'http://apis.juhe.cn/fapig/nba/query'# NBA比賽狀態映射
NBA_STATUS_MAPPING = {'1': {'text': '未開賽', 'is_finished': False},'2': {'text': '進行中', 'is_finished': False}, '3': {'text': '完賽', 'is_finished': True}
}# ---------------------- 創建 MCP Server 對象 ----------------------
mcp = FastMCP("MyTools")# 🔧 工具 1??:計算器工具
@mcp.tool()
def calculator(python_expression: str) -> dict:"""數學表達式計算器功能說明:計算任意合法 Python 表達式的結果,適合用于數學推理。內置 math 和 random 模塊,可以使用如 math.pi、random.randint 等。使用場景:當用戶詢問如"直徑為 8 的圓面積是多少"、"√2+5是多少"等數學問題時,自動調用此工具。參數說明:python_expression (str): 要計算的 Python 表達式,例如 "math.pi * 4 ** 2"返回值:dict: {"success": True, "result": 計算結果}"""result = eval(python_expression)logger.info(f"計算表達式: {python_expression} = {result}")return {"success": True, "result": result}# 🔧 工具 2??:中文新聞搜索工具
@mcp.tool()
def search_chinese_news(news_category: str = "top", max_articles: int = 3, page_number: int = 1, include_content: bool = False) -> dict:"""中文新聞搜索工具功能說明:獲取指定分類的中文新聞,支持多種新聞分類和分頁,數據來源于聚合數據API。支持獲取新聞標題、內容、作者、日期等完整信息。使用場景:當用戶詢問"查看今天的頭條新聞"、"獲取科技新聞"、"查看國內新聞"等需要中文新聞時,自動調用此工具。參數說明:news_category (str): 新聞分類,可選值: top(頭條), guonei(國內), guoji(國際), yule(娛樂), tiyu(體育), junshi(軍事), keji(科技), caijing(財經), shishang(時尚)max_articles (int): 最大返回文章數量,默認為3篇,最大不超過5篇page_number (int): 頁碼,默認第1頁include_content (bool): 是否包含新聞內容,默認False(僅標題),True時返回完整內容返回值:dict: {"success": True, "news": [{"title": "標題", "content": "內容", "author": "作者", "date": "日期", "category": "分類", "url": "鏈接", "uniquekey": "唯一標識"}]}"""# 檢查API密鑰if JUHE_NEWS_API_KEY == 'YOUR_API_KEY_HERE' or not JUHE_NEWS_API_KEY:return {"success": False, "error": "請先配置聚合數據API密鑰"}# 驗證分類參數if news_category not in NEWS_TYPE_MAPPING:news_category = "top"# 限制文章數量和頁碼max_articles = min(max_articles, 5)page_number = max(1, page_number)try:# 構造API請求 - 根據官方文檔完善參數url = JUHE_NEWS_BASE_URLparams = {'type': news_category,'key': JUHE_NEWS_API_KEY,'page': page_number,'page_size': max_articles,'is_filter': 1 # 過濾垃圾信息}logger.info(f"請求新聞API: {url}, 參數: {params}")# 發送請求start_time = time.time()response = requests.get(url, params=params, timeout=15)response_time = time.time() - start_timeif response.status_code != 200:logger.error(f"聚合數據API錯誤: {response.status_code}")return {"success": False, "error": f"API請求失敗,狀態碼: {response.status_code}"}data = response.json()logger.info(f"API返回狀態: error_code={data.get('error_code')}, reason={data.get('reason')}")if data.get('error_code') != 0:error_msg = data.get('reason', '未知錯誤')logger.error(f"聚合數據API返回錯誤: {error_msg}")return {"success": False, "error": f"API錯誤: {error_msg}"}result = data.get('result', {})articles = result.get('data', [])if not articles:category_name = NEWS_TYPE_MAPPING.get(news_category, news_category)return {"success": True, "news": [], "message": f"未找到 {category_name} 新聞"}# 調試:記錄API返回的數據結構if articles:sample_article = articles[0]available_fields = list(sample_article.keys())logger.info(f"API返回數據字段: {available_fields}")# 處理文章數據processed_news = []total_length = 0for i, article in enumerate(articles):# 獲取基本信息title = article.get('title', '無標題')[:120]author = article.get('author_name', '未知作者')date = article.get('date', '未知日期')category_name = NEWS_TYPE_MAPPING.get(news_category, news_category)url = article.get('url', '')uniquekey = article.get('uniquekey', '')thumbnail = article.get('thumbnail_pic_s', '')# 獲取新聞內容content = ""if include_content:# 根據官方文檔,content字段包含新聞內容raw_content = article.get('content', '')if raw_content:content = str(raw_content)[:300] # 限制內容長度else:content = "暫無詳細內容"else:content = "如需查看詳細內容,請設置include_content=True"news_data = {"title": title,"content": content,"author": author,"date": date,"category": category_name,"url": url,"uniquekey": uniquekey,"thumbnail": thumbnail}# 檢查總長度是否超過MCP限制news_str = str(news_data)if total_length + len(news_str) > 1200:logger.info(f"達到內容長度限制,停止添加更多新聞")breakprocessed_news.append(news_data)total_length += len(news_str)logger.info(f"中文新聞: {category_name}, 頁碼: {page_number}, 返回 {len(processed_news)} 篇, 響應時間: {response_time:.2f}秒")return {"success": True,"news": processed_news,"category": category_name,"page": page_number,"total_found": len(articles),"include_content": include_content,"api_response_time": f"{response_time:.2f}秒"}except requests.exceptions.Timeout:logger.error(f"聚合數據API請求超時: {news_category}")return {"success": False, "error": "請求超時,請稍后重試"}except requests.exceptions.RequestException as e:logger.error(f"聚合數據API網絡錯誤: {str(e)}")return {"success": False, "error": "網絡連接錯誤"}except Exception as e:logger.error(f"獲取中文新聞出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 3??:新聞詳情查詢工具(基于uniquekey)
@mcp.tool()
def get_news_detail_by_uniquekey(news_uniquekey: str) -> dict:"""根據新聞唯一標識獲取詳細內容功能說明:通過新聞的uniquekey獲取該新聞的完整詳細信息,包括標題、內容、作者等。這是獲取特定新聞詳細內容的推薦方式。使用場景:當用戶想要查看某條特定新聞的詳細內容時,可以使用此工具。通常配合新聞搜索工具使用,先搜索新聞獲取uniquekey,再查詢詳情。參數說明:news_uniquekey (str): 新聞的唯一標識符,從新聞搜索結果中獲取返回值:dict: {"success": True, "title": "標題", "content": "詳細內容", "author": "作者", "date": "日期", "url": "鏈接"}"""# 檢查API密鑰if JUHE_NEWS_API_KEY == 'YOUR_API_KEY_HERE' or not JUHE_NEWS_API_KEY:return {"success": False, "error": "請先配置聚合數據API密鑰"}# 驗證參數if not news_uniquekey or not isinstance(news_uniquekey, str):return {"success": False, "error": "請提供有效的新聞uniquekey"}try:# 構造API請求 - 使用uniquekey查詢特定新聞詳情url = "http://v.juhe.cn/toutiao/content" # 新聞詳情查詢接口params = {'key': JUHE_NEWS_API_KEY,'uniquekey': news_uniquekey}logger.info(f"查詢新聞詳情: uniquekey={news_uniquekey}")# 發送請求start_time = time.time()response = requests.get(url, params=params, timeout=15)response_time = time.time() - start_timeif response.status_code != 200:logger.error(f"新聞詳情API錯誤: {response.status_code}")return {"success": False, "error": f"API請求失敗,狀態碼: {response.status_code}"}data = response.json()if data.get('error_code') != 0:error_msg = data.get('reason', '未知錯誤')logger.error(f"新聞詳情API返回錯誤: {error_msg}")return {"success": False, "error": f"API錯誤: {error_msg}"}result = data.get('result', {})if not result:return {"success": False, "error": "未找到該新聞詳情"}# 提取詳細信息title = result.get('title', '無標題')content = result.get('content', '暫無內容')detail = result.get('detail', '暫無詳細信息')author = result.get('author_name', '未知作者')date = result.get('date', '未知日期')category = result.get('category', '未知分類')url = result.get('url', '')# 由于MCP返回值長度限制,適當截取內容if len(content) > 800:content = content[:800] + "...[內容過長已截取]"logger.info(f"成功獲取新聞詳情: {title[:30]}..., 響應時間: {response_time:.2f}秒")return {"success": True,"uniquekey": news_uniquekey,"title": title,"content": content,"detail": detail,"author": author,"date": date,"category": category,"url": url,"api_response_time": f"{response_time:.2f}秒"}except requests.exceptions.Timeout:logger.error(f"新聞詳情API請求超時: {news_uniquekey}")return {"success": False, "error": "請求超時,請稍后重試"}except requests.exceptions.RequestException as e:logger.error(f"新聞詳情API網絡錯誤: {str(e)}")return {"success": False, "error": "網絡連接錯誤"}except Exception as e:logger.error(f"獲取新聞詳情出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 4??:新聞URL內容提取工具(保留作為備用)
@mcp.tool()
def extract_news_content_from_url(news_url: str) -> dict:"""從新聞鏈接提取內容(備用方案)功能說明:通過新聞URL直接抓取網頁內容,作為獲取新聞詳情的備用方案。注意:由于網站反爬蟲機制,成功率可能較低,推薦優先使用uniquekey查詢方式。使用場景:當無法通過uniquekey獲取詳情時的備用方案。參數說明:news_url (str): 新聞的完整URL鏈接返回值:dict: {"success": True, "content": "提取的內容", "url": "原鏈接"}"""if not news_url or not news_url.startswith('http'):return {"success": False, "error": "無效的新聞鏈接"}try:# 設置請求頭,模擬瀏覽器訪問headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3','Connection': 'keep-alive',}response = requests.get(news_url, headers=headers, timeout=10)if response.status_code != 200:return {"success": False, "error": f"無法訪問新聞鏈接,狀態碼: {response.status_code}"}# 簡單的內容提取content = response.text# 由于MCP返回值長度限制,只返回前600字符if len(content) > 600:content = content[:600] + "...[內容已截取]"logger.info(f"成功提取URL內容: {news_url[:50]}...")return {"success": True,"content": content,"url": news_url,"method": "URL抓取","note": "此方法為備用方案,推薦使用uniquekey查詢獲取更準確的內容"}except requests.exceptions.Timeout:return {"success": False, "error": "請求超時"}except requests.exceptions.RequestException as e:return {"success": False, "error": f"網絡錯誤: {str(e)}"}except Exception as e:logger.error(f"URL內容提取出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 5??:NBA賽程賽果查詢工具
@mcp.tool()
def query_nba_schedule_results(max_days: int = 7, include_finished_games: bool = True, include_upcoming_games: bool = True) -> dict:"""NBA賽程賽果查詢工具功能說明:查詢NBA近期的賽程安排和比賽結果,支持靈活的過濾條件。數據來源于聚合數據NBA API,包含詳細的比賽信息。使用場景:當用戶詢問"查看NBA最近的比賽結果"、"今天有什么NBA比賽"、"NBA賽程查詢"、"湖人隊最近比賽"等NBA相關問題時,自動調用此工具。參數說明:max_days (int): 返回最近幾天的賽程,默認7天,建議不超過10天include_finished_games (bool): 是否包含已完賽的比賽,默認Trueinclude_upcoming_games (bool): 是否包含未開賽的比賽,默認True返回值:dict: {"success": True, "league_info": {"title": "聯賽名稱", "season": "賽季"}, "schedule": [賽程數據], "summary": {統計摘要}}"""# 檢查API密鑰if not JUHE_NBA_API_KEY:return {"success": False, "error": "請先配置NBA API密鑰"}# 驗證參數max_days = max(1, min(max_days, 10)) # 限制在1-10天之間if not include_finished_games and not include_upcoming_games:return {"success": False, "error": "至少需要包含一種比賽類型(已完賽或未開賽)"}try:# 構造API請求url = JUHE_NBA_BASE_URLparams = {'key': JUHE_NBA_API_KEY}logger.info(f"請求NBA賽程API: {url}")# 發送請求start_time = time.time()response = requests.get(url, params=params, timeout=15)response_time = time.time() - start_timeif response.status_code != 200:logger.error(f"NBA API錯誤: {response.status_code}")return {"success": False, "error": f"API請求失敗,狀態碼: {response.status_code}"}data = response.json()logger.info(f"NBA API返回狀態: error_code={data.get('error_code')}, reason={data.get('reason')}")if data.get('error_code') != 0:error_msg = data.get('reason', '未知錯誤')logger.error(f"NBA API返回錯誤: {error_msg}")return {"success": False, "error": f"API錯誤: {error_msg}"}result = data.get('result', {})if not result:return {"success": False, "error": "未獲取到NBA賽程數據"}# 提取聯賽信息league_info = {"title": result.get('title', 'NBA'),"season": result.get('duration', '未知賽季')}matches = result.get('matchs', [])if not matches:return {"success": True, "league_info": league_info, "schedule": [], "message": "暫無賽程數據"}# 處理賽程數據processed_schedule = []total_games = 0finished_games = 0upcoming_games = 0current_length = 0for day_data in matches[:max_days]:date = day_data.get('date', '未知日期')week = day_data.get('week', '未知')day_games = day_data.get('list', [])if not day_games:continue# 過濾比賽filtered_games = []for game in day_games:status = game.get('status', '1')status_info = NBA_STATUS_MAPPING.get(status, {'text': '未知狀態', 'is_finished': False})# 根據參數過濾比賽if status_info['is_finished'] and not include_finished_games:continueif not status_info['is_finished'] and not include_upcoming_games:continue# 處理比賽數據game_data = {"time": game.get('time_start', '未知時間'),"status": status_info['text'],"team1": game.get('team1', '未知球隊1'),"team2": game.get('team2', '未知球隊2'),"score1": game.get('team1_score', '-'),"score2": game.get('team2_score', '-'),"is_finished": status_info['is_finished']}filtered_games.append(game_data)total_games += 1if status_info['is_finished']:finished_games += 1else:upcoming_games += 1# 如果當天有比賽,添加到結果中if filtered_games:day_schedule = {"date": date,"week": week,"games": filtered_games}# 檢查長度限制day_str = str(day_schedule)if current_length + len(day_str) > 1500: # MCP長度限制logger.info(f"達到內容長度限制,截止到{date}")breakprocessed_schedule.append(day_schedule)current_length += len(day_str)# 構建統計摘要summary = {"total_days": len(processed_schedule),"total_games": total_games,"finished_games": finished_games,"upcoming_games": upcoming_games}logger.info(f"NBA賽程查詢完成: {summary['total_days']}天, {summary['total_games']}場比賽, 響應時間: {response_time:.2f}秒")return {"success": True,"league_info": league_info,"schedule": processed_schedule,"summary": summary,"filters": {"max_days": max_days,"include_finished": include_finished_games,"include_upcoming": include_upcoming_games},"api_response_time": f"{response_time:.2f}秒"}except requests.exceptions.Timeout:logger.error(f"NBA API請求超時")return {"success": False, "error": "請求超時,請稍后重試"}except requests.exceptions.RequestException as e:logger.error(f"NBA API網絡錯誤: {str(e)}")return {"success": False, "error": "網絡連接錯誤"}except Exception as e:logger.error(f"獲取NBA賽程出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🔧 工具 6??:NBA特定球隊賽程查詢工具
@mcp.tool()
def query_nba_team_schedule(team_name: str, max_games: int = 5) -> dict:"""NBA特定球隊賽程查詢工具功能說明:查詢指定NBA球隊的近期賽程和比賽結果。通過球隊名稱關鍵詞匹配,支持中文球隊名稱。使用場景:當用戶詢問"湖人隊最近的比賽"、"勇士隊賽程"、"查看熱火隊比賽結果"等針對特定球隊的NBA問題時,自動調用此工具。參數說明:team_name (str): 球隊名稱關鍵詞,支持中文名稱,如"湖人"、"勇士"、"熱火"等max_games (int): 返回最大比賽數量,默認5場返回值:dict: {"success": True, "team_name": "球隊名稱", "games": [比賽數據], "summary": {統計信息}}"""# 檢查API密鑰if not JUHE_NBA_API_KEY:return {"success": False, "error": "請先配置NBA API密鑰"}# 驗證參數if not team_name or not isinstance(team_name, str):return {"success": False, "error": "請提供有效的球隊名稱"}team_name = team_name.strip()max_games = max(1, min(max_games, 10)) # 限制在1-10場之間try:# 先獲取所有賽程數據schedule_result = query_nba_schedule_results(max_days=10, include_finished_games=True, include_upcoming_games=True)if not schedule_result.get('success'):return schedule_result # 直接返回錯誤信息schedule_data = schedule_result.get('schedule', [])if not schedule_data:return {"success": True, "team_name": team_name, "games": [], "message": "暫無賽程數據"}# 搜索包含指定球隊的比賽team_games = []for day_data in schedule_data:date = day_data.get('date', '')week = day_data.get('week', '')games = day_data.get('games', [])for game in games:team1 = game.get('team1', '')team2 = game.get('team2', '')# 檢查球隊名稱是否匹配(支持模糊匹配)if team_name in team1 or team_name in team2:game_info = {"date": date,"week": week,"time": game.get('time', ''),"status": game.get('status', ''),"team1": team1,"team2": team2,"score1": game.get('score1', '-'),"score2": game.get('score2', '-'),"is_finished": game.get('is_finished', False),"is_home": team_name in team1 # 判斷是否主場}team_games.append(game_info)# 限制返回數量if len(team_games) >= max_games:breakif len(team_games) >= max_games:break# 統計信息finished_count = sum(1 for game in team_games if game['is_finished'])upcoming_count = len(team_games) - finished_counthome_count = sum(1 for game in team_games if game['is_home'])away_count = len(team_games) - home_countsummary = {"total_games": len(team_games),"finished_games": finished_count,"upcoming_games": upcoming_count,"home_games": home_count,"away_games": away_count}logger.info(f"球隊賽程查詢完成: {team_name}, 找到{len(team_games)}場比賽")if not team_games:return {"success": True, "team_name": team_name, "games": [], "message": f"未找到包含'{team_name}'的比賽"}return {"success": True,"team_name": team_name,"games": team_games,"summary": summary,"note": f"根據關鍵詞'{team_name}'匹配到的比賽結果"}except Exception as e:logger.error(f"查詢球隊賽程出錯: {str(e)}")return {"success": False, "error": f"處理請求時發生錯誤: {str(e)}"}# 🚀 啟動 MCP Server 主程序
if __name__ == "__main__":mcp.run(transport="stdio")# 🧪 測試函數(開發調試用)
def test_news_api():"""測試新聞API功能的驗證函數僅在開發階段使用,用于驗證API改進效果"""print("=" * 50)print("📰 新聞API功能測試")print("=" * 50)# 測試1:基礎新聞搜索(不包含內容)print("\n🔸 測試1:基礎新聞搜索")result1 = search_chinese_news(news_category="keji", max_articles=2, include_content=False)print(f"成功: {result1.get('success')}")if result1.get('success'):print(f"新聞數量: {len(result1.get('news', []))}")if result1.get('news'):first_news = result1['news'][0]print(f"標題示例: {first_news.get('title', '')[:50]}...")print(f"uniquekey: {first_news.get('uniquekey', 'N/A')}")# 測試2:包含內容的新聞搜索print("\n🔸 測試2:包含內容的新聞搜索")result2 = search_chinese_news(news_category="keji", max_articles=1, include_content=True)print(f"成功: {result2.get('success')}")if result2.get('success') and result2.get('news'):news_with_content = result2['news'][0]content = news_with_content.get('content', '')print(f"內容長度: {len(content)} 字符")print(f"內容預覽: {content[:100]}..." if len(content) > 100 else f"完整內容: {content}")# 測試3:基于uniquekey的詳情查詢if result1.get('success') and result1.get('news') and result1['news'][0].get('uniquekey'):print("\n🔸 測試3:uniquekey詳情查詢")uniquekey = result1['news'][0]['uniquekey']result3 = get_news_detail_by_uniquekey(uniquekey)print(f"成功: {result3.get('success')}")if result3.get('success'):print(f"詳情內容長度: {len(result3.get('content', ''))} 字符")print("\n" + "=" * 50)print("? 新聞API測試完成")print("=" * 50)# 🧪 NBA API測試函數(開發調試用)
def test_nba_api():"""測試NBA API功能的驗證函數僅在開發階段使用,用于驗證NBA API功能"""print("=" * 50)print("🏀 NBA API功能測試")print("=" * 50)# 測試1:基礎NBA賽程查詢print("\n🔸 測試1:基礎NBA賽程查詢")result1 = query_nba_schedule_results(max_days=3, include_finished_games=True, include_upcoming_games=True)print(f"成功: {result1.get('success')}")if result1.get('success'):league_info = result1.get('league_info', {})print(f"聯賽: {league_info.get('title', 'N/A')}")print(f"賽季: {league_info.get('season', 'N/A')}")summary = result1.get('summary', {})print(f"總天數: {summary.get('total_days', 0)}")print(f"總比賽: {summary.get('total_games', 0)}")print(f"已完賽: {summary.get('finished_games', 0)}")print(f"未開賽: {summary.get('upcoming_games', 0)}")# 顯示第一天的比賽示例schedule = result1.get('schedule', [])if schedule:first_day = schedule[0]print(f"示例日期: {first_day.get('date', '')} {first_day.get('week', '')}")games = first_day.get('games', [])if games:print(f"該日比賽數: {len(games)}")first_game = games[0]print(f"比賽示例: {first_game.get('team1', '')} vs {first_game.get('team2', '')} ({first_game.get('status', '')})")# 測試2:只查詢已完賽的比賽print("\n🔸 測試2:只查詢已完賽比賽")result2 = query_nba_schedule_results(max_days=5, include_finished_games=True, include_upcoming_games=False)print(f"成功: {result2.get('success')}")if result2.get('success'):summary2 = result2.get('summary', {})print(f"僅已完賽比賽: {summary2.get('finished_games', 0)}場")# 測試3:球隊特定查詢print("\n🔸 測試3:球隊特定查詢")# 使用從前面測試中獲取的球隊名稱if result1.get('success') and result1.get('schedule'):schedule = result1['schedule']test_team = Nonefor day_data in schedule:games = day_data.get('games', [])if games:first_game = games[0]team1 = first_game.get('team1', '')if team1:# 提取球隊名稱的關鍵詞if '湖人' in team1:test_team = '湖人'elif '勇士' in team1:test_team = '勇士'elif '熱火' in team1:test_team = '熱火'else:# 取球隊名稱的前2個字符作為關鍵詞test_team = team1[:2]breakif test_team:breakif test_team:result3 = query_nba_team_schedule(team_name=test_team, max_games=3)print(f"測試球隊: {test_team}")print(f"成功: {result3.get('success')}")if result3.get('success'):summary3 = result3.get('summary', {})print(f"找到比賽: {summary3.get('total_games', 0)}場")print(f"主場比賽: {summary3.get('home_games', 0)}場")print(f"客場比賽: {summary3.get('away_games', 0)}場")print("\n" + "=" * 50)print("? NBA API測試完成")print("=" * 50)# 如果需要測試,取消下面的注釋
# test_news_api()
# test_nba_api()