使用Go开发MCP Server, 太简单了!
快速掌握Go语言开发MCP Server,从入门到实践。
核心内容:
- Go生态中MCP库的选择与比较
- 使用mark3labs/mcp-go库快速搭建MCP服务
- 资源与工具的添加,实现数据暴露与LLM执行

好,不绕弯子,假设你对MCP(Model Context Protocol)的背景已经心里有数。目前Go生态里有两款比较知名的MCP开发库:一个是mark3labs/mcp-go[1],另一个是metoro-io/mcp-golang[2]。两个用起来都很顺手,这次咱们按star数量选第一个来做演示。
mcp-go 入门
mark3labs/mcp-go这个库提供了丰富的示例代码,而且README里已经把每种MCP Tool类型的写法都列出来了,算是很贴心的入门指南。
启动服务
MCP支持SSE和标准输入输出两种通信方式。平时在自己机器上开发测试,标准输入输出(stdio)是最常见的用法:
// 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)
}
几行代码就能跑起一个MCP服务,没什么复杂的。
增加资源
资源是你向LLM暴露数据的方式。它们可以来自任何地方:文件、API响应、数据库查询、系统信息等等。资源分两种:
- 静态资源(固定URI)
- 动态资源(使用URI模板)
下面是一个静态资源的例子——暴露一个README文件:
// 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
})
看到没?定义资源的时候指定一个URI和MIME类型,再写一个handler读取真实数据,一个数据暴露通道就打通了。
增加工具
工具让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
})
工具的用途远不止计算,它可以做很多事情:
- 数据库查询
- 文件操作
- 外部API调用
- 计算
- 系统操作
每个工具设计时都应该注意:
- 有清晰的描述
- 验证输入
- 优雅处理错误
- 返回结构化的响应
- 使用适当的结果类型
增加提示
提示(Prompt)是预定义的模板,LLM会按模板和用户进行交互。下面是一个问候提示的例子,它需要一个名字参数,然后返回一段问候语:
// 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
理论知识够用了,来一个真正能用的东西——查询IP地址地理位置的MCP工具。
先写出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地址。具体逻辑交给ipQueryHandler函数去实现。通信方式选的是标准输入输出,本地测试最方便。
接下来是ipQueryHandler的实现。之前我搭过一个IP地址查询网站 https://ip.rpcx.io,直接调用它的API,封装起来非常简单。
请求到的数据(JSON格式)直接以文本方式返回给MCP Client。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
}
注意参数合法性检查和错误处理这些常规操作。2025年了,这类琐事交给GitHub Copilot这类工具自动补全,一路回车就行。
编译成可执行二进制文件:
go build -o ip-mcp
你可以把它放在系统目录里方便调用,也可以不放在系统目录——后面配置MCP Server时需要指定它的路径。
测试MCP Server
测试工具我们选用deepchat[3],它已经原生支持MCP,配置起来极其简单。
在MCP配置界面,新增一个MCP Server,配置如下(注意把ip-mcp工具的路径换成你本地的实际路径):
然后启动它:
此时在对话框窗口应该能看到运行着的MCP Tool列表,我们刚写的IP查询工具就在其中:
直接打一句“查询一下8.8.8.8的地理位置”试试——
测试正常。而且看起来deepchat还会把工具的返回结果自动交给deepseek进一步处理,体验很流畅。
就这样,一个简单的MCP Server实现了,并且跑通了全流程。可以看到,我们可以非常快速地把已有的API或产品封装成MCP工具——这也是百度地图、高德地图能迅速推出MCP Server的原因。