如何使用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. 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. 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开发一个客户端和服务端的更多相关文章

  1. 使用GSoap开发WebService客户端与服务端

    Gsoap 编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现, 从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多. 用gsoap开发web service的大致思路 我 ...

  2. python实现一个客户端与服务端的通信

    函数介绍 Socket对象方法: 服务端: 函数 描述 .bind() 绑定地址关键字,AF_INET下以元组的形式表示地址.常用bind((host,port)) .listen() 监听TCP,可 ...

  3. 二、网络编程-socket之TCP协议开发客户端和服务端通信

    知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...

  4. python网络编程TCP服务多客户端的服务端开发

    #服务多客户端TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何 ...

  5. Java基础---Java---网络编程---TCP的传输、客户端和服务端的互访、建立一个文本转换器、编写一个聊天程序

    演示TCP的传输的客户端和服务端的互访 需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息. 客户端: 1.建立Socket服务,指定要连接方朵和端口 2.获取Socket流中的输出流,将数 ...

  6. SignalR 实现web浏览器客户端与服务端的推送功能

    SignalR 是一个集成的客户端与服务器库,基于浏览器的客户端和基于 ASP.NET 的服务器组件可以借助它来进行双向多步对话. 换句话说,该对话可不受限制地进行单个无状态请求/响应数据交换:它将继 ...

  7. Asp.Net MVC 模型验证详解-实现客户端、服务端双重验证

    概要 在asp.net webform开发中经常会对用户提交输入的信息进行校验,一般为了安全起见大家都会在客户端进行Javascript(利于交互).服务端双重校验(安全).书写校验代码是一个繁琐的过 ...

  8. Android BLE与终端通信(三)——客户端与服务端通信过程以及实现数据通信

    Android BLE与终端通信(三)--客户端与服务端通信过程以及实现数据通信 前面的终究只是小知识点,上不了台面,也只能算是起到一个科普的作用,而同步到实际的开发上去,今天就来延续前两篇实现蓝牙主 ...

  9. 从零讲解搭建一个NIO消息服务端

    本文首发于本博客,如需转载,请申明出处. 假设 假设你已经了解并实现过了一些OIO消息服务端,并对异步消息服务端更有兴趣,那么本文或许能带你更好的入门,并了解JDK部分源码的关系流程,正如题目所说,笔 ...

  10. Netty入门——客户端与服务端通信

    Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...

随机推荐

  1. gorm事务的rollback和commit操作

    一个事务内同一操作二次回滚(Rollback)会报错,二次提交(commit)也会报错, 如果回滚完又进行提交操作,一样会报错 循环注意把事务开启tx.Begin放在事务操作前边,操作完回滚或者提交

  2. php通过Curl给接口上传文件。

    在 PHP 中使用 cURL 上传文件至接口,你可以通过 CURLOPT_POSTFIELDS 选项来设置文件的内容.以下是一个示例: function uploadFile($url, $fileP ...

  3. Apollo批量给新创建的用户授可编辑权限

    背景: 我们要在Apollo中批量给新创建的用户授可编辑权限 apollo系统版本: java-2.1.0 管理员账号:Apollo 可编辑账号:guoyabin 过程: 在没写这段代码的时候从网上搜 ...

  4. Oracle for 循环

    Oracle for in loop 循环的一些实例,以作学习和加强使用熟练度及场景应用. 一些技巧 for 语句后面的 loop end loop 可以类比成 c#/java 等编程语言 for 语 ...

  5. 最爱lx-music的音源哪里去了?

    最爱lx-music,让你满心喜欢,可是音源没有了,因为被投诉给全部关了. 公心作者增加了自定义源. 六音提供了音源,做了一件大善事.注意的是音源会一直初始化.那就下载适合的版本: 欣赏阿鲁阿卓如痴如 ...

  6. DAY2--IMU测量单元&emmc内存

    打智能车比赛天天碰到imu这个东西,今天给他弄清楚去 1.IMU介绍 惯性测量单元(Inertial measurement unit,简称 IMU),是测量物体三轴姿态角及加速度的装置.一般IMU包 ...

  7. 使用Python可视化偶极子的电场

    引言 在电学中,偶极子是一个非常重要且有趣的概念.它由两个电荷(一个正电荷和一个负电荷)组成,并且这两个电荷具有相同的大小和相反的符号.偶极子的电场分布具有独特的特点,能够帮助我们深入理解电场的性质. ...

  8. 阿里云服务器中Linux下centos7.6安装mysql8.0.11

    1.下载安装 MySQL最新下载地址:https://dev.mysql.com/downloads/mysql/  选择的是Linux 64位通用的二级制版本,这样不在需要进行编译安装,系统安装依赖 ...

  9. kettle介绍-Step之Value Mapper

    Value Mapper值映射介绍 值映射步骤是将字符串值从一个值映射为另一个值.值映射步骤提供了一个简单的替代方法,在输入流中选中一个字段,通过字段值设置源值和目标值,再将映射值输出给后续步骤使用. ...

  10. 在Avalonia/C#中使用依赖注入过程记录

    前言 使用依赖注入可以让我们的程序变得更加好维护与测试. 今天分享的是在Avalonia/C#中使用依赖注入. 我准备了一个简单的不使用依赖注入与使用依赖注入的demo. 该demo已上传至GitHu ...