golang实现MCP Server

golang实现MCP Server

本文主要展示如何通过golang编写mcp服务,以及如何将多个工具整合在一个mcp中,并且同时支持STDIO和SSE两种方式,不涉及过多的原理性概念性内容~

什么是MCP?

MCP(model context protocol)是由Anthropic(claude的母公司)推出的一套开放协议,协议旨在提供一种大模型与外部工具集成的标准方式。

简单形象的理解就是AI届的USB-C接口。

MCP带来的意义

有了这个协议,理论上我们就能无限强大AI的能力,去根据自己的定制需求扩展AI。

打个比方,今天你想从北京海淀去天安门游玩,在这个过程中你还需要去吃一顿饭,还想去商场买一件衣服。按照现有的大模型能力,他只能基于已有的训练数据告诉你大致的路径,可能的商场和饭店有哪些,然后你根据他的推荐去完成自己目标。但是现在有了MCP,比如前段时间高德导航就发布了自己的MCP服务,此时你将该MCP接入自己的大模型,大模型就会自动发现高德导航这个工具,然后去调用高德导航工具,工具的背后对应的就是高德整个服务资源和应用能力,至此你的大模型就具有了高德导航的能力,他能够帮你精确的导航并规划好从海淀到天安门怎么走,会路过哪些饭店、商场。如果未来大众点评也开发了自己的MCP服务,那我们再将大众点评接入,大模型就能自动为我们匹配出来路过的这些饭店、商场哪一家的评分最高,最终直接帮助我们规划出来一条最佳的路径,既能从海淀到天安门,又能找到最便利,评价最高的食堂、商场。你看,MCP对于LLM来说就是如虎添翼,就像钢铁侠有了贾维斯。并且哪天这条路径修路了你也不用担心,因为MCP都是实时调用的,只要高德更新了这段路况,你的大模型就不会因为知识过旧给出你错误的方案。

总结下MCP带来的意义:

  1. 提供LLM集成外部工具的统一标准,降低了开发者的开发难度

  2. 实时数据更新,支持外部动态服务所以数据每次都是服务提供的最新的

  3. 自动发现工具,LLM可以同时集成很多工具,并且能够根据语义在不同的对话场景中选择合适的工具

我们如何编写一个MCP服务

当前的MCP服务分为两种通信方式,一种是stdio,一种是基于SSE的HTTP通信方式(官方最新的通信方式已经可以不使用SSE了)。

stdio的方式是一种本地通信的方式,这种方式要求大模型和MCP服务必须在同一台计算机中。

基于SSE的HTTP通信方式可以使得MCP服务能够部署在远程,客户端通过HTTP协议去进行访问。

golang实现一个MCP服务

为了简化需求,我们接下来实现一个两个数相加永远等于1的MCP服务。这个例子非常简单,但主要是为了梳理实现一个MCP服务都需要哪些步骤,看官姥爷以后做自己的MCP服务时候只需要把这个无厘头的逻辑封装的方法换成自己实际的业务方法就好了。

该例子会同时实现stdio和sse两种方式的MCP,方便大家直观的了解这两种方案。

话不多说先看代码,具体的逻辑会注释在代码中,最后会对整个流程进行总结概括

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package main

import (
"context"
"errors"
"flag"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)

const (
// 服务常量
serviceName = "test_mcp_server"
serviceVersion = "1.0.0"

// 工具常量
toolName = "get_sum_num"
toolDescription = "Calculate the value of two numbers"
paramNumA = "numA"
paramNumADesc = "The first addition (required)"
paramNumB = "numB"
paramNumBDesc = "The second addition (required)"
)

