{"id":61,"date":"2025-11-13T22:29:27","date_gmt":"2025-11-13T14:29:27","guid":{"rendered":"http:\/\/81.68.81.149\/?p=61"},"modified":"2025-11-13T22:32:10","modified_gmt":"2025-11-13T14:32:10","slug":"mcp-%e6%9c%8d%e5%8a%a1%e5%bc%80%e5%8f%91%e5%ae%8c%e6%95%b4%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e9%9b%b6%e6%9e%84%e5%bb%ba%e6%99%ba%e8%83%bd%e6%96%b0%e9%97%bb%e5%88%86%e6%9e%90%e7%b3%bb%e7%bb%9f","status":"publish","type":"post","link":"http:\/\/81.68.81.149\/index.php\/2025\/11\/13\/mcp-%e6%9c%8d%e5%8a%a1%e5%bc%80%e5%8f%91%e5%ae%8c%e6%95%b4%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e9%9b%b6%e6%9e%84%e5%bb%ba%e6%99%ba%e8%83%bd%e6%96%b0%e9%97%bb%e5%88%86%e6%9e%90%e7%b3%bb%e7%bb%9f\/","title":{"rendered":"MCP \u670d\u52a1\u5f00\u53d1\u5b8c\u6574\u6307\u5357\uff1a\u4ece\u96f6\u6784\u5efa\u667a\u80fd\u65b0\u95fb\u5206\u6790\u7cfb\u7edf"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">\ud83d\udcd6 \u524d\u8a00<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Model Context Protocol (MCP) \u662f\u4e00\u4e2a\u5f3a\u5927\u7684\u6846\u67b6\uff0c\u5141\u8bb8 AI \u6a21\u578b\u901a\u8fc7\u6807\u51c6\u5316\u7684\u534f\u8bae\u8c03\u7528\u5916\u90e8\u5de5\u5177\u548c\u670d\u52a1\u3002\u672c\u6587\u5c06\u901a\u8fc7\u4e00\u4e2a\u5b9e\u9645\u6848\u4f8b\u2014\u2014<strong>\u667a\u80fd\u65b0\u95fb\u5206\u6790\u4e0e\u90ae\u4ef6\u53d1\u9001\u7cfb\u7edf<\/strong>\uff0c\u6df1\u5165\u7406\u89e3\u5982\u4f55\u5f00\u53d1\u4e00\u4e2a\u5b8c\u6574\u7684 MCP \u670d\u52a1\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf \u9879\u76ee\u6982\u8ff0<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6211\u4eec\u5c06\u6784\u5efa\u4e00\u4e2a\u7cfb\u7edf\uff0c\u5b83\u80fd\u591f\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\ud83d\udd0d \u641c\u7d22 Google \u65b0\u95fb<\/li>\n\n\n\n<li>\ud83e\udde0 \u4f7f\u7528 AI \u8fdb\u884c\u60c5\u611f\u5206\u6790<\/li>\n\n\n\n<li>\ud83d\udce7 \u81ea\u52a8\u53d1\u9001\u90ae\u4ef6\u62a5\u544a<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u6280\u672f\u6808<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>MCP \u6846\u67b6<\/strong>: <code>mcp<\/code> \u548c <code>fastmcp<\/code><\/li>\n\n\n\n<li><strong>AI \u6a21\u578b<\/strong>: \u901a\u4e49\u5343\u95ee (Qwen)<\/li>\n\n\n\n<li><strong>\u65b0\u95fb API<\/strong>: Serper API<\/li>\n\n\n\n<li><strong>\u90ae\u4ef6\u670d\u52a1<\/strong>: SMTP (QQ \u90ae\u7bb1)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfd7\ufe0f \u67b6\u6784\u8bbe\u8ba1<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 &nbsp; &nbsp; &nbsp; &nbsp; \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 &nbsp; &nbsp; &nbsp; &nbsp; \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 &nbsp; \u7528\u6237\u8f93\u5165 &nbsp; \u2502  \u2500\u2500\u25b6 &nbsp;  \u2502  MCP Client  \u2502  \u2500\u2500\u25b6 &nbsp;  \u2502 MCP Server  \u2502<br>\u2502  \u81ea\u7136\u8bed\u8a00 &nbsp;  \u2502 &nbsp; &nbsp; &nbsp; &nbsp; \u2502  (client.py) \u2502 &nbsp; &nbsp; &nbsp; &nbsp; \u2502 (server.py) \u2502<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2502 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2502<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2502 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u25b6 \ud83d\udd0d \u641c\u7d22\u65b0\u95fb<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2502 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u25b6 \ud83e\udde0 \u60c5\u611f\u5206\u6790<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u25bc &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u25b6 \ud83d\udce7 \u53d1\u9001\u90ae\u4ef6<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2502 AI \u6a21\u578b  \u2502<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2502  (Qwen)  \u2502<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcdd \u7b2c\u4e00\u6b65\uff1a\u521b\u5efa MCP Server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MCP Server \u8d1f\u8d23\u5b9a\u4e49\u548c\u5b9e\u73b0\u5177\u4f53\u7684\u5de5\u5177\u529f\u80fd\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 \u521d\u59cb\u5316\u670d\u52a1\u5668<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">from mcp.server.fastmcp import FastMCP<br>from dotenv import load_dotenv<br>\u200b<br># \u52a0\u8f7d\u73af\u5883\u53d8\u91cf<br>load_dotenv()<br>\u200b<br># \u521d\u59cb\u5316 MCP \u670d\u52a1\u5668<br>mcp = FastMCP(\"NewsServer\")<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.2 \u5b9a\u4e49\u5de5\u5177\uff1a\u641c\u7d22\u65b0\u95fb<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u4f7f\u7528 <code>@mcp.tool()<\/code> \u88c5\u9970\u5668\u5b9a\u4e49\u5de5\u5177\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">import httpx<br>import json<br>import os<br>from datetime import datetime<br>\u200b<br>@mcp.tool()<br>async def search_google_news(keyword: str) -&gt; str:<br> &nbsp; &nbsp;\"\"\"<br> &nbsp;  \u4f7f\u7528 Serper API \u641c\u7d22\u65b0\u95fb\u5185\u5bb9\uff0c\u8fd4\u56de\u524d5\u6761\u6807\u9898\u3001\u63cf\u8ff0\u548c\u94fe\u63a5\u3002<br> &nbsp; &nbsp;<br> &nbsp;  \u53c2\u6570:<br> &nbsp; &nbsp; &nbsp;  keyword (str): \u641c\u7d22\u5173\u952e\u8bcd\uff0c\u5982 \"\u5c0f\u7c73\u6c7d\u8f66\"<br> &nbsp; &nbsp;<br> &nbsp;  \u8fd4\u56de:<br> &nbsp; &nbsp; &nbsp;  str: JSON \u5b57\u7b26\u4e32\uff0c\u5305\u542b\u65b0\u95fb\u6807\u9898\u3001\u63cf\u8ff0\u3001\u94fe\u63a5<br> &nbsp;  \"\"\"<br> &nbsp; &nbsp;api_key = os.getenv(\"SERPER_API_KEY\")<br> &nbsp; &nbsp;if not api_key:<br> &nbsp; &nbsp; &nbsp; &nbsp;return \"\u274c \u672a\u914d\u7f6e SERPER_API_KEY\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;url = \"https:\/\/google.serper.dev\/news\"<br> &nbsp; &nbsp;headers = {<br> &nbsp; &nbsp; &nbsp; &nbsp;\"X-API-KEY\": api_key,<br> &nbsp; &nbsp; &nbsp; &nbsp;\"Content-Type\": \"application\/json\"<br> &nbsp;  }<br> &nbsp; &nbsp;payload = {\"q\": keyword}<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;async with httpx.AsyncClient() as client:<br> &nbsp; &nbsp; &nbsp; &nbsp;response = await client.post(url, headers=headers, json=payload)<br> &nbsp; &nbsp; &nbsp; &nbsp;data = response.json()<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;if \"news\" not in data:<br> &nbsp; &nbsp; &nbsp; &nbsp;return \"\u274c \u672a\u83b7\u53d6\u5230\u641c\u7d22\u7ed3\u679c\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u63d0\u53d6\u524d5\u6761\u65b0\u95fb<br> &nbsp; &nbsp;articles = [<br> &nbsp; &nbsp; &nbsp;  {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"title\": item.get(\"title\"),<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"desc\": item.get(\"snippet\"),<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"url\": item.get(\"link\")<br> &nbsp; &nbsp; &nbsp;  } for item in data[\"news\"][:5]<br> &nbsp;  ]<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u4fdd\u5b58\u5230\u672c\u5730<br> &nbsp; &nbsp;output_dir = \".\/google_news\"<br> &nbsp; &nbsp;os.makedirs(output_dir, exist_ok=True)<br> &nbsp; &nbsp;filename = f\"google_news_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json\"<br> &nbsp; &nbsp;file_path = os.path.join(output_dir, filename)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;with open(file_path, \"w\", encoding=\"utf-8\") as f:<br> &nbsp; &nbsp; &nbsp; &nbsp;json.dump(articles, f, ensure_ascii=False, indent=2)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;return (<br> &nbsp; &nbsp; &nbsp; &nbsp;f\"\u2705 \u5df2\u83b7\u53d6\u4e0e [{keyword}] \u76f8\u5173\u7684\u524d5\u6761 Google \u65b0\u95fb\uff1a\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp;f\"{json.dumps(articles, ensure_ascii=False, indent=2)}\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp;f\"\ud83d\udcc4 \u5df2\u4fdd\u5b58\u5230\uff1a{file_path}\"<br> &nbsp;  )<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.3 \u5b9a\u4e49\u5de5\u5177\uff1a\u60c5\u611f\u5206\u6790<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">from openai import OpenAI<br>\u200b<br>@mcp.tool()<br>async def analyze_sentiment(text: str, filename: str) -&gt; str:<br> &nbsp; &nbsp;\"\"\"<br> &nbsp;  \u5bf9\u6587\u672c\u8fdb\u884c\u60c5\u611f\u5206\u6790\uff0c\u5e76\u4fdd\u5b58\u4e3a Markdown \u6587\u4ef6\u3002<br> &nbsp; &nbsp;<br> &nbsp;  \u53c2\u6570:<br> &nbsp; &nbsp; &nbsp;  text (str): \u65b0\u95fb\u63cf\u8ff0\u6216\u6587\u672c\u5185\u5bb9<br> &nbsp; &nbsp; &nbsp;  filename (str): \u4fdd\u5b58\u7684 Markdown \u6587\u4ef6\u540d\uff08\u4e0d\u542b\u8def\u5f84\uff09<br> &nbsp; &nbsp;<br> &nbsp;  \u8fd4\u56de:<br> &nbsp; &nbsp; &nbsp;  str: \u4ec5\u8fd4\u56de\u6587\u4ef6\u540d\uff08\u7528\u4e8e\u90ae\u4ef6\u53d1\u9001\uff09<br> &nbsp;  \"\"\"<br> &nbsp; &nbsp;# \u521d\u59cb\u5316 AI \u5ba2\u6237\u7aef<br> &nbsp; &nbsp;openai_key = os.getenv(\"DASHSCOPE_API_KEY\")<br> &nbsp; &nbsp;model = os.getenv(\"MODEL\")<br> &nbsp; &nbsp;client = OpenAI(<br> &nbsp; &nbsp; &nbsp; &nbsp;api_key=openai_key, <br> &nbsp; &nbsp; &nbsp; &nbsp;base_url=os.getenv(\"BASE_URL\")<br> &nbsp;  )<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u6784\u9020\u63d0\u793a\u8bcd<br> &nbsp; &nbsp;prompt = f\"\u8bf7\u5bf9\u4ee5\u4e0b\u65b0\u95fb\u5185\u5bb9\u8fdb\u884c\u60c5\u7eea\u503e\u5411\u5206\u6790\uff0c\u5e76\u8bf4\u660e\u539f\u56e0\uff1a\\n\\n{text}\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u8c03\u7528 AI \u6a21\u578b<br> &nbsp; &nbsp;response = client.chat.completions.create(<br> &nbsp; &nbsp; &nbsp; &nbsp;model=model,<br> &nbsp; &nbsp; &nbsp; &nbsp;messages=[{\"role\": \"user\", \"content\": prompt}]<br> &nbsp;  )<br> &nbsp; &nbsp;result = response.choices[0].message.content.strip()<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u751f\u6210 Markdown \u62a5\u544a<br> &nbsp; &nbsp;markdown = f\"\"\"# \u8206\u60c5\u5206\u6790\u62a5\u544a<br>\u200b<br>**\u5206\u6790\u65f6\u95f4\uff1a** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}<br>\u200b<br>---<br>\u200b<br>## \ud83d\udce5 \u539f\u59cb\u6587\u672c<br>\u200b<br>{text}<br>\u200b<br>---<br>\u200b<br>## \ud83d\udcca \u5206\u6790\u7ed3\u679c<br>\u200b<br>{result}<br>\"\"\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u4fdd\u5b58\u6587\u4ef6<br> &nbsp; &nbsp;output_dir = \".\/sentiment_reports\"<br> &nbsp; &nbsp;os.makedirs(output_dir, exist_ok=True)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;if not filename:<br> &nbsp; &nbsp; &nbsp; &nbsp;filename = f\"sentiment_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;file_path = os.path.join(output_dir, filename)<br> &nbsp; &nbsp;with open(file_path, \"w\", encoding=\"utf-8\") as f:<br> &nbsp; &nbsp; &nbsp; &nbsp;f.write(markdown)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u53ea\u8fd4\u56de\u6587\u4ef6\u540d\uff0c\u65b9\u4fbf\u540e\u7eed\u90ae\u4ef6\u53d1\u9001<br> &nbsp; &nbsp;return filename<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.4 \u5b9a\u4e49\u5de5\u5177\uff1a\u53d1\u9001\u90ae\u4ef6<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">import smtplib<br>from email.message import EmailMessage<br>\u200b<br>@mcp.tool()<br>async def send_email_with_attachment(<br> &nbsp; &nbsp;to: str, <br> &nbsp; &nbsp;subject: str, <br> &nbsp; &nbsp;body: str, <br> &nbsp; &nbsp;filename: str<br>) -&gt; str:<br> &nbsp; &nbsp;\"\"\"<br> &nbsp;  \u53d1\u9001\u5e26\u9644\u4ef6\u7684\u90ae\u4ef6\u3002<br> &nbsp; &nbsp;<br> &nbsp;  \u53c2\u6570:<br> &nbsp; &nbsp; &nbsp;  to: \u6536\u4ef6\u4eba\u90ae\u7bb1\u5730\u5740<br> &nbsp; &nbsp; &nbsp;  subject: \u90ae\u4ef6\u6807\u9898<br> &nbsp; &nbsp; &nbsp;  body: \u90ae\u4ef6\u6b63\u6587<br> &nbsp; &nbsp; &nbsp;  filename: \u6587\u4ef6\u540d\uff08\u4e0d\u542b\u8def\u5f84\uff09<br> &nbsp; &nbsp;<br> &nbsp;  \u8fd4\u56de:<br> &nbsp; &nbsp; &nbsp;  \u90ae\u4ef6\u53d1\u9001\u72b6\u6001\u8bf4\u660e<br> &nbsp;  \"\"\"<br> &nbsp; &nbsp;# \u83b7\u53d6 SMTP \u914d\u7f6e<br> &nbsp; &nbsp;smtp_server = os.getenv(\"SMTP_SERVER\") &nbsp;# smtp.qq.com<br> &nbsp; &nbsp;smtp_port = int(os.getenv(\"SMTP_PORT\", 465))<br> &nbsp; &nbsp;sender_email = os.getenv(\"EMAIL_USER\")<br> &nbsp; &nbsp;sender_pass = os.getenv(\"EMAIL_PASS\") &nbsp;# QQ \u90ae\u7bb1\u6388\u6743\u7801<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u83b7\u53d6\u9644\u4ef6\u6587\u4ef6\u8def\u5f84<br> &nbsp; &nbsp;full_path = os.path.abspath(<br> &nbsp; &nbsp; &nbsp; &nbsp;os.path.join(\".\/sentiment_reports\", filename)<br> &nbsp;  )<br> &nbsp; &nbsp;if not os.path.exists(full_path):<br> &nbsp; &nbsp; &nbsp; &nbsp;return f\"\u274c \u9644\u4ef6\u8def\u5f84\u65e0\u6548\uff0c\u672a\u627e\u5230\u6587\u4ef6: {full_path}\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u521b\u5efa\u90ae\u4ef6<br> &nbsp; &nbsp;msg = EmailMessage()<br> &nbsp; &nbsp;msg[\"Subject\"] = subject<br> &nbsp; &nbsp;msg[\"From\"] = sender_email<br> &nbsp; &nbsp;msg[\"To\"] = to<br> &nbsp; &nbsp;msg.set_content(body)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u6dfb\u52a0\u9644\u4ef6<br> &nbsp; &nbsp;try:<br> &nbsp; &nbsp; &nbsp; &nbsp;with open(full_path, \"rb\") as f:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;file_data = f.read()<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;file_name = os.path.basename(full_path)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;msg.add_attachment(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;file_data, <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;maintype=\"application\", <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;subtype=\"octet-stream\", <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;filename=file_name<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp;except Exception as e:<br> &nbsp; &nbsp; &nbsp; &nbsp;return f\"\u274c \u9644\u4ef6\u8bfb\u53d6\u5931\u8d25: {str(e)}\"<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;# \u53d1\u9001\u90ae\u4ef6<br> &nbsp; &nbsp;try:<br> &nbsp; &nbsp; &nbsp; &nbsp;with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;server.login(sender_email, sender_pass)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;server.send_message(msg)<br> &nbsp; &nbsp; &nbsp; &nbsp;return f\"\u2705 \u90ae\u4ef6\u5df2\u6210\u529f\u53d1\u9001\u7ed9 {to}\uff0c\u9644\u4ef6: {full_path}\"<br> &nbsp; &nbsp;except Exception as e:<br> &nbsp; &nbsp; &nbsp; &nbsp;return f\"\u274c \u90ae\u4ef6\u53d1\u9001\u5931\u8d25: {str(e)}\"<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.5 \u542f\u52a8\u670d\u52a1\u5668<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">if __name__ == \"__main__\":<br> &nbsp; &nbsp;mcp.run(transport='stdio')<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udd16 \u7b2c\u4e8c\u6b65\uff1a\u521b\u5efa MCP Client<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MCP Client \u8d1f\u8d23\u4e0e AI \u6a21\u578b\u4ea4\u4e92\uff0c\u89c4\u5212\u5de5\u5177\u8c03\u7528\u94fe\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 \u521d\u59cb\u5316\u5ba2\u6237\u7aef<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">import asyncio<br>import os<br>import json<br>from typing import Optional, List<br>from contextlib import AsyncExitStack<br>from datetime import datetime<br>import re<br>from openai import OpenAI<br>from dotenv import load_dotenv<br>from mcp import ClientSession, StdioServerParameters<br>from mcp.client.stdio import stdio_client<br>\u200b<br>load_dotenv()<br>\u200b<br>class MCPClient:<br> &nbsp; &nbsp;def __init__(self):<br> &nbsp; &nbsp; &nbsp; &nbsp;self.exit_stack = AsyncExitStack()<br> &nbsp; &nbsp; &nbsp; &nbsp;self.openai_api_key = os.getenv(\"DASHSCOPE_API_KEY\")<br> &nbsp; &nbsp; &nbsp; &nbsp;self.base_url = os.getenv(\"BASE_URL\")<br> &nbsp; &nbsp; &nbsp; &nbsp;self.model = os.getenv(\"MODEL\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;if not self.openai_api_key:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;raise ValueError(\"\u274c \u672a\u627e\u5230 API Key\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;self.client = OpenAI(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;api_key=self.openai_api_key, <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;base_url=self.base_url<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;self.session: Optional[ClientSession] = None<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 \u8fde\u63a5\u5230 MCP Server<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"> &nbsp; &nbsp;async def connect_to_server(self, server_script_path: str):<br> &nbsp; &nbsp; &nbsp; &nbsp;\"\"\"\u8fde\u63a5\u5230 MCP \u670d\u52a1\u5668\"\"\"<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u5224\u65ad\u670d\u52a1\u5668\u811a\u672c\u7c7b\u578b<br> &nbsp; &nbsp; &nbsp; &nbsp;is_python = server_script_path.endswith('.py')<br> &nbsp; &nbsp; &nbsp; &nbsp;is_js = server_script_path.endswith('.js')<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;if not (is_python or is_js):<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;raise ValueError(\"\u670d\u52a1\u5668\u811a\u672c\u5fc5\u987b\u662f .py \u6216 .js \u6587\u4ef6\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u786e\u5b9a\u542f\u52a8\u547d\u4ee4<br> &nbsp; &nbsp; &nbsp; &nbsp;command = \"python\" if is_python else \"node\"<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u6784\u9020\u670d\u52a1\u5668\u53c2\u6570<br> &nbsp; &nbsp; &nbsp; &nbsp;server_params = StdioServerParameters(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;command=command, <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;args=[server_script_path], <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;env=None<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u542f\u52a8\u670d\u52a1\u8fdb\u7a0b<br> &nbsp; &nbsp; &nbsp; &nbsp;stdio_transport = await self.exit_stack.enter_async_context(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;stdio_client(server_params)<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;self.stdio, self.write = stdio_transport<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u521b\u5efa\u4f1a\u8bdd<br> &nbsp; &nbsp; &nbsp; &nbsp;self.session = await self.exit_stack.enter_async_context(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ClientSession(self.stdio, self.write)<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u521d\u59cb\u5316\u4f1a\u8bdd<br> &nbsp; &nbsp; &nbsp; &nbsp;await self.session.initialize()<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u83b7\u53d6\u5de5\u5177\u5217\u8868<br> &nbsp; &nbsp; &nbsp; &nbsp;response = await self.session.list_tools()<br> &nbsp; &nbsp; &nbsp; &nbsp;tools = response.tools<br> &nbsp; &nbsp; &nbsp; &nbsp;print(\"\\n\u5df2\u8fde\u63a5\u5230\u670d\u52a1\u5668\uff0c\u652f\u6301\u4ee5\u4e0b\u5de5\u5177:\", <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  [tool.name for tool in tools])<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.3 \u5de5\u5177\u8c03\u7528\u89c4\u5212\uff08\u6838\u5fc3\u903b\u8f91\uff09<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u8fd9\u662f MCP Client \u7684\u6838\u5fc3\u529f\u80fd\u2014\u2014\u8ba9 AI \u6a21\u578b\u89c4\u5212\u5de5\u5177\u8c03\u7528\u94fe\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> &nbsp; &nbsp;async def plan_tool_usage(<br> &nbsp; &nbsp; &nbsp; &nbsp;self, <br> &nbsp; &nbsp; &nbsp; &nbsp;query: str, <br> &nbsp; &nbsp; &nbsp; &nbsp;tools: List[dict]<br> &nbsp;  ) -&gt; List[dict]:<br> &nbsp; &nbsp; &nbsp; &nbsp;\"\"\"\u4f7f\u7528 AI \u89c4\u5212\u5de5\u5177\u8c03\u7528\u94fe\"\"\"<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;print(\"\\n\ud83d\udce4 \u63d0\u4ea4\u7ed9\u5927\u6a21\u578b\u7684\u5de5\u5177\u5b9a\u4e49:\")<br> &nbsp; &nbsp; &nbsp; &nbsp;print(json.dumps(tools, ensure_ascii=False, indent=2))<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u6784\u9020\u5de5\u5177\u5217\u8868\u6587\u672c<br> &nbsp; &nbsp; &nbsp; &nbsp;tool_list_text = \"\\n\".join([<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f\"- {tool['function']['name']}: \"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f\"{tool['function']['description']}\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;for tool in tools<br> &nbsp; &nbsp; &nbsp;  ])<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u6784\u9020\u7cfb\u7edf\u63d0\u793a\u8bcd<br> &nbsp; &nbsp; &nbsp; &nbsp;system_prompt = {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"role\": \"system\",<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"content\": (<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"\u4f60\u662f\u4e00\u4e2a\u667a\u80fd\u4efb\u52a1\u89c4\u5212\u52a9\u624b\uff0c\u7528\u6237\u4f1a\u7ed9\u51fa\u81ea\u7136\u8bed\u8a00\u8bf7\u6c42\u3002\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"\u4f60\u53ea\u80fd\u4ece\u4ee5\u4e0b\u5de5\u5177\u4e2d\u9009\u62e9\uff08\u4e25\u683c\u4f7f\u7528\u5de5\u5177\u540d\u79f0\uff09\uff1a\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f\"{tool_list_text}\\n\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"**\u91cd\u8981\u89c4\u5219**\uff1a\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"1. \u5982\u679c\u7528\u6237\u8981\u6c42'\u641c\u7d22\u65b0\u95fb\u5e76\u5206\u6790\u5e76\u53d1\u9001\u90ae\u4ef6'\uff0c\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"\u4f60\u5fc5\u987b\u89c4\u52123\u4e2a\u6b65\u9aa4\uff1a\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\" &nbsp; \u6b65\u9aa41: search_google_news (\u641c\u7d22\u65b0\u95fb)\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\" &nbsp; \u6b65\u9aa42: analyze_sentiment (\u5206\u6790\u5e76\u751f\u6210\u6587\u4ef6)\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\" &nbsp; \u6b65\u9aa43: send_email_with_attachment (\u53d1\u9001\u90ae\u4ef6)\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"2. \u540e\u7eed\u6b65\u9aa4\u53ef\u4ee5\u4f7f\u7528 {{\u5de5\u5177\u540d}} \u6765\u5f15\u7528\u4e0a\u4e00\u6b65\u7684\u8f93\u51fa\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"3. analyze_sentiment \u8fd4\u56de\u7684\u662f\u6587\u4ef6\u540d\uff08\u4e0d\u542b\u8def\u5f84\uff09\uff0c\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"\u76f4\u63a5\u7528\u4e8e send_email_with_attachment \u7684 filename \u53c2\u6570\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"4. \u8fd4\u56de\u683c\u5f0f\u5fc5\u987b\u662f\u7eaf JSON \u6570\u7ec4\uff0c\u4e0d\u8981\u6dfb\u52a0\u4efb\u4f55\u89e3\u91ca\\n\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"\u793a\u4f8b\u8f93\u51fa\u683c\u5f0f\uff1a\\n\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'[\\n'<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'  {\"name\": \"search_google_news\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"arguments\": {\"keyword\": \"\u5c0f\u7c73SU7\"}},\\n'<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'  {\"name\": \"analyze_sentiment\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"arguments\": {\"text\": \"{{search_google_news}}\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"filename\": \"report.md\"}},\\n'<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'  {\"name\": \"send_email_with_attachment\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"arguments\": {\"to\": \"user@qq.com\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"subject\": \"\u62a5\u544a\", \"body\": \"\u8bf7\u67e5\u6536\", '<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'\"filename\": \"{{analyze_sentiment}}\"}}\\n'<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;']'<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp;  }<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u8c03\u7528 AI \u6a21\u578b\u89c4\u5212<br> &nbsp; &nbsp; &nbsp; &nbsp;planning_messages = [<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;system_prompt,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  {\"role\": \"user\", \"content\": query}<br> &nbsp; &nbsp; &nbsp;  ]<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;response = self.client.chat.completions.create(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;model=self.model,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;messages=planning_messages,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tools=tools,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_choice=\"none\"<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u63d0\u53d6 JSON \u5185\u5bb9<br> &nbsp; &nbsp; &nbsp; &nbsp;content = response.choices[0].message.content.strip()<br> &nbsp; &nbsp; &nbsp; &nbsp;match = re.search(r\"```(?:json)?\\s*([\\s\\S]+?)\\s*```\", content)<br> &nbsp; &nbsp; &nbsp; &nbsp;if match:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;json_text = match.group(1)<br> &nbsp; &nbsp; &nbsp; &nbsp;else:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;json_text = content<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u89e3\u6790\u5e76\u8fd4\u56de\u8ba1\u5212<br> &nbsp; &nbsp; &nbsp; &nbsp;try:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;plan = json.loads(json_text)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(f\"\\n\ud83d\udd27 AI \u751f\u6210\u7684\u5de5\u5177\u8c03\u7528\u8ba1\u5212:\")<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(json.dumps(plan, ensure_ascii=False, indent=2))<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return plan if isinstance(plan, list) else []<br> &nbsp; &nbsp; &nbsp; &nbsp;except Exception as e:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(f\"\u274c \u5de5\u5177\u8c03\u7528\u94fe\u89c4\u5212\u5931\u8d25: {e}\\n\u539f\u59cb\u8fd4\u56de: {content}\")<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return []<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.4 \u6267\u884c\u5de5\u5177\u8c03\u7528\u94fe<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"> &nbsp; &nbsp;async def process_query(self, query: str) -&gt; str:<br> &nbsp; &nbsp; &nbsp; &nbsp;\"\"\"\u5904\u7406\u7528\u6237\u67e5\u8be2\"\"\"<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u83b7\u53d6\u53ef\u7528\u5de5\u5177<br> &nbsp; &nbsp; &nbsp; &nbsp;response = await self.session.list_tools()<br> &nbsp; &nbsp; &nbsp; &nbsp;available_tools = [<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"type\": \"function\",<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"function\": {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"name\": tool.name,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"description\": tool.description,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"input_schema\": tool.inputSchema<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  }<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  } for tool in response.tools<br> &nbsp; &nbsp; &nbsp;  ]<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u751f\u6210\u6587\u4ef6\u540d\uff08\u7528\u4e8e\u5206\u6790\u62a5\u544a\uff09<br> &nbsp; &nbsp; &nbsp; &nbsp;keyword_match = re.search(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;r'(\u5173\u4e8e|\u5206\u6790|\u67e5\u8be2|\u641c\u7d22|\u67e5\u770b)([^\u7684\\s\uff0c\u3002\u3001\uff1f\\n]+)', <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;query<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;keyword = keyword_match.group(2) if keyword_match else \"\u5206\u6790\u5bf9\u8c61\"<br> &nbsp; &nbsp; &nbsp; &nbsp;safe_keyword = re.sub(r'[\\\\\/:*?\"&lt;&gt;|]', '', keyword)[:20]<br> &nbsp; &nbsp; &nbsp; &nbsp;timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')<br> &nbsp; &nbsp; &nbsp; &nbsp;md_filename = f\"sentiment_{safe_keyword}_{timestamp}.md\"<br> &nbsp; &nbsp; &nbsp; &nbsp;md_path = os.path.join(\".\/sentiment_reports\", md_filename)<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u5c06\u6587\u4ef6\u540d\u4fe1\u606f\u6ce8\u5165\u67e5\u8be2<br> &nbsp; &nbsp; &nbsp; &nbsp;query_with_filename = (<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f\"{query.strip()} \"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f\"[md_filename={md_filename}] [md_path={md_path}]\"<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u89c4\u5212\u5de5\u5177\u8c03\u7528\u94fe<br> &nbsp; &nbsp; &nbsp; &nbsp;tool_plan = await self.plan_tool_usage(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;query_with_filename, <br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;available_tools<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u6267\u884c\u5de5\u5177\u8c03\u7528<br> &nbsp; &nbsp; &nbsp; &nbsp;tool_outputs = {}<br> &nbsp; &nbsp; &nbsp; &nbsp;messages = [{\"role\": \"user\", \"content\": query_with_filename}]<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;for step in tool_plan:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_name = step[\"name\"]<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_args = step[\"arguments\"]<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u89e3\u6790\u5360\u4f4d\u7b26\uff08\u5982 {{search_google_news}}\uff09<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;for key, val in tool_args.items():<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if isinstance(val, str) and \\<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; val.startswith(\"{{\") and val.endswith(\"}}\"):<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ref_key = val.strip(\"{} \")<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;resolved_val = tool_outputs.get(ref_key, val)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_args[key] = resolved_val<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u6ce8\u5165\u6587\u4ef6\u540d<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if tool_name == \"analyze_sentiment\" and \\<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \"filename\" not in tool_args:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_args[\"filename\"] = md_filename<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if tool_name == \"send_email_with_attachment\" and \\<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \"attachment_path\" not in tool_args:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_args[\"attachment_path\"] = md_path<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u8c03\u7528\u5de5\u5177<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;result = await self.session.call_tool(tool_name, tool_args)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u4fdd\u5b58\u8f93\u51fa<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tool_outputs[tool_name] = result.content[0].text<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u5c06\u5de5\u5177\u8f93\u51fa\u6dfb\u52a0\u5230\u6d88\u606f\u6d41<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# \u6ce8\u610f\uff1a\u4f7f\u7528 assistant \u89d2\u8272\u800c\u975e tool\uff0c\u907f\u514d API \u9650\u5236<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;messages.append({<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"role\": \"assistant\",<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"content\": f\"[tool:{tool_name} \u8f93\u51fa]\\n{result.content[0].text}\"<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  })<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u751f\u6210\u6700\u7ec8\u56de\u590d<br> &nbsp; &nbsp; &nbsp; &nbsp;final_response = self.client.chat.completions.create(<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;model=self.model,<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;messages=messages<br> &nbsp; &nbsp; &nbsp;  )<br> &nbsp; &nbsp; &nbsp; &nbsp;final_output = final_response.choices[0].message.content<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;# \u4fdd\u5b58\u5bf9\u8bdd\u8bb0\u5f55<br> &nbsp; &nbsp; &nbsp; &nbsp;def clean_filename(text: str) -&gt; str:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;text = text.strip()<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;text = re.sub(r'[\\\\\/:*?\"&lt;&gt;|]', '', text)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return text[:50]<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;safe_filename = clean_filename(query)<br> &nbsp; &nbsp; &nbsp; &nbsp;timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')<br> &nbsp; &nbsp; &nbsp; &nbsp;filename = f\"{safe_filename}_{timestamp}.txt\"<br> &nbsp; &nbsp; &nbsp; &nbsp;output_dir = \".\/llm_outputs\"<br> &nbsp; &nbsp; &nbsp; &nbsp;os.makedirs(output_dir, exist_ok=True)<br> &nbsp; &nbsp; &nbsp; &nbsp;file_path = os.path.join(output_dir, filename)<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;with open(file_path, \"w\", encoding=\"utf-8\") as f:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f.write(f\"\ud83d\udde3 \u7528\u6237\u63d0\u95ee\uff1a{query}\\n\\n\")<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;f.write(f\"\ud83e\udd16 \u6a21\u578b\u56de\u590d\uff1a\\n{final_output}\\n\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;print(f\"\ud83d\udcc4 \u5bf9\u8bdd\u8bb0\u5f55\u5df2\u4fdd\u5b58\u4e3a\uff1a{file_path}\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;return final_output<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.5 \u4ea4\u4e92\u5faa\u73af<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"> &nbsp; &nbsp;async def chat_loop(self):<br> &nbsp; &nbsp; &nbsp; &nbsp;\"\"\"\u4e3b\u4ea4\u4e92\u5faa\u73af\"\"\"<br> &nbsp; &nbsp; &nbsp; &nbsp;print(\"\\n\ud83e\udd16 MCP \u5ba2\u6237\u7aef\u5df2\u542f\u52a8\uff01\u8f93\u5165 'quit' \u9000\u51fa\")<br> &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp;while True:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;try:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;query = input(\"\\n\u4f60: \").strip()<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if query.lower() == 'quit':<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;response = await self.process_query(query)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(f\"\\n\ud83e\udd16 AI: {response}\")<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;except Exception as e:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(f\"\\n\u26a0\ufe0f \u53d1\u751f\u9519\u8bef: {str(e)}\")<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;async def cleanup(self):<br> &nbsp; &nbsp; &nbsp; &nbsp;\"\"\"\u6e05\u7406\u8d44\u6e90\"\"\"<br> &nbsp; &nbsp; &nbsp; &nbsp;await self.exit_stack.aclose()<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.6 \u4e3b\u5165\u53e3<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">async def main():<br> &nbsp; &nbsp;server_script_path = os.path.join(<br> &nbsp; &nbsp; &nbsp; &nbsp;os.path.dirname(__file__), <br> &nbsp; &nbsp; &nbsp; &nbsp;\"server.py\"<br> &nbsp;  )<br> &nbsp; &nbsp;client = MCPClient()<br> &nbsp; &nbsp;try:<br> &nbsp; &nbsp; &nbsp; &nbsp;await client.connect_to_server(server_script_path)<br> &nbsp; &nbsp; &nbsp; &nbsp;await client.chat_loop()<br> &nbsp; &nbsp;finally:<br> &nbsp; &nbsp; &nbsp; &nbsp;await client.cleanup()<br>\u200b<br>if __name__ == \"__main__\":<br> &nbsp; &nbsp;asyncio.run(main())<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2699\ufe0f \u7b2c\u4e09\u6b65\uff1a\u73af\u5883\u914d\u7f6e<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 \u521b\u5efa <code>.env<\/code> \u6587\u4ef6<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"># AI \u6a21\u578b\u914d\u7f6e\uff08\u901a\u4e49\u5343\u95ee\uff09<br>BASE_URL=https:\/\/dashscope.aliyuncs.com\/compatible-mode\/v1<br>MODEL=qwen-plus<br>DASHSCOPE_API_KEY=sk-your-api-key-here<br>\u200b<br># \u65b0\u95fb\u641c\u7d22 API<br>SERPER_API_KEY=your-serper-api-key<br>\u200b<br># QQ \u90ae\u7bb1 SMTP \u914d\u7f6e<br>SMTP_SERVER=smtp.qq.com<br>SMTP_PORT=465<br>EMAIL_USER=your-email@qq.com<br>EMAIL_PASS=your-authorization-code<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3.2 \u83b7\u53d6 QQ \u90ae\u7bb1\u6388\u6743\u7801<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u767b\u5f55 <a href=\"https:\/\/mail.qq.com\/\">QQ \u90ae\u7bb1\u7f51\u9875\u7248<\/a><\/li>\n\n\n\n<li>\u8bbe\u7f6e \u2192 \u8d26\u6237 \u2192 \u5f00\u542f <strong>SMTP \u670d\u52a1<\/strong><\/li>\n\n\n\n<li>\u751f\u6210\u6388\u6743\u7801\uff0816\u4f4d\u5b57\u7b26\uff09<\/li>\n\n\n\n<li>\u5c06\u6388\u6743\u7801\u586b\u5165 <code>EMAIL_PASS<\/code><\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">3.3 \u5b89\u88c5\u4f9d\u8d56<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">pip install mcp openai python-dotenv httpx<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 \u7b2c\u56db\u6b65\uff1a\u8fd0\u884c\u7cfb\u7edf<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 \u542f\u52a8\u5ba2\u6237\u7aef<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">python client.py<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4.2 \u4f7f\u7528\u793a\u4f8b<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">\u4f60: \u5206\u6790\u4e00\u4e0b\u5c0f\u7c73SU7\u8fd1\u671f\u7684\u70ed\u70b9\u65b0\u95fb\uff0c\u5e76\u53d1\u9001\u90ae\u4ef6\u7ed9 user@qq.com<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4.3 \u6267\u884c\u6d41\u7a0b<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">\ud83d\udce4 \u63d0\u4ea4\u7ed9\u5927\u6a21\u578b\u7684\u5de5\u5177\u5b9a\u4e49<br>\ud83d\udd27 AI \u751f\u6210\u7684\u5de5\u5177\u8c03\u7528\u8ba1\u5212:<br>[<br>  {<br> &nbsp;  \"name\": \"search_google_news\",<br> &nbsp;  \"arguments\": {\"keyword\": \"\u5c0f\u7c73SU7\"}<br>  },<br>  {<br> &nbsp;  \"name\": \"analyze_sentiment\",<br> &nbsp;  \"arguments\": {<br> &nbsp; &nbsp;  \"text\": \"{{search_google_news}}\", <br> &nbsp; &nbsp;  \"filename\": \"sentiment_\u5c0f\u7c73su7_20251113_213000.md\"<br> &nbsp;  }<br>  },<br>  {<br> &nbsp;  \"name\": \"send_email_with_attachment\",<br> &nbsp;  \"arguments\": {<br> &nbsp; &nbsp;  \"to\": \"user@qq.com\",<br> &nbsp; &nbsp;  \"subject\": \"\u5c0f\u7c73SU7\u8fd1\u671f\u70ed\u70b9\u65b0\u95fb\u60c5\u611f\u5206\u6790\u62a5\u544a\",<br> &nbsp; &nbsp;  \"body\": \"\u60a8\u597d\uff0c\u8fd9\u662f\u5206\u6790\u62a5\u544a\uff0c\u8bf7\u67e5\u6536\u3002\",<br> &nbsp; &nbsp;  \"filename\": \"{{analyze_sentiment}}\"<br> &nbsp;  }<br>  }<br>]<br>\u200b<br>\u2705 \u5df2\u83b7\u53d6\u65b0\u95fb...<br>\u2705 \u5df2\u751f\u6210\u5206\u6790\u62a5\u544a...<br>\u2705 \u90ae\u4ef6\u5df2\u6210\u529f\u53d1\u9001...<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf93 \u6838\u5fc3\u77e5\u8bc6\u70b9\u603b\u7ed3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. MCP Server \u8bbe\u8ba1\u8981\u70b9<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 \u4f7f\u7528 <code>@mcp.tool()<\/code> \u88c5\u9970\u5668\u5b9a\u4e49\u5de5\u5177<\/li>\n\n\n\n<li>\u2705 \u63d0\u4f9b\u6e05\u6670\u7684 docstring\uff08\u4f1a\u81ea\u52a8\u4f5c\u4e3a\u5de5\u5177\u63cf\u8ff0\uff09<\/li>\n\n\n\n<li>\u2705 \u53c2\u6570\u7c7b\u578b\u6ce8\u89e3\u5fc5\u987b\u51c6\u786e<\/li>\n\n\n\n<li>\u2705 \u8fd4\u56de\u503c\u5e94\u8be5\u662f\u5b57\u7b26\u4e32\uff08\u4fbf\u4e8e AI \u7406\u89e3\uff09<\/li>\n\n\n\n<li>\u2705 \u4f7f\u7528 <code>mcp.run(transport='stdio')<\/code> \u542f\u52a8<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. MCP Client \u8bbe\u8ba1\u8981\u70b9<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 \u901a\u8fc7 <code>StdioServerParameters<\/code> \u8fde\u63a5 Server<\/li>\n\n\n\n<li>\u2705 \u4f7f\u7528 <code>ClientSession<\/code> \u7ba1\u7406\u4f1a\u8bdd<\/li>\n\n\n\n<li>\u2705 \u8c03\u7528 <code>list_tools()<\/code> \u83b7\u53d6\u53ef\u7528\u5de5\u5177<\/li>\n\n\n\n<li>\u2705 \u4f7f\u7528 AI \u6a21\u578b\u89c4\u5212\u5de5\u5177\u8c03\u7528\u94fe<\/li>\n\n\n\n<li>\u2705 \u5904\u7406\u5de5\u5177\u95f4\u7684\u6570\u636e\u4f20\u9012\uff08\u5982 <code>{{tool_name}}<\/code>\uff09<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u5de5\u5177\u8c03\u7528\u94fe\u89c4\u5212\u6280\u5de7<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u5173\u952e\u70b9<\/strong>\uff1a\u901a\u8fc7\u7cbe\u5fc3\u8bbe\u8ba1\u7684 system prompt\uff0c\u8ba9 AI \u7406\u89e3\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u53ef\u7528\u5de5\u5177\u53ca\u5176\u529f\u80fd<\/li>\n\n\n\n<li>\u5de5\u5177\u8c03\u7528\u7684\u987a\u5e8f\u903b\u8f91<\/li>\n\n\n\n<li>\u6570\u636e\u5982\u4f55\u5728\u5de5\u5177\u95f4\u4f20\u9012<\/li>\n\n\n\n<li>\u8fd4\u56de\u683c\u5f0f\uff08JSON \u6570\u7ec4\uff09<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">4. \u5e38\u89c1\u95ee\u9898\u4e0e\u89e3\u51b3\u65b9\u6848<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u95ee\u9898<\/th><th>\u539f\u56e0<\/th><th>\u89e3\u51b3\u65b9\u6848<\/th><\/tr><\/thead><tbody><tr><td><code>role='tool'<\/code> \u9519\u8bef<\/td><td>API \u8981\u6c42 tool \u6d88\u606f\u5fc5\u987b\u6709\u5bf9\u5e94 tool_calls<\/td><td>\u6539\u7528 <code>role='assistant'<\/code> \u5e76\u6807\u6ce8\u5de5\u5177\u540d<\/td><\/tr><tr><td>SMTP \u8fde\u63a5\u8d85\u65f6<\/td><td>\u670d\u52a1\u5668\u5730\u5740\u9519\u8bef<\/td><td>\u786e\u4fdd\u4f7f\u7528 <code>smtp.qq.com<\/code> \u800c\u975e <code>qq.com<\/code><\/td><\/tr><tr><td>\u5de5\u5177\u672a\u88ab\u8c03\u7528<\/td><td>AI \u6ca1\u6709\u7406\u89e3\u4efb\u52a1<\/td><td>\u5f3a\u5316 system prompt\uff0c\u63d0\u4f9b\u793a\u4f8b<\/td><\/tr><tr><td>\u6587\u4ef6\u8def\u5f84\u95ee\u9898<\/td><td>\u5de5\u5177\u95f4\u8def\u5f84\u683c\u5f0f\u4e0d\u4e00\u81f4<\/td><td>\u7edf\u4e00\u4f7f\u7528\u6587\u4ef6\u540d\uff08\u4e0d\u542b\u8def\u5f84\uff09<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd25 \u8fdb\u9636\u6269\u5c55<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u6dfb\u52a0\u66f4\u591a\u5de5\u5177<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">@mcp.tool()<br>async def generate_chart(data: str) -&gt; str:<br> &nbsp; &nbsp;\"\"\"\u6839\u636e\u6570\u636e\u751f\u6210\u56fe\u8868\"\"\"<br> &nbsp; &nbsp;# \u4f7f\u7528 matplotlib \u751f\u6210\u56fe\u8868<br> &nbsp; &nbsp;pass<br>\u200b<br>@mcp.tool()<br>async def translate_text(text: str, target_lang: str) -&gt; str:<br> &nbsp; &nbsp;\"\"\"\u7ffb\u8bd1\u6587\u672c\"\"\"<br> &nbsp; &nbsp;# \u8c03\u7528\u7ffb\u8bd1 API<br> &nbsp; &nbsp;pass<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u652f\u6301\u6d41\u5f0f\u54cd\u5e94<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">async def stream_process_query(self, query: str):<br> &nbsp; &nbsp;\"\"\"\u6d41\u5f0f\u5904\u7406\u67e5\u8be2\"\"\"<br> &nbsp; &nbsp;async for chunk in self.client.chat.completions.create(<br> &nbsp; &nbsp; &nbsp; &nbsp;model=self.model,<br> &nbsp; &nbsp; &nbsp; &nbsp;messages=messages,<br> &nbsp; &nbsp; &nbsp; &nbsp;stream=True<br> &nbsp;  ):<br> &nbsp; &nbsp; &nbsp; &nbsp;print(chunk.choices[0].delta.content, end=\"\")<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u6dfb\u52a0\u9519\u8bef\u91cd\u8bd5\u673a\u5236<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">from tenacity import retry, stop_after_attempt, wait_exponential<br>\u200b<br>@retry(<br> &nbsp; &nbsp;stop=stop_after_attempt(3),<br> &nbsp; &nbsp;wait=wait_exponential(multiplier=1, min=4, max=10)<br>)<br>async def call_tool_with_retry(self, tool_name, tool_args):<br> &nbsp; &nbsp;\"\"\"\u5e26\u91cd\u8bd5\u7684\u5de5\u5177\u8c03\u7528\"\"\"<br> &nbsp; &nbsp;return await self.session.call_tool(tool_name, tool_args)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. \u5de5\u5177\u6743\u9650\u63a7\u5236<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">class ToolPermissionManager:<br> &nbsp; &nbsp;def __init__(self):<br> &nbsp; &nbsp; &nbsp; &nbsp;self.allowed_tools = {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"user1\": [\"search_google_news\", \"analyze_sentiment\"],<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\"admin\": [\"*\"] &nbsp;# \u6240\u6709\u5de5\u5177<br> &nbsp; &nbsp; &nbsp;  }<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;def check_permission(self, user: str, tool_name: str) -&gt; bool:<br> &nbsp; &nbsp; &nbsp; &nbsp;if \"*\" in self.allowed_tools.get(user, []):<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return True<br> &nbsp; &nbsp; &nbsp; &nbsp;return tool_name in self.allowed_tools.get(user, [])<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcca \u6027\u80fd\u4f18\u5316\u5efa\u8bae<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u5e76\u884c\u6267\u884c\u72ec\u7acb\u5de5\u5177<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">import asyncio<br>\u200b<br># \u5982\u679c\u5de5\u5177\u95f4\u65e0\u4f9d\u8d56\uff0c\u53ef\u5e76\u884c\u6267\u884c<br>results = await asyncio.gather(<br> &nbsp; &nbsp;self.session.call_tool(\"tool1\", args1),<br> &nbsp; &nbsp;self.session.call_tool(\"tool2\", args2)<br>)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u7f13\u5b58\u5de5\u5177\u7ed3\u679c<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">from functools import lru_cache<br>\u200b<br>@lru_cache(maxsize=100)<br>def get_cached_news(keyword: str):<br> &nbsp; &nbsp;# \u7f13\u5b58\u65b0\u95fb\u641c\u7d22\u7ed3\u679c<br> &nbsp; &nbsp;pass<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u9650\u6d41\u63a7\u5236<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">from aiolimiter import AsyncLimiter<br>\u200b<br>limiter = AsyncLimiter(10, 60) &nbsp;# \u6bcf\u5206\u949f\u6700\u591a10\u6b21<br>\u200b<br>async def rate_limited_call(self, tool_name, args):<br> &nbsp; &nbsp;async with limiter:<br> &nbsp; &nbsp; &nbsp; &nbsp;return await self.session.call_tool(tool_name, args)<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf \u6700\u4f73\u5b9e\u8df5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 DO\uff08\u63a8\u8350\u505a\u6cd5\uff09<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6e05\u6670\u7684\u5de5\u5177\u547d\u540d<\/strong>\uff1a<code>search_google_news<\/code> \u800c\u975e <code>tool1<\/code><\/li>\n\n\n\n<li><strong>\u8be6\u7ec6\u7684 docstring<\/strong>\uff1a\u5305\u542b\u53c2\u6570\u8bf4\u660e\u3001\u8fd4\u56de\u503c\u8bf4\u660e<\/li>\n\n\n\n<li><strong>\u7edf\u4e00\u7684\u9519\u8bef\u5904\u7406<\/strong>\uff1a\u8fd4\u56de\u5e26 \u274c \u7684\u9519\u8bef\u4fe1\u606f<\/li>\n\n\n\n<li><strong>\u65e5\u5fd7\u8bb0\u5f55<\/strong>\uff1a\u8bb0\u5f55\u6240\u6709\u5de5\u5177\u8c03\u7528\u548c\u7ed3\u679c<\/li>\n\n\n\n<li><strong>\u53c2\u6570\u9a8c\u8bc1<\/strong>\uff1a\u5728\u5de5\u5177\u5185\u90e8\u9a8c\u8bc1\u53c2\u6570\u5408\u6cd5\u6027<\/li>\n\n\n\n<li><strong>\u5e42\u7b49\u6027\u8bbe\u8ba1<\/strong>\uff1a\u5de5\u5177\u53ef\u88ab\u91cd\u590d\u8c03\u7528\u800c\u4e0d\u4ea7\u751f\u526f\u4f5c\u7528<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u274c DON&#8217;T\uff08\u907f\u514d\u505a\u6cd5\uff09<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u5de5\u5177\u529f\u80fd\u8fc7\u4e8e\u590d\u6742<\/strong>\uff1a\u4e00\u4e2a\u5de5\u5177\u53ea\u505a\u4e00\u4ef6\u4e8b<\/li>\n\n\n\n<li><strong>\u8fd4\u56de\u590d\u6742\u5bf9\u8c61<\/strong>\uff1a\u59cb\u7ec8\u8fd4\u56de\u5b57\u7b26\u4e32\u6216\u7b80\u5355 JSON<\/li>\n\n\n\n<li><strong>\u786c\u7f16\u7801\u8def\u5f84<\/strong>\uff1a\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u6216\u76f8\u5bf9\u8def\u5f84<\/li>\n\n\n\n<li><strong>\u5ffd\u7565\u5f02\u5e38\u5904\u7406<\/strong>\uff1a\u6240\u6709\u5f02\u5e38\u90fd\u5e94\u88ab\u6355\u83b7\u5e76\u8fd4\u56de\u53cb\u597d\u4fe1\u606f<\/li>\n\n\n\n<li><strong>\u5de5\u5177\u95f4\u5f3a\u8026\u5408<\/strong>\uff1a\u5de5\u5177\u5e94\u72ec\u7acb\u53ef\u6d4b\u8bd5<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddea \u6d4b\u8bd5\u5efa\u8bae<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u5355\u5143\u6d4b\u8bd5\u793a\u4f8b<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">import pytest<br>from server import search_google_news<br>\u200b<br>@pytest.mark.asyncio<br>async def test_search_google_news():<br> &nbsp; &nbsp;result = await search_google_news(\"\u6d4b\u8bd5\u5173\u952e\u8bcd\")<br> &nbsp; &nbsp;assert \"\u2705\" in result or \"\u274c\" in result<br>\u200b<br>@pytest.mark.asyncio<br>async def test_analyze_sentiment():<br> &nbsp; &nbsp;text = \"\u8fd9\u662f\u4e00\u6761\u6b63\u9762\u65b0\u95fb\"<br> &nbsp; &nbsp;filename = \"test_report.md\"<br> &nbsp; &nbsp;result = await analyze_sentiment(text, filename)<br> &nbsp; &nbsp;assert result == filename<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u96c6\u6210\u6d4b\u8bd5<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">@pytest.mark.asyncio<br>async def test_full_workflow():<br> &nbsp; &nbsp;client = MCPClient()<br> &nbsp; &nbsp;await client.connect_to_server(\"server.py\")<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;query = \"\u641c\u7d22\u5e76\u5206\u6790\u6d4b\u8bd5\u65b0\u95fb\"<br> &nbsp; &nbsp;response = await client.process_query(query)<br> &nbsp; &nbsp;<br> &nbsp; &nbsp;assert \"\u2705\" in response<br> &nbsp; &nbsp;await client.cleanup()<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcda \u53c2\u8003\u8d44\u6e90<\/h2>\n\n\n\n<div class=\"wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-8f761849 wp-block-group-is-layout-flex\">\n<ul class=\"wp-block-list\">\n<li><strong>MCP \u5b98\u65b9\u6587\u6863<\/strong>: <a href=\"https:\/\/modelcontextprotocol.io\/\">https:\/\/modelcontextprotocol.io\/<\/a><\/li>\n\n\n\n<li><strong>FastMCP GitHub<\/strong>: <a href=\"https:\/\/github.com\/jlowin\/fastmcp\">https:\/\/github.com\/jlowin\/fastmcp<\/a><\/li>\n\n\n\n<li><strong>\u901a\u4e49\u5343\u95ee\u6587\u6863<\/strong>: <a href=\"https:\/\/help.aliyun.com\/zh\/model-studio\/\">https:\/\/help.aliyun.com\/zh\/model-studio\/<\/a><\/li>\n\n\n\n<li><strong>Serper API<\/strong>: <a href=\"https:\/\/serper.dev\/\">https:\/\/serper.dev\/<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"http:\/\/81.68.81.149\/index.php\/2025\/11\/13\/mcp-%e6%9c%8d%e5%8a%a1%e5%bc%80%e5%8f%91%e5%ae%8c%e6%95%b4%e6%8c%87%e5%8d%97%ef%bc%9a%e4%bb%8e%e9%9b%b6%e6%9e%84%e5%bb%ba%e6%99%ba%e8%83%bd%e6%96%b0%e9%97%bb%e5%88%86%e6%9e%90%e7%b3%bb%e7%bb%9f\/\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/p>\n<\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udca1 \u603b\u7ed3<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u901a\u8fc7\u672c\u6587\uff0c\u6211\u4eec\u5b8c\u6574\u5b9e\u73b0\u4e86\u4e00\u4e2a <strong>MCP \u667a\u80fd\u65b0\u95fb\u5206\u6790\u7cfb\u7edf<\/strong>\uff0c\u6db5\u76d6\u4e86\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u2705 MCP Server \u7684\u5de5\u5177\u5b9a\u4e49\u4e0e\u5b9e\u73b0<\/li>\n\n\n\n<li>\u2705 MCP Client \u7684\u5de5\u5177\u8c03\u7528\u89c4\u5212<\/li>\n\n\n\n<li>\u2705 AI \u6a21\u578b\u4e0e\u5de5\u5177\u7684\u534f\u540c\u5de5\u4f5c<\/li>\n\n\n\n<li>\u2705 \u5b8c\u6574\u7684\u9519\u8bef\u5904\u7406\u4e0e\u65e5\u5fd7\u8bb0\u5f55<\/li>\n\n\n\n<li>\u2705 \u5b9e\u7528\u7684\u90ae\u4ef6\u53d1\u9001\u529f\u80fd<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">MCP \u6846\u67b6\u7684\u6838\u5fc3\u4ef7\u503c\u5728\u4e8e\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd0c <strong>\u6807\u51c6\u5316<\/strong>\uff1a\u7edf\u4e00\u7684\u5de5\u5177\u8c03\u7528\u534f\u8bae<\/li>\n\n\n\n<li>\ud83e\udde9 <strong>\u53ef\u7ec4\u5408<\/strong>\uff1a\u5de5\u5177\u53ef\u7075\u6d3b\u7ec4\u5408\u6210\u5de5\u4f5c\u6d41<\/li>\n\n\n\n<li>\ud83e\udd16 <strong>AI \u9a71\u52a8<\/strong>\uff1a\u7531 AI \u81ea\u4e3b\u89c4\u5212\u6267\u884c\u8def\u5f84<\/li>\n\n\n\n<li>\ud83d\ude80 <strong>\u53ef\u6269\u5c55<\/strong>\uff1a\u8f7b\u677e\u6dfb\u52a0\u65b0\u5de5\u5177<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u5e0c\u671b\u8fd9\u7bc7\u6587\u7ae0\u80fd\u5e2e\u52a9\u4f60\u5feb\u901f\u4e0a\u624b MCP \u5f00\u53d1\uff01\u5982\u6709\u95ee\u9898\uff0c\u6b22\u8fce\u4ea4\u6d41\u8ba8\u8bba\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u4f5c\u8005<\/strong>\uff1aYumo_Neeko  <strong>\u65e5\u671f<\/strong>\uff1a2025\u5e7411\u670813\u65e5 <strong>\u6807\u7b7e<\/strong>\uff1a#MCP #AI #Python #\u81ea\u52a8\u5316<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcdd \u9644\u5f55\uff1a\u5b8c\u6574\u9879\u76ee\u7ed3\u6784<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">mcp-project\/<br>\u251c\u2500\u2500 .env &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  # \u73af\u5883\u53d8\u91cf\u914d\u7f6e<br>\u251c\u2500\u2500 .gitignore &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # Git \u5ffd\u7565\u6587\u4ef6<br>\u251c\u2500\u2500 pyproject.toml &nbsp; &nbsp; &nbsp; &nbsp; # \u9879\u76ee\u4f9d\u8d56<br>\u251c\u2500\u2500 README.md &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  # \u9879\u76ee\u8bf4\u660e<br>\u251c\u2500\u2500 client.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  # MCP \u5ba2\u6237\u7aef<br>\u251c\u2500\u2500 server.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  # MCP \u670d\u52a1\u5668<br>\u251c\u2500\u2500 google_news\/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # \u65b0\u95fb\u6570\u636e\u76ee\u5f55<br>\u251c\u2500\u2500 sentiment_reports\/ &nbsp; &nbsp; # \u5206\u6790\u62a5\u544a\u76ee\u5f55<br>\u2514\u2500\u2500 llm_outputs\/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # \u5bf9\u8bdd\u8bb0\u5f55\u76ee\u5f55<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Happy Coding! \ud83c\udf89<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udcd6 \u524d\u8a00 Model Context Protocol (MCP) \u662f\u4e00\u4e2a\u5f3a\u5927\u7684\u6846\u67b6\uff0c\u5141\u8bb8 AI \u6a21\u578b\u901a\u8fc7\u6807\u51c6 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":64,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-61","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-learn"],"_links":{"self":[{"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/posts\/61","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/comments?post=61"}],"version-history":[{"count":3,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/posts\/61\/revisions"}],"predecessor-version":[{"id":66,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/posts\/61\/revisions\/66"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/media\/64"}],"wp:attachment":[{"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/media?parent=61"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/categories?post=61"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/81.68.81.149\/index.php\/wp-json\/wp\/v2\/tags?post=61"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}