docs: 项目概览(OVERVIEW.md) + 插件开发指南(PLUGIN_DEV.md) + MCP 适配器插件

- OVERVIEW.md: 非技术用户友好的项目介绍和目标
- PLUGIN_DEV.md: 完整插件开发指南(含三种开发方式、API 参考、最佳实践)
- internal/plugins/mcp/: MCP 协议适配器插件(JSON-RPC over stdio/SSE)
  - client.go: MCP 客户端(ListTools / CallTool)
  - stdio.go: 子进程 stdin/stdout 传输
  - sse.go: HTTP POST 传输
  - plugin.go: init() 自注册 + Settings 配置读取
- README.md 更新链接
This commit is contained in:
root
2026-07-03 17:58:20 +08:00
parent 2d314b3e9c
commit 197f932932
8 changed files with 987 additions and 27 deletions

View File

@ -0,0 +1,122 @@
package mcp
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os/exec"
"sync"
)
// StdioTransport 通过子进程 stdin/stdout 进行 JSON-RPC 通信
type StdioTransport struct {
cmd *exec.Cmd
stdin io.WriteCloser
stdout *bufio.Reader
mu sync.Mutex
pending map[int]chan *rpcResponse
done chan struct{}
}
func NewStdioTransport(command string, args []string, env []string) (*StdioTransport, error) {
cmd := exec.Command(command, args...)
if len(env) > 0 {
cmd.Env = env
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("stdin pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("stdout pipe: %w", err)
}
// 忽略 stderr(MCP 服务器可能输出日志到 stderr)
cmd.Stderr = nil
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("start %s: %w", command, err)
}
t := &StdioTransport{
cmd: cmd,
stdin: stdin,
stdout: bufio.NewReader(stdout),
pending: make(map[int]chan *rpcResponse),
done: make(chan struct{}),
}
go t.readLoop()
return t, nil
}
func (t *StdioTransport) readLoop() {
dec := json.NewDecoder(t.stdout)
for {
var resp rpcResponse
if err := dec.Decode(&resp); err != nil {
close(t.done)
// 通知所有等待的请求
t.mu.Lock()
for _, ch := range t.pending {
close(ch)
}
t.pending = make(map[int]chan *rpcResponse)
t.mu.Unlock()
return
}
t.mu.Lock()
ch, ok := t.pending[resp.ID]
delete(t.pending, resp.ID)
t.mu.Unlock()
if ok {
ch <- &resp
}
}
}
func (t *StdioTransport) Send(req *rpcRequest) (*rpcResponse, error) {
data, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
}
ch := make(chan *rpcResponse, 1)
t.mu.Lock()
t.pending[req.ID] = ch
t.mu.Unlock()
if _, err := t.stdin.Write(data); err != nil {
t.mu.Lock()
delete(t.pending, req.ID)
t.mu.Unlock()
return nil, fmt.Errorf("write stdin: %w", err)
}
if _, err := t.stdin.Write([]byte("\n")); err != nil {
t.mu.Lock()
delete(t.pending, req.ID)
t.mu.Unlock()
return nil, fmt.Errorf("write newline: %w", err)
}
select {
case resp := <-ch:
return resp, nil
case <-t.done:
return nil, fmt.Errorf("mcp transport closed")
}
}
func (t *StdioTransport) Close() error {
t.stdin.Close()
if t.cmd.Process != nil {
t.cmd.Process.Kill()
}
<-t.done
return t.cmd.Wait()
}