啥是MCP协议
1.1 基本介绍
MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 公司(Claude 大模型的缔造者)于 2024 年 11 月推出的一种开放标准协议,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信方式。核心目标是解决当前 AI 应用开发中的数据孤岛和碎片化集成问题。
1.2 协议特点
在传统的应用服务场景下,API要求对每种服务进行不同的身份验证和集成,也存在不同的数据标准。这就好比如不同的锁需要不同的钥匙。
而MCP 可以被理解为 AI 大模型的"万能接口",类似于 USB-C 接口在硬件领域的作用,它提供了一种标准化的方法,使 AI 模型能够与不同的数据源和工具进行无缝交互。通过 MCP,开发者可以更轻松地构建复杂的 AI 应用,而无需为每个工具或数据源编写专门的集成代码。他有如下特性:
- 标准协议:
AI 模型能够与不同的 API 和数据源无缝交互。
- 建立通用标准:
服务商基于协议来推出它们自己服务的 AI 能力,构建完整的AI生态。
- 上下文会话保持:
在不同的应用/服务之间保持上下文,从而增强整体自主执行任务的能力。
可以理解为 MCP 是将不同任务进行分层处理,每一层都提供特定的能力、描述和限制。而 MCP Client 端根据不同的任务判断,选择是否需要调用某个能力,然后通过每层的输入和输出,构建一个可以处理复杂、多步对话和统一上下文的 Agent。
1.3 AI Agent 和 MCP 间的关系
-
AI Agent 是一个智能系统,它可以自主运行以实现特定目标。传统的 AI 聊天仅提供建议或者需要手动执行任务,AI Agent 则可以分析具体情况,做出决策,并自行采取行动。
-
AI Agent 可以利用 MCP 提供的功能描述来理解更多的上下文,并在各种平台/服务自动执行任务。
1.4 MCP 如何工作
MCP 采用客户端-服务器架构,MCP 架构主要包含以下核心组件:Host、Client 和 Server。
- MCP Hosts:
负责接收用户提问并与 AI 模型交互,充当容器和协调者
- MCP Clients:
与服务器保持 1:1 连接的协议客户端,将Server连接到的客户端中,组成不同功能的应用。
- MCP Servers:
轻量级程序,通过 MCP 原语暴露resources、tools 和prompts,每个程序都通过标准化模型上下文协议公开特定功能,比如你可以把你的日程、待办等构建成MCP Server。
参考上图,MCP 的基本工作流程如下:
-
用户向 AI 模型(MCP 客户端)发送请求
-
AI 模型分析请求,确定需要调用的外部工具或数据
-
AI 模型通过 MCP 协议向相应的 MCP 服务器发送请求
-
MCP 服务器处理请求并返回结果
-
AI 模型整合结果,生成最终回复给用户
2 构建你的MCP Server
目前网上比较火的Golang MCP框架主要是:https://github.com/mark3labs/mcp-go ,当前有4.2K的Star,我们这边基于mcp-go框架构建MCP Server 和 MCP Client。
1. 在Golang项目的终端下获取go-mcp框架
go get github.com/mark3labs/mcp-go
2. 编写一个简单的时间检索服务,根据给定的时区获取当前时间
注释非常清楚,这边就不赘述了
/* 这边把MCP服务下的所有工作都放在 tools 这个package下面 */
package tools
import (
"context"
"fmt"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// CurrentTimeTool 为 MCPServer 添加一个获取当前时间的工具
// 参数:
//
// s *server.MCPServer:指向 MCPServer 实例的指针
funcCurrentTimeTool(s *server.MCPServer) {
// Add tool
tool := mcp.NewTool("current time",
mcp.WithDescription("Get current time with timezone, Asia/Shanghai is default"),
mcp.WithString("timezone",
mcp.Required(),
mcp.Description("current time timezone"),
),
)
// Add tool handler
s.AddTool(tool, currentTimeHandler)
}
// currentTimeHandler 是一个处理函数,用于获取指定时区的当前时间。
//
// 参数:
//
// ctx context.Context: 请求的上下文环境。
// request mcp.CallToolRequest: 包含请求参数的请求结构体。
//
// 返回值:
//
// *mcp.CallToolResult: 包含处理结果的响应结构体指针。
// error: 处理过程中可能发生的错误。
funccurrentTimeHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
timezone, ok := request.Params.Arguments["timezone"].(string)
if !ok {
return mcp.NewToolResultError("timezone must be a string"), nil
}
loc, err := time.LoadLocation(timezone)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("parse timezone with error: %v", err)), nil
}
t := time.Now().In(loc)
result := fmt.Sprintf("当前时间(%s): %s", loc, t.Format("2006-01-02 15:04:05"))
return mcp.NewToolResultText(result), nil
}
3. 在主函数中注册Tools并启用MCP Server
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"archite_mpc_server/conf"
"archite_mpc_server/tools"
"github.com/mark3labs/mcp-go/server"
)
funcmain() {
fmt.Println("Hello, archite_mcp_server!")
// 获取可执行文件所在目录
exePath, err := os.Executable()
if err != nil {
log.Fatalf("获取可执行文件路径失败: %v", err)
}
exeDir := filepath.Dir(exePath)
cfg, err := conf.InitConfig(filepath.Join(exeDir, "conf/app.toml"))
if err != nil {
log.Fatalf("加载配置文件失败: %v", err)
} else {
fmt.Println("配置加载成功,应用名称:", cfg.Base.AppName)
}
// 注册工具并启动服务
registerTools()
}
// registerTools 函数用于注册工具并启动服务
funcregisterTools() {
// Register the tool with the server
// Create MCP server
s := server.NewMCPServer(
"Archite_Mcp_Server 🚀",
"1.0.0",
)
// Register time tools
tools.CurrentTimeTool(s) // 获取时间的MCPServer
// tools.CurrentWeatherTool(s) // 获取天气的MCPServer
// tools.ScheduleTool(s) // 获取行程安排的MCPServer
// Start the stdio server
// 表示通过标准 I/O 进行 RPC 通信
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
到这边为止,我们完成了一个简单的时间MCP Server的创建,我们来解读下代码:
-
通过 mcp.NewTool 定义一个叫做
current time
的 tool,它接受一个叫做timezone
的参数,默认值为Asia/Shanghai
。 -
currentTimeHandler 是一个处理函数,用于获取指定时区的当前时间。
-
server.ServeStdio 表示通过标准 I/O 进行 RPC 通信。
4. 通过 go build 生成一个叫做 archite_mcp_server 的可执行程序,提供给MCP Client调用
go build -v -o archite_mcp_server
3 构建测试使用的MCP Client
构建完MCP Server之后,我们需要调用测试,可以在另一个项目中,我们构建一个Client来测试。
3.1 创建Client
整个过程包含:初始化、建立连接、信息交换、关闭Client
funcmcp_client_init() {
// 初始化MCP客户端
mcpClient, err := client.NewStdioMCPClient("/Users/xxx ... xxx/go/archite_mcp_server", []string{})
if err != nil {
panic(err)
}
// 确保在函数返回前关闭客户端连接
defer mcpClient.Close()
// 设置超时时间
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "Archite_Mcp_Client 🚀",
Version: "1.0.0",
}
// 初始化MCP客户端
initResult, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
panic(err)
}
fmt.Printf("初始化成功,服务器信息: %s %s\n", initResult.ServerInfo.Name, initResult.ServerInfo.Version)
// 调用工具
toolRequest := mcp.CallToolRequest{
Request: mcp.Request{
Method: "tools/call",
},
}
// 配置需要访问的工具名和参数
toolRequest.Params.Name = "current time"
toolRequest.Params.Arguments = map[string]any{
"timezone": "Asia/Shanghai",
}
result, err := mcpClient.CallTool(ctx, toolRequest)
if err != nil {
panic(err)
}
fmt.Println("调用工具结果:", result.Content[0].(mcp.TextContent).Text)
}
3.2 执行结果
~ % go run main.go
Hello, archite!
初始化成功,服务器信息: Archite_Mcp_Server 🚀 1.0.0
调用工具结果: 当前时间(Asia/Shanghai): 2025-04-2412:20:57
3.3 MCP-Go核心方法
Request Method | 发起方 | 响应方 | 描述 |
---|---|---|---|
initialized | Client | Server | 初始化会话 |
tools-list | Client | Server | 发现可用的工具 |
tools/call | Client | Server | 调用工具 |
resources/list | Client | Server | 发现可用的资源 |
resources/read | Client | Server | 获取资源内容 |
resources/templates | Client | Server | 发现可用的参数化资源 |
resources/subscribe | Client | Server | 订阅特定资源,监听其变化事件 |
prompts/list | Client | Server | 发现可用的提示词 |
prompts/get | Client | Server | 获取特定提示词 |
roots/list | Server | Client | 列出服务器有权访问的客户端文件系统根节点(暴露目录和文件) |
sampling/create | Server | Client | 启用服务器的AI生成能力( sampling creation ) |
4 使用MCP Inspector 测试
写Client进行测试也是比较麻烦,MCP官方提供一个交互式调试工具 Inspector,它是专为MCP Server设计的调试利器,支持开发者通过多种方式快速测试与优化服务端功能。
参考Github链接:https://github.com/modelcontextprotocol/inspector
4.1 启用MCP Inspector
~ % npx @modelcontextprotocol/inspector
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
npx启动依赖于你电脑是否安装nodejs,如果没有安装进入以下页面下载安装即可:https://nodejs.org/en
Run起来之后会提供给你一个地址: http://127.0.0.1:6274 ,这个就是 inspector 调试器的地址了。
4.2 使用MCP进行调试
-
这里的
<Command>
是指让MCP服务器端执行起来的命令路由,比如我们刚才上面构建的MCP Server的地址 -
MCP服务器启动需要参数和环境变量,则是可以通过
<Arguments>
方式传递,记不记得我们上面代码中的Context,负责的就是上下文数据传递。
4.3 完整的测试过程
-
完成MCP服务链接之后,可以点击ListTool来获取它的Tools
-
可以看到我们有3个Tools:curren time、current weather、user schedule,其中 curren time 就是我们前面介绍过的
-
输入参数时区:Asia/Shanghai(或者Asia/Tokyo、America/Chicago)
-
点击 Run Tool,可以获取相应时区的时间
5 构建简单的日程智能助手
如果我想实现一个简单的智能行程助手,显然获取时间这样还是不够的,我还需要天气和日程信息,来进行合理的行程安排。
所以我们在原来的MCP Server上补充两个Tool。
5.1 天气信息(+公有云接口)
-
参数是城市中文名
-
调用腾讯云开放的天气接口:🔗 https://market.cloud.tencent.com/products/38348
-
返回完整的天气信息
package tools
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
gourl "net/url"
"strings"
"time"
"archite_mpc_server/conf"
"github.com/hashicorp/go-uuid"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// CurrentWeatherTool 向 MCPServer 添加一个查询当前天气的工具
funcCurrentWeatherTool(s *server.MCPServer) {
// Add tool
tool := mcp.NewTool("current weather",
mcp.WithDescription("Get current weather with area, 北京 is default,需要输入中文城市名称"),
mcp.WithString("city",
mcp.Required(),
mcp.Description("city name, 北京 is default, 中文城市名称"),
),
)
// Add tool handler
s.AddTool(tool, currentWeatherHandler)
}
// currentWeatherHandler 函数处理获取当前天气信息的请求
//
// 参数:
//
// ctx context.Context: 请求的上下文信息
// request mcp.CallToolRequest: 包含请求参数的结构体
//
// 返回值:
//
// *mcp.CallToolResult: 包含返回结果的结构体指针
// error: 错误信息,如果请求成功,则为nil
funccurrentWeatherHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
city, ok := request.Params.Arguments["city"].(string)
if !ok {
return mcp.NewToolResultError("city must be a string"), nil
}
body, err := getWeather(city)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("read response body with error: %v", err)), nil
}
return mcp.NewToolResultText(string(body)), nil
}
// GetWeather 根据城市名称获取天气信息
//
// 参数:
// city string: 城市名称
//
// 返回值:
// ret string: 返回的天气信息
// err error: 如果获取天气信息失败,返回错误信息
funcgetWeather(city string) (ret string, err error) {
// 默认配置值
const (
defaultSecretId = "xxxx"
defaultSecretKey = "xxxxxx"
defaultWeatherLink = "https://ap-guangzhou.cloudmarket-apigw.com/service-6drgk6su/lundear/weather1d"
)
// 访问全局配置信息,使用默认值回退
var secretId, secretKey, weatherLink string
if conf.GlobalConfig != nil {
fmt.Println("应用名称:", conf.GlobalConfig.Base.AppName)
secretId = conf.GlobalConfig.Weather.SecretId
secretKey = conf.GlobalConfig.Weather.SecretKey
weatherLink = conf.GlobalConfig.Weather.WeatherLink
} else {
fmt.Println("警告: 使用默认天气信息配置")
secretId = defaultSecretId
secretKey = defaultSecretKey
weatherLink = defaultWeatherLink
}
// 签名
auth, _, _ := calcAuthorization(secretId, secretKey)
// 请求方法
method := "GET"
reqID, err := uuid.GenerateUUID()
if err != nil {
panic(err)
}
// 请求头
headers := map[string]string{"Authorization": auth, "request-id": reqID}
// 查询参数
queryParams := make(map[string]string)
queryParams["areaCn"] = city
queryParams["areaCode"] = ""
queryParams["ip"] = ""
queryParams["lat"] = ""
queryParams["lng"] = ""
// body参数
bodyParams := make(map[string]string)
bodyParamStr := urlencode(bodyParams)
// url参数拼接
url := weatherLink
iflen(queryParams) > 0 {
url = fmt.Sprintf("%s?%s", url, urlencode(queryParams))
}
bodyMethods := map[string]bool{"POST": true, "PUT": true, "PATCH": true}
var body io.Reader = nil
if bodyMethods[method] {
body = strings.NewReader(bodyParamStr)
headers["Content-Type"] = "application/x-www-form-urlencoded"
}
client := &http.Client{
Timeout: 5 * time.Second,
}
httpRequest, err := http.NewRequest(method, url, body)
if err != nil {
panic(err)
}
for k, v := range headers {
httpRequest.Header.Set(k, v)
}
response, err := client.Do(httpRequest)
if err != nil {
panic(err)
}
defer response.Body.Close()
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
fmt.Println(string(bodyBytes))
returnstring(bodyBytes), nil
}
// calcAuthorization 函数用于计算授权字符串
//
// 参数:
//
// secretId string: 密钥ID
// secretKey string: 密钥
//
// 返回值:
//
// auth string: 授权字符串
// datetime string: 当前时间字符串
// err error: 错误信息
//
// 说明:
//
// 该函数根据密钥ID和密钥计算授权字符串,并返回授权字符串、当前时间字符串和错误信息。
// 授权字符串包含密钥ID、当前时间字符串和签名。签名是通过HMAC-SHA1算法计算得到的。
funccalcAuthorization(secretId string, secretKey string) (auth string, datetime string, err error) {
timeLocation, _ := time.LoadLocation("Etc/GMT")
datetime = time.Now().In(timeLocation).Format("Mon, 02 Jan 2006 15:04:05 GMT")
signStr := fmt.Sprintf("x-date: %s", datetime)
// hmac-sha1
mac := hmac.New(sha1.New, []byte(secretKey))
mac.Write([]byte(signStr))
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
auth = fmt.Sprintf("{\"id\":\"%s\", \"x-date\":\"%s\", \"signature\":\"%s\"}",
secretId, datetime, sign)
return auth, datetime, nil
}
// urlencode 函数将给定的参数映射(map)转换为 URL 编码的查询字符串。
//
// 参数:
//
// params: 一个字符串到字符串的映射,表示需要编码的参数。
//
// 返回值:
//
// 返回参数映射的 URL 编码字符串。
funcurlencode(params map[string]string)string {
var p = gourl.Values{}
for k, v := range params {
p.Add(k, v)
}
return p.Encode()
}
测试效果:
5.2 日程信息(+数据库)
- 参数是用户名
,可以通过context上下文可以传递进来我们当前登录的用户名
-
这边日程表是我们自己的数据库数据,所以这个算私域数据了
-
返回指定用户的日程信息
package tools
import (
"context"
"fmt"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// 用户日程数据结构
type Schedule struct {
Username string
Events []Event
}
type Event struct {
StartTime string
EndTime string
Content string
}
// ScheduleTool 函数用于在MCPServer中添加一个工具,用于获取用户日程。
//
// 参数:
//
// s *server.MCPServer: MCP服务器的指针
funcScheduleTool(s *server.MCPServer) {
// Add tool
tool := mcp.NewTool("user schedule",
mcp.WithDescription("Get someone today schedule, Brand is default"),
mcp.WithString("username",
mcp.Required(),
mcp.Description("Get someone today schedule, Brand is default"),
),
)
// Add tool handler
s.AddTool(tool, userScheduleHandler)
}
// userScheduleHandler 根据用户名查询并返回用户的日程安排。
//
// ctx:上下文对象,用于传递请求信息和取消信号。
// request:请求对象,包含用户请求的参数。
//
// 返回值:
//
// *mcp.CallToolResult:返回用户的日程安排信息。
// error:如果发生错误,则返回错误信息。
funcuserScheduleHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
username, ok := request.Params.Arguments["username"].(string)
if !ok {
return mcp.NewToolResultError("username must be a string"), nil
}
// 通过数据库获取用户日程
schedule, exists := userSchedules.getByName(username)
if !exists {
return mcp.NewToolResultError(fmt.Sprintf("用户 %s 的日程不存在", username)), nil
}
// 格式化输出日程
today := time.Now().Format("2006-01-02")
result := fmt.Sprintf("%s 的今日日程(%s):\n", username, today)
for _, event := range schedule.Events {
result += fmt.Sprintf("%s-%s %s\n", event.StartTime, event.EndTime, event.Content)
}
return mcp.NewToolResultText(result), nil
}
测试效果:
6 结合大模型实现智能应用
MCP Server构建完成之后,当然不是简单的调用他,那样就跟以前的API接口没什么两样,而是要通过接入大模型来实现自动连接、查询、信息处理、问答等,还还能进行联网搜索和深度思考。
🌻 一般的做法是通过MCP Host 对接LLM来完成的
官方首推的 Host 是 Claude 客户端,因为我们在VSCode上进行编码的,所以直接VScode +Cline来实现。
6.1 安装Cline
在 VSCode 搜索 Cline 安装最新版即可
安装完成后,需要做下大模型配置,我这边选用的是qwen-max版本
6.2 安装我们自建的MCP-Server
做好LLM 配置后,我们点击 Cline 顶部第二个按钮( MCP Servers ),即可配置我们的 MCP Server 服务:
我们给这个服务起名 mcp-go-server,并配置对应的command、args信息,Done之后就出现了一个mcp-go-server服务,并且是绿色标识,代表是连接通的
# 附配置代码
{
"mcpServers": {
"mcp-go-server": {
"command": "/~...~/archite_mcp_server",
"args": [],
"env": {}
}
}
}
6.3 调试使用
我输入以下Question之后,它执行相应的处理,分别执行3个MCP Server的检索
-
时间(本地时间计算服务)
-
天气(Remote调用腾讯云的天气接口)
-
日程表(Remote调用日程数据库),这是个模拟的测试数据库
拿到信息之后,进行信息整理,最终返回给我一段符合逻辑性的回复
7 MCP优势及典型应用场景
7.1 MCP 的核心优势
从系统集成和开放集成的角度来看,我认为 MCP会带来以下的突破:
- 打破数据孤岛
:通过统一协议连接异构系统(如本地文档、云服务),减少大量的适配代码开发量。
- 双向动态交互
:支持实时请求-响应和主动通知(如 WebSocket),相比传统 API 的静态交互更灵活。
- 隐私与安全:
a. 数据隔离:敏感操作(如医疗数据处理)在本地 Server 完成,无需向 LLM 提供商暴露密钥。
b. 权限控制:Server 可自主定义访问范围,防止越权操作。 - 开发效率提升
: 开发者只需关注业务逻辑,无需重复实现通信层,例如通过 Python SDK 快速构建天气查询服务。
7.2 MCP 目前比较典型的应用场景
我认为目前 MCP 的一些典型应用场景会在以下几个方面,不过这个应该发展会很快,未来也许会有更为复杂的应用场景出现。
7.2.1 智能助手增强
MCP 可以显著增强智能助手的能力,使其能够:
-
访问实时信息(如天气、新闻、股票价格等)
-
执行复杂计算
-
查询和操作数据库
-
控制外部设备和系统
7.2.2 企业知识管理
在企业环境中,MCP 可以帮助:
-
构建智能知识库
-
实现跨部门数据共享
-
自动化文档处理和分析
-
提供个性化的员工支持