Skip to content

Commit 151c7d3

Browse files
committed
feat: add MCP client capabilities and server request handling
1 parent 8e018cf commit 151c7d3

File tree

17 files changed

+322
-383
lines changed

17 files changed

+322
-383
lines changed

README.md

Lines changed: 73 additions & 230 deletions
Large diffs are not rendered by default.

client/client.go

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func WithSSETransport(url string) Option {
8787
func WithStreamableHTTPTransport(url string) Option {
8888
return func(c *MCPClient) error {
8989
t := streamable.New(url,
90-
streamable.WithProtocolVersion("2025-03-26"))
90+
streamable.WithProtocolVersion(protocol.MCPVersion))
9191
c.transport = t
9292
return nil
9393
}
@@ -181,6 +181,11 @@ func (c *MCPClient) receiveLoop(ctx context.Context) {
181181
}
182182
}
183183

184+
// 处理服务器发起的请求
185+
if message.Method != "" && !message.IsNotification() {
186+
c.handleServerRequest(ctx, &message)
187+
}
188+
184189
// 处理通知消息
185190
if message.Method != "" && message.IsNotification() {
186191
c.handleNotification(&message)
@@ -189,6 +194,48 @@ func (c *MCPClient) receiveLoop(ctx context.Context) {
189194
}
190195
}
191196

