Skip to content

Commit dc45349

Browse files
authored
增加MCP协议支持 (#9)
* 增加MCP服务器支持 * 增加支持stdio以及编写README
1 parent bb3e86b commit dc45349

10 files changed

+167
-5
lines changed

Diff for: README.md

+24
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,30 @@ brew upgrade cloudsword
5151

5252
完整用法与介绍可以查看 [云鉴使用手册](https://wiki.teamssix.com/cloudsword)
5353

54+
## MCP协议支持
55+
56+
cloudsword 从v0.0.2 版本开始支持MCP协议,支持SSE以及STDIO方式
57+
58+
使用命令 `./cloudsword sse http://localhost:8080` 即可在本地监听8080端口
59+
60+
**SSE模式**
61+
62+
以Chrerry stdio为例 填入 http://localhost:8080/sse 即可获得到工具信息
63+
64+
![image-20250401193340509](./static/image-20250401193340509.png)
65+
66+
**STDIO**
67+
68+
![image-20250401193444375](./static/image-20250401193444375.png)
69+
70+
**使用示例**
71+
72+
![image-20250401194214015](./static/image-20250401194214015.png)
73+
74+
75+
76+
77+
5478
## 集成模块
5579

5680
以下是云鉴目前所支持使用的模块:

Diff for: cmd/mcpServer.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"github.com/mark3labs/mcp-go/mcp"
8+
"github.com/mark3labs/mcp-go/server"
9+
"github.com/wgpsec/cloudsword/utils"
10+
"github.com/wgpsec/cloudsword/utils/global"
11+
"github.com/wgpsec/cloudsword/utils/logger"
12+
"io"
13+
"os"
14+
"strings"
15+
)
16+
17+
func capturePrint(f func()) string {
18+
old := os.Stdout
19+
r, w, _ := os.Pipe()
20+
os.Stdout = w
21+
f() // 调用包含 fmt.Print 的函数
22+
w.Close()
23+
os.Stdout = old
24+
var buf bytes.Buffer
25+
io.Copy(&buf, r)
26+
return buf.String()
27+
}
28+
29+
func fnModel(m global.Module) server.ToolHandlerFunc {
30+
return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
31+
for _, b := range global.GetBasicOptionsWithId(m.ID) {
32+
v, ok := req.Params.Arguments[b.Key].(string)
33+
if !ok && b.Required {
34+
return nil, fmt.Errorf("invalid name parameter%s", b.Key)
35+
}
36+
if b.Key == global.Detail {
37+
value := strings.ToLower(v)
38+
if utils.Contains([]string{"0", "false", "f", "no", "n"}, value) {
39+
v = global.False
40+
} else if utils.Contains([]string{"1", "true", "t", "yes", "y"}, value) {
41+
v = global.True
42+
} else {
43+
logger.Println.Error(fmt.Sprintf("%s 的值类型错误,请设置成 True 或者 False。", b.Key))
44+
}
45+
}
46+
global.UpdateBasicOptionValue(b.Key, v)
47+
fmt.Println(b.Key + " ==> " + v)
48+
}
49+
output := capturePrint(func() {
50+
runModule(getModuleByID(m.ID))
51+
})
52+
53+
return mcp.NewToolResultText(output), nil
54+
}
55+
}
56+
57+
func MCPServer(transport string, addr string) {
58+
s := server.NewMCPServer(
59+
global.Name,
60+
global.Version,
61+
)
62+
modes := Modules()
63+
for _, mode := range modes {
64+
opts := make([]mcp.ToolOption, 0)
65+
// 添加调用model的描述
66+
opts = append(opts, mcp.WithDescription(mode.Desc))
67+
// 批量增加调用model需要的参数的名称、描述、是否必须
68+
for _, opt := range mode.BasicOptions {
69+
optTemp := []mcp.PropertyOption{mcp.Description(opt.Introduce)}
70+
if opt.Required {
71+
optTemp = append(optTemp, mcp.Required())
72+
}
73+
opts = append(opts, mcp.WithString(opt.Key, optTemp...))
74+
}
75+
// 批量把调用的参数和模型绑定函数添加
76+
s.AddTool(mcp.NewTool(mode.Introduce, opts...), fnModel(mode))
77+
}
78+
79+
if transport == "sse" {
80+
port := strings.Split(addr, ":")[2]
81+
sseServer := server.NewSSEServer(s, server.WithBaseURL(addr))
82+
logger.Println.Infof("SSE server listening on :%s", addr)
83+
if err := sseServer.Start(":" + port); err != nil {
84+
logger.Println.Fatalf("Server error: %v\n", err)
85+
}
86+
} else {
87+
if err := server.ServeStdio(s); err != nil {
88+
logger.Println.Fatalf("Server error: %v\n", err)
89+
}
90+
}
91+
92+
}

