[Langchain网页抓取与天气查询实战]MCP篇
MCP (Model Context Protocol) 网页抓取与天气查询实战 项目概述项目内容功能MCP 服务端提供工具客户端由 LLM 自动判断调用哪个工具工具fetch_webpage(网页抓取)、get_weather(天气查询)通信协议SSE (Server-Sent Events)️ 架构设计┌─────────────────────────────────────────────────────────────┐ │ 用户输入 │ │ 上海天气怎么样 │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MCP Web 客户端 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ LLM (qwen-plus) │ │ │ │ tool_choiceauto │ │ │ │ 自动判断是否需要调用工具 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ ▼ ▼ │ │ [无需工具直接回答] [需要工具调用 MCP] │ └─────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MCP Web 服务端 │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ fetch_webpage│ │ get_weather │ │ │ │ 网页抓取工具 │ │ 天气查询工具 │ │ │ └──────────────┘ └──────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ httpx.get() OpenWeather API │ │ 任意网页 2d49a9ae... │ └─────────────────────────────────────────────────────────────┘ 文件清单文件说明McpServer.pyMCP 服务端提供两个工具McpClient.pyMCP 客户端LLM 自动路由 核心实现1. MCP 服务端 (McpServer.py)1.1 极简 MCP 服务类class MCPWebServer: 极简版 MCP 服务类支持 SSE 传输协议 def __init__(self, name: str, host: str, port: int): self.name name self.host host self.port port self._tools {} # 存储注册的工具函数 def tool(self): 实现 mcp.tool() 装饰器 def decorator(func): self._tools[func.__name__] func return func return decorator def run(self, transport: str): 启动 MCP 服务 logger.info(f启动 MCP Web 服务器监听 http://{self.host}:{self.port}/sse) self._keep_alive()设计要点无第三方依赖纯原生实现适配 Python 3.13mcp.tool()装饰器注册工具函数_tools字典存储所有已注册的工具1.2 工具1: 网页抓取 (fetch_webpage)mcp.tool() def fetch_webpage(url: str, max_length: int 5000) - str: 抓取指定网页的文本内容。 参数: url: 网页的完整 URL 地址 max_length: 最大抓取字符数默认 5000 返回: 网页的文本内容已去除 HTML 标签 headers {User-Agent: Mozilla/5.0...} resp httpx.get(url, headersheaders, timeout15) # HTML 标签去除 html re.sub(rscript[^]*.*?/script, , html, flagsre.DOTALL) html re.sub(rstyle[^]*.*?/style, , html, flagsre.DOTALL) text re.sub(r[^], , html) # 截断超长内容 if len(text) max_length: text text[:max_length] ...[内容已截断] return json.dumps({url: url, content: text, status: success})功能特点✅ 自动去除script和style标签✅ 移除所有 HTML 标签✅ 清理多余空白字符✅ 自动截断超长内容❌ 无法处理 JavaScript 动态渲染的页面1.3 工具2: 天气查询 (get_weather)mcp.tool() def get_weather(city: str) - str: 查询指定城市的即时天气信息。 参数 city: 城市英文名如 Beijing 返回: OpenWeather API 的 JSON 字符串 api_key os.getenv(OPENWEATHER_API_KEY) if not api_key: api_key #################### # 备用默认值 params { q: city, appid: api_key, units: metric, lang: zh_cn } resp httpx.get(https://api.openweathermap.org/data/2.5/weather, paramsparams) return json.dumps(resp.json())API Key 优先级环境变量 OPENWEATHER_API_KEY 内置默认值2. MCP 客户端 (McpClient_WebFetch.py)2.1 MCP 客户端类# ---------------------- LLM 配置 ---------------------- # 使用阿里云 DashScope API client OpenAI( api_keyos.getenv(QWEN_API_KEY, os.getenv(aliQwen-api, )), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 ) MODEL_NAME qwen-plusclass MCPWebClient: MCP Web 服务客户端由 LLM 自动判断调用哪个工具 def __init__(self, mcp_instance): self.mcp_instance mcp_instance self.available_tools mcp_instance._tools logger.info(f已连接 MCP 服务可用工具: {list(self.available_tools.keys())}) def call_tool(self, tool_name: str, **kwargs): 调用指定工具 if tool_name not in self.available_tools: logger.error(f工具 {tool_name} 不存在) return None return self.available_tools[tool_name](**kwargs)2.2 动态构建 Tools Schemadef build_tools_schema(): 根据 MCP 注册的工具自动生成 OpenAI function calling 的 tools 参数 tools [] # 工具1: fetch_webpage tools.append({ type: function, function: { name: fetch_webpage, description: 抓取指定网页的文本内容..., parameters: { type: object, properties: { url: {type: string, description: 网页 URL}, max_length: {type: integer, default: 5000} }, required: [url] } } }) # 工具2: get_weather tools.append({ type: function, function: { name: get_weather, description: 查询指定城市的即时天气信息..., parameters: { type: object, properties: { city: {type: string, description: 城市英文名} }, required: [city] } } }) return tools2.3 核心对话逻辑 (三阶段)def chat(user_input: str, mcp_client: MCPWebClient): LLM 自动判断是否调用工具并返回最终回复 # 阶段1: LLM 判断 response client.chat.completions.create( modelqwen-plus, messages[{role: user, content: user_input}], toolsbuild_tools_schema(), tool_choiceauto # LLM 自动判断是否调用工具 ) # 阶段2: 执行工具 if message.tool_calls: for tool_call in message.tool_calls: tool_name tool_call.function.name tool_args json.loads(tool_call.function.arguments) # 执行 MCP 工具 tool_result mcp_client.call_tool(tool_name, **tool_args) # 把工具结果返回给 LLM messages.append({ role: tool, tool_call_id: tool_call.id, content: tool_result }) # 阶段3: 生成最终回复 final_response client.chat.completions.create( modelqwen-plus, messagesmessages ) return final_response.choices[0].message.content三阶段流程图用户输入 │ ▼ ┌───────────────────────────────────────┐ │ 阶段1: LLM 判断 │ │ tool_choiceauto │ │ 判断是否需要调用工具 │ └───────────────┬───────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ 是需要工具 否不需要工具 │ │ ▼ │ ┌───────────────────┐ │ │ 阶段2: 执行工具 │ │ │ mcp_client │ │ │ .call_tool() │ │ └─────────┬─────────┘ │ │ │ ▼ │ ┌───────────────────┐ │ │ 阶段3: 生成回复 │ │ │ LLM 根据工具结果 │ │ │ 生成最终回复 │ │ └─────────┬─────────┘ │ │ │ └──────┬──────┘ ▼ 最终回复 使用方法环境准备# 安装依赖 pip install loguru httpx openai python-dotenv # 或使用 uv uv pip install loguru httpx openai python-dotenv预期输出2026-05-05 09:00:00 | INFO | MCP Web 服务器 (提供网页抓取 天气查询) 2026-05-05 09:00:00 | INFO | 监听地址: http://127.0.0.1:8000/sse 2026-05-05 09:00:00 | INFO | 已注册工具: [fetch_webpage, get_weather]启动客户端# 终端2 python McpClient_WebFetch.py对话示例 MCP 智能客户端 (输入问题LLM 自动选择工具) 可用工具: fetch_webpage / get_weather 输入 quit 退出 你: 上海天气怎么样 [LLM 决定调用工具] get_weather({city: Shanghai}) [工具返回结果] {coord: {lon: 121.45, lat: 31.22}, weather: [{description: 多云}], main: {temp: 24.55...}} 助手: 上海目前天气为**多云**气温约 **24.6°C**体感温度约 **24.0°C** - 湿度37% - 气压1017 hPa - 风速3.94 m/s 你: 帮我抓取百度首页 [LLM 决定调用工具] fetch_webpage({url: https://www.baidu.com}) [工具返回结果] {url: https://www.baidu.com, content: , status: success} 助手: 已成功访问百度首页但网页内容未返回可能因反爬机制。 你: 你好 助手: 你好有什么我可以帮你的吗 你: quit 再见! 测试结果测试场景输入LLM 判断工具调用结果天气查询北京今天天气怎么样✅ 调用工具get_weather(cityBeijing)✅ 成功20.1°C 阴天气查询上海天气怎么样✅ 调用工具get_weather(cityShanghai)✅ 成功24.6°C 多云网页抓取帮我抓取百度首页✅ 调用工具fetch_webpage(url...)⚠️ 成功但内容为空普通对话你好❌ 不调用无✅ 直接回答⚠️ 已知问题1. 网页抓取返回空内容现象抓取百度等大型网站时返回content: 原因目标网站有反爬机制网页内容由 JavaScript 动态渲染简单的 HTML 解析无法获取动态内容解决方案对于动态网页考虑使用 Selenium/Playwright对于反爬网站考虑添加更多请求头或使用代理2. Python 3.13 兼容性问题现象McpServerByFastMCP.py使用 FastMCP 时报错ModuleNotFoundError: No module named pywintypes原因pywin32尚未适配 Python 3.13解决方案使用本项目的极简版 MCP 服务类不依赖 FastMCP 扩展方向1. 增加更多工具mcp.tool() def search_news(keyword: str) - str: 搜索新闻 # 实现搜索逻辑 pass mcp.tool() def translate(text: str, target_lang: str) - str: 翻译文本 # 实现翻译逻辑 pass2. 支持 STDIO 传输协议# 当前: SSE mcp.run(transportsse) # 扩展: STDIO mcp.run(transportstdio)3. 集成 LangChainfrom langchain.agents import initialize_agent, Tool from langchain.llms import OpenAI # 将 MCP 工具转换为 LangChain Tools tools [ Tool( namefetch_webpage, funcfetch_webpage, description抓取网页内容 ), Tool( nameget_weather, funcget_weather, description查询天气 ) ] # 使用 LangChain Agent agent initialize_agent(tools, llm, agentzero-shot-react-description) 总结本项目实现了一个完整的 MCP (Model Context Protocol) 示例组件实现服务端极简版 MCP 服务类无第三方依赖工具注册mcp.tool()装饰器模式客户端LLM 自动判断工具调用工具描述动态生成 OpenAI function calling schema通信协议SSE (Server-Sent Events)核心价值理解了 MCP 协议的基本工作原理掌握了 LLM Function Calling 的使用实现了 Tool 动态注册和调用机制为 LangChain Agent 开发打下基础