197+
// handleServerRequest 处理服务器发起的请求
198+
func (c *MCPClient) handleServerRequest(ctx context.Context, message *protocol.JSONRPCMessage) {
199+
var response *protocol.JSONRPCMessage
200+
var err error
201+
202+
switch message.Method {
203+
case "sampling/createMessage":
204+
// 处理 LLM 采样请求
205+
response, err = c.handleSamplingRequest(ctx, message)
206+
case "roots/list":
207+
// 处理根目录列表请求
208+
response, err = c.handleRootsListRequest(ctx, message)
209+
default:
210+
// 未知方法,返回方法未找到错误
211+
response = &protocol.JSONRPCMessage{
212+
JSONRPC: protocol.JSONRPCVersion,
213+
ID: message.ID,
214+
Error: &protocol.JSONRPCError{
215+
Code: protocol.MethodNotFound,
216+
Message: fmt.Sprintf("method not found: %s", message.Method),
217+
},
218+
}
219+
}
220+
221+
if err != nil {
222+
response = &protocol.JSONRPCMessage{
223+
JSONRPC: protocol.JSONRPCVersion,
224+
ID: message.ID,
225+
Error: &protocol.JSONRPCError{
226+
Code: protocol.InternalError,
227+
Message: err.Error(),
228+
},
229+
}
230+
}
231+
232+
// 发送响应
233+
if response != nil {
234+
responseBytes, _ := json.Marshal(response)
235+
c.transport.Send(ctx, responseBytes)
236+
}
237+
}
238+
192239
// handleNotification 处理服务端通知
193240
func (c *MCPClient) handleNotification(message *protocol.JSONRPCMessage) {
194241
switch message.Method {
@@ -211,6 +258,40 @@ func (c *MCPClient) handleNotification(message *protocol.JSONRPCMessage) {
211258
}
212259
}
213260

261+
// handleRootsListRequest 处理根目录列表请求
262+
func (c *MCPClient) handleRootsListRequest(ctx context.Context, message *protocol.JSONRPCMessage) (*protocol.JSONRPCMessage, error) {
263+
// 默认返回空的根目录列表
264+
// 实际应用中,客户端应该返回其暴露的文件系统根目录
265+
result := map[string]interface{}{
266+
"roots": []interface{}{},
267+
}
268+
269+
resultBytes, err := json.Marshal(result)
270+
if err != nil {
271+
return nil, err
272+
}
273+
274+
return &protocol.JSONRPCMessage{
275+
JSONRPC: protocol.JSONRPCVersion,
276+
ID: message.ID,
277+
Result: json.RawMessage(resultBytes),
278+
}, nil
279+
}
280+
281+
// handleSamplingRequest 处理 LLM 采样请求
282+
func (c *MCPClient) handleSamplingRequest(ctx context.Context, message *protocol.JSONRPCMessage) (*protocol.JSONRPCMessage, error) {
283+
// 默认返回不支持采样的错误
284+
// 实际应用中,客户端应该将请求转发给 LLM 并返回结果
285+
return &protocol.JSONRPCMessage{
286+
JSONRPC: protocol.JSONRPCVersion,
287+
ID: message.ID,
288+
Error: &protocol.JSONRPCError{
289+
Code: protocol.MethodNotFound,
290+
Message: "sampling not implemented",
291+
},
292+
}, nil
293+
}
294+
214295
// sendRequest 发送请求并等待响应
215296
func (c *MCPClient) sendRequest(ctx context.Context, method string, params interface{}) (*protocol.JSONRPCMessage, error) {
216297
id := uuid.New().String()
@@ -277,7 +358,11 @@ func (c *MCPClient) Initialize(ctx context.Context, clientInfo protocol.ClientIn
277358
initRequest := protocol.InitializeRequest{
278359
ProtocolVersion: c.protocolVersion,
279360
Capabilities: protocol.ClientCapabilities{
280-
// 客户端能力可以根据需要扩展
361+
// 根据 MCP 规范声明客户端能力
362+
Roots: &protocol.RootsCapability{
363+
ListChanged: true, // 支持根目录变更通知
364+
},
365+
Sampling: &protocol.SamplingCapability{}, // 支持 LLM 采样
281366
Experimental: make(map[string]interface{}),
282367
},
283368
ClientInfo: c.clientInfo,

examples/calculator/client/main.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func main() {
2525
defer mcpClient.Close()
2626

2727
// 初始化握手
28-
fmt.Println("🔄 连接到计算器服务...")
28+
fmt.Println("连接到计算器服务...")
2929
initResult, err := mcpClient.Initialize(ctx, protocol.ClientInfo{
3030
Name: "计算器客户端",
3131
Version: "1.0.0",
@@ -34,7 +34,7 @@ func main() {
3434
log.Fatalf("初始化失败: %v", err)
3535
}
3636

37-
fmt.Printf("连接成功!服务器: %s v%s\n",
37+
fmt.Printf("连接成功!服务器: %s v%s\n",
3838
initResult.ServerInfo.Name, initResult.ServerInfo.Version)
3939

4040
// 发送初始化完成通知
@@ -48,15 +48,15 @@ func main() {
4848
log.Fatalf("获取工具列表失败: %v", err)
4949
}
5050

51-
fmt.Println("\n🔧 可用工具:")
51+
fmt.Println("\n可用工具:")
5252
for _, tool := range toolsResult.Tools {
5353
fmt.Printf(" - %s: %s\n", tool.Name, tool.Description)
5454
}
5555
fmt.Println()
5656

5757
// 测试加法
58-
fmt.Println("🧮 测试计算功能:")
59-
result, err := mcpClient.CallTool(ctx, "add", map[string]interface{}{
58+
fmt.Println("测试计算功能:")
59+
result, err := mcpClient.CallTool(ctx, "add", map[string]any{
6060
"a": 5.0,
6161
"b": 3.0,
6262
})
@@ -71,7 +71,7 @@ func main() {
7171
}
7272

7373
// 测试减法
74-
result, err = mcpClient.CallTool(ctx, "subtract", map[string]interface{}{
74+
result, err = mcpClient.CallTool(ctx, "subtract", map[string]any{
7575
"a": 10.0,
7676
"b": 4.0,
7777
})
@@ -86,7 +86,7 @@ func main() {
8686
}
8787

8888
// 测试乘法
89-
result, err = mcpClient.CallTool(ctx, "multiply", map[string]interface{}{
89+
result, err = mcpClient.CallTool(ctx, "multiply", map[string]any{
9090
"a": 6.0,
9191
"b": 7.0,
9292
})
@@ -101,7 +101,7 @@ func main() {
101101
}
102102

103103
// 测试除法
104-
result, err = mcpClient.CallTool(ctx, "divide", map[string]interface{}{
104+
result, err = mcpClient.CallTool(ctx, "divide", map[string]any{
105105
"a": 20.0,
106106
"b": 5.0,
107107
})
@@ -116,7 +116,7 @@ func main() {
116116
}
117117

118118
// 测试除零错误
119-
result, err = mcpClient.CallTool(ctx, "divide", map[string]interface{}{
119+
result, err = mcpClient.CallTool(ctx, "divide", map[string]any{
120120
"a": 20.0,
121121
"b": 0.0,
122122
})
@@ -129,7 +129,7 @@ func main() {
129129
}
130130

131131
// 获取帮助提示模板
132-
fmt.Println("\n💡 获取帮助信息:")
132+
fmt.Println("\n获取帮助信息:")
133133
promptResult, err := mcpClient.GetPrompt(ctx, "calculator_help", nil)
134134
if err != nil {
135135
log.Fatalf("获取提示模板失败: %v", err)
@@ -143,5 +143,5 @@ func main() {
143143
}
144144
}
145145

146-
fmt.Println("\n✨ 计算器演示完成!")
146+
fmt.Println("\n end!")
147147
}

examples/calculator/server/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func main() {
3030
mcp.Tool("add", "两个数字相加").
3131
WithNumberParam("a", "第一个数字", true).
3232
WithNumberParam("b", "第二个数字", true).
33-
Handle(func(ctx context.Context, args map[string]interface{}) (*protocol.CallToolResult, error) {
33+
Handle(func(ctx context.Context, args map[string]any) (*protocol.CallToolResult, error) {
3434
a, ok := args["a"].(float64)
3535
if !ok {
3636
return protocol.NewToolResultError("参数 'a' 必须是数字"), nil
@@ -47,7 +47,7 @@ func main() {
4747
mcp.Tool("subtract", "一个数字减去另一个数字").
4848
WithNumberParam("a", "被减数", true).
4949
WithNumberParam("b", "减数", true).
50-
Handle(func(ctx context.Context, args map[string]interface{}) (*protocol.CallToolResult, error) {
50+
Handle(func(ctx context.Context, args map[string]any) (*protocol.CallToolResult, error) {
5151
a, ok := args["a"].(float64)
5252
if !ok {
5353
return protocol.NewToolResultError("参数 'a' 必须是数字"), nil
@@ -64,7 +64,7 @@ func main() {
6464
mcp.Tool("multiply", "两个数字相乘").
6565
WithNumberParam("a", "第一个数字", true).
6666
WithNumberParam("b", "第二个数字", true).
67-
Handle(func(ctx context.Context, args map[string]interface{}) (*protocol.CallToolResult, error) {
67+
Handle(func(ctx context.Context, args map[string]any) (*protocol.CallToolResult, error) {
6868
a, ok := args["a"].(float64)
6969
if !ok {
7070
return protocol.NewToolResultError("参数 'a' 必须是数字"), nil
@@ -81,7 +81,7 @@ func main() {
8181
mcp.Tool("divide", "一个数字除以另一个数字").
8282
WithNumberParam("a", "被除数", true).
8383
WithNumberParam("b", "除数", true).
84-
Handle(func(ctx context.Context, args map[string]interface{}) (*protocol.CallToolResult, error) {
84+
Handle(func(ctx context.Context, args map[string]any) (*protocol.CallToolResult, error) {
8585
a, ok := args["a"].(float64)
8686
if !ok {
8787
return protocol.NewToolResultError("参数 'a' 必须是数字"), nil

examples/chatbot/client/main.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func main() {
2626
defer mcpClient.Close()
2727

2828
// 执行 MCP 初始化握手
29-
fmt.Println("🔄 连接到聊天机器人服务...")
29+
fmt.Println("连接到聊天机器人服务...")
3030
initResult, err := mcpClient.Initialize(ctx, protocol.ClientInfo{
3131
Name: "聊天机器人客户端",
3232
Version: "1.0.0",
@@ -35,7 +35,7 @@ func main() {
3535
log.Fatalf("初始化失败: %v", err)
3636
}
3737

38-
fmt.Printf("连接成功!服务器: %s v%s\n",
38+
fmt.Printf("连接成功!服务器: %s v%s\n",
3939
initResult.ServerInfo.Name, initResult.ServerInfo.Version)
4040

4141
// 发送初始化完成通知
@@ -52,7 +52,7 @@ func main() {
5252
}
5353

5454
// 获取问候语
55-
result, err := mcpClient.CallTool(ctx, "greeting", map[string]interface{}{
55+
result, err := mcpClient.CallTool(ctx, "greeting", map[string]any{
5656
"name": username,
5757
})
5858
if err != nil {
@@ -88,7 +88,7 @@ func main() {
8888
fmt.Println()
8989

9090
for {
91-
fmt.Print("💬 > ")
91+
fmt.Print("> ")
9292
scanner.Scan()
9393
input := strings.TrimSpace(scanner.Text())
9494

@@ -111,75 +111,75 @@ func main() {
111111

112112
city = strings.TrimSpace(city)
113113
if city == "" {
114-
fmt.Println("请指定城市名称")
114+
fmt.Println("请指定城市名称")
115115
continue
116116
}
117117

118-
result, err := mcpClient.CallTool(ctx, "weather", map[string]interface{}{
118+
result, err := mcpClient.CallTool(ctx, "weather", map[string]any{
119119
"city": city,
120120
})
121121
if err != nil {
122-
fmt.Printf("错误: %v\n", err)
122+
fmt.Printf("错误: %v\n", err)
123123
continue
124124
}
125125

126126
if len(result.Content) > 0 {
127127
if textContent, ok := result.Content[0].(protocol.TextContent); ok {
128-
fmt.Printf("🌤️ %s\n", textContent.Text)
128+
fmt.Printf(" %s\n", textContent.Text)
129129
}
130130
}
131131
} else if strings.Contains(input, " to ") {
132132
// 处理翻译请求
133133
parts := strings.Split(input, " to ")
134134
if len(parts) != 2 || !strings.HasPrefix(parts[0], "translate ") {
135-
fmt.Println("格式错误。请使用: translate [文本] to [zh/en]")
135+
fmt.Println("格式错误。请使用: translate [文本] to [zh/en]")
136136
continue
137137
}
138138

139139
text := strings.TrimSpace(strings.TrimPrefix(parts[0], "translate "))
140140
targetLang := strings.TrimSpace(parts[1])
141141

142142
if text == "" {
143-
fmt.Println("请提供要翻译的文本")
143+
fmt.Println("请提供要翻译的文本")
144144
continue
145145
}
146146

147147
if targetLang != "zh" && targetLang != "en" {
148-
fmt.Println("目标语言必须是 'zh' 或 'en'")
148+
fmt.Println("目标语言必须是 'zh' 或 'en'")
149149
continue
150150
}
151151

152-
result, err := mcpClient.CallTool(ctx, "translate", map[string]interface{}{
152+
result, err := mcpClient.CallTool(ctx, "translate", map[string]any{
153153
"text": text,
154154
"target_lang": targetLang,
155155
})
156156
if err != nil {
157-
fmt.Printf("错误: %v\n", err)
157+
fmt.Printf("错误: %v\n", err)
158158
continue
159159
}
160160

161161
if result.IsError && len(result.Content) > 0 {
162162
if textContent, ok := result.Content[0].(protocol.TextContent); ok {
163-
fmt.Printf("%s\n", textContent.Text)
163+
fmt.Printf("%s\n", textContent.Text)
164164
}
165165
} else if len(result.Content) > 0 {
166166
if textContent, ok := result.Content[0].(protocol.TextContent); ok {
167-
fmt.Printf("🔤 翻译结果: %s\n", textContent.Text)
167+
fmt.Printf("翻译结果: %s\n", textContent.Text)
168168
}
169169
}
170170
} else if input == "help" || input == "帮助" {
171171
// 显示帮助信息
172-
fmt.Println("📖 可用命令:")
172+
fmt.Println("可用命令:")
173173
fmt.Println(" - weather [城市] 或 天气 [城市] - 查看指定城市的天气")
174174
fmt.Println(" - translate [文本] to [zh/en] - 翻译中英文")
175175
fmt.Println(" - help 或 帮助 - 显示此帮助信息")
176176
fmt.Println(" - exit 或 退出 - 退出程序")
177177
} else {
178178
// 未识别的命令
179-
fmt.Printf("❓ 我不理解这个命令: '%s'\n", input)
180-
fmt.Println("💡 请尝试 'weather [城市]'、'translate [文本] to [zh/en]'、'help' 或 'exit'")
179+
fmt.Printf("未知命令: '%s'\n", input)
180+
fmt.Println("请尝试 'weather [城市]'、'translate [文本] to [zh/en]'、'help' 或 'exit'")
181181
}
182182
}
183183

184-
fmt.Println("\n👋 再见!感谢使用聊天机器人!")
184+
fmt.Println("\n end!")
185185
}

0 commit comments

Comments
 (0)