Diff for: go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/google/go-github/v39 v39.2.0
2121
github.com/hashicorp/go-version v1.7.0
2222
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
23+
github.com/mark3labs/mcp-go v0.17.0
2324
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056
2425
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1056
2526
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.1056
@@ -45,6 +46,7 @@ require (
4546
github.com/clbanning/mxj/v2 v2.5.5 // indirect
4647
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
4748
github.com/google/go-querystring v1.1.0 // indirect
49+
github.com/google/uuid v1.6.0 // indirect
4850
github.com/jmespath/go-jmespath v0.4.0 // indirect
4951
github.com/json-iterator/go v1.1.12 // indirect
5052
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
@@ -66,6 +68,7 @@ require (
6668
github.com/pkg/term v1.2.0-beta.2 // indirect
6769
github.com/rivo/uniseg v0.4.7 // indirect
6870
github.com/tjfoc/gmsm v1.4.1 // indirect
71+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
6972
golang.org/x/crypto v0.21.0 // indirect
7073
golang.org/x/net v0.23.0 // indirect
7174
golang.org/x/sync v0.9.0 // indirect

Diff for: go.sum

+8-3
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
138138
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
139139
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
140140
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
141-
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
142141
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
142+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
143+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
143144
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
144145
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
145146
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@@ -164,6 +165,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
164165
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
165166
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
166167
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
168+
github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930=
169+
github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
167170
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
168171
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
169172
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -224,8 +227,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
224227
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
225228
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
226229
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
227-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
228-
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
230+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
231+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
229232
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056 h1:NmAts73tvzaoo6wrc0eZQvi/2wRAUSABs2Q8ZzaMiII=
230233
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056/go.mod h1:AfhIJqhOKdRAuGvrq9JOWsKMr2WPtPm2mBLgXJlNMIo=
231234
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1056 h1:y29c+VfVtFF5ddlH2lsj4RjKEokBILHjE0W0LSUP9Kc=
@@ -249,6 +252,8 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
249252
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
250253
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
251254
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
255+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
256+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
252257
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
253258
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
254259
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

Diff for: main.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
package main
22

3-
import "github.com/wgpsec/cloudsword/cmd"
3+
import (
4+
"github.com/wgpsec/cloudsword/cmd"
5+
"os"
6+
)
47

58
func main() {
6-
cmd.Run()
9+
if len(os.Args) > 1 {
10+
addr := "http://localhost:8080"
11+
if len(os.Args) > 2 {
12+
addr = os.Args[2]
13+
}
14+
cmd.MCPServer(os.Args[1], addr)
15+
} else {
16+
cmd.Run()
17+
}
18+
719
}

Diff for: static/image-20250401193340509.png

531 KB
Loading

Diff for: static/image-20250401193444375.png

448 KB
Loading

Diff for: static/image-20250401194214015.png

239 KB
Loading

Diff for: utils/global/global.go

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type Module struct {
7373
Name string
7474
ModuleProvider string
7575
Introduce string
76+
Desc string
7677
Level int
7778
Info string
7879
BasicOptions []BasicOptions

Diff for: utils/text.go

+25
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ func GetModule(id, level int, provider, name, moduleProvider, introduce, moreInf
5555
Level: level,
5656
Info: generateDetailedInformation(id, level, getProvider(provider).Name, name, moduleProvider, introduce,
5757
GenerateTable(GetBasicOptions(global.GetBasicOptionsWithId(id))), moreInfo),
58+
Desc: generateMarkdownInformation(id, level, getProvider(provider).Name, name, moduleProvider, introduce,
59+
moreInfo),
5860
BasicOptions: global.GetBasicOptionsWithId(id),
5961
}
6062
}
@@ -72,6 +74,29 @@ func GetBasicOptions(basicOptions []global.BasicOptions) ([]string, [][]string,
7274
return header, rows, []int{15, 8, 40, 50}
7375
}
7476

77+
func generateMarkdownInformation(id, level int, provider, name, moduleProvider, introduce, moreInfo string) string {
78+
79+
return fmt.Sprintf(`
80+
# 介绍
81+
ID: %s
82+
云提供商:%s
83+
名称: %s
84+
推荐评级: %d
85+
模块提供者: %s
86+
模块简介: %s
87+
# 操作
88+
%s
89+
90+
`,
91+
fmt.Sprintf("%d", id),
92+
provider,
93+
name,
94+
level,
95+
moduleProvider,
96+
introduce,
97+
moreInfo)
98+
}
99+
75100
func generateDetailedInformation(id, level int, provider, name, moduleProvider, introduce, basicOptions, moreInfo string) string {
76101
header := []string{"类型", "信息"}
77102
rows := [][]string{}

0 commit comments

Comments
 (0)