0908
This commit is contained in:
@@ -8,67 +8,120 @@ aio-system 的自定义 MCP 服务器(Go)。作为 AI Agent 的「手」,
|
|||||||
|
|
||||||
```
|
```
|
||||||
aio-mcp/
|
aio-mcp/
|
||||||
├── main.go # 入口:选择 stdio / http 传输
|
├── cmd/
|
||||||
|
│ └── aio-mcp/
|
||||||
|
│ └── main.go # 入口:解析配置 → 初始化日志 → 装配并启动服务器
|
||||||
└── internal/
|
└── internal/
|
||||||
|
├── config/
|
||||||
|
│ └── config.go # 启动配置:transport / addr / log-level / data-dir
|
||||||
|
├── logging/
|
||||||
|
│ └── logging.go # 日志初始化(固定输出 stderr,详见注意事项)
|
||||||
|
├── deps/
|
||||||
|
│ └── deps.go # 工具共享依赖容器(数据目录,后续 SQLite 连接等)
|
||||||
├── server/
|
├── server/
|
||||||
│ └── server.go # 服务器装配(创建实例 + 注册工具)
|
│ ├── server.go # 服务器装配:创建实例 + 注册工具 + 注入依赖
|
||||||
|
│ └── serve.go # 传输层:stdio / streamable HTTP 的启动逻辑
|
||||||
└── tools/
|
└── tools/
|
||||||
├── registry.go # 工具注册总表 RegisterAll
|
├── registry.go # 工具注册表:按业务域分组,统一 RegisterAll
|
||||||
├── ping.go # 示例工具:健康检查(无参数)
|
└── system/ # 工具组:系统类
|
||||||
└── echo.go # 示例工具:参数传递(有参数)
|
├── system.go # 组内注册入口
|
||||||
|
├── ping.go # 健康检查(无参数)
|
||||||
|
└── echo.go # 参数传递演示(有参数)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
分层约定:
|
||||||
|
|
||||||
|
- `cmd/aio-mcp`:只有一个可执行文件,所有编排逻辑放在 `internal/` 下,`main.go` 保持极薄。
|
||||||
|
- `internal/config` / `internal/logging`:与 MCP 无关的横切能力,独立成包便于加测试。
|
||||||
|
- `internal/deps`:**共享依赖不放在 `internal/tools`**——工具子包要 import 它,若放在 `tools` 会与 `registry.go` 形成循环依赖。
|
||||||
|
- `internal/tools/<组>`:一个业务域一个子包,组内工具各自一个文件;注册入口统一叫 `Register(s *mcp.Server, d *deps.Deps)`。
|
||||||
|
- 暂无 `pkg/`:当前没有需要对外复用的公共 API。若将来要被 membank 其他 Go 项目(如 auto-check)直接 import,再新增 `pkg/` 并把稳定接口下沉过去。
|
||||||
|
|
||||||
## 运行
|
## 运行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -o aio-mcp.exe . # 构建
|
go build -o aio-mcp.exe ./cmd/aio-mcp # 构建
|
||||||
|
|
||||||
# stdio 模式(默认,供本地 Agent 拉起,如 Reasonix / Claude)
|
# http 模式(默认,streamable HTTP,远程客户端访问)
|
||||||
aio-mcp.exe
|
aio-mcp.exe
|
||||||
|
aio-mcp.exe -transport http -addr localhost:8000 # 同上,显式指定监听地址
|
||||||
|
|
||||||
# http 模式(远程客户端访问)
|
# stdio 模式(本地 Agent 直接拉起进程时使用)
|
||||||
aio-mcp.exe -transport http -addr localhost:8000
|
aio-mcp.exe -transport stdio
|
||||||
|
|
||||||
|
# 其他参数
|
||||||
|
aio-mcp.exe -log-level debug -data-dir D:\data\aio-mcp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
服务以 **streamable HTTP** 为主要对外方式:POST `/mcp`,请求头需同时接受
|
||||||
|
`application/json` 与 `text/event-stream`,响应为 SSE(`event: message` + `data: {...}`)。默认监听 `localhost:8000`。
|
||||||
|
|
||||||
依赖拉取走内网代理,若需直连官方源:`go env -w GOPROXY=https://proxy.golang.org,direct`。
|
依赖拉取走内网代理,若需直连官方源:`go env -w GOPROXY=https://proxy.golang.org,direct`。
|
||||||
|
|
||||||
## 新增一个工具
|
## 新增一个工具
|
||||||
|
|
||||||
1. 在 `internal/tools/` 新建文件(如 `weather.go`),用参数 struct + 泛型 `mcp.AddTool` 注册:
|
1. 在对应业务域子包(如 `internal/tools/sqlite/`)新建文件 `query.go`,用参数 struct + 泛型 `mcp.AddTool` 注册:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package tools
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type weatherArgs struct {
|
type queryArgs struct {
|
||||||
City string `json:"city" jsonschema:"城市名,必填"`
|
SQL string `json:"sql" jsonschema:"要执行的 SELECT 语句,必填"`
|
||||||
Days int `json:"days,omitempty" jsonschema:"预报天数,默认 1"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerWeather(s *mcp.Server) {
|
func registerQuery(s *mcp.Server, d *deps.Deps) {
|
||||||
mcp.AddTool(s, &mcp.Tool{
|
mcp.AddTool(s, &mcp.Tool{
|
||||||
Name: "weather",
|
Name: "sqlite_query",
|
||||||
Description: "查询某城市未来几天的天气。",
|
Description: "在指定库上执行只读 SQL 查询。",
|
||||||
}, func(_ context.Context, _ *mcp.CallToolRequest, args weatherArgs) (*mcp.CallToolResult, any, error) {
|
}, func(_ context.Context, _ *mcp.CallToolRequest, args queryArgs) (*mcp.CallToolResult, any, error) {
|
||||||
// ...业务逻辑...
|
// ...业务逻辑,用到共享依赖时取 d.DataDir / d.DB...
|
||||||
return &mcp.CallToolResult{
|
return &mcp.CallToolResult{
|
||||||
Content: []mcp.Content{&mcp.TextContent{Text: "晴,25℃"}},
|
Content: []mcp.Content{&mcp.TextContent{Text: "..."}},
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 在 `internal/tools/registry.go` 的 `RegisterAll` 中追加一行:`registerWeather(s)`。
|
2. 在该组的 `Register`(如 `sqlite/sqlite.go`)中追加一行:`registerQuery(s, d)`。
|
||||||
|
3. 若是一个新的业务域,再在 `internal/tools/registry.go` 的 `RegisterAll` 中追加一行 `sqlite.Register(s, d)`。
|
||||||
|
|
||||||
工具需要共享状态(SQLite 连接等)时,参考 `registry.go` 顶部注释,把 `RegisterAll` 演进为带 `deps` 参数。
|
工具需要共享状态(SQLite 连接、HTTP client 等)时,在 `internal/deps/deps.go` 加字段并在 `deps.New` 中构造,各工具从 `*deps.Deps` 取用,不要自建。
|
||||||
|
|
||||||
## 冒烟测试
|
## 冒烟测试
|
||||||
|
|
||||||
stdio 模式用换行分隔的 JSON-RPC 直接喂协议消息:
|
HTTP 模式(推荐):`initialize` 响应头里的 `Mcp-Session-Id` 要带回后续请求,
|
||||||
|
否则会报 `method "tools/list" is invalid during session initialization`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1) 初始化,取会话 ID
|
||||||
|
curl -s -i -X POST http://localhost:8000/mcp \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json, text/event-stream" \
|
||||||
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.1"}}}'
|
||||||
|
# => Mcp-Session-Id: xxxxx ;响应体为 SSE:event: message / data: {...}
|
||||||
|
|
||||||
|
SID=<上一步的会话 ID>
|
||||||
|
A="Content-Type: application/json"; B="Accept: application/json, text/event-stream"
|
||||||
|
|
||||||
|
# 2) 通知已初始化(返回 202)
|
||||||
|
curl -s -X POST http://localhost:8000/mcp -H "$A" -H "$B" -H "Mcp-Session-Id: $SID" \
|
||||||
|
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
||||||
|
|
||||||
|
# 3) 列工具
|
||||||
|
curl -s -X POST http://localhost:8000/mcp -H "$A" -H "$B" -H "Mcp-Session-Id: $SID" \
|
||||||
|
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
|
||||||
|
|
||||||
|
# 4) 调工具
|
||||||
|
curl -s -X POST http://localhost:8000/mcp -H "$A" -H "$B" -H "Mcp-Session-Id: $SID" \
|
||||||
|
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"你好","times":2}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
stdio 模式(bash/WSL 下用换行分隔的 JSON-RPC 直接喂协议消息):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
@@ -76,25 +129,28 @@ printf '%s\n' \
|
|||||||
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
||||||
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
||||||
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"你好","times":2}}}' \
|
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"你好","times":2}}}' \
|
||||||
| aio-mcp.exe
|
| aio-mcp.exe -transport stdio
|
||||||
```
|
```
|
||||||
|
|
||||||
http 模式:
|
注意:Windows PowerShell 的管道会给字节流加 BOM/转码,导致服务端报
|
||||||
|
`invalid character 'ï' looking for beginning of value`,stdio 自检请用 bash/WSL 或直接用 MCP 客户端。
|
||||||
```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 为例)
|
## 接入 Agent(以 Reasonix / Claude 为例)
|
||||||
|
|
||||||
在客户端 MCP 配置中注册,指向构建产物:
|
HTTP 模式:先启动服务(或交由 systemd / 容器守护),再在客户端 MCP 配置中指向地址:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "mcpServers": { "aio-mcp": { "command": "D:\\workspace\\membank\\aio-mcp\\aio-mcp.exe" } } }
|
{ "mcpServers": { "aio-mcp": { "url": "http://localhost:8000/mcp" } } }
|
||||||
|
```
|
||||||
|
|
||||||
|
stdio 模式(客户端自行拉起进程):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "mcpServers": { "aio-mcp": { "command": "D:\\workspace\\membank\\aio-mcp\\aio-mcp.exe", "args": ["-transport", "stdio"] } } }
|
||||||
```
|
```
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
- stdio 模式下 **stdout 是协议通道**:日志已固定输出到 stderr,业务代码请勿往 stdout 打印。
|
- stdio 模式下 **stdout 是协议通道**:日志已固定输出到 stderr,业务代码请勿往 stdout 打印。
|
||||||
|
- HTTP 模式下客户端必须带 `Accept: application/json, text/event-stream`,否则服务端返回 400。
|
||||||
- 工具名只允许 `a-z A-Z 0-9 _ - .`。
|
- 工具名只允许 `a-z A-Z 0-9 _ - .`。
|
||||||
|
|||||||
44
aio-mcp/cmd/aio-mcp/main.go
Normal file
44
aio-mcp/cmd/aio-mcp/main.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// aio-mcp:aio-system 的自定义 MCP 服务器。
|
||||||
|
//
|
||||||
|
// 作为 AI Agent 的「手」,以 MCP 协议对外暴露工具。
|
||||||
|
// 默认以 streamable HTTP 传输运行(远程客户端访问),也可切换为
|
||||||
|
// stdio 供本地 Agent 直接拉起。
|
||||||
|
//
|
||||||
|
// 本文件只负责「装配 + 启动」,具体逻辑分别在:
|
||||||
|
// - internal/config :启动参数解析
|
||||||
|
// - internal/logging :日志初始化(固定 stderr)
|
||||||
|
// - internal/server :MCP 服务器装配与传输
|
||||||
|
// - internal/tools :工具注册表
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"aio-mcp/internal/config"
|
||||||
|
"aio-mcp/internal/logging"
|
||||||
|
"aio-mcp/internal/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// version 可用 -ldflags "-X main.version=..." 在构建时覆盖。
|
||||||
|
var version = "0.1.0"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Parse(os.Args[1:], os.Stderr)
|
||||||
|
if err != nil {
|
||||||
|
// -h/--help 属于正常退出,参数错误才是异常退出。
|
||||||
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.Setup(cfg.LogLevel)
|
||||||
|
|
||||||
|
if err := server.Serve(server.New(version, cfg), cfg); err != nil {
|
||||||
|
slog.Error("aio-mcp exited with error", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
aio-mcp/internal/config/config.go
Normal file
45
aio-mcp/internal/config/config.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Package config 解析 aio-mcp 的启动配置(命令行参数)。
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 支持的传输方式。
|
||||||
|
const (
|
||||||
|
TransportStdio = "stdio"
|
||||||
|
TransportHTTP = "http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config 是 aio-mcp 的运行时配置。
|
||||||
|
type Config struct {
|
||||||
|
Transport string // MCP 传输方式:stdio 或 http
|
||||||
|
Addr string // http 模式下的监听地址
|
||||||
|
LogLevel string // 日志级别:debug / info / warn / error
|
||||||
|
DataDir string // 数据目录:SQLite 库文件与工具产物的存放位置
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse 解析命令行参数。args 不含程序名(通常传 os.Args[1:]),
|
||||||
|
// 用法与错误信息写到 out。
|
||||||
|
func Parse(args []string, out io.Writer) (*Config, error) {
|
||||||
|
cfg := &Config{}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("aio-mcp", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(out)
|
||||||
|
fs.StringVar(&cfg.Transport, "transport", TransportHTTP, "传输方式:http(默认,streamable HTTP)或 stdio(本地 Agent 拉起)")
|
||||||
|
fs.StringVar(&cfg.Addr, "addr", "localhost:8000", "http 模式下的监听地址")
|
||||||
|
fs.StringVar(&cfg.LogLevel, "log-level", "info", "日志级别:debug / info / warn / error")
|
||||||
|
fs.StringVar(&cfg.DataDir, "data-dir", "data", "数据目录:SQLite 库文件与工具产物的存放位置")
|
||||||
|
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Transport != TransportStdio && cfg.Transport != TransportHTTP {
|
||||||
|
return nil, fmt.Errorf("未知 transport: %q(支持: %s, %s)", cfg.Transport, TransportStdio, TransportHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
26
aio-mcp/internal/deps/deps.go
Normal file
26
aio-mcp/internal/deps/deps.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Package deps 定义工具共享的依赖容器。
|
||||||
|
//
|
||||||
|
// 工具分组(internal/tools/xxx)在注册时接收同一个 *Deps,
|
||||||
|
// 避免各工具各自打开数据库连接、各自读配置。
|
||||||
|
package deps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aio-mcp/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deps 是注册工具时注入的共享依赖。
|
||||||
|
//
|
||||||
|
// 目前只有数据目录;后续按需在这里加字段即可:
|
||||||
|
// SQLite 连接(*sql.DB)、HTTP client、打印机客户端、检测服务客户端等。
|
||||||
|
// 由 internal/deps.New 统一构造,各工具只从参数取值,不再自建。
|
||||||
|
type Deps struct {
|
||||||
|
// DataDir 是数据目录,SQLite 库文件与工具产物的根路径。
|
||||||
|
DataDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 依据启动配置构造共享依赖。
|
||||||
|
func New(cfg *config.Config) *Deps {
|
||||||
|
return &Deps{
|
||||||
|
DataDir: cfg.DataDir,
|
||||||
|
}
|
||||||
|
}
|
||||||
27
aio-mcp/internal/logging/logging.go
Normal file
27
aio-mcp/internal/logging/logging.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package logging 负责 aio-mcp 的全局日志初始化。
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup 初始化全局 slog 默认 logger。
|
||||||
|
//
|
||||||
|
// 日志固定输出到 stderr:stdio 模式下 stdout 是 MCP 协议通道,
|
||||||
|
// 任何日志混入都会破坏 JSON-RPC 报文。业务代码同样禁止打印到 stdout。
|
||||||
|
func Setup(level string) {
|
||||||
|
var lvl slog.Level
|
||||||
|
switch level {
|
||||||
|
case "debug":
|
||||||
|
lvl = slog.LevelDebug
|
||||||
|
case "warn":
|
||||||
|
lvl = slog.LevelWarn
|
||||||
|
case "error":
|
||||||
|
lvl = slog.LevelError
|
||||||
|
default:
|
||||||
|
lvl = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: lvl})))
|
||||||
|
}
|
||||||
32
aio-mcp/internal/server/serve.go
Normal file
32
aio-mcp/internal/server/serve.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
|
"aio-mcp/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serve 按传输方式启动服务器,阻塞直到 stdin 关闭、监听失败或出错。
|
||||||
|
func Serve(s *mcp.Server, cfg *config.Config) error {
|
||||||
|
switch cfg.Transport {
|
||||||
|
case config.TransportStdio:
|
||||||
|
slog.Info("aio-mcp starting", "transport", "stdio")
|
||||||
|
// server.Run 会阻塞,直到 stdin 关闭或出错。
|
||||||
|
return s.Run(context.Background(), &mcp.StdioTransport{})
|
||||||
|
|
||||||
|
case config.TransportHTTP:
|
||||||
|
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return s }, nil)
|
||||||
|
slog.Info("aio-mcp starting", "transport", "http", "addr", cfg.Addr)
|
||||||
|
return http.ListenAndServe(cfg.Addr, handler)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// config.Parse 已校验过,这里仅为防御性兜底。
|
||||||
|
return fmt.Errorf("未知 transport: %q(支持: %s, %s)",
|
||||||
|
cfg.Transport, config.TransportStdio, config.TransportHTTP)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
// Package server 负责 MCP 服务器的装配:创建 server 实例并注册全部工具。
|
// Package server 负责 MCP 服务器的装配与运行。
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
|
"aio-mcp/internal/config"
|
||||||
|
"aio-mcp/internal/deps"
|
||||||
"aio-mcp/internal/tools"
|
"aio-mcp/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New 创建并装配 aio-mcp 服务器。
|
// New 创建并装配 aio-mcp 服务器。
|
||||||
// version 为服务器实现版本号,随 MCP initialize 握手上报给客户端。
|
//
|
||||||
func New(version string) *mcp.Server {
|
// version 为服务器实现版本号,随 MCP initialize 握手上报给客户端;
|
||||||
|
// cfg 决定的共享依赖(数据目录、后续的连接池等)通过 deps 注入各工具。
|
||||||
|
func New(version string, cfg *config.Config) *mcp.Server {
|
||||||
s := mcp.NewServer(&mcp.Implementation{
|
s := mcp.NewServer(&mcp.Implementation{
|
||||||
Name: "aio-mcp",
|
Name: "aio-mcp",
|
||||||
Version: version,
|
Version: version,
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
// 注册全部工具。新增工具后在 tools.RegisterAll 里追加即可。
|
// 注册全部工具。新增工具组后在 tools.RegisterAll 里追加即可。
|
||||||
tools.RegisterAll(s)
|
tools.RegisterAll(s, deps.New(cfg))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,40 @@
|
|||||||
// Package tools 集中管理 aio-mcp 的全部 MCP 工具。
|
// Package tools 是 aio-mcp 的工具注册表:按业务域分组,统一注册。
|
||||||
//
|
//
|
||||||
// # 新增工具的固定步骤
|
// # 新增一个工具组
|
||||||
//
|
//
|
||||||
// 1. 在本目录新建一个文件(如 weather.go),实现
|
// 1. 在 internal/tools/ 下新建子包(如 sqlite/),实现
|
||||||
// func registerXXX(s *mcp.Server)
|
// func Register(s *mcp.Server, d *deps.Deps)
|
||||||
// 2. 在 RegisterAll 中追加一行 registerXXX(s)
|
// 2. 在 RegisterAll 中追加一行 sqlite.Register(s, d)
|
||||||
|
//
|
||||||
|
// # 在已有组内新增一个工具
|
||||||
|
//
|
||||||
|
// 在对应子包内新建文件(如 sqlite/query.go),实现
|
||||||
|
// func registerQuery(s *mcp.Server, d *deps.Deps),
|
||||||
|
// 并在该组的 Register 中追加一行。
|
||||||
//
|
//
|
||||||
// 工具通过泛型 mcp.AddTool[In, Out] 注册:输入参数用 struct 定义,
|
// 工具通过泛型 mcp.AddTool[In, Out] 注册:输入参数用 struct 定义,
|
||||||
// 加 json 与 jsonschema tag(描述、必填),SDK 会自动生成 inputSchema
|
// 加 json 与 jsonschema tag(描述、必填),SDK 会自动生成 inputSchema
|
||||||
// 并做输入校验,无需手工写 JSON Schema。
|
// 并做输入校验,无需手工写 JSON Schema。
|
||||||
//
|
//
|
||||||
// 后续工具需要共享状态(SQLite 连接、配置、http client 等)时,
|
// 注意:共享依赖放在 internal/deps 而不是本包,
|
||||||
// 把 RegisterAll 演进为 RegisterAll(s *mcp.Server, deps *Deps),
|
// 否则工具子包 import 本包会形成循环依赖。
|
||||||
// 在 server.New 里构造 deps 传入即可,各工具文件只改签名。
|
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
|
"aio-mcp/internal/deps"
|
||||||
|
"aio-mcp/internal/tools/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterAll 注册所有工具。
|
// RegisterAll 注册所有工具组。
|
||||||
func RegisterAll(s *mcp.Server) {
|
func RegisterAll(s *mcp.Server, d *deps.Deps) {
|
||||||
registerPing(s)
|
// 系统组:健康检查与协议演示。
|
||||||
registerEcho(s)
|
system.Register(s, d)
|
||||||
|
|
||||||
|
// 待接入的业务组(目录尚未创建,接入时在此追加):
|
||||||
|
// sqlite.Register(s, d) // 本地数据查询
|
||||||
|
// file.Register(s, d) // 文件读写
|
||||||
|
// print.Register(s, d) // 打印/报告输出
|
||||||
|
// check.Register(s, d) // 硬件检测
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package tools
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package tools
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
14
aio-mcp/internal/tools/system/system.go
Normal file
14
aio-mcp/internal/tools/system/system.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Package system 承载 aio-mcp 自身的系统类工具:健康检查、协议演示等。
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
|
"aio-mcp/internal/deps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register 注册本组全部工具。
|
||||||
|
func Register(s *mcp.Server, _ *deps.Deps) {
|
||||||
|
registerPing(s)
|
||||||
|
registerEcho(s)
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
1. 构建docker 镜像,创建docker compose 文件
|
1. 构建docker 镜像,创建docker compose 文件
|
||||||
2. 部署到机器 100.66.1.5 用户名 admin 密码 admin123
|
2. 部署到机器 100.66.1.5 用户名 admin 密码 admin123
|
||||||
|
3. 部署完把归纳一下,部署的经验,到这个文档,方便下次部署的时候使用
|
||||||
|
|||||||
Reference in New Issue
Block a user