从0开始研究MCP应用与安全篇(一)
在互联网大厂工作中接触学习了MCP与安全的应用以及MCP本身的安全,本文为MCP应用与安全开篇,先带读者从0开始学习研究MCP底层逻辑原理,并实操开发两个MCP Server服务,从基础架构入手,探讨MCP的核心机制及安全挑战。
引言
在互联网大厂工作中接触学习了MCP与安全的应用以及MCP本身的安全,本文为MCP应用与安全开篇,先带读者从0开始学习研究MCP底层逻辑原理,并实操开发两个MCP Server服务,从基础架构入手,探讨MCP的核心机制及安全挑战。
MCP介绍
MCP是什么?
MCP(Model Context Protocol)是一种开放标准协议,旨在让大型语言模型(LLM)与外部工具和数据源无缝通信。用个简单的比喻,MCP 就像是 AI 的“通用翻译器”,让它能安全、可控地访问你的文件、应用或网络服务,并执行具体任务。
MCP 的三大核心组件
•MCP 主机:你与 AI 互动的应用程序,比如 Claude Desktop,相当于 AI 的“大本营”。
•MCP 服务器:专门的小程序,提供特定功能(如访问文件或调用 API),就像为 AI 服务的“专业导游”。
•MCP 客户端:连接主机和服务器的桥梁,确保通信顺畅,通常无需用户直接操作。
通过这种设计,MCP 让 AI 助手从单纯的对话工具,进化成能操作现实世界的强大助手。
CS架构
MCP 遵循客户端-服务器模型:
•宿主:启动连接的 LLM 应用程序,如 Claude Desktop 或 AI IDE。
•客户端:在宿主内部与服务器保持 1:1 连接,负责通信协调。
•服务器:为客户端提供上下文、工具和提示,执行具体任务。
stdio和SSE通信
特性 | stdio | SSE |
通信方向 | 双向(但必须轮流) | 单向(服务器→客户端) |
实时性 | 低(同步阻塞) | 高(异步推送) |
连接方式 | 进程管道 | HTTP长连接 |
多请求处理 | 不支持并行 | 支持多个事件流 |
典型场景 | 命令行工具、批处理 | Web进度更新、实时监控 |
- 需要 简单本地调用? → 用
stdio(比如运行一个Python脚本处理数据) - 需要 实时状态推送? → 用
sse(比如在网页上显示训练进度)
消息类型
MCP 定义了四种主要消息类型:
•请求(Request):期望响应的消息,如:{ method: "getWeather", params: { city: "Beijing" } }
•通知(Notification):单向消息,无需回应,如 { method: "logEvent", params: { event: "start" } }
•结果(Result):请求的成功响应,如 { temperature: 25 }
•错误(Error):请求失败的反馈,如 { code: -32602, message: "Invalid params" }
为什么 MCP 有用?
•统一接口:LLM 只需理解 MCP,无需学习各种 API。
•可插拔架构:新功能只需添加 MCP 服务器。
•工作流自动化:多个服务器可串联成复杂流程。
MCP开发
FastMCP 天气服务项目
这个系统采用了分层架构:
- 通信层:处理与MCP服务器的stdio通信
- 工具管理层:管理可用的工具调用
- AI处理层:使用OpenAI API进行自然语言处理
- 用户交互层:提供命令行界面
环境配置
安装 uv:Python 包管理器和环境管理工具
curl -LsSf https://astral.sh/uv/install.sh | sh创建项目目录
uv init mcp_server_test
cd mcp_server_test创建虚拟环境并激活
uv venv
source .venv/bin/activate安装依赖包
uv add "mcp[cli]" httpx requestsMCP WeatherServer编写
import json
import httpx
from typing import Any
from mcp.server.fastmcp import FastMCP
#OpenWeather API配置
OPENWEATHER_API="https://api.openweathermap.org/data/2.5/weather"
API_KEY=""
USER_AGENT="weather-app/1.0"
#初始化MCP服务器
mcp=FastMCP()
async def get_weather(city:str)->dict[str,Any]|None:
"""
获取指定城市的天气信息
:param city: 城市名称(需要英文)
:return: 天气信息字典或None
"""
params = {
"q": city,
"appid": API_KEY,
"units": "metric", # 使用摄氏度
"lang": "zh_cn" # 中文
}
headers={"User-Agent": USER_AGENT}
async with httpx.AsyncClient() as client:
try:
response=await client.get(OPENWEATHER_API,params=params,
headers=headers,timeout=30.0)
response.raise_for_status() # 检查请求是否成功
# 解析JSON响应
return response.json()
except httpx.HTTPStatusError as e:
return {"error":f"请求失败:{e.response.status_code} {e.response.reason_phrase}"}
except Exception as e:
return {"error":f"请求失败:{str(e)}"}
def format_weather(data:dict[str,Any]|str)->str:
"""
格式化天气信息
:param data: 天气信息字典或错误信息字符串
:return: 格式化后的天气信息字符串
"""
if isinstance(data, str):
try:
data=json.loads(data)
except Exception as e:
return f"数据解析错误:{str(e)}"
if "error" in data:
return f"错误:{data['error']}"
# 提取数据时做容错处理
city = data.get("name", "未知")
country = data.get("sys", {}).get("country", "未知")
temp = data.get("main", {}).get("temp", "N/A")
humidity = data.get("main", {}).get("humidity", "N/A")
wind_speed = data.get("wind", {}).get("speed", "N/A")
# weather 可能为空列表,因此用 [0] 前先提供默认字典
weather_list = data.get("weather", [{}])
description = weather_list[0].get("description", "未知")
return (
f"🌍 {city}, {country}\n"
f"🌡 温度: {temp}°C\n"
f"💧 湿度: {humidity}%\n"
f"🌬 风速: {wind_speed} m/s\n"
f"🌤 天气: {description}\n"
)
@mcp.tool()
async def query_weather(city:str)->str:
"""
查询指定城市的天气信息
:param city: 城市名称(英文)
:return: 格式化后的天气信息字符串
"""
data=await get_weather(city)
return format_weather(data)
if __name__ == "__main__":
# 启动I/O MCP服务器
mcp.run(transport='stdio')MCP Inspector调试开发
mcp dev main.py 启动 MCP Inspector(Model Context Protocol Inspector),用于调试、监控或管理某种模型/协议交互的开发工具
sessiontoken填入后就可以在Tools中list tools选择server然后传参即可
MCP Client开发
环境配置
先安装venv的pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.pyvenv里面安装openai
m pip install openaiClient编写
此client连接了QWEN AI,也就相当于Ai agengt内包含一个MCP Client,运行时指定 MCP Server脚本即可启动MCP Server建立通信。当用户对话ai,然后MCP Client调用list_tools请求MCP服务器的工具列表,根据对话内容理解调用tool,最后将tool的结果返回给大模型生成对话结果
通过mcp,挂DeepSeek在线模型,调用服务端天气查询智能体。
完整代码如下
import asyncio
from typing import Optional
from contextlib import AsyncExitStack
from openai import OpenAI
from mcp import ClientSession,StdioServerParameters
from mcp.client.stdio import stdio_client
import sys
class MCPClient:
def __init__(self):
'''Initialize the MCPClient'''
self.openai_api_key="sk-blvnnivvsysypkenyyywxaxjtageyowdviqcdzgyzgrqxarx"
self.base_url="https://api.siliconflow.cn/v1/"
self.model="Qwen/Qwen3-8B"
if not self.openai_api_key:
raise ValueError("OpenAI API key is not set in the environment variables.")
#创建 OpenAI 客户端实例
self.client=OpenAI(api_key=self.openai_api_key, base_url=self.base_url) #OpenAI 类: 这是 OpenAI 官方 Python 客户端库的主要类,用于与 OpenAI 的 API 进行交互
self.session: Optional[ClientSession]=None
self.exit_stack=AsyncExitStack()
async def connect_to_server(self,server_script_path:str):
'''连接到MCP服务器并列出可用的工具'''
is_python=server_script_path.endswith('.py')
if_js=server_script_path.endswith('.js')
if not (is_python or if_js):
raise ValueError("Server script must be a Python (.py) or JavaScript (.js) file.")
#确定执行命令python or node
command="python" if is_python else "node"
server_params=StdioServerParameters(
command=command,
args=[server_script_path],
env=None
)
#启动MCP服务器并建立通信
stdio_transport=await self.exit_stack.enter_async_context(
stdio_client(server_params)#用于创建一个标准输入/输出(stdio)的客户端传输层
)
self.stdio,self.write=stdio_transport
self.session=await self.exit_stack.enter_async_context(
ClientSession(
self.stdio,
self.write,
)
)
await self.session.initialize()
# 获取可用工具列表
response=await self.session.list_tools()
tools=response.tools
print("\n已连接到服务器,支持以下工具:",[tool.name for tool in tools])
async def process_query(self,query:str)->str:
'''
使用大模型处理查询并调用可用的MCP工具(Function)
'''
messages=[{"role":"user","content":query}]
response=await self.session.list_tools()
available_tools=[{
"type":"function",
"function":{
"name":tool.name,
"description":tool.description,
"input_schema":tool.inputSchema
}
}for tool in response.tools]
response=self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=available_tools
)
#处理返回内容
content=response.choices[0]
if content.finish_reason=="tool_calls":
#如果是需要使用工具,就解析工具
tool_call=content.message.tool_calls[0]
tool_name=tool_call.function.name
tool_args=json.loads(tool_call.function.arguments)
#执行工具
result=await self.session.call_tool(tool_name,tool_args)
print(f"\n\n🌟Calling tool {tool_name} with args {tool_args} ....\n\n")
#将模型返回的调用的tool和tool执行后的数据存入
messages.append(content.message.model_dump())
messages.append({
"role":"tool",
"content":result.content[0].text,
"tool_call_id":tool_call.id
})
#将上面结果返回给大模型用于生产最终的结果
response=self.client.chat.completions.create(
model=self.model,
messages=messages
)
return response.choices[0].message.content
return content.message.content
async def chat_loop(self):
''''运行交互式聊天循环'''
print("\n🤖 MCP客户端已启动!输入 'quit' 退出")
while True:
try:
query= input("\n请输入您的查询(或输入 'quit' 退出):")
if query.lower() == 'quit':
print("退出聊天...")
break
response=await self.process_query(query)
print(f"\n🤖 OpenAI:{response}")
except Exception as e:
print(f"发生错误:{str(e)}")
async def close(self):
'''关闭MCP客户端'''
await self.exit_stack.aclose()
print("MCP客户端已关闭。")
async def main():
if len(sys.argv) < 2:
print("🌟 Usage: python client.py <path_to_server_srcipt>")
sys.exit(1)
client=MCPClient()
try:
await client.connect_to_server(sys.argv[1])
await client.chat_loop()
finally:
await client.close()
if __name__ == "__main__":
import sys
asyncio.run(main())OpenAI 类是 OpenAI 官方 Python 客户端库的主要类,用于与 OpenAI 的 API 进行交互
self.client=OpenAI(api_key=self.openai_api_key, base_url=self.base_url)声明可选的 ClientSession
self.session: Optional[ClientSession] = NoneClientSession: 来自aiohttp库的类,用于管理 HTTP 客户端会话,通常在异步编程中使用。Optional: 表示这个变量可以是ClientSession类型,也可以是None。
self.exit_stack = AsyncExitStack()AsyncExitStack: 来自contextlib模块,用于管理异步上下文管理器的工具。它可以帮助你在一个地方集中管理多个需要异步清理的资源。
这段代码向 AI发送请求,并允许模型选择是否调用外部工具(Tools)。返回的 response 包含模型的回复,可能包含
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=available_tools
)- 普通文本回复(如果模型决定直接回答)。
- 工具调用请求(如果模型决定调用某个工具,如查询天气、执行计算等)。
messages作用:提供对话历史,让模型理解上下文。
role可以是:
"system"(系统提示,设定 AI 的行为)"user"(用户消息)"assistant"(AI 的回复)"tool"(工具调用的返回结果)
tools作用:定义可用的工具(函数),让模型决定是否调用它们。
- 格式:
available_tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取某个城市的天气",
"input_schema":tool.inputSchema
}
}
]- `name`:工具名称(模型调用时使用)。
- `description`:工具描述(帮助模型决定是否调用)。
- `input_schema`:定义工具需要的参数(JSON Schema 格式)。获取模型返回的 choices[0]
content = response.choices[0]response是 OpenAI API 返回的响应对象。response.choices[0]是模型生成的主要回复(通常只有一个choice,除非指定n > 1)。content现在包含:
"stop":模型正常结束,返回了文本回复。"tool_calls":模型决定调用外部工具(需要解析工具信息)。"length":因达到max_tokens限制而停止。"content_filter":因内容过滤被拦截。
tool_call 是第一个工具调用的详细信息,结构如下:
{
"id": "call_123", # 工具调用的唯一 ID
"type": "function",
"function": {
"name": "get_weather", # 工具名称
"arguments": "{\"location\": \"北京\"}" # 工具参数的 JSON 字符串
}
}运行测试
能够循环对话,将用户要求转换为MCP Server的参数,拿到天气数据再喂给AI成果输出自然语言
MCP文件系统
MCP Server有读取、写入、列目录功能
from typing import Any
from mcp.server.fastmcp import FastMCP
from openai import OpenAI
import os
#初始化MCP服务器
mcp=FastMCP()
@mcp.tool("get_content")
async def get_content(path:str)->dict[str,Any]|None:
"""
获取指定路径的内容
:param path: 文件路径
:return: 文件内容字典或None
"""
try:
with open(path, 'r', encoding='utf-8') as file:
content = file.read()
return {"content": content}
except FileNotFoundError:
return {"error": "文件未找到"}
except Exception as e:
return {"error": f"读取文件失败:{str(e)}"}
@mcp.tool("write_content")
async def write_content(path:str, content:str)->dict[str,Any]|None:
"""
写入内容到指定路径的文件
:param path: 文件路径
:param content: 要写入的内容
:return: 成功或错误信息字典
"""
try:
with open(path, 'w', encoding='utf-8') as file:
file.write(content)
return {"message": "内容写入成功"}
except Exception as e:
return {"error": f"写入文件失败:{str(e)}"}
@mcp.tool("list_files")
async def list_files(directory:str)->dict[str,Any]|None:
"""
列出指定目录下的所有文件
:param directory: 目录路径
:return: 文件列表字典或错误信息字典
"""
try:
files = os.listdir(directory)
return {"files": files}
except FileNotFoundError:
return {"error": "目录未找到"}
except Exception as e:
return {"error": f"列出文件失败:{str(e)}"}
if __name__ == "__main__":
mcp.run(transport='stdio')测试结果
列出项目文件并解释
将文件内容写入文件
查看文件内容
安全问题
MCP(Model Context Protocol作为连接AI与外部系统的开放协议,其应用潜力巨大,但同时也面临安全风险。
MCP的核心架构采用客户端-服务器模型,通过标准化的JSON消息实现AI与工具间的通信。例如,当用户请求“查询北京天气”时,MCP客户端将结构化请求转发至天气服务器,返回数据经AI处理后生成自然语言回复。这一流程依赖stdio(同步)和SSE(异步)两种通信模式,分别适用于本地脚本调用和实时数据推送。然而,这种开放性也带来安全隐患:
- 输入验证风险
- 恶意构造的请求(如路径遍历../../../etc/passwd)可能使文件操作类服务器泄露敏感数据。
- 权限管控缺失
- 默认情况下,MCP服务器可能以当前用户权限运行,若AI被诱导调用危险命令(如rm -rf),将导致系统破坏。
- 数据泄露与滥用
- 第三方工具服务器若未加密通信,可能被中间人攻击窃取API密钥或用户隐私。
实验表明,一个未经验证的MCP文件服务器可被利用读取系统文件,而天气查询服务若未限制请求频率,可能成为DDoS攻击的跳板。后续研究将深入探讨沙箱隔离、OAuth2.0集成等防护方案,为构建安全可靠的MCP生态奠定基础。