MCP客户端极简教程:快速接入大模型API的完整指南

# MCP客户端极简教程:快速接入大模型API的完整指南


MCP(Model Context Protocol)客户端是连接大模型与外部工具的关键桥梁。本文将手把手教你从零开始构建MCP客户端,实现与大模型API的无缝对接。


## MCP客户端基础架构


理解MCP协议的核心概念是构建客户端的第一步。


```python

# mcp_client/core.py

import json

import asyncio

from typing import Dict, List, Any, Optional

from dataclasses import dataclass

import aiohttp


@dataclass

class MCPRequest:

    """MCP请求数据结构"""

    jsonrpc: str = "2.0"

    id: Optional[str] = None

    method: str = ""

    params: Optional[Dict[str, Any]] = None

    

    def to_dict(self) -> Dict[str, Any]:

        """转换为字典格式"""

<"www.suzhou.gov.cn.felli.cn">

<"www.changsha.gov.cn.felli.cn">

<"www.baoding.gov.cn.felli.cn">

        data = {

            "jsonrpc": self.jsonrpc,

            "method": self.method

        }

        if self.id:

            data["id"] = self.id

        if self.params:

            data["params"] = self.params

        return data


@dataclass

class MCPResponse:

    """MCP响应数据结构"""

    jsonrpc: str

    id: Optional[str]

    result: Optional[Dict[str, Any]] = None

    error: Optional[Dict[str, Any]] = None

    

    @classmethod

    def from_dict(cls, data: Dict[str, Any]) -> 'MCPResponse':

        """从字典创建响应对象"""

        return cls(

            jsonrpc=data.get("jsonrpc", "2.0"),

            id=data.get("id"),

            result=data.get("result"),

            error=data.get("error")

        )

    

    def is_success(self) -> bool:

        """判断请求是否成功"""

        return self.error is None


class MCPClient:

    """MCP客户端核心类"""

    

    def __init__(self, server_url: str):

        self.server_url = server_url

        self.session: Optional[aiohttp.ClientSession] = None

        self.request_id = 0

        

    async def connect(self):

        """连接到MCP服务器"""

        self.session = aiohttp.ClientSession()

        

    async def close(self):

        """关闭连接"""

<"www.handan.gov.cn.felli.cn">

<"www.wuxi.gov.cn.felli.cn">

<"www.qinhuangdao.gov.cn.felli.cn">

        if self.session:

            await self.session.close()

            

    async def send_request(self, method: str, params: Dict[str, Any] = None) -> MCPResponse:

        """发送MCP请求"""

        if not self.session:

            raise RuntimeError("客户端未连接,请先调用connect()方法")

            

        self.request_id += 1

        request = MCPRequest(

            id=str(self.request_id),

            method=method,

            params=params or {}

        )

        

        try:

            async with self.session.post(

                self.server_url,

                json=request.to_dict(),

                headers={"Content-Type": "application/json"}

            ) as response:

                

                if response.status == 200:

                    data = await response.json()

                    return MCPResponse.from_dict(data)

                else:

                    return MCPResponse(

                        jsonrpc="2.0",

                        id=request.id,

                        error={

                            "code": -32000,

                            "message": f"HTTP错误: {response.status}"

                        }

                    )

                    

        except Exception as e:

            return MCPResponse(

                jsonrpc="2.0",

                id=request.id,

                error={

                    "code": -32603,

                    "message": f"请求失败: {str(e)}"

                }

            )

```


## 基础功能实现


实现MCP协议的核心方法,建立与服务器的完整通信。


