主题
02 - 用 Go 实现一个 MCP Server
目标
我们要实现一个 "笔记管理" MCP Server:
┌──────────────────────────────────────────────────┐
│ notes-server (stdio MCP Server) │
│ │
│ Tools: │
│ ├── create_note(title, content) → 创建笔记 │
│ ├── list_notes() → 列出所有笔记 │
│ └── search_notes(query) → 搜索笔记 │
│ │
│ Resources: │
│ └── note://{id} → 获取笔记内容 │
│ │
│ 通信: stdio (JSON-RPC 2.0) │
│ 可以被 Claude Desktop / Claude Code 调用 │
└──────────────────────────────────────────────────┘完整代码
go
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
// ==================== JSON-RPC 数据结构 ====================
type JSONRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
ID any `json:"id,omitempty"`
}
type JSONRPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result any `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
ID any `json:"id"`
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
}
// ==================== MCP 协议结构 ====================
type ServerInfo struct {
Name string `json:"name"`
Version string `json:"version"`
}
type Capabilities struct {
Tools *struct{} `json:"tools,omitempty"`
Resources *struct{} `json:"resources,omitempty"`
}
type InitializeResult struct {
ProtocolVersion string `json:"protocolVersion"`
ServerInfo ServerInfo `json:"serverInfo"`
Capabilities Capabilities `json:"capabilities"`
}
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
}
type ToolCallParams struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}
type ContentItem struct {
Type string `json:"type"`
Text string `json:"text"`
}
type ToolResult struct {
Content []ContentItem `json:"content"`
}
type ResourceDef struct {
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description"`
MimeType string `json:"mimeType"`
}
// ==================== 笔记数据 ====================
type Note struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreatedAt string `json:"created_at"`
}
var notes = map[string]*Note{}
var noteCounter = 0
// ==================== MCP Server ====================
type MCPServer struct {
scanner *bufio.Scanner
}
func NewMCPServer() *MCPServer {
return &MCPServer{
scanner: bufio.NewScanner(os.Stdin),
}
}
// 发送 JSON-RPC 响应
func (s *MCPServer) sendResponse(id any, result any) {
resp := JSONRPCResponse{
JSONRPC: "2.0",
Result: result,
ID: id,
}
data, _ := json.Marshal(resp)
fmt.Println(string(data))
}
func (s *MCPServer) sendError(id any, code int, message string) {
resp := JSONRPCResponse{
JSONRPC: "2.0",
Error: &Error{Code: code, Message: message},
ID: id,
}
data, _ := json.Marshal(resp)
fmt.Println(string(data))
}
// 处理请求
func (s *MCPServer) handleRequest(req JSONRPCRequest) {
switch req.Method {
case "initialize":
s.sendResponse(req.ID, InitializeResult{
ProtocolVersion: "2024-11-05",
ServerInfo: ServerInfo{Name: "notes-server", Version: "1.0.0"},
Capabilities: Capabilities{
Tools: &struct{}{},
Resources: &struct{}{},
},
})
case "notifications/initialized":
// 通知类消息,不需要回复
case "tools/list":
s.sendResponse(req.ID, map[string]any{
"tools": []ToolDef{
{
Name: "create_note",
Description: "创建一条新笔记",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"title": {"type": "string", "description": "笔记标题"},
"content": {"type": "string", "description": "笔记内容"}
},
"required": ["title", "content"]
}`),
},
{
Name: "list_notes",
Description: "列出所有笔记的标题和ID",
InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`),
},
{
Name: "search_notes",
Description: "按关键词搜索笔记",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"}
},
"required": ["query"]
}`),
},
},
})
case "tools/call":
var params ToolCallParams
json.Unmarshal(req.Params, ¶ms)
result := s.executeTool(params)
s.sendResponse(req.ID, result)
case "resources/list":
var resources []ResourceDef
for id, note := range notes {
resources = append(resources, ResourceDef{
URI: "note://" + id,
Name: note.Title,
Description: "笔记: " + note.Title,
MimeType: "text/plain",
})
}
s.sendResponse(req.ID, map[string]any{"resources": resources})
case "resources/read":
var params struct{ URI string `json:"uri"` }
json.Unmarshal(req.Params, ¶ms)
noteID := strings.TrimPrefix(params.URI, "note://")
if note, ok := notes[noteID]; ok {
s.sendResponse(req.ID, map[string]any{
"contents": []map[string]string{{
"uri": params.URI,
"mimeType": "text/plain",
"text": fmt.Sprintf("# %s\n\n%s", note.Title, note.Content),
}},
})
} else {
s.sendError(req.ID, -1, "笔记不存在: "+noteID)
}
default:
s.sendError(req.ID, -32601, "未知方法: "+req.Method)
}
}
// 执行工具
func (s *MCPServer) executeTool(params ToolCallParams) ToolResult {
switch params.Name {
case "create_note":
noteCounter++
id := fmt.Sprintf("note-%d", noteCounter)
title, _ := params.Arguments["title"].(string)
content, _ := params.Arguments["content"].(string)
notes[id] = &Note{
ID: id,
Title: title,
Content: content,
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
return ToolResult{Content: []ContentItem{{
Type: "text",
Text: fmt.Sprintf("笔记创建成功! ID: %s, 标题: %s", id, title),
}}}
case "list_notes":
if len(notes) == 0 {
return ToolResult{Content: []ContentItem{{
Type: "text", Text: "暂无笔记",
}}}
}
var lines []string
for id, note := range notes {
lines = append(lines, fmt.Sprintf("- [%s] %s (%s)", id, note.Title, note.CreatedAt))
}
return ToolResult{Content: []ContentItem{{
Type: "text", Text: strings.Join(lines, "\n"),
}}}
case "search_notes":
query, _ := params.Arguments["query"].(string)
query = strings.ToLower(query)
var results []string
for id, note := range notes {
if strings.Contains(strings.ToLower(note.Title), query) ||
strings.Contains(strings.ToLower(note.Content), query) {
results = append(results, fmt.Sprintf("- [%s] %s", id, note.Title))
}
}
if len(results) == 0 {
return ToolResult{Content: []ContentItem{{
Type: "text", Text: "未找到匹配的笔记",
}}}
}
return ToolResult{Content: []ContentItem{{
Type: "text", Text: fmt.Sprintf("找到 %d 条结果:\n%s", len(results), strings.Join(results, "\n")),
}}}
default:
return ToolResult{Content: []ContentItem{{
Type: "text", Text: "未知工具: " + params.Name,
}}}
}
}
// 主循环
func (s *MCPServer) Run() {
s.scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for s.scanner.Scan() {
line := s.scanner.Text()
if line == "" {
continue
}
var req JSONRPCRequest
if err := json.Unmarshal([]byte(line), &req); err != nil {
continue
}
s.handleRequest(req)
}
}
func main() {
server := NewMCPServer()
server.Run()
}配置和使用
编译:
go build -o notes-server main.go
在 Claude Desktop 中配置 (~/.claude/claude_desktop_config.json):
{
"mcpServers": {
"notes": {
"command": "/path/to/notes-server"
}
}
}
在 Claude Code 中配置 (.claude/settings.json):
{
"mcpServers": {
"notes": {
"command": "/path/to/notes-server"
}
}
}
配置后 Claude 就能使用 create_note / list_notes / search_notes 工具了!
┌──────────────────────────────────────────────────┐
│ 用户: "帮我记一条笔记:明天下午2点开会" │
│ │
│ Claude: │
│ [Thought] 用户要创建笔记 │
│ [Tool Call] create_note( │
│ title="会议提醒", │
│ content="明天下午2点开会" │
│ ) │
│ [Result] 笔记创建成功! ID: note-1 │
│ [Answer] 已经帮你记下了! │
└──────────────────────────────────────────────────┘模块六完成!
下一个模块: 模块七:Claude Code 底层原理