大家好,我是阿坡,今天我通过极简单的案例,带大家手搓一个MCP客户端和服务端代码,来快速了解MCP是什么?
MCP是一个典型的CS架构,对于有编程基础的同学来说,很容易理解,因为开发中常见的MySql就是典型的CS架构,程序员日常开发过程中,会经常接触到CS架构的产品。
本文不做太多关于MCP是什么的解释,尽最可能减少无关的噪音,只需要知道,MCP(Model Context Protocol)是一个标准化协议,通过客户端-服务端架构,让AI模型能够安全地调用工具、访问外部数据源,并实时获取信息。
这个案例的内容就是:让AI根据你的输入自动规划并调用MCP服务端,给本地电脑创建一个文件,并写入一句话。
如果这个案例你跑通了,你就会对MCP有一个初步的且正确的认知了。后面,你再去看网上其他关于MCP的大段文字科普,或者眼花缭乱的客户端配置MCP服务器的教程,再或者通过Dify等工作流与MCP结合,就会有种拨云见日,一览众山小的感觉。
一、环境安装
一)下载并安装python
二)安装uv
1、uv介绍
MCP开发要求借助uv进行虚拟环境创建和依赖管理。uv 是一个Python 依赖管理工具,采用 Rust 编写,功能类似于pip,venv,但它更快、更高效,并且可以更好地管理 Python 虚拟环境和依赖项,也就是说他兼有了创建虚拟环境和包管理工具的功能,可以平替pip,venv。
它完全兼容 pip :支持 requirements.txt 和 pyproject.toml 依赖管理。 跨平台:支持 Windows、macOS 和 Linux。
2、uv安装
ctrl+r,打开命令行,输入一下命令安装uv
pip install uv
uv常见使用命令可以自行问AI或百度,此处不再赘述。
二、案例场景概述
我们先从最简单的案例入手,创建client端,server端,然后联调通,对吗,mcp建立起一个初步完整的认知即可,尽最大可能避免引入复杂的东西。
所以,我们的案例就是:通过手动创建的client运行起来后,来调用server端的逻辑,server端的逻辑就是:创建一个名为aaa.txt
文件,写入 今天天气真好!
,即可。
三、MCP客户端
一)初始化client项目
进入自己的代码目录下,创建一个文件夹:
bash
# 创建并初始化项目目录
uv init mcp_client
# 进入文件夹
cd mcp_client
可以看到一个完整的初始化项目目录:包含项目入口文件,依赖管理文件等
二)创建MCP客户端虚拟环境
这里我们用cursor打开项目,进行后面的操作。
我们选择Command Prompt
来执行后面的命令:
bash
# 创建虚拟环境
uv venv
# 激活虚拟环境
# On Windows:
.venv\Scripts\activate
# On Unix or MacOS:
source .venv/bin/activate
这里,uv会自动识别当前项目主目录,然后自动创建虚拟环境。
现在,我们通过add方法在虚拟环境中安装相关的库。
注意:如果网络问题,安装依赖失败,可以更改镜像源,或者使用科学上网工具。
csharp
# 安装 MCP依赖包
uv add mcp
三)新增client所需依赖
后面,为了支持调用大模型,读取env环境变量中的API_KEY等信息,需要先安装如下依赖:
csharp
uv add mcp openai python-dotenv
四)接入DeepSeek在线大模型
1、创建env文件
env文件用来存放大模型API Key等配置信息:
ini
BASE_URL=https://api.deepseek.com
MODEL=deepseek-chat
API_KEY="你的API_KEY"
2、编写MCP客户端
具体也可以参考官方示例:modelcontextprotocol.io/quickstart/...
客户端代码如下:
python
import asyncio
import os
from openai import OpenAI
from dotenv import load_dotenv
from contextlib import AsyncExitStack
# 加载 .env 文件
load_dotenv()
class MCPClient:
def __init__(self):
"""初始化 MCP 客户端"""
self.exit_stack = AsyncExitStack()
self.openai_api_key = os.getenv("API_KEY") # 读取 OpenAI API Key
self.base_url = os.getenv("BASE_URL") # 读取 BASE URL
self.model = os.getenv("MODEL") # 读取 model
if not self.openai_api_key:
raise ValueError("未找到 API KEY. 请在.env文件中配置API_KEY")
self.client = OpenAI(api_key=self.openai_api_key,
base_url=self.base_url)
async def process_query(self, query: str) -> str:
"""调用 OpenAI API 处理用户问题"""
messages = [{
"role": "system",
"content": "你是一个智能助手,帮助用户回答问题。"
}, {
"role": "user",
"content": query
}]
try:
# 调用 大模型API
response = await asyncio.get_event_loop().run_in_executor(
None,
lambda: self.client.chat.completions.create(model=self.model,
messages=messages))
return response.choices[0].message.content
except Exception as e:
return f"调用模型API时出错: {str(e)}"
async def chat_loop(self):
"""运行交互式聊天循环"""
print("MCP 客户端已启动!输入 'exit' 退出")
while True:
try:
query = input("问: ").strip()
if query.lower() == 'exit':
break
response = await self.process_query(query)
print(f"AI回复: {response}")
except Exception as e:
print(f"发生错误: {str(e)}")
async def clean(self):
"""清理资源"""
await self.exit_stack.aclose()
async def main():
client = MCPClient()
try:
await client.chat_loop()
finally:
await client.clean()
if __name__ == "__main__":
asyncio.run(main())
3、运行client
arduino
uv run client.py
五)接入ollama本地大模型
大模型部署的详细教程,这里不再赘述,可以参考之前我写的文章:DeepSeek + Dify :零成本搭建企业级本地私有化知识库保姆级喂饭教程
1、确保ollama已启动
没有启动的,输入如下命令来启动ollama服务
bash
# 列出所有已安装模型
ollama start
# 运行大模型
ollama run qwq:latest
2、修改env配置文件
注意:有些模型是不支持tool调用的,比如:ollama中下载的deepseek-r1
,所以选大模型的时候,要选择支持tool的大模型
ini
BASE_URL=http://localhost:11434/v1/
MODEL=qwq:latest
API_KEY=ollama
3、运行client
四、MCP服务端
一)服务端代码
创建服务端文件 server.py
:
python
from mcp.server.fastmcp import FastMCP
# 初始化FastMCP服务器
mcp = FastMCP("filesystem")
@mcp.tool()
async def create_file(file_name: str, content: str) -> str:
"""
创建文件
:param file_name: 文件名
:param content: 文件内容
"""
with open(file_name, "w", encoding="utf-8") as file:
file.write(content)
return "创建成功"
@mcp.tool()
async def read_file(file_name: str) -> str:
"""
读取文件内容
:param file_name: 文件名
"""
with open(file_name, "r", encoding="utf-8") as file:
return file.read()
@mcp.tool()
async def write_file(file_name: str, content: str) -> str:
"""
写入文件内容
:param file_name: 文件名
:param content: 文件内容
"""
with open(file_name, "w", encoding="utf-8") as file:
file.write(content)
return "写入成功"
if __name__ == "__main__":
# 以标准 I/O 方式运行 MCP 服务器
mcp.run(transport="stdio")
二)服务端代码详解
服务端代码的逻辑主要是:
1、初始化服务
启动一个名为 filesystem
的MCP服务
ini
mcp = FastMCP("filesystem")
2、创建tool
方法
通过@mcp.tool()
装饰器注册三个 MCP 服务器工具:read_file
、write_file
,create_file
,能够被客户端调用。
注意:方法注释一定要写上,内容包含:tool的功能,入参,出参,他是服务端tool的说明书,是大模型能否成功调用该方法的关键。
python
@mcp.tool()
async def read_file(file_name: str) -> str:
"""
读取文件内容
:param file_name: 文件名
"""
with open(file_name, "r", encoding="utf-8") as file:
return file.read()
@mcp.tool()
async def write_file(file_name: str, content: str) -> str:
"""
写入文件内容
:param file_name: 文件名
:param content: 文件内容
"""
with open(file_name, "w", encoding="utf-8") as file:
file.write(content)
return "写入成功"
@mcp.tool()
async def create_file(file_name: str, content: str) -> str:
"""
创建文件
:param file_name: 文件名
:param content: 文件内容
"""
with open(file_name, "w", encoding="utf-8") as file:
file.write(content)
return "创建成功"
3、服务端入口代码
ini
# 以标准 I/O 方式运行 MCP 服务器
mcp.run(transport="stdio")
通过 mcp.run(transport='stdio')
启动 MCP 服务器,采用标准 I/O 通信方式,等待客户 端调用,适合客户端和服务端在同一台电脑上通信。
stdio
模式是一种本地进程间通信(IPC,Inter-Process Communication)的方式,它需要服务端程序作为子进程运行,并通过标准输入输出 ( stdin / stdout )进行数据交换。
因此,当我们指定 transport='stdio' 运行 MCP 服务器时,需要先启动服务端程序server.py
,然后再启动客户端程序 client.py
。
这样客户端才能和服务端通信。
4、启动服务
我们可以在启动一个命令行运行服务端程序:
arduino
uv run server.py
五、修改客户端使其能与服务端通信
一)客户端核心逻辑
前面,我们写了客户端代码,但是只是说接通了在线或本地大模型,客户端若需要与服务端通信,就需要修改客户端代码了,客户端的核心逻辑就要调整为:
1、启动并初始化 MCP 客户端
2、连接MCP服务端
2、列出 MCP 服务器上的工具
3、运行交互式聊天循环,处理用户对话
- 将用户输入发送给 OpenAI 模型
- 如果模型想调用 MCP 工具(Function Calling),就执行 call_tool
- 将结果重新发给模型,并返回最终回答
二)修改原有客户端代码
在原有基础上修改代码,只需要增加或修改一下代码即可:
1、增加连接服务端的方法
python
async def connect_to_server(self, server_script_path: str):
"""
连接到 MCP 服务器
"""
is_python = server_script_path.endswith('.py')
is_js = server_script_path.endswith('.js')
if not (is_python or is_js):
raise ValueError("不支持的文件类型")
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))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(self.stdio, self.write))
await self.session.initialize()
2、增加列出服务端工具列表的方法
python
async def list_tools(self):
"""列出所有工具"""
# 列出 MCP 服务器上的工具
response = await self.session.list_tools()
tools = response.tools
print("已连接到服务器,server支持以下工具:", [tool.name for tool in tools])
3、修改用户对话逻辑
python
async def process_query(self, query: str) -> str:
"""
调用大模型处理用户输入
"""
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]
print('服务端工具列表', available_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")
# 将模型返回的原始消息和工具执行的结果都添加到messages中
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
4、入口方法也要做调整
csharp
async def main():
# 启动并初始化 MCP 客户端
client = MCPClient()
try:
# 连接到 MCP 服务器
await client.connect_to_server('server.py')
# 列出 MCP 服务器上的工具
await client.list_tools()
# 运行交互式聊天循环,处理用户对话
await client.chat_loop()
finally:
# 清理资源
await client.clean()
5、开头的导入内容也要对应做修改
python
import asyncio
import os
from openai import OpenAI
from dotenv import load_dotenv
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import json
6、客户端最终完整代码
python
import asyncio
import os
from openai import OpenAI
from dotenv import load_dotenv
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import json
# 加载 .env 文件
load_dotenv()
class MCPClient:
def __init__(self):
"""初始化 MCP 客户端"""
self.exit_stack = AsyncExitStack()
self.api_key = os.getenv("API_KEY") # 读取 OpenAI API Key
self.base_url = os.getenv("BASE_URL") # 读取 BASE URL
self.model = os.getenv("MODEL") # 读取 model
if not self.api_key:
raise ValueError("未找到 API KEY. 请在.env文件中配置API_KEY")
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
async def process_query(self, query: str) -> str:
"""
调用大模型处理用户查询并根据返回的tools列表调用对应工具
"""
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]
print('服务端工具列表', available_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")
# 将模型返回的原始消息和工具执行的结果都添加到messages中
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("MCP 客户端已启动!输入 'exit' 退出")
while True:
try:
query = input("问: ").strip()
if query.lower() == 'exit':
break
response = await self.process_query(query)
print(f"AI回复: {response}")
except Exception as e:
print(f"发生错误: {str(e)}")
async def clean(self):
"""清理资源"""
await self.exit_stack.aclose()
async def connect_to_server(self, server_script_path: str):
"""
连接到 MCP 服务器
"""
is_python = server_script_path.endswith('.py')
is_js = server_script_path.endswith('.js')
if not (is_python or is_js):
raise ValueError("不支持的文件类型")
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))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(self.stdio, self.write))
await self.session.initialize()
async def list_tools(self):
"""列出所有工具"""
# 列出 MCP 服务器上的工具
response = await self.session.list_tools()
tools = response.tools
print("已连接到服务器,server支持以下工具:", [tool.name for tool in tools])
async def main():
# 启动并初始化 MCP 客户端
client = MCPClient()
try:
# 连接到 MCP 服务器
await client.connect_to_server('server.py')
# 列出 MCP 服务器上的工具
await client.list_tools()
# 运行交互式聊天循环,处理用户对话
await client.chat_loop()
finally:
# 清理资源
await client.clean()
if __name__ == "__main__":
asyncio.run(main())
7、重新启动客户端
注意:在运行客户端之前,要确保服务端已经运行
arduino
uv run client.py
启动客户端后,我们让AI通过MCP服务帮我们做一件事:在当前目录下aaa.txt文件内写入一句话:今天天气不错,心情很好!若文件不存在就创建
如下图,客户端启动后,列出了服务端可用的工具列表:['read_file', 'write_file', 'create_file']
然后,AI自动调用了服务端的工具创建了文件,并写入了内容
六、更多MCP服务器合集导航地址
号称最大的MCP集合导航站:mcp.so/
MCP集合:github.com/ahujasid/bl...
官方MCP合集:github.com/modelcontex...
Github热门MCP导航:github.com/punkpeye/aw...
七、结语
以上,我们通过一个极其简单的案例,让MCP客户端与服务端进行通信,并成功调用服务端的对应工具,帮我们完成了一件事情。这意味着,AI可以通过MCP服务完成几乎任何事情,比如:操作电脑本地文件夹,帮我们浏览网页并获取内容存,然后存入本地等等很多场景,想象空间很大,而这一切都是由AI驱动完成的!
MCP的功能远不止此,它还支持SSE传输模式,实现服务器与客户端异地运行;提供Resources类资源接口和Prompt类提示词模板;通过标准化协议促进开发者协作,已有数千服务器可供调用。
快快实战起来吧,何以破局,唯有行动!