```python

<"www.dongguan.gov.cn.felli.cn">

<"www.cangzhou.gov.cn.felli.cn">

<"www.langfang.gov.cn.felli.cn">

# mcp_client/basic_operations.py

from .core import MCPClient, MCPResponse

from typing import List, Dict, Any


class BasicMCPClient(MCPClient):

    """基础MCP客户端功能"""

    

    async def initialize(self, client_name: str = "Python MCP Client") -> MCPResponse:

        """初始化连接"""

        params = {

            "protocolVersion": "2024-11-07",

            "capabilities": {

                "roots": {"listChanged": True},

                "tools": {"listChanged": True}

            },

            "clientInfo": {

                "name": client_name,

                "version": "1.0.0"

            }

        }

        return await self.send_request("initialize", params)

    

    async def list_tools(self) -> List[Dict[str, Any]]:

        """获取可用工具列表"""

        response = await self.send_request("tools/list")

        if response.is_success() and response.result:

            return response.result.get("tools", [])

        return []

    

    async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:

        """调用工具"""

        params = {

            "name": tool_name,

            "arguments": arguments

        }

        response = await self.send_request("tools/call", params)

        

        if response.is_success() and response.result:

            return response.result.get("content")

        else:

            error_msg = response.error.get("message", "未知错误") if response.error else "未知错误"

            raise Exception(f"工具调用失败: {error_msg}")

    

    async def list_resources(self) -> List[Dict[str, Any]]:

<"www.shenyang.gov.cn.felli.cn">

<"www.jinzhou.gov.cn.felli.cn">

<"www.yingkou.gov.cn.felli.cn">

        """获取资源列表"""

        response = await self.send_request("resources/list")

        if response.is_success() and response.result:

            return response.result.get("resources", [])

        return []

    

    async def read_resource(self, resource_uri: str) -> Any:

        """读取资源"""

        params = {

            "uri": resource_uri

        }

        response = await self.send_request("resources/read", params)

        

        if response.is_success() and response.result:

            return response.result.get("contents")

        else:

            error_msg = response.error.get("message", "未知错误") if response.error else "未知错误"

            raise Exception(f"资源读取失败: {error_msg}")

```


## 工具调用封装


提供更友好的工具调用接口,简化开发流程。


```python

# mcp_client/tool_wrappers.py

from typing import Dict, Any, List

<"www.changchun.gov.cn.felli.cn">

<"www.haerbin.gov.cn.felli.cn">

<"www.kunming.gov.cn.felli.cn">

import json


class ToolManager:

    """工具管理器"""

    

    def __init__(self, client: BasicMCPClient):

        self.client = client

        self.available_tools: List[Dict[str, Any]] = []

        

    async def refresh_tools(self):

        """刷新工具列表"""

        self.available_tools = await self.client.list_tools()

        

    def get_tool_info(self, tool_name: str) -> Dict[str, Any]:

        """获取工具信息"""

        for tool in self.available_tools:

            if tool.get("name") == tool_name:

                return tool

        raise ValueError(f"工具未找到: {tool_name}")

    

    async def execute_tool(self, tool_name: str, **kwargs) -> Any:

        """执行工具"""

        return await self.client.call_tool(tool_name, kwargs)

    

    def list_tool_names(self) -> List[str]:

        """获取所有工具名称"""

        return [tool.get("name") for tool in self.available_tools]

    

    def describe_tool(self, tool_name: str) -> str:

        """获取工具描述"""

        tool_info = self.get_tool_info(tool_name)

        description = tool_info.get("description", "无描述")

        parameters = tool_info.get("inputSchema", {}).get("properties", {})

        

        param_descriptions = []

        for param_name, param_info in parameters.items():

            param_type = param_info.get("type", "unknown")

            param_desc = param_info.get("description", "无描述")

            param_descriptions.append(f"  - {param_name} ({param_type}): {param_desc}")

        

        params_text = "\n".join(param_descriptions) if param_descriptions else "  无参数"

        

        return f"{description}\n参数:\n{params_text}"


# 预定义工具包装器

<"www.dalian.gov.cn.felli.cn">

<"www.quanzhou.gov.cn.felli.cn">

<"www.shijiazhuang.gov.cn.felli.cn">

class CommonTools:

    """常用工具包装器"""

    

    def __init__(self, tool_manager: ToolManager):

        self.tm = tool_manager

        

    async def web_search(self, query: str, max_results: int = 5) -> str:

        """网络搜索"""

        return await self.tm.execute_tool("web_search", query=query, max_results=max_results)

    

    async def calculator(self, expression: str) -> float:

        """计算器"""

        return await self.tm.execute_tool("calculator", expression=expression)

    

    async def file_read(self, filepath: str) -> str:

        """读取文件"""

        return await self.tm.execute_tool("file_read", filepath=filepath)

    

    async def file_write(self, filepath: str, content: str) -> str:

        """写入文件"""

        return await self.tm.execute_tool("file_write", filepath=filepath, content=content)

    

    async def get_weather(self, location: str) -> Dict[str, Any]:

        """获取天气"""

        return await self.tm.execute_tool("get_weather", location=location)

```


