Hello,欢迎来到流穿的AI探索之路系列专栏,作为一名AI应用工程师,我会在这儿更新一些前沿技术,欢迎关注哦
2024年底,Anthropic发布了 MCP协议。Anthropic就是发布了Claude系列模型的公司(现在gpt基础模型不更新还退步,claude真的很棒)
除去官方的各种美化和包装,这到底是什么?我大胆说一说:这其实是另一种形式的 Function Calling
简单看下效果:
一、简单解释 Function Calling
大模型可以看作在一个封闭的“盒子”里,无法实时获取外部信息。为了让大模型获取外部数据,我们可以提供一些“方法”,例如:
方法: 获取天气
需要传入的参数: 经度、纬度
返回结果:空气质量、温度、湿度...
在提问时,我们携带一个方法列表,里面写着各种方法。大模型决定调用某个方法时,会输出方法名和参数;该方法在本地执行,并将结果返回给大模型。
二、快速开始
简介
模型上下文协议 (MCP) 是一种开放协议,可实现 LLM 应用与外部数据源和工具之间的无缝集成。
GitHub地址
在此处我们还是跳开这些枯燥的理论,直接上手官方案例(有条件的小伙伴可以亲自试试!)
我是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
def main():
"""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()
async def handle_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()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
if not 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}")
async def main():
async with 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:
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 后,点击工具栏中的锤子图标:
基于可以看到我们实现的工具了:
输入问题,即可看到 Claude 调用本地服务并返回答案:
四、原理解读
(一)过程分析
- 我们向 Claude 提问;
- Claude 分析可用工具并决定调用某个工具;
- 客户端(Claude for Desktop)通过 MCP 协议调用工具;
- 结果返回给 Claude;
- Claude 根据结果生成答案。
(二)代码核心
代码的核心功能集中在以下两部分:
# 列出工具,供 LLM 选择
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
# 执行方法,解析并处理 LLM 调用请求
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
注: 示例代码中
handle_call_tool
返回固定回复,是因为原版调用美国国家气象数据局 API,配置较麻烦。实际使用时,可替换为真实 API。
更多问题可参考官方 故障排除文档。
那么到这儿其实就基本能看明白了,后续深入会视情况再推进