
【转】使用Go开发MCP服务
✍ 道路千万条,安全第一条。操作不规范,运维两行泪。
MCP是什么
MCP,全名是Model Context Protocol
,它是一个开放的、通用的、有共识的协议标准。
- MCP是标准的协议,就像给AI大模型的一个
万能接口
,让AI模型能够与不同的数据源和工具进行无缝交互。 - MCP 旨在替换碎片化的 Agent 代码集成,从而使 AI 系统更可靠,更有效。通过建立通用标准,服务商可以基于协议来推出它们自己服务的 AI 能力,从而支持开发者更快的构建更强大的 AI 应用。开发者也不需要重复造轮子,通过开源项目可以建立强大的 AI Agent 生态。
- MCP 可以在不同的应用 / 服务之间保持上下文,从而增强整体自主执行任务的能力。
MCP遵循CS架构,包含以下几个核心部分:
- MCP主机:发起请求的AI应用程序,比如聊天机器人,AI驱动的IDE等。
- MCP客户端:在主机程序内部,与MCP服务器保持一对一的连接。
- MCP服务器:为MCP客户端提供上下文、工具和提示信息。
- 本地资源:本地计算机中可供MCP服务器安全访问的资源,如文件、数据库。
- 远程资源:MCP服务器可以连接到的远程资源,如通过API提供的数据。
MCP的工作流程可以简单的概括为以下几个步骤:
1、连接:MCP主机连接到一个或者多个服务器。
2、请求:主机发送请求以获取数据或者执行工具。
3、处理:服务器处理请求,访问相关数据源或外部服务。
4、返回:服务器将结果返回给主机。
5、响应:主机将信息提供给AI模型,用于生成用户响应。
下面,我们来开发一个简单的MCP服务。
MCP服务开发
当前 Go 生态圈有两个比较知名的开发 MCP 的库,一个是mark3labs/mcp-go
,另一个是metoro-io/mcp-golang
,两个使用起来都很简单,这里我们将采用第一个库进行演示。
mcp-go介绍
(1)启动服务
MCP 支持 SSE 和标准输入输出两种类型,一般我们在自己的机器上常用标准输入输出传输方式:
// Create a basic server
s := server.NewMCPServer(
"My Server", // Server name
"1.0.0", // Version
)
// Start the server using stdio
if err := server.ServeStdio(s); err != nil {
log.Fatalf("Server error: %v", err)
}
(2)增加资源
资源是你向 LLMs 暴露数据的方式。它们可以是任何东西:文件、API 响应、数据库查询、系统信息等。资源可以是:
-
静态资源(固定 URI)
-
动态资源(使用 URI 模板)
这是一个静态资源的简单示例:
// Static resource example - exposing a README file
resource := mcp.NewResource(
"docs://readme",
"Project README",
mcp.WithResourceDescription("The project's README file"),
mcp.WithMIMEType("text/markdown"),
)
// Add resource with its handler
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
(3)增加工具
工具让 LLM 通过你的服务器执行操作。与资源不同,工具预期会进行计算并产生副作用。它们类似于 REST API 中的 POST 端点。 下面是一个算术运算的工具示例:
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("Perform basic arithmetic calculations"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("The arithmetic operation to perform"),
mcp.Enum("add", "subtract", "multiply", "divide"),
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("First number"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("Second number"),
),
)
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
op := request.Params.Arguments["operation"].(string)
x := request.Params.Arguments["x"].(float64)
y := request.Params.Arguments["y"].(float64)
var result float64
switch op {
case "add":
result = x + y
case "subtract":
result = x - y
case "multiply":
result = x * y
case "divide":
if y == 0 {
return nil, errors.New("Division by zero is not allowed")
}
result = x / y
}
return mcp.FormatNumberResult(result), nil
})
工具可以用于任何种类的计算:
- Database queries 数据库查询
- File operations 文件操作
- External API calls 外部 API 调用
- Calculations 计算
- System operations 系统操作
每个工具应该:
- 有清晰的描述
- 验证输入
- 优雅处理错误
- 返回结构化的响应
- 使用适当的结果类型
(4)增加提示
下面是一个简单的提示词示例,它需要一个名称参数,然后返回一个问候提示词:
// Simple greeting prompt
s.AddPrompt(mcp.NewPrompt("greeting",
mcp.WithPromptDescription("A friendly greeting prompt"),
mcp.WithArgument("name",
mcp.ArgumentDescription("Name of the person to greet"),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
name := request.Params.Arguments["name"]
if name == "" {
name = "friend"
}
return mcp.NewGetPromptResult(
"A friendly greeting",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(fmt.Sprintf("Hello, %s! How can I help you today?", name)),
),
},
), nil
})
开发一个查询IP地理信息的MCP Tool
首先我们先实现这个 MCP Tool main 函数的主逻辑:
package main
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"github.com/kataras/golog"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create MCP server
s := server.NewMCPServer(
"ip-mcp",
"1.0.0",
)
// Add tool
tool := mcp.NewTool("ip_query",
mcp.WithDescription("query geo location of an IP address"),
mcp.WithString("ip",
mcp.Required(),
mcp.Description("IP address to query"),
),
)
// Add tool handler
s.AddTool(tool, ipQueryHandler)
// Start the stdio server
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
这里我们增加了一个ip_query
查询 IP 信息的工具,描述清楚它的功能是查询 IP 地址的地理位置。 这个工具需要一个参数:待查询的 IP 地址。 然后这个工具具体的实现是ipQueryHandler
函数。 这个 MCP Server 使用标准输入输出传输。
接下来就是ipQueryHandler
函数实现了。先前我实现了一个 IP 地址查询的网站 https://ip.rpcx.io, 所以这里我调用它的 API 进行封装就很简单了。
我将请求的数据以文本的方式返回 MCP Client。 因为这个数据是 JSON 格式,所以 MCP Client 会得到一个 JSON 格式的文本。
func ipQueryHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
ip, ok := request.Params.Arguments["ip"].(string)
if !ok {
return nil, errors.New("ip must be a string")
}
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
golog.Errorf("invalid IP address: %s", ip)
return nil, errors.New("invalid IP address")
}
resp, err := http.Get("https://ip.rpcx.io/api/ip?ip=" + ip)
if err != nil {
golog.Errorf("Error fetching IP information: %v", err)
return nil, fmt.Errorf("Error fetching IP information: %v", err)
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
golog.Errorf("Error reading response body: %v", err)
return nil, fmt.Errorf("Error reading response body: %v", err)
}
return mcp.NewToolResultText(string(data)), nil
}
注意检查参数的合法性,处理程序中的 error。在 2025 年的今天,这些琐碎的事都可以由代码辅助工具如 github copilot 自动完成,我们一路回车即可。
测试MCP服务
我们使用deepchat[3]来测试我们开发的这个 IP 查询的 MCP 工具。 deepchat 已经支持 MCP 了,而且配置起来也非常简单。
在 MCP 配置界面,我们增加一个 MCP Server,配置如下(注意:ip-mcp 工具的实际路径):
然后启动它:
这个时候你应该在对话框窗口看到运行着的 MCP Tool,包括我们的 IP 查询工具:
对话框中测试一把:
测试正常。看起来 deepchat 还会把工具的返回结果交给 deepseek 处理。
这样,我们就简单了实现了一个 MCP Server 并测试成功。可以看到我们可以对既有的 API 或者产品很快的进行封装成 MCP 工具,这也是 baidu 地图、高德地图能够很快推出 MCP Server 的原因。