MCP详解
什么是 MCP
Model Context Protocol (MCP) 是一个开放协议,它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能。无论你是构建 AI 驱动的 IDE、改善 chat 交互,还是构建自定义的 AI 工作流,MCP 提供了一种标准化的方式,将 LLM 与它们所需的上下文连接起来。
目前,MCP 已经积累了足够的临界规模和动能,因此它被视为 2023-2025 年“代理开放标准”之争的潜在赢家。有人预计,按照当前的速度,MCP 将在 7 月超OpenAPI:
MCP 如何工作
通用架构
MCP 的核心是一个 client-server 架构,host 应用程序可以连接到多个服务器:
- MCP Hosts: 像 Claude Desktop、IDEs 或 AI 工具这样的程序,它们希望通过 MCP 访问资源
- MCP Clients: 维护与服务器 1:1 连接的协议客户端
- MCP Servers: 轻量级程序,通过标准化的 Model Context Protocol 暴露特定功能
- Local Resources: 你的计算机资源(数据库、文件、服务),MCP 服务器可以安全地访问这些资源
- Remote Resources: 通过互联网可用的资源(例如,通过 APIs),MCP 服务器可以连接到这些资源
MCP 客户端
MCP客户端是模型上下文协议(MCP)架构中的核心组件,负责建立和管理与MCP服务器的连接。它实现了协议的客户端部分,处理以下功能:
- 协议版本协商以确保与服务器的兼容性
- 能力协商以确定可用功能
- 消息传输和JSON-RPC通信
- 工具发现和执行
- 资源访问和管理
- 提示系统交互
- 可选功能如根目录管理和采样支持
MCP client 的工作流程如下:
- MCP client 首先从 MCP server 获取可用的工具列表。
- 将用户的查询连同工具描述通过 function calling 一起发送给 LLM。
- LLM 决定是否需要使用工具以及使用哪些工具。
- 如果需要使用工具,MCP client 会通过 MCP server 执行相应的工具调用。
- 工具调用的结果会被发送回 LLM。
- LLM 基于所有信息生成自然语言响应。
- 最后将响应展示给用户。
MCP 服务端
MCP服务器是模型上下文协议(MCP)架构中的基础组件,为客户端提供工具、资源和功能。它实现了协议的服务器端,负责:
- 暴露客户端可以发现和执行的工具
- 管理基于URI的资源访问模式
- 提供提示模板并处理提示请求
- 支持与客户端的能力协商
- 实现服务器端协议操作
- 管理并发客户端连接
- 提供结构化日志和通知
连接生命周期
1.初始化
- Client 发送包含协议版本和能力的 initialize 请求
- Server 以其协议版本和能力响应
- Client 发送 initialized 通知作为确认
- 开始正常消息交换
2. 消息交换
初始化后,支持以下模式:
- 请求-响应:客户端或服务器发送请求,另一方响应
- 通知:任一方发送单向消息
3.终止
任一方可以终止连接:
- 通过 close() 进行干净关闭
- 传输断开
- 错误条件
快速入门
SQLite 实现一个集中示例
- Claude Desktop 作为我们的 MCP 客户端
- 一个 SQLite MCP 服务器提供安全的数据库访问
- 你的本地 SQLite 数据库存储实际数据
SQLite MCP 服务器和你的本地 SQLite 数据库之间的通信完全发生在你的机器上 — 你的 SQLite 数据库不会暴露在互联网上。Model Context Protocol 确保 Claude Desktop 只能通过定义良好的接口执行批准的数据库作。这为你提供了一种安全的方式,让 Claude 分析和交互你的本地数据,同时完全控制它可以访问的内容。
前提条件
- macOS 或 Windows
- 安装最新版本的 Claude Desktop
- UV 0.4.18 或更高版本( 检查)uv --version
- Git( 检查)git --version
- SQLite( 检查)sqlite3 --version
例如安装(Windows):
# 使用 winget
winget install --id=astral-sh.uv -e
winget install git.git sqlite.sqlite
# 或直接下载:
# uv: https://docs.astral.sh/uv/
# Git: https://git-scm.com
# SQLite: https://www.sqlite.org/download.html
1.安装
创建一个简单Windows的 SQLite 数据库进行测试:
# 创建一个新的 SQLite 数据库
$sql = @'
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT,
price REAL
);
INSERT INTO products (name, price) VALUES
('Widget', 19.99),
('Gadget', 29.99),
('Gizmo', 39.99),
('Smart Watch', 199.99),
('Wireless Earbuds', 89.99),
('Portable Charger', 24.99),
('Bluetooth Speaker', 79.99),
('Phone Stand', 15.99),
('Laptop Sleeve', 34.99),
('Mini Drone', 299.99),
('LED Desk Lamp', 45.99),
('Keyboard', 129.99),
('Mouse Pad', 12.99),
('USB Hub', 49.99),
('Webcam', 69.99),
('Screen Protector', 9.99),
('Travel Adapter', 27.99),
('Gaming Headset', 159.99),
('Fitness Tracker', 119.99),
('Portable SSD', 179.99);
'@
cd ~
& sqlite3 test.db $sql
2.配置 Claude Desktop
在文本编辑器中打开 中的 Claude Desktop 应用配置。%APPDATA%\Claude\claude_desktop_config.json
例如,如果你安装了 VS Code:
code $env:AppData\Claude\claude_desktop_config.json\
添加以下配置(将 YOUR_USERNAME 替换为你的实际用户名):
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": [
"mcp-server-sqlite",
"--db-path",
"C:\\Users\\YOUR_USERNAME\\test.db"
]
}
}
}
这告诉 Claude Desktop:
- 有一个名为 “sqlite” 的 MCP 服务器
- 通过运行 启动它uvx mcp-server-sqlite
- 将其连接到你的测试数据库
保存文件,并重新启动 Claude Desktop。
3.测试
问题:
你能连接到我的 SQLite 数据库并告诉我有哪些产品及其价格吗?
Claude Desktop 将会:
- 连接到 SQLite MCP 服务器
- 查询你的本地数据库
- 格式化并展示结果
原理解析
背后发生了什么?
当你使用 MCP 与 Claude Desktop 交互时:
-
服务器发现:Claude Desktop 在启动时连接到你配置的 MCP 服务器
-
协议握手:当你询问数据时,Claude Desktop:
- 确定哪个 MCP 服务器可以提供帮助(在本例中是 sqlite)
- 通过协议协商能力
- 从 MCP 服务器请求数据或作
-
交互流程:
-
安全性:
- MCP 服务器仅暴露特定、受控的功能
- MCP 服务器在你的机器上本地运行,它们访问的资源不会暴露在互联网上
- Claude Desktop 需要用户确认以进行敏感作
Python 创建一个简单的 MCP 服务器
先决条件
- 您需要 Python 3.10 或更高版本:
python --version # Should be 3.10 or higher
- 通过 homebrew 安装 uv
brew install uv
uv --version # Should be 0.4.18 or higher
有关更多信息,请参阅 https://docs.astral.sh/uv/ - 使用 MCP 项目创建器创建新项目
uvx create-mcp-server --path weather_service
cd weather_service - 安装其他依赖项
uv add httpx python-dotenv
- 设置环境
OPENWEATHER_API_KEY=your-api-key-here #创造:.env
创建您的服务器
-
添加基本导入和设置
在weather_service/src/weather_service/server.py
import os import json import logging from datetime import datetime, timedelta from collections.abc import Sequence from functools import lru_cache from typing import Any import httpx import asyncio from dotenv import load_dotenv from mcp.server import Server from mcp.types import ( Resource, Tool, TextContent, ImageContent, EmbeddedResource, LoggingLevel ) from pydantic import AnyUrl # Load environment variables load_dotenv() # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("weather-server") # API configuration API_KEY = os.getenv("OPENWEATHER_API_KEY") if not API_KEY: raise ValueError("OPENWEATHER_API_KEY environment variable required") API_BASE_URL = "http://api.openweathermap.org/data/2.5" DEFAULT_CITY = "London" CURRENT_WEATHER_ENDPOINT = "weather" FORECAST_ENDPOINT = "forecast" # The rest of our server implementation will go here
-
添加天气获取功能
# Create reusable params http_params = { "appid": API_KEY, "units": "metric" } async def fetch_weather(city: str) -> dict[str, Any]: async with httpx.AsyncClient() as client: response = await client.get( f"{API_BASE_URL}/weather", params={"q": city, **http_params} ) response.raise_for_status() data = response.json() return { "temperature": data["main"]["temp"], "conditions": data["weather"][0]["description"], "humidity": data["main"]["humidity"], "wind_speed": data["wind"]["speed"], "timestamp": datetime.now().isoformat() } app = Server("weather-server")
-
实现资源处理程序
将这些与资源相关的处理程序添加到我们的 main 函数中:
app = Server("weather-server") @app.list_resources() async def list_resources() -> list[Resource]: """List available weather resources.""" uri = AnyUrl(f"weather://{DEFAULT_CITY}/current") return [ Resource( uri=uri, name=f"Current weather in {DEFAULT_CITY}", mimeType="application/json", description="Real-time weather data" ) ] @app.read_resource() async def read_resource(uri: AnyUrl) -> str: """Read current weather data for a city.""" city = DEFAULT_CITY if str(uri).startswith("weather://") and str(uri).endswith("/current"): city = str(uri).split("/")[-2] else: raise ValueError(f"Unknown resource: {uri}") try: weather_data = await fetch_weather(city) return json.dumps(weather_data, indent=2) except httpx.HTTPError as e: raise RuntimeError(f"Weather API error: {str(e)}")
-
实施工具处理程序
添加以下与工具相关的处理程序:
app = Server("weather-server") # Resource implementation ... @app.list_tools() async def list_tools() -> list[Tool]: """List available weather tools.""" return [ Tool( name="get_forecast", description="Get weather forecast for a city", inputSchema={ "type": "object", "properties": { "city": { "type": "string", "description": "City name" }, "days": { "type": "number", "description": "Number of days (1-5)", "minimum": 1, "maximum": 5 } }, "required": ["city"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]: """Handle tool calls for weather forecasts.""" if name != "get_forecast": raise ValueError(f"Unknown tool: {name}") if not isinstance(arguments, dict) or "city" not in arguments: raise ValueError("Invalid forecast arguments") city = arguments["city"] days = min(int(arguments.get("days", 3)), 5) try: async with httpx.AsyncClient() as client: response = await client.get( f"{API_BASE_URL}/{FORECAST_ENDPOINT}", params={ "q": city, "cnt": days * 8, # API returns 3-hour intervals **http_params, } ) response.raise_for_status() data = response.json() forecasts = [] for i in range(0, len(data["list"]), 8): day_data = data["list"][i] forecasts.append({ "date": day_data["dt_txt"].split()[0], "temperature": day_data["main"]["temp"], "conditions": day_data["weather"][0]["description"] }) return [ TextContent( type="text", text=json.dumps(forecasts, indent=2) ) ] except httpx.HTTPError as e: logger.error(f"Weather API error: {str(e)}") raise RuntimeError(f"Weather API error: {str(e)}")
-
添加 main 函数
将此添加到 的末尾:weather_service/src/weather_service/server.py
async def main(): # Import here to avoid issues with event loops from mcp.server.stdio import stdio_server async with stdio_server() as (read_stream, write_stream): await app.run( read_stream, write_stream, app.create_initialization_options() )```
-
在 init.py 中检查您的切入点
将此添加到 的末尾:weather_service/src/weather_service/init.py
from . import server import asyncio def main(): """Main entry point for the package.""" asyncio.run(server.main()) # Optionally expose other important items at package level __all__ = ['main', 'server']
连接到 Claude Desktop
-
更新 Claude 配置
搭:claude_desktop_config.json
{ "mcpServers": { "weather": { "command": "uv", "args": [ "--directory", "path/to/your/project", "run", "weather-service" ], "env": { "OPENWEATHER_API_KEY": "your-api-key" } } } }
-
重启 Claude
-
彻底退出 Claude
-
再次启动 Claude
-
在🔌菜单中查找您的天气服务器
-
测试
- 问天气:
What’s the current weather in San Francisco? Can you analyze the conditions and tell me if it’s a good day for outdoor activities?
- 比较天气
Can you analyze the forecast for both Tokyo and San Francisco and tell me which city would be better for outdoor photography this week?
参考理解:
Python SDK:https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#documentation
MCP中文文档:https://mcp-docs.cn/clients