## 完整客户端实现


整合所有功能,提供开箱即用的完整客户端。


```python

# mcp_client/complete_client.py

import asyncio

from typing import Dict, Any, List

from .basic_operations import BasicMCPClient

from .tool_wrappers import ToolManager, CommonTools


class CompleteMCPClient:

    """完整的MCP客户端"""

    

    def __init__(self, server_url: str):

        self.base_client = BasicMCPClient(server_url)

        self.tool_manager = ToolManager(self.base_client)

        self.common_tools = CommonTools(self.tool_manager)

        self.is_connected = False

        

    async def connect(self, client_name: str = "MCP Client"):

        """连接到服务器"""

        await self.base_client.connect()

        

        # 初始化连接

        init_result = await self.base_client.initialize(client_name)

        if not init_result.is_success():

            await self.base_client.close()

            raise ConnectionError(f"初始化失败: {init_result.error}")

            

        # 刷新工具列表

        await self.tool_manager.refresh_tools()

        self.is_connected = True

        

        print(f"✅ 成功连接到MCP服务器")

        print(f"? 可用工具: {len(self.tool_manager.available_tools)}个")

        

    async def close(self):

        """关闭连接"""

        await self.base_client.close()

        self.is_connected = False

<"www.nanning.gov.cn.felli.cn">

<"www.panjin.gov.cn.felli.cn">

<"www.tieling.gov.cn.felli.cn">

        print("? 连接已关闭")

        

    async def list_tools(self) -> List[str]:

        """列出所有工具"""

        if not self.is_connected:

            raise RuntimeError("客户端未连接")

        return self.tool_manager.list_tool_names()

    

    async def get_tool_help(self, tool_name: str) -> str:

        """获取工具帮助信息"""

        if not self.is_connected:

            raise RuntimeError("客户端未连接")

        return self.tool_manager.describe_tool(tool_name)

    

    async def execute(self, tool_name: str, **kwargs) -> Any:

        """执行工具"""

        if not self.is_connected:

            raise RuntimeError("客户端未连接")

        return await self.tool_manager.execute_tool(tool_name, **kwargs)

    

    # 便捷方法

    async def search(self, query: str, max_results: int = 5) -> str:

        """搜索(便捷方法)"""

        return await self.common_tools.web_search(query, max_results)

    

    async def calculate(self, expression: str) -> float:

        """计算(便捷方法)"""

        return await self.common_tools.calculator(expression)

    

    async def read_file(self, filepath: str) -> str:

        """读取文件(便捷方法)"""

        return await self.common_tools.file_read(filepath)

    

    async def write_file(self, filepath: str, content: str) -> str:

        """写入文件(便捷方法)"""

        return await self.common_tools.file_write(filepath, content)


# 上下文管理器支持

class MCPClientSession:

    """MCP客户端会话(上下文管理器)"""

    

    def __init__(self, server_url: str, client_name: str = "MCP Client"):

        self.client = CompleteMCPClient(server_url)

        self.client_name = client_name

        

    async def __aenter__(self):

        await self.client.connect(self.client_name)

        return self.client

        

    async def __aexit__(self, exc_type, exc_val, exc_tb):

        await self.client.close()

<"www.taiyuan.gov.cn.felli.cn">

<"www.jilin.gov.cn.felli.cn">

<"www.siping.gov.cn.felli.cn">

```


## 使用示例和演示


通过具体示例展示客户端的使用方法。


