如何使用MCP开发一个客户端和服务端
如何使用MCP开发一个客户端和服务端
一、MCP和API以及Function Call核心概念对比
| 特性 | API | Function Call | MCP (Model Context Protocol) |
| 定位 | 通用应用程序接口 | 大模型原生扩展能力 | 标准化模型-服务交互协议 |
| 耦合度 | 与具体服务绑定 | 与模型强绑定 (如 GPT-4-turbo) | 与模型解耦,跨平台通用 |
| 交互模式 | 直接请求-响应 | 模型生成结构化调用建议 | JSON-RPC 2.0 标准化通信 |
| 典型场景 | 数据集成、微服务通信 | 简单实时操作 (天气/订单查询) | 复杂异步任务 & 跨系统整合 |
二、 MCP 协议
1. 什么是MCP协议
模型上下文协议(Model Context Protocol)是一种专为大语言模型设计的标准化协议,它允许LLM以安全、一致的方式与外部系统交互。MCP协议常被描述为"AI的USB-C接口",提供了一种统一的方式连接LLM与它们可以使用的资源。
MCP协议的核心功能包括:
- • 资源(Resources):类似于GET端点,用于将信息加载到LLM的上下文中
- • 工具(Tools):类似于POST端点,用于执行代码或产生副作用
- • 提示(Prompts):可重用的LLM交互模板
- • 上下文(Context):提供额外的交互功能,如日志记录和进度报告
2. 核心价值
- • 标准化:统一 AI 与外部服务的交互格式,解决工具碎片化问题
- • 解耦设计:模型无需硬编码 API 逻辑,通过声明式函数描述调用服务
- • 异步支持:适用于多步骤工作流(如爬取数据→分析→存储)
3. 工作流程
MCP 大概的工作方式: Claude Desktop、Cursor 这些工具,在内部实现了 MCP Client,然后MCP Client 通过标准的 MCP 协议和 MCP Server 进行交互,由各种三方开发者提供的 MCP Server 负责实现各种和三方资源交互的逻辑,比如访问数据库、浏览器、本地文件,最终再通过 标准的 MCP 协议返回给 MCP Client,最终给用户进行展示。
下图是一个通过查询天气来简单展示其对应的工作方式:
External_ServiceMCP_ServerAI_ModelUserExternal_ServiceMCP_ServerAI_ModelUser“查询北京天气”解析意图,生成MCP调用{ "function": "get_weather", "params": {"city":"北京"} }调用天气API返回原始数据{ "temperature": "22°C", "condition": "晴" }“北京今天晴天,气温22°C”
3. 代码实现mcp客户端和服务端
现在python编写mcp server和mcp client的有两个分别是FastMCP 和MCP,其中MCP是官方的pythonsdk,这两个之间的关系是官方收编了FastMCP的第一个版本的包,但官方集成的是 fastmcp 的 v1.0 版本。然而,jlowin 继续开发 fastmcp,还发布了 v2.0 版本,其中包含代理和客户端采样等新功能。以下的演示以官方版本MCP为例,
安装:uv add "mcp[cli]” 或者pip install "mcp[cli]”
(1) MCP 服务端
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
# mcp = FastMCP(name="demo",host="127.0.0.1",port=8256,sse_path="/sse") ### 启动方式为sse时使用
mcp = FastMCP()
@mcp.tool()
def add_2_numbers(a: int, b: int) -> int:
"""两个数字相加"""
return a + b
@mcp.resource("config://app")
def get_config() -> str:
"""Static configuration data"""
return "App configuration here"
@mcp.prompt()
def debug_error(error: str) -> list[base.Message]:
return [
base.UserMessage("I'm seeing this error:"),
base.UserMessage(error),
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
@mcp.tool()
def multiply_2_numbers(a: int, b: int):
"""两个数字相乘"""
return a * b
if __name__ == "__main__":
# mcp.run(transport='sse') ## 启动方式为sse
mcp.run(transport='stdio') ## 启动方式为stdio
解释:
- • Tools(工具)是MCP中最常用的功能之一,它允许LLM执行特定的操作或函数。使用
@mcp.tool()装饰器可以轻松将Python函数转换为LLM可调用的工具: - • Resources(资源)用于向LLM提供数据和上下文信息。与工具不同,资源主要用于读取数据而非执行操作
- • Prompts(提示)允许您创建可重用的提示模板,这些模板可以被参数化并用于标准化LLM交互
简单验证服务端功能可以通过mcp dev server.py进入界面检测
(2) MCP 客户端
MCP客户端一般分别按照服务端的stdio和sse分别写了两个,具体融合的最后修改一下即可。
- 1. STDIO客户端
import asyncio
import json
import re
from contextlib import AsyncExitStack
from typing import Optional
from lxml import etree
from mcp import ClientSession, StdioServerParameters, stdio_client
from mcp.client.sse import sse_client
from openai import AsyncOpenAI
class Stdio_MCPClient():
def __init__(self,api_key, base_url, model):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
self.model = model
self.message = []
with open("MCP_Prompt.txt", "r", encoding="utf-8") as f:
self.system_prompt = f.read()
async def connect_to_stdio_server(self, mcp_name, command,args,env={}):
server_params = StdioServerParameters(
command=command,
args=args,
env=env
)
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()
response = await self.session.list_tools()
tools = response.tools
print(f"成功链接到{mcp_name}服务,对应的tools:",[tool.name for tool in tools])
self.available_tools = [{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema
}
} for tool in response.tools]
async def process_query(self, query: str, stream: bool = False):
# self.message.append({"role": "system", "content": self.system_prompt})
self.message.append({"role": "user", "content": query})
response = await self.client.chat.completions.create(
model=self.model,
messages=self.message,
tools=self.available_tools
)
final_text = []
assistant_message = response.choices[0].message
while assistant_message.tool_calls:
for tool_call in assistant_message.tool_calls:
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"calling tools {tool_name},wirh args {tool_args}")
print("Result:", result.content[0].text)
self.message.extend([
{
"role": "assistant",
"content": None,
"tool_calls": [tool_call]
},
{
"role": "tool",
"content": result.content[0].text,
"tool_call_id": tool_call.id
}
])
response = await self.client.chat.completions.create(
model=self.model,
messages=self.message,
tools=self.available_tools,
max_tokens=8048
)
assistant_message = response.choices[0].message
content = assistant_message.content
final_text.append(content)
return "\n".join(final_text)
async def chat_loop(self,stream_mode=True):
self.message = []
while True:
try:
query = input("\nQuery: ").strip()
if query.lower() == 'quit':
break
if query.strip() == '':
continue
response = await self.process_query(query, stream=stream_mode)
print("\nAI:", response)
except Exception as e:
print(f"\nError: {str(e)}")
async def cleanup(self):
await self.exit_stack.aclose()
async def main():
with open("config.json", "r") as f:
config = json.load(f)
client = Stdio_MCPClient(config["llm"]["api_key"], config["llm"]["base_url"], config["llm"]["model"])
try:
env = {}
await client.connect_to_stdio_server("testserver","python",["server.py",],{})
await client.chat_loop()
except Exception as e:
print(e)
finally:
await client.cleanup()
if __name__ == '__main__':
asyncio.run(main())
- 1. SSE客户端
import asyncio
import json
import re
from contextlib import AsyncExitStack
from typing import Optional
from lxml import etree
from mcp import ClientSession, StdioServerParameters, stdio_client
from mcp.client.sse import sse_client
from openai import AsyncOpenAI
class SSE_MCPClient():
def __init__(self,api_key, base_url, model):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
self.model = model
self.message = []
with open("MCP_Prompt.txt", "r", encoding="utf-8") as f:
self.system_prompt = f.read()
async def connect_to_sse_server(self, mcp_name, server_url,headers=None):
self.service = [{
"name": mcp_name,
"url": server_url,
"headers": headers
}]
sse_transport = await self.exit_stack.enter_async_context(sse_client(server_url, headers,timeout=30,sse_read_timeout=30))
self.sse,self.write = sse_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.sse, self.write))
await self.session.initialize()
response = await self.session.list_tools()
tools = response.tools
print(f"成功链接到{mcp_name}服务,对应的tools:",[tool.name for tool in tools])
self.available_tools = [{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema
}
} for tool in response.tools]
async def reconnect_sse_server(self):
for service in self.service:
mcp_name = service["name"]
server_url = service["url"]
headers = service.get("headers", None)
sse_transport = await self.exit_stack.enter_async_context(sse_client(server_url, headers))
self.sse, self.write = sse_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.sse, self.write))
await self.session.initialize()
print(f"重新成功链接到 {mcp_name} 服务")
async def process_query(self, query: str, stream: bool = False):
# self.message.append({"role": "system", "content": self.system_prompt})
self.message.append({"role": "user", "content": query})
response = await self.client.chat.completions.create(
model=self.model,
messages=self.message,
tools=self.available_tools
)
final_text = []
assistant_message = response.choices[0].message
while assistant_message.tool_calls:
for tool_call in assistant_message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
await self.reconnect_sse_server()
result = await self.session.call_tool(tool_name, tool_args)
print(f"calling tools {tool_name},wirh args {tool_args}")
print("Result:", result.content[0].text)
self.message.extend([
{
"role": "assistant",
"content": None,
"tool_calls": [tool_call]
},
{
"role": "tool",
"content": result.content[0].text,
"tool_call_id": tool_call.id
}
])
response = await self.client.chat.completions.create(
model=self.model,
messages=self.message,
tools=self.available_tools,
max_tokens=8048
)
assistant_message = response.choices[0].message
content = assistant_message.content
final_text.append(content)
return "\n".join(final_text)
async def chat_loop(self,stream_mode=True):
self.message = []
while True:
try:
query = input("\nQuery: ").strip()
if query.lower() == 'quit':
break
if query.strip() == '':
continue
response = await self.process_query(query, stream=stream_mode)
print("\nAI:", response)
except Exception as e:
print(f"\nError: {str(e)}")
async def cleanup(self):
await self.exit_stack.aclose()
async def main():
with open("config.json", "r") as f:
config = json.load(f)
client = Stdio_MCPClient(config["llm"]["api_key"], config["llm"]["base_url"], config["llm"]["model"])
try:
await client.connect_to_sse_server(mcp_name="test", server_url="https://127.0.0.1:7860/sse")
await client.chat_loop()
except Exception as e:
print(e)
finally:
await client.cleanup()
if __name__ == '__main__':
asyncio.run(main())
注意: sse链接,我增加了一个reconnect_sse_server 函数,主要原因是sse链接过程中过2分钟会自然断开,不论什么办法都无法处理,因此增加这样一个操作。
(3)版本的自然更新
有了上面两种客户端的连接方法,自然而然结合两个就可以做到同时结合sse和stdio的方法只需要增加一个分别调用的方法即可,后续代码微微改动便可使用。
当然官方的MCP也是在不段更新的,看了官方有发布Streamable HTTP Transport ,这种方式在取代sse,以及通过with来启动执行服务的更新等等,一些简单的更新参考下面,更多更新可以前往github上看
from mcp.server.fastmcp import FastMCP
# Stateful server (maintains session state)
mcp = FastMCP("StatefulServer")
# Stateless server (no session persistence)
mcp = FastMCP("StatelessServer", stateless_http=True)
# Stateless server (no session persistence, no sse stream with supported client)
mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)
# Run server with streamable_http transport
mcp.run(transport="streamable-http")
其余高级用法可参考页面:https://github.com/modelcontextprotocol/python-sdk#advanced-usage
三、典型应用场景
1. MCP 适用场景
- • 企业系统整合 将 CRM/ERP 封装为 MCP 服务,供多个 Agent 安全调用
# MCP 连接数据库示例
@app.post("/mcp")
def query_database(request: dict):
if request["function"] == "get_user_orders":
user_id = request["parameters"]["user_id"]
# 执行SQL查询 (伪代码)
return {"orders": db.query(f"SELECT * FROM orders WHERE user_id={user_id}")} - • 跨平台自动化 组合 GitHub + Slack 的 MCP 服务实现 CI/CD 流程:
# 自动化工作流:提交代码→构建→通知
def ci_cd_pipeline():
call_mcp("github", {"action": "pull_code", "repo": "my-app"})
build_result = call_mcp("jenkins", {"job": "build"})
call_mcp("slack", {"channel": "dev-team", "message": f"构建结果:{build_result}"})
2. Function Call 适用场景
# 简单实时查询(无需MCP)
def get_stock_price(symbol: str):
return yahoo_finance_api(symbol)
# 注册函数到模型
functions = [{
"name": "get_stock_price",
"parameters": {"symbol": {"type": "string"}}
}]
# 模型直接调用
response = model.generate("AAPL当前股价?", functions=functions)
if response.function_call:
print(get_stock_price(response.function_call.arguments["symbol"]))
3. 传统 API 调用
# 直接调用 REST API(无AI参与)
import requests
def fetch_weather(city: str):
response = requests.get(f"https://api.weather.com/v1/{city}")
return response.json()["temperature"]
四、技术选型建议
| 场景 | 推荐方案 | 原因 |
| 简单同步任务(天气/股价查询) | Function Call | 低延迟,与模型紧密集成 |
| 跨系统异步任务(数据分析流水线) | MCP | 标准化协议支持复杂工作流 |
| 企业内部系统暴露服务 | MCP | 统一认证 + 访问控制 |
| 第三方公共服务调用 | API + Function Call | 无需额外协议层 |
关键结论:MCP 的核心价值在于建立企业级 AI 基础设施。当系统需要连接多个异构数据源、要求严格的协议标准化或涉及长周期任务时,MCP 是优于 Function Call 的选择。
如果您对前沿科技、人工智能,尤其是多模态语言模型的应用前景充满好奇,那么这里就是您获取最新资讯、深入解析的绝佳平台。我们不仅分享创新技术,还探讨它们如何塑造我们的未来。
想要不错过任何一篇精彩内容,就请订阅我们的公众号吧!您的关注是我们持续探索和分享的动力。在这里,我们一起揭开AI的神秘面纱,见证科技如何让世界变得更加精彩。