func main() {
// 定义命令行参数
mode := flag.String("mode", "stdio", "服务模式: stdio, sse, 或 both")
port := flag.String("port", "3001", "HTTP服务器端口 (仅在sse模式下使用)")
flag.Parse()

// 创建MCP服务器
s := server.NewMCPServer(serviceName, serviceVersion)

// 添加工具
tool := mcp.NewTool(toolName,
mcp.WithDescription(toolDescription),
mcp.WithNumber(paramNumA,
mcp.Required(),
mcp.Description(paramNumADesc),
),
mcp.WithNumber(paramNumB,
mcp.Required(),
mcp.Description(paramNumBDesc),
),
)

// 将工具和工具处理逻辑加入MCP服务器
s.AddTool(tool, getValueOfTwoNumbers)

// 根据模式启动相应的服务器
if *mode == "sse" {
// 仅启动HTTP服务器
fmt.Printf("正在启动HTTP服务器,端口: %s...\n", *port)
sseServer := server.NewSSEServer(s)
err := sseServer.Start(":" + *port)
if err != nil {
fmt.Printf("sse服务Start错误: %v\n", err)
}
} else {
// 仅启动stdio服务器
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Stdio服务器错误: %v\n", err)
}
}
}

// 该方法的关键就在于自己业务逻辑处理后将处理后的结果交给mcp,也就是方法的最后一行代码
func getValueOfTwoNumbers(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
paramA, ok := request.Params.Arguments[paramNumA].(float64)
if !ok {
return nil, errors.New("get paramA fail")
}
paramB, ok := request.Params.Arguments[paramNumB].(float64)
if !ok {
return nil, errors.New("get paramB fail")
}

response, err := calculateTwoNumbers(paramA, paramB)
if err != nil {
return nil, err
}

return mcp.NewToolResultText(response), nil
}

// 这个方法没什么逻辑就是不管两数多少都返回1。在现实业务中具体实现时可以根据业务场景替换方法,比如你的业务是请求后段某个接口获取一些数据,那么就可以在这里加一个http客户端去请求服务端拿数据
func calculateTwoNumbers(numA float64, numB float64) (string, error) {
return "1", nil
}


整个逻辑可以分为以下几步:

  1. 创建mcp服务器

  2. 添加工具

  3. 将工具和工具逻辑处理器添加进MCP服务器

  4. 根据用户的选择启动不同方式的MCP服务(基于stdio还是sse的)

其中1,2,4步其实都是固定的套路,作为开发者我们只需要关注工具的创建和具体工具逻辑的实现,也就是代码中的getValueOfTwoNumbers方法和calculateTwoNumbers方法。 calculateTwoNumbers方法就是我们具体的业务逻辑,getValueOfTwoNumbers相当于一个service层用来过渡,当然你也可以把这两个方法融为一个,看个人爱好以及代码分层习惯了。

测试MCP服务

MCP 交互是典型的CS架构,测试MCP服务我们需要有一个客户端。在实际的使用过程中调用方一般是HOST+Client的组合,HOST可以理解为LLM启动连接的应用程序,比如Cursor,Cline等等。Client是用来在HOST应用程序内维护与MCP server之间1:1的连接。

在这里我们简化了client的概念,不引入LLM,直接使用MCP官方提供的MCP检查器。这个检查器可以直接调用MCP服务,列出MCP服务中的工具。如果在这里MCP服务能够正常使用,那么之后不管使用什么大模型的客户端只需要把你的MCP服务添加进去就好了。

mcp server服务启动

项目打包为二进制服务,根据不同参数选择不同的启动方式,如果是stdio的方式则在客户端配置server的启动,如果是sse的方式自己本地把服务跑起来就ok

运行MCP Inspector

执行命令 npx @modelcontextprotocol/inspector(需要安装node相关环境)

配置STDIO的连接方式

从图中可以看出来,mcp client已经能够识别出来我们的mcp server了。stdio的调用方式成功

配置SSE的连接方法

同样的,如果使用Cherry Studio也能够配置成功

至此,就成功的完成了一个MCP Server服务!能够集成大模型完成定制化的需求了(你可以给朋友说你有个非常笨的大模型,算什么算数都是1,hh~)

实现一个MCP服务包含多个工具

上述的mcp中只有一个计算工具,那么我们如何在一个mcp中实现多个工具的集成?达到在MCP Inspector中能够列出多个工具呢?总不能做一个功能就写一个mcp服务占用一个端口吧。接下来将继续实现这个需求。

//todo 作者摸鱼去了

作者

seevae

发布于

2025-04-17

更新于

2025-04-24

许可协议