```python

# examples/basic_usage.py

import asyncio

import sys

import os


# 添加客户端路径

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))


from mcp_client.complete_client import CompleteMCPClient, MCPClientSession


async def demo_basic_operations():

    """基础操作演示"""

    print("? MCP客户端基础操作演示")

    print("=" * 50)

    

    # 方法1:使用上下文管理器(推荐)

    async with MCPClientSession("http://localhost:8080/mcp") as client:

        # 列出所有工具

        tools = await client.list_tools()

        print(f"? 可用工具: {tools}")

        

        # 获取工具帮助

        if tools:

            help_text = await client.get_tool_help(tools[0])

            print(f"? 工具帮助:\n{help_text}")

            

        # 执行计算

        try:

            result = await client.calculate("(125 + 37) * 2")

            print(f"? 计算结果: {result}")

        except Exception as e:

            print(f"❌ 计算失败: {e}")

    

    print("演示完成!")


async def demo_advanced_usage():

    """高级用法演示"""

    print("\n? MCP客户端高级用法演示")

    print("=" * 50)

    

    # 方法2:手动管理连接

    client = CompleteMCPClient("http://localhost:8080/mcp")

    

    try:

        await client.connect("高级演示客户端")

        

        # 批量执行多个工具

        tasks = [

            client.search("人工智能最新发展"),

            client.calculate("3.14 * 25"),

            # 添加更多任务...

        ]

        

        results = await asyncio.gather(*tasks, return_exceptions=True)

        

        for i, result in enumerate(results):

            if isinstance(result, Exception):

                print(f"❌ 任务 {i+1} 失败: {result}")

            else:

                print(f"✅ 任务 {i+1} 成功: {result}")

                

    finally:

        await client.close()


async def demo_custom_tools():

<"www.liaoyuan.gov.cn.felli.cn">

<"www.mudanjiang.gov.cn.felli.cn">

<"www.yantai.gov.cn.felli.cn">

    """自定义工具演示"""

    print("\n? 自定义工具演示")

    print("=" * 50)

    

    async with MCPClientSession("http://localhost:8080/mcp") as client:

        # 查看所有工具

        tools = await client.list_tools()

        print("可用工具:", tools)

        

        # 尝试执行各种工具

        for tool_name in tools:

            try:

                print(f"\n尝试执行工具: {tool_name}")

                help_text = await client.get_tool_help(tool_name)

                print(f"工具描述: {help_text}")

                

                # 根据工具类型执行不同的参数

                if "search" in tool_name.lower():

                    result = await client.execute(tool_name, query="Python编程")

                    print(f"搜索结果: {result}")

                    

                elif "calc" in tool_name.lower() or "math" in tool_name.lower():

                    result = await client.execute(tool_name, expression="2 + 3 * 4")

                    print(f"计算结果: {result}")

                    

                elif "file" in tool_name.lower() and "read" in tool_name.lower():

                    result = await client.execute(tool_name, filepath="example.txt")

                    print(f"文件内容: {result}")

                    

            except Exception as e:

                print(f"工具 {tool_name} 执行失败: {e}")


# 交互式客户端

async def interactive_client():

    """交互式客户端"""

    print("? MCP交互式客户端")

    print("=" * 50)

    print("输入 'help' 查看帮助, 'exit' 退出")

    

    client = CompleteMCPClient("http://localhost:8080/mcp")

    

    try:

        await client.connect("交互式客户端")

        

        while True:

            try:

                user_input = input("\n? 请输入命令: ").strip()

                

                if user_input.lower() in ['exit', 'quit']:

                    break

                elif user_input.lower() == 'help':

                    print_help()

                elif user_input.lower() == 'list':

                    tools = await client.list_tools()

                    print("可用工具:", tools)

                elif user_input.startswith('help '):

                    tool_name = user_input[5:].strip()

                    help_text = await client.get_tool_help(tool_name)

                    print(help_text)

                elif user_input.startswith('search '):

                    query = user_input[7:].strip()

                    result = await client.search(query)

                    print(f"搜索结果: {result}")

                elif user_input.startswith('calc '):

                    expression = user_input[5:].strip()

                    result = await client.calculate(expression)

                    print(f"计算结果: {result}")

                else:

                    print("未知命令,输入 'help' 查看可用命令")

                    

            except KeyboardInterrupt:

                break

            except Exception as e:

                print(f"错误: {e}")

                

    finally:

        await client.close()


def print_help():

    """打印帮助信息"""

    help_text = """

可用命令:

  help          - 显示此帮助信息

  list          - 列出所有可用工具

  help <工具名> - 显示特定工具的帮助

  search <查询> - 执行搜索

  calc <表达式> - 执行计算

  exit          - 退出程序

    """

    print(help_text)


# 主函数

async def main():

    """主函数"""

    if len(sys.argv) > 1:

        if sys.argv[1] == "interactive":

            await interactive_client()

        elif sys.argv[1] == "demo":

            await demo_basic_operations()

            await demo_advanced_usage()

            await demo_custom_tools()

        else:

            print("用法: python -m examples.basic_usage [interactive|demo]")

    else:

        await demo_basic_operations()


if __name__ == "__main__":

<"www.tangshan.gov.cn.felli.cn">

<"www.changzhou.gov.cn.felli.cn">

<"www.nantong.gov.cn.felli.cn">

    asyncio.run(main())

```


