MCP从开发到应用实践(入门篇)

[TOC]

# 介绍

帮助小白快速认识、开发和使用MCP。其实我也是小白哦,若有问题或错误欢迎指出,感谢

## 1. 什么是MCP

MCP(Model Context Protocol)即模型上下文协议,是由 Anthropic 公司于 2024 年底开源发布, 用于标准化应用程序如何向大型语言模型(LLMs)提供上下文。将 MCP 想象成 AI 应用的 USB-C 接口。就像 USB-C 提供了一种标准化的方式来连接设备到各种外设和配件一样,MCP 提供了一种标准化的方式来连接 AI 模型到不同的数据源和工具。

![img](images/20250521-001.png)

## 2. 为什么用MCP

MCP 帮助你在 LLMs 上构建代理和复杂的工作流。LLMs 经常需要与数据和工具集成,而 MCP 提供了:

- 一系列不断增长的预构建集成,你的 LLM 可以直接插入

- MCP 对各个服务的接口进行了统一,这样 M 个 Agent 可以直接使用这 N 个服务,大幅降低重复开发和适配的成本

- 确保你的数据在你的基础设施内最佳实践

- 在 LLM 提供者和供应商之间切换的灵活性

![image-20250521163629012](images/20250521-002.png)

## 3. MCP、智能体、大模型 关系

![img](images/20250521-003.png)

## 4. MCP 架构与组件

- **主机(Host)**:与大模型直接交互,处理大模型输入输出,保障数据准确完整,执行安全策略,处理延迟可降低至平均 50 毫秒以内。通常是 AI 应用(Agent),比如 Anthropic Claude Desktop、Cursor、Cline 等,负责选择并调用 MCP Client,以便使用各种 MCP Server 提供的能力。

- **客户端(Client)**:位于外部系统,将外部需求转为 MCP 协议格式请求,再把主机响应转换为外部系统能理解的格式 ,请求准备时间可缩短至平均 30 毫秒以内,支持多种编程语言和平台。

- **服务器(Server)**:处理主机请求并提供服务,可提供数据查询、文件操作等服务 ,处理能力可达每秒处理 1000 个请求以上,具备灵活扩展机制。提供资源、工具或 Prompts 的服务,如文件系统、数据库、API 等。


  ![img](images/20250521-004.png)

Client 和 Server 之间使用双向的 JSON-RPC 2.0 进行通信。当前支持 stdio 和 Streamable HTTP 两种传输机制。

## 5. MCP关键概念

**Resources  资源:**将服务器中的数据和内容公开给 LLM,

> 它允许服务器公开可由客户端读取并用作 LLM 交互上下文的数据和内容。

**Tools  工具:**使 LLM 能够通过您的服务器执行操作

> 它使服务器能够向客户端公开可执行功能。通过工具,LLM 可以与外部系统交互、执行计算并在现实世界中采取行动。(执行者,API)

**Prompts  提示:** 创建可重用的提示模板和工作流

> 使服务器能够定义可重用的提示模板和工作流,客户端可以轻松地向用户和 LLM 显示这些模板和工作流。它们提供了一种强大的方法来标准化和共享常见的 LLM 交互。

**Sampling  采样:**让您的服务器从 LLM 请求完成

> 它允许服务器通过客户端请求 LLM 完成,从而在维护安全性和隐私性的同时实现复杂的代理行为。

>

> MCP Server 可以发起请求到 MCP Client,再MCP Client 接收到请求后,执行相应的动作,比如:人工确认或者是调用大模型等。

>

> ![image-20250521161220839](images/20250521-005.png)

**Roots  根:**用于定义服务器可以运行的边界。它们为客户端提供了一种将相关资源及其位置通知服务器的方法。

> 根是客户端建议服务器应该关注的 URI。当客户端连接到服务器时,它会声明服务器应该使用哪些根。虽然 root 主要用于文件系统路径,但 root 可以是任何有效的 URI,包括 HTTP URL。

>

> - Project directories      项目目录

> - Repository locations    存储库位置

> - API endpoints            API 终端节点

> - Configuration locations  配置位置

> - Resource boundaries      资源边界

**Transports  运输:**模型上下文协议 (MCP) 中的传输为客户端和服务器之间的通信提供了基础。传输处理消息发送和接收方式的基本机制。