如何使用MCP开发一个客户端和服务端的更多相关文章
- 使用GSoap开发WebService客户端与服务端
Gsoap 编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现, 从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多. 用gsoap开发web service的大致思路 我 ...
- python实现一个客户端与服务端的通信
函数介绍 Socket对象方法: 服务端: 函数 描述 .bind() 绑定地址关键字,AF_INET下以元组的形式表示地址.常用bind((host,port)) .listen() 监听TCP,可 ...
- 二、网络编程-socket之TCP协议开发客户端和服务端通信
知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...
- python网络编程TCP服务多客户端的服务端开发
#服务多客户端TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何 ...
- Java基础---Java---网络编程---TCP的传输、客户端和服务端的互访、建立一个文本转换器、编写一个聊天程序
演示TCP的传输的客户端和服务端的互访 需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息. 客户端: 1.建立Socket服务,指定要连接方朵和端口 2.获取Socket流中的输出流,将数 ...
- SignalR 实现web浏览器客户端与服务端的推送功能
SignalR 是一个集成的客户端与服务器库,基于浏览器的客户端和基于 ASP.NET 的服务器组件可以借助它来进行双向多步对话. 换句话说,该对话可不受限制地进行单个无状态请求/响应数据交换:它将继 ...
- Asp.Net MVC 模型验证详解-实现客户端、服务端双重验证
概要 在asp.net webform开发中经常会对用户提交输入的信息进行校验,一般为了安全起见大家都会在客户端进行Javascript(利于交互).服务端双重校验(安全).书写校验代码是一个繁琐的过 ...
- Android BLE与终端通信(三)——客户端与服务端通信过程以及实现数据通信
Android BLE与终端通信(三)--客户端与服务端通信过程以及实现数据通信 前面的终究只是小知识点,上不了台面,也只能算是起到一个科普的作用,而同步到实际的开发上去,今天就来延续前两篇实现蓝牙主 ...
- 从零讲解搭建一个NIO消息服务端
本文首发于本博客,如需转载,请申明出处. 假设 假设你已经了解并实现过了一些OIO消息服务端,并对异步消息服务端更有兴趣,那么本文或许能带你更好的入门,并了解JDK部分源码的关系流程,正如题目所说,笔 ...
- Netty入门——客户端与服务端通信
Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...
随机推荐
- gorm事务的rollback和commit操作
一个事务内同一操作二次回滚(Rollback)会报错,二次提交(commit)也会报错, 如果回滚完又进行提交操作,一样会报错 循环注意把事务开启tx.Begin放在事务操作前边,操作完回滚或者提交
- php通过Curl给接口上传文件。
在 PHP 中使用 cURL 上传文件至接口,你可以通过 CURLOPT_POSTFIELDS 选项来设置文件的内容.以下是一个示例: function uploadFile($url, $fileP ...
- Apollo批量给新创建的用户授可编辑权限
背景: 我们要在Apollo中批量给新创建的用户授可编辑权限 apollo系统版本: java-2.1.0 管理员账号:Apollo 可编辑账号:guoyabin 过程: 在没写这段代码的时候从网上搜 ...
- Oracle for 循环
Oracle for in loop 循环的一些实例,以作学习和加强使用熟练度及场景应用. 一些技巧 for 语句后面的 loop end loop 可以类比成 c#/java 等编程语言 for 语 ...
- 最爱lx-music的音源哪里去了?
最爱lx-music,让你满心喜欢,可是音源没有了,因为被投诉给全部关了. 公心作者增加了自定义源. 六音提供了音源,做了一件大善事.注意的是音源会一直初始化.那就下载适合的版本: 欣赏阿鲁阿卓如痴如 ...
- DAY2--IMU测量单元&emmc内存
打智能车比赛天天碰到imu这个东西,今天给他弄清楚去 1.IMU介绍 惯性测量单元(Inertial measurement unit,简称 IMU),是测量物体三轴姿态角及加速度的装置.一般IMU包 ...
- 使用Python可视化偶极子的电场
引言 在电学中,偶极子是一个非常重要且有趣的概念.它由两个电荷(一个正电荷和一个负电荷)组成,并且这两个电荷具有相同的大小和相反的符号.偶极子的电场分布具有独特的特点,能够帮助我们深入理解电场的性质. ...
- 阿里云服务器中Linux下centos7.6安装mysql8.0.11
1.下载安装 MySQL最新下载地址:https://dev.mysql.com/downloads/mysql/ 选择的是Linux 64位通用的二级制版本,这样不在需要进行编译安装,系统安装依赖 ...
- kettle介绍-Step之Value Mapper
Value Mapper值映射介绍 值映射步骤是将字符串值从一个值映射为另一个值.值映射步骤提供了一个简单的替代方法,在输入流中选中一个字段,通过字段值设置源值和目标值,再将映射值输出给后续步骤使用. ...
- 在Avalonia/C#中使用依赖注入过程记录
前言 使用依赖注入可以让我们的程序变得更加好维护与测试. 今天分享的是在Avalonia/C#中使用依赖注入. 我准备了一个简单的不使用依赖注入与使用依赖注入的demo. 该demo已上传至GitHu ...