## 错误处理和调试


提供完善的错误处理和调试功能。


```python

# mcp_client/debug_utils.py

import logging

from typing import Dict, Any

from .core import MCPClient, MCPResponse


# 配置日志

logging.basicConfig(

    level=logging.INFO,

    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

)

logger = logging.getLogger("MCPClient")


class DebugMCPClient(MCPClient):

    """带调试功能的MCP客户端"""

    

    async def send_request(self, method: str, params: Dict[str, Any] = None) -> MCPResponse:

        """发送请求(带调试信息)"""

        logger.info(f"? 发送请求: {method}")

        logger.debug(f"请求参数: {params}")

        

        start_time = asyncio.get_event_loop().time()

        response = await super().send_request(method, params)

        end_time = asyncio.get_event_loop().time()

        

        logger.info(f"? 收到响应: {method} (耗时: {(end_time - start_time)*1000:.2f}ms)")

        

        if response.is_success():

            logger.debug(f"响应结果: {response.result}")

        else:

            logger.error(f"请求失败: {response.error}")

            

        return response


def enable_debug_logging():

    """启用调试日志"""

    logging.getLogger("MCPClient").setLevel(logging.DEBUG)

    

def create_client_with_retry(server_url: str, max_retries: int = 3):

    """创建带重试机制的客户端"""

    class RetryMCPClient(DebugMCPClient):

        async def send_request_with_retry(self, method: str, params: Dict[str, Any] = None) -> MCPResponse:

            """带重试的请求发送"""

            for attempt in range(max_retries):

                try:

                    response = await self.send_request(method, params)

                    if response.is_success():

                        return response

                    elif attempt < max_retries - 1:

                        wait_time = 2 ** attempt  # 指数退避

                        logger.warning(f"请求失败,{wait_time}秒后重试...")

                        await asyncio.sleep(wait_time)

                except Exception as e:

                    if attempt < max_retries - 1:

                        wait_time = 2 ** attempt

                        logger.warning(f"请求异常,{wait_time}秒后重试: {e}")

                        await asyncio.sleep(wait_time)

                    else:

                        raise

            return response

            

    return RetryMCPClient(server_url)


# 使用示例

async def debug_demo():

    """调试演示"""

    # 启用调试日志

    enable_debug_logging()

    

    # 创建带重试的客户端

    client = create_client_with_retry("http://localhost:8080/mcp")

    

    try:

        await client.connect()

        

        # 使用重试机制发送请求

        response = await client.send_request_with_retry("tools/list")

        if response.is_success():

            print("工具列表获取成功!")

            

    finally:

        await client.close()

```


通过本文的完整指南,你可以快速构建功能完善的MCP客户端,实现与大模型API的高效交互。这个极简客户端提供了完整的MCP协议支持、友好的工具调用接口和强大的调试功能,是接入大模型生态的理想起点。



请使用浏览器的分享功能分享到微信等