> MCP 使用 [JSON-RPC](https://www.jsonrpc.org/) 2.0 作为其传输格式。传输层负责将 MCP 协议消息转换为 JSON-RPC 格式进行传输,并将收到的 JSON-RPC 消息转换回 MCP 协议消息。

## 6. 通信方式(Transport)

| 通信方式          | 介绍                                                        | 场景                                                        |

| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |

| STDIO              | 标准输入/输出                                                | - 本地集成和命令行工具特别有用;- 构建命令行工具;- 本地集成;- 简单的通信;- 使用 shell 脚本 |

| Server-Sent-Events | 服务端推流,单向                                            | - 需要服务端到客户端的流式传输;- 网络受限                  |

| Streamable HTTP    | 流式 Http,将替换 SSE。[原因](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/206) | 高效灵活,支持更大模块的分布式部署                          |

# 实战

## 1. 构建MCP

### 1.1 前置准备

- 开发工具:

  - vscode

- 开发环境:

  - py 3.13

  - nodejs 18

- 调试工具:

  - modelcontextprotocol/inspector@0.6.0 (依赖node18+)

- 主机

  - Cherry studio

  - Vscode

### 1.2 环境搭建

👉 推荐使用uv https://docs.astral.sh/uv/getting-started/installation/

#### 1.2.1 初始化工程

```Plain

# 初始化框架

uv init mcp-math-demo

# 切换目录

cd mcp-math-demo

# 创建目录(为什么 —> 因为据说是python工程规范)

mkdir -p src/example

# 将生成的main移动到example

mv main.py src/example

```

#### 1.2.2 创建py虚拟环境

```Bash

# 创建虚拟环境(工程中会多一个.venv 文件),并激活环境

uv venv

# 激活环境,mac和window 有点差异,需注意下

source .venv/bin/activate

```

#### 1.2.3 配置pyproject.toml

```Bash

....其他已经存在的配置....

dependencies = [

    "fastmcp>=2.3.4",

    "mcp[cli]>=1.6.0"

]

[project.scripts]

example = "example.main:main"

[[tool.uv.index]]

name = "mypypi"

url = "https://pypi.tuna.tsinghua.edu.cn/simple"

publish-url = "http://your.pypi/"

default = true

....其他已经存在的配置....

```

#### 1.2.4 安装依赖

```Bash

uv pip install -e .

```

#### 1.2.5 运行代码

```Bash

# 执行工程代码

uv run example

# 若成功则打印结果:Hello from mcp-math-demo!

```

### 1.3 快速编码

一下两种方式任选一种,其中Sse方式需要提前启动

#### 1.3.1 Stdio方式

启动命令测试:`uv run src/example/server-math-stdio.py`

```Python

# server-math-stdio.py

from mcp.server.fastmcp import FastMCP

import logging

# 配置日志记录器

logging.basicConfig(

    level=logging.INFO,  # 设置日志级别为 INFO

    format="%(asctime)s - %(levelname)s - %(message)s"  # 日志格式

)

logger = logging.getLogger(__name__)

# 创建 FastMCP 实例

mcp = FastMCP("Math")

@mcp.tool()

def add(a: int, b: int) -> int:

    """Add two numbers"""

    logger.info("The add method is called: a=%d, b=%d", a, b)  # 记录加法调用日志

    return a + b

@mcp.tool()

def multiply(a: int, b: int) -> int:

    """Multiply two numbers"""

    logger.info("The multiply method is called: a=%d, b=%d", a, b)  # 记录乘法调用日志

    return a * b

if __name__ == "__main__":

    logger.info("Start math server through MCP-STDIO")  # 记录服务启动日志

    mcp.run(transport="stdio")  # 启动服务并使用标准输入输出通信

```

#### 1.3.2 Sse方式

👉 该方式需要先启动服务,然后在使用HOST取连接

启动命令测试:`uv run src/example/server-math-stdio.py`

```Python

# server-math-sse.py

from mcp.server.fastmcp import FastMCP

import logging

# 配置日志记录器

logging.basicConfig(

    level=logging.INFO,  # 设置日志级别为 INFO

    format="%(asctime)s - %(levelname)s - %(message)s"  # 日志格式

)

logger = logging.getLogger(__name__)

# 创建 FastMCP 实例

mcp = FastMCP("Math")

@mcp.tool()

def add(a: int, b: int) -> int:

    """Add two numbers"""

    logger.info("The add method is called: a=%d, b=%d", a, b)  # 记录加法调用日志

    return a + b

@mcp.tool()

def multiply(a: int, b: int) -> int:

    """Multiply two numbers"""

    logger.info("The multiply method is called: a=%d, b=%d", a, b)  # 记录乘法调用日志

    return a * b

if __name__ == "__main__":

    logger.info("Start math server through MCP-SSE")  # 记录服务启动日志

    mcp.run(transport="sse")  # 启动服务并使用标准输入输出通信

    #mcp.run(transport="streamable-http")  # 启动服务并使用标准输入输出通信

```

### 1.4 调试MCP

#### 方式一:单独运行调试工具

- 按照插件(需要按照node18+,建议使用nvm)

```Bash

# 打开命令行执行脚本

npx @modelcontextprotocol/inspector@0.6.0

```

Sse

![img](images/20250521-006.png)

Stdio

![img](images/20250521-007.png)

#### 方式二:通过mcp进行调试(实则方式一)

```Bash

## 这种本质上就是方式一

uv run mcp dev server-math-stdio.py

```

## 2. 使用MCP

以**非开发、开发人员**视角演示使用,下面是常用标准的MCP配置

```JSON

{

    ## 数学计算 方式一:stdio --> server-math-stdio.py

    "math-stdio": {

        # 这个也可以是 .venv/bin/python3

        "command": "uv",

        # Replace with absolute path to your server-math-stdio.py file

        "args": ["run","xxxx/src/example/server-math-stdio.py"],

        "transport": "stdio",

    },

    ## 数学计算 方式二:sse  --> server-math-sse.py

    "math-sse": {

        "url":"http://127.0.0.1:8000/sse",

        "transport": "sse"

    },

}

```

### 2.1 Cherry studio

#### 2.1.1 配置模型

这里可以点击硅基流动进行注册获取(新用户有免费额度),或者根据自己实际情况配置

![img](images/20250521-008.png)

#### 2.1.2 配置MCP服务

##### 2.1.2.1 方式一:stdio

![img](images/20250521-009.png)

```Bash

--directory

xxx/mcp-math-demo/src/example/

run

server-math-stdio.py

```

##### 2.1.2.2 方式二:sse

先启动服务:

![img](images/20250521-010.png)

在配置

![img](images/20250521-011.png)

##### 2.1.2.3 查看MCP具体工具

![img](images/20250521-012.png)

#### 2.1.3 选择MCP服务和大模型

![img](images/20250521-013.png)

#### 2.1.4 对话使用MCP

![img](images/20250521-014.png)

### 2.2 Vscode

#### 2.2.1 下载插件

我用的Roo Code,大家可以下载其他插件:  Cline ...

![img](images/20250521-015.png)

#### 2.2.2 配置模型

我这边注册的DeepSeek

![img](images/20250521-022.png)

#### 2.2.3 配置mcp

![img](images/20250521-016.png)

注意:

comand:指定可以运行脚本的命令,环境不要混了

args:参数指定绝对路径

transport: 依据实际,  stdio|sse

#### 2.2.4 对话 (过程中自动批准MCP)

输入3*3

![img](images/20250521-017.png)

### 2.3 Code 代码调用

#### 2.3.1 需要安装依赖

```sh

uv add langchain_mcp_adapters langgraph langchain_deepseek

```

#### 2.3.2  客户端代码 client-langchain.py

```Bash

import asyncio

from langchain_mcp_adapters.client import MultiServerMCPClient

from langgraph.prebuilt import create_react_agent

from example.utils import init_model

## 模型

llm = init_model()

async def main():

    client = MultiServerMCPClient(

        {

            # # 数学计算 sse  --> server-math-sse.py

            "math-sse": {

                "url":"http://127.0.0.1:8000/sse",

                "transport": "sse"

            },

            # 数学计算 stdio --> server-math-stdio.py

            # "math-stdio": {

            #    "command": "python",

            #    "args": ["src/example/server-math-stdio.py"],

            #    "transport": "stdio",

            # }

        }

    )

    tools = await client.get_tools()

    agent = create_react_agent(

        llm,

        tools,

        debug=True

    )

    # 循环接收用户输入

    while True:

        try:

            # 提示用户输入问题

            user_input = input("\n请输入您的问题(或输入 'exit' 退出):")

            if user_input.lower() == "exit":

                print("感谢使用!再见!")

                break

            # 调用代理处理问题

            agent_response = await agent.ainvoke({"messages": [{"role": "user", "content": user_input}]})

            print("\n>>>>>>>>>>>>>>>>>>>>>>> 开始输出结果 >>>>>>>>>>>>>>>>>>>>>>>\n")

            # 调用抽取的方法处理输出结果

            print_optimized_result(agent_response)

            print("\n<<<<<<<<<<<<<<<<<<<<<<< 结果输出完成 <<<<<<<<<<<<<<<<<<<<<<<\n")

        except Exception as e:

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

            continue

# 解析并输出结果

def print_optimized_result(agent_response):

    """

    解析代理响应并输出优化后的结果。

    :param agent_response: 代理返回的完整响应

    """

    messages = agent_response.get("messages", [])

    steps = []  # 用于记录计算步骤

    final_answer = None  # 最终答案

    for message in messages:

        if hasattr(message, "additional_kwargs") and "tool_calls" in message.additional_kwargs:

            # 提取工具调用信息

            tool_calls = message.additional_kwargs["tool_calls"]

            for tool_call in tool_calls:

                tool_name = tool_call["function"]["name"]

                tool_args = tool_call["function"]["arguments"]

                steps.append(f"调用工具: {tool_name}({tool_args})")

        elif message.type == "tool":

            # 提取工具执行结果

            tool_name = message.name

            tool_result = message.content

            steps.append(f"{tool_name} 的结果是: {tool_result}")

        elif message.type == "ai":

            # 提取最终答案

            final_answer = message.content

    # 打印优化后的结果

    print("\n***执行过程***:")

    for step in steps:

        print(f"- {step}")

    if final_answer:

        print(f"\n***最终结果***\n> {final_answer}\n")

if __name__ == "__main__":

    asyncio.run(main())

```

#### 2.3.3 模型工具类utils.py

```python

from langchain_deepseek import ChatDeepSeek

#from langchain.chat_models import init_chat_model

#from langchain_core.language_models import BaseChatModel

def init_model():

    ## 公网-通义模型

    # from langchain_community.chat_models.tongyi import ChatTongyi #dashscope

    # llm = ChatTongyi(

    #    temperature=0,

    #    api_key="sk-XXXX",   

    #    )

    # 公网-Deepseek模型

    llm = ChatDeepSeek(

        model="deepseek-chat",  # 尝试不同的模型名称

        api_key="sk-xxxx"

    )

    ## 不行坑比较多,可能是版本功能问题,初始化后的llm 总是报各种错误

    # llm = load_chat_model(f"{model_provider}/{model_name}",{

    #    "api_key": api_key,

    #    "base_url": base_url,

    #    # enable_auto_tool_choice: True,

    #    # tool_call_parser:True

    # })

    return llm;

```

#### 2.3.4 调试过程

- 启动MCP服务端

![img](images/20250521-018.png)

- 启动MCP客户端并输入问题:3*4

![img](images/20250521-019.png)

调用流程分析:

![img](images/20250521-020.png)

## 3. 常见问题

- python虚拟环境

  - 环境混乱导致包不兼容,命令行中的环境生效

  - 调试工具中的uv环境和工程实际环境不对应

  - ```Bash

    # 推荐(局部)

    uv --directory xxx/mcp-math-demo/src/example run server-math-stdio.py

    # 不推荐(依赖全局)

    uv run xxx/mcp-math-demo/src/example/server-math-stdio.py

- 模型支持

  - mcp 在对话中未调用工具(换模型)基于`langchain_mcp_adapters` 代码实现来,

    > model="deepseek-chat"  *# 成功*

    >

    > model="qwen-turbo"  *# 成功*

  - 自己搭建的模型不行

    > 可以检查一下模型是否支持工具,是否开启工具

- 权限问题

  - window中通过uv执行代码虚拟环境共用出错


    >  可以单独创建,就是不要同时在一个虚拟环境中启动两个MCP

## 4. 相关文档

- MCP

> 官网:https://modelcontextprotocol.io/introduction

- Uv/Uvx:一个帮你高效管理python虚拟环境的工具

> 官网:https://docs.astral.sh/uv/

- SpringAI:提供AI编程框架和解决方案,完美整合Spring体系(java17+)

> - 官网:https://spring.io/projects/spring-ai

> - 案例:https://github.com/spring-projects/spring-ai-examples/

- npx:让你可以无需安装就能运行npm包里的命令,方便快捷地使用各种Node.js工具和库。(node18+)

> - 说明文档:https://www.tkcnn.com/npm/commands/npx.html

>

> 这里用于启动调试工具 `npx @modelcontextprotocol/inspector@0.6.0`

- Nvm: nvm(Node Version Manager)是一个用于管理Node.js版本的工具,可以方便地在不同项目之间切换Node.js版本,确保开发环境的一致性

> - unix/linux: https://github.com/nvm-sh/nvm

> - Window: https://github.com/coreybutler/nvm-windows (记得申请管理员权限,不然安装不了)

## 5. 总结

- 简单认识MCP

  - MCP 解决了大模型与外部工具和数据源集成的标准化问题,类似于硬件领域的 USB-C 标准

  - MCP 架构主要包含三个核心组件:主机(Host)、客户端(Client)和服务器(Server),它们通过 JSON-RPC 协议进行通信

  - MCP 提供了四种关键能力:资源(Resources)、工具(Tools)、提示(Prompts)和采样(Sampling),使大模型能够更高效地与外部系统交互

  - MCP 支持多种通信方式,包括标准输入/输出(STDIO)、服务端推流(SSE)和流式 HTTP,适用于不同的使用场景

- MCP快速入门实战

  - 我们通过构建简单的数学计算 MCP 服务,展示了如何快速开发和部署 MCP 服务,并通过多种方式(Cherry studio、VSCode、自定义代码)调用这些服务。这为开发者提供了清晰的入门指导,帮助理解 MCP 的实际应用流程。

## 6. 附录

- 样例代码:

  - https://gitee.com/W006/mcp-math-demo.git

  - https://github.com/Wen006/mcp-math-demo.git

- https://blog.csdn.net/damoxing0101/article/details/147517195

- https://www.woshipm.com/ai/6198952.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容