土猛的员外

MCP这么火,来一波简单实操记录

本文由TorchV的流穿(蓝海晨)于2024年底撰写的关于MCP操作的文章。

Image

2024年底,Anthropic发布了 MCP协议。Anthropic就是发布了Claude系列模型的公司(现在gpt基础模型不更新还退步,claude真的很棒)

除去官方的各种美化和包装,这到底是什么?我大胆说一说:这其实是另一种形式的 Function Calling


一、简单解释 Function Calling

大模型可以看作在一个封闭的“盒子”里,无法实时获取外部信息。为了让大模型获取外部数据,我们可以提供一些“方法”,例如:

方法: 获取天气
需要传入的参数: 经度、纬度
返回结果:空气质量、温度、湿度...

在提问时,我们携带一个方法列表,里面写着各种方法。大模型决定调用某个方法时,会输出方法名和参数;该方法在本地执行,并将结果返回给大模型。


二、快速开始

简介

模型上下文协议 (MCP) 是一种开放协议,可实现 LLM 应用与外部数据源和工具之间的无缝集成。

GitHub地址(https://github.com/modelcontextprotocol](https://github.com/modelcontextprotocol)

在此处我们还是跳开这些枯燥的理论,直接上手官方案例(有条件的小伙伴可以亲自试试!)

我是Mac环境、Python语言(TS也支持)

(一)安装包管理器 - uv

执行以下命令安装 uv:

curl -LsSf https://astral.sh/uv/install.sh | sh

提示:安装后需重启终端。如果环境变量写入失败,手动修改 ~/.zshrc:

export PATH="$HOME/.local/bin:$PATH"

吐槽:官方居然选了个刚发布不久的依赖管理工具 uv,坑点一堆。我后续探索下给换成poetry版本的吧

(二)初始化项目

以下是完整步骤:

# 创建项目
uv init weather
cd weather

# 创建虚拟环境
uv venv
source .venv/bin/activate

# 安装依赖
uv add mcp httpx

# 删除示例文件
rm hello.py

# 创建文件
mkdir -p src/weather
touch src/weather/__init__.py
touch src/weather/server.py

(三)编写代码

1. 修改 pyproject.toml

在 pyproject.toml 尾部添加以下内容:

[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[project.scripts]
weather = "weather:main"

2. 编写 __init__.py

文件路径:src/weather/__init__.py

from . import server
import asyncio

defmain():
"""Main entry point for the package."""
    asyncio.run(server.main())

# Optionally expose other important items at package level
__all__ = ['main', 'server']

3. 编写 server.py

文件路径:src/weather/server.py

from typing import Any
import asyncio
import httpx
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio

NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

server = Server("weather")

@server.list_tools()
asyncdefhandle_list_tools() -> list[types.Tool]:
return [
        types.Tool(
            name="get-alerts",
            description="Get weather alerts for a state",
            inputSchema={
"type": "object",
"properties": {
"state": {
"type": "string",
"description": "Two-letter state code (e.g. CA, NY)",
                    },
                },
"required": ["state"],
            },
        ),
        types.Tool(
            name="get-forecast",
            description="Get weather forecast for a location",
            inputSchema={
"type": "object",
"properties": {
"latitude": {
"type": "number",
"description": "Latitude of the location",
                    },
"longitude": {
"type": "number",
"description": "Longitude of the location",
                    },
                },
"required": ["latitude", "longitude"],
            },
        ),
    ]

@server.call_tool()
asyncdefhandle_call_tool(
    name: str, arguments: dict | None
)
 -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:

ifnot arguments:
raise ValueError("Missing arguments")

if name == "get-alerts":
return [
            types.TextContent(
                type="text",
                text="气温38,湿度60%"
            )
        ]
elif name == "get-forecast":
return [types.TextContent(
            type="text",
            text="气温18,湿度10%"
        )]
else:
raise ValueError(f"Unknown tool: {name}")

asyncdefmain():
asyncwith mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="weather",
                server_version="0.1.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

if __name__ == "__main__":
    asyncio.run(main())

这里我做了优化,第四章会讲

(四)运行项目

项目编写完成后,运行以下命令启动服务:

uv run src/weather/server.py

三、使用 Claude for Desktop 测试

第二章我们就完成了代码部分,接下来就要看效果

我前面说:MCP 是 Function Calling 的另一种实现。因此需要一个大模型主动调用。 这里我们使用 Claude for Desktop,它可以作为测试客户端(不是唯一的,你也可以用vscode搭建一个客户端)

Claude for Desktop 下载链接(https://claude.ai/download)

修改配置文件

将第二章写的服务注册到 Claude for Desktop:

vim ~/Library/Application Support/Claude/claude_desktop_config.json

添加如下内容:

{
"mcpServers": {
"weather": {
"command": "uv", //缺少环境变量的话可以写绝对路径
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",# 目录记得改
"run",
"weather"
            ]
        }
    }
}

保存后,重启 Claude for Desktop。

测试 MCP 工具

重新打开 Claude 后,点击工具栏中的锤子图标:

Image
工具列表

基于可以看到我们实现的工具了:

Image
可用工具

输入问题,即可看到 Claude 调用本地服务并返回答案:

Image
调用结果

四、原理解读

(一)过程分析

  1. 我们向 Claude 提问;
  2. Claude 分析可用工具并决定调用某个工具;
  3. 客户端(Claude for Desktop)通过 MCP 协议调用工具;
  4. 结果返回给 Claude;
  5. Claude 根据结果生成答案。

(二)代码核心

代码的核心功能集中在以下两部分:

# 列出工具,供 LLM 选择
@server.list_tools()
asyncdefhandle_list_tools() -> list[types.Tool]:

# 执行方法,解析并处理 LLM 调用请求
@server.call_tool()
asyncdefhandle_call_tool(
    name: str, arguments: dict | None
)
 -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:

注: 示例代码中 handle_call_tool 返回固定回复,是因为原版调用美国国家气象数据局 API,配置较麻烦。实际使用时,可替换为真实 API。

更多问题可参考官方 故障排除文档(https://modelcontextprotocol.io/quickstart#troubleshooting)。

那么到这儿其实就基本能看明白了,后续深入会视情况再推进。