chore: 同步本地更改
- 新增 aio-mcp 项目框架 - 新增 .trae/skills/ OCR/SKU 设计工具 - 更新 auto-check 配置和 stress 包 - 删除 fnhelp 文档目录 - 更新 Docker 压力测试脚本
This commit is contained in:
12
aio-mcp/.gitignore
vendored
Normal file
12
aio-mcp/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# 构建产物
|
||||
/aio-mcp
|
||||
/aio-mcp.exe
|
||||
/dist/
|
||||
*.exe
|
||||
|
||||
# 运行时数据
|
||||
/data/
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
100
aio-mcp/README.md
Normal file
100
aio-mcp/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# aio-mcp
|
||||
|
||||
aio-system 的自定义 MCP 服务器(Go)。作为 AI Agent 的「手」,以 MCP 协议对外暴露工具;后续的 SQLite / 文件 / 打印 / 检测等自定义工具都往这里加。
|
||||
|
||||
基于官方 SDK:[github.com/modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk)(v1.7.0)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
aio-mcp/
|
||||
├── main.go # 入口:选择 stdio / http 传输
|
||||
└── internal/
|
||||
├── server/
|
||||
│ └── server.go # 服务器装配(创建实例 + 注册工具)
|
||||
└── tools/
|
||||
├── registry.go # 工具注册总表 RegisterAll
|
||||
├── ping.go # 示例工具:健康检查(无参数)
|
||||
└── echo.go # 示例工具:参数传递(有参数)
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
go build -o aio-mcp.exe . # 构建
|
||||
|
||||
# stdio 模式(默认,供本地 Agent 拉起,如 Reasonix / Claude)
|
||||
aio-mcp.exe
|
||||
|
||||
# http 模式(远程客户端访问)
|
||||
aio-mcp.exe -transport http -addr localhost:8000
|
||||
```
|
||||
|
||||
依赖拉取走内网代理,若需直连官方源:`go env -w GOPROXY=https://proxy.golang.org,direct`。
|
||||
|
||||
## 新增一个工具
|
||||
|
||||
1. 在 `internal/tools/` 新建文件(如 `weather.go`),用参数 struct + 泛型 `mcp.AddTool` 注册:
|
||||
|
||||
```go
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
type weatherArgs struct {
|
||||
City string `json:"city" jsonschema:"城市名,必填"`
|
||||
Days int `json:"days,omitempty" jsonschema:"预报天数,默认 1"`
|
||||
}
|
||||
|
||||
func registerWeather(s *mcp.Server) {
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "weather",
|
||||
Description: "查询某城市未来几天的天气。",
|
||||
}, func(_ context.Context, _ *mcp.CallToolRequest, args weatherArgs) (*mcp.CallToolResult, any, error) {
|
||||
// ...业务逻辑...
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{&mcp.TextContent{Text: "晴,25℃"}},
|
||||
}, nil, nil
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
2. 在 `internal/tools/registry.go` 的 `RegisterAll` 中追加一行:`registerWeather(s)`。
|
||||
|
||||
工具需要共享状态(SQLite 连接等)时,参考 `registry.go` 顶部注释,把 `RegisterAll` 演进为带 `deps` 参数。
|
||||
|
||||
## 冒烟测试
|
||||
|
||||
stdio 模式用换行分隔的 JSON-RPC 直接喂协议消息:
|
||||
|
||||
```bash
|
||||
printf '%s\n' \
|
||||
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}' \
|
||||
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
||||
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
||||
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"你好","times":2}}}' \
|
||||
| aio-mcp.exe
|
||||
```
|
||||
|
||||
http 模式:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/mcp -H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.1"}}}'
|
||||
```
|
||||
|
||||
## 接入 Agent(以 Reasonix / Claude 为例)
|
||||
|
||||
在客户端 MCP 配置中注册,指向构建产物:
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "aio-mcp": { "command": "D:\\workspace\\membank\\aio-mcp\\aio-mcp.exe" } } }
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- stdio 模式下 **stdout 是协议通道**:日志已固定输出到 stderr,业务代码请勿往 stdout 打印。
|
||||
- 工具名只允许 `a-z A-Z 0-9 _ - .`。
|
||||
16
aio-mcp/go.mod
Normal file
16
aio-mcp/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module aio-mcp
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require github.com/modelcontextprotocol/go-sdk v1.7.0
|
||||
|
||||
require (
|
||||
github.com/google/jsonschema-go v0.4.3 // indirect
|
||||
github.com/segmentio/asm v1.1.3 // indirect
|
||||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
24
aio-mcp/go.sum
Normal file
24
aio-mcp/go.sum
Normal file
@@ -0,0 +1,24 @@
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
|
||||
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
|
||||
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
|
||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
|
||||
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
22
aio-mcp/internal/server/server.go
Normal file
22
aio-mcp/internal/server/server.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Package server 负责 MCP 服务器的装配:创建 server 实例并注册全部工具。
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"aio-mcp/internal/tools"
|
||||
)
|
||||
|
||||
// New 创建并装配 aio-mcp 服务器。
|
||||
// version 为服务器实现版本号,随 MCP initialize 握手上报给客户端。
|
||||
func New(version string) *mcp.Server {
|
||||
s := mcp.NewServer(&mcp.Implementation{
|
||||
Name: "aio-mcp",
|
||||
Version: version,
|
||||
}, nil)
|
||||
|
||||
// 注册全部工具。新增工具后在 tools.RegisterAll 里追加即可。
|
||||
tools.RegisterAll(s)
|
||||
|
||||
return s
|
||||
}
|
||||
40
aio-mcp/internal/tools/echo.go
Normal file
40
aio-mcp/internal/tools/echo.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// echoArgs 是 echo 工具的输入参数。
|
||||
// json tag 决定参数名;jsonschema tag 提供描述,供客户端(LLM)理解参数。
|
||||
type echoArgs struct {
|
||||
Message string `json:"message" jsonschema:"要回显的文本,必填"`
|
||||
Times int `json:"times,omitempty" jsonschema:"回显次数,默认 1"`
|
||||
}
|
||||
|
||||
// registerEcho 注册 echo 工具:演示带参数的工具写法。
|
||||
func registerEcho(s *mcp.Server) {
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "echo",
|
||||
Description: "把传入的文本原样回显指定次数,用于演示参数传递与工具调用。",
|
||||
}, func(_ context.Context, _ *mcp.CallToolRequest, args echoArgs) (*mcp.CallToolResult, any, error) {
|
||||
times := args.Times
|
||||
if times <= 0 {
|
||||
times = 1
|
||||
}
|
||||
// 用换行分隔,避免重复文本粘连。
|
||||
line := strings.TrimRight(args.Message, "\n")
|
||||
lines := make([]string, times)
|
||||
for i := range lines {
|
||||
lines[i] = line
|
||||
}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: fmt.Sprintf("%s\n(共 %d 次)", strings.Join(lines, "\n"), times)},
|
||||
},
|
||||
}, nil, nil
|
||||
})
|
||||
}
|
||||
22
aio-mcp/internal/tools/ping.go
Normal file
22
aio-mcp/internal/tools/ping.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// registerPing 注册 ping 工具:无参数健康检查,返回 pong 与服务器时间。
|
||||
func registerPing(s *mcp.Server) {
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "ping",
|
||||
Description: "健康检查。返回 pong 与服务器当前时间,用于确认 aio-mcp 连接正常。",
|
||||
}, func(_ context.Context, _ *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "pong " + time.Now().Format(time.RFC3339)},
|
||||
},
|
||||
}, nil, nil
|
||||
})
|
||||
}
|
||||
26
aio-mcp/internal/tools/registry.go
Normal file
26
aio-mcp/internal/tools/registry.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Package tools 集中管理 aio-mcp 的全部 MCP 工具。
|
||||
//
|
||||
// # 新增工具的固定步骤
|
||||
//
|
||||
// 1. 在本目录新建一个文件(如 weather.go),实现
|
||||
// func registerXXX(s *mcp.Server)
|
||||
// 2. 在 RegisterAll 中追加一行 registerXXX(s)
|
||||
//
|
||||
// 工具通过泛型 mcp.AddTool[In, Out] 注册:输入参数用 struct 定义,
|
||||
// 加 json 与 jsonschema tag(描述、必填),SDK 会自动生成 inputSchema
|
||||
// 并做输入校验,无需手工写 JSON Schema。
|
||||
//
|
||||
// 后续工具需要共享状态(SQLite 连接、配置、http client 等)时,
|
||||
// 把 RegisterAll 演进为 RegisterAll(s *mcp.Server, deps *Deps),
|
||||
// 在 server.New 里构造 deps 传入即可,各工具文件只改签名。
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// RegisterAll 注册所有工具。
|
||||
func RegisterAll(s *mcp.Server) {
|
||||
registerPing(s)
|
||||
registerEcho(s)
|
||||
}
|
||||
53
aio-mcp/main.go
Normal file
53
aio-mcp/main.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// aio-mcp:aio-system 的自定义 MCP 服务器。
|
||||
//
|
||||
// 作为 AI Agent 的「手」,以 MCP 协议对外暴露工具。
|
||||
// 默认以 stdio 传输运行(本地 Agent 直接拉起),也可切换为
|
||||
// streamable HTTP 供远程客户端访问。
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"aio-mcp/internal/server"
|
||||
)
|
||||
|
||||
// version 可用 -ldflags "-X main.version=..." 在构建时覆盖。
|
||||
var version = "0.1.0"
|
||||
|
||||
func main() {
|
||||
transport := flag.String("transport", "stdio", "传输方式:stdio(默认,供本地 Agent 拉起)或 http")
|
||||
addr := flag.String("addr", "localhost:8000", "http 模式下的监听地址")
|
||||
flag.Parse()
|
||||
|
||||
// 日志统一走 stderr:stdio 模式下 stdout 是 MCP 协议通道,绝不能混入日志。
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
|
||||
|
||||
svr := server.New(version)
|
||||
|
||||
switch *transport {
|
||||
case "stdio":
|
||||
slog.Info("aio-mcp starting", "transport", "stdio")
|
||||
// server.Run 会阻塞,直到 stdin 关闭或出错。
|
||||
if err := svr.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
|
||||
slog.Error("stdio server failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case "http":
|
||||
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return svr }, nil)
|
||||
slog.Info("aio-mcp starting", "transport", "http", "addr", *addr)
|
||||
if err := http.ListenAndServe(*addr, handler); err != nil {
|
||||
slog.Error("http server failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "未知 transport: %q(支持: stdio, http)\n", *transport)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user