什么是 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的资源访问模式提供提示模板并处理提示请求支持与客户端的能力协商实现服务器端协议操作管理并发客户端连接提供结构化日志和通知
上手实战
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/ 下载解压后, 将ux.exe所在目录配置到环境变量
# Git: https://git-scm.com
# SQLite: https://www.sqlite.org/download.html
检查uv是否安装成功
- 安装
创建一个简单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
打开powerSheel, 输入以上代码
- 下载并配置 Claude Desktop
遗憾的是国内目前无法安装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。
- 测试
问题:
你能连接到我的 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?
总结
寻找国内Claude Desktop平替, 实现mcp强大的ai扩展能力