【AI Agent 与 Super Agent 构建实战】第 31 篇:Go 实现 MCP Server — 暴露 Agent 能力为标准服务
本系列定位:用 Go 语言从零构建各类 AI Agent,覆盖 ReAct、Plan-and-Execute、Multi-Agent、Super Agent 等核心设计模式的完整工程实现。
本篇你将学到
- 用 Go 从零实现 MCP Server(JSON-RPC 2.0 over stdio)
- Tool 注册为 MCP Tool 的完整映射
- Resource 暴露与 Prompts 模板定义
- AgentForge 作为 MCP Server 运行
学完本篇,AgentForge 将能作为标准 MCP Server 对外服务——任何 MCP 兼容客户端(包括其他 Agent 框架)都能连接并使用你定义的工具。
一、MCP Server 架构
1.1 Server 内部结构
#mermaid-svg-23Gklku4Zaj4y2fd{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-23Gklku4Zaj4y2fd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-23Gklku4Zaj4y2fd .error-icon{fill:#552222;}#mermaid-svg-23Gklku4Zaj4y2fd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-23Gklku4Zaj4y2fd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-23Gklku4Zaj4y2fd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-23Gklku4Zaj4y2fd .marker.cross{stroke:#333333;}#mermaid-svg-23Gklku4Zaj4y2fd svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-23Gklku4Zaj4y2fd p{margin:0;}#mermaid-svg-23Gklku4Zaj4y2fd .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-23Gklku4Zaj4y2fd .cluster-label text{fill:#333;}#mermaid-svg-23Gklku4Zaj4y2fd .cluster-label span{color:#333;}#mermaid-svg-23Gklku4Zaj4y2fd .cluster-label span p{background-color:transparent;}#mermaid-svg-23Gklku4Zaj4y2fd .label text,#mermaid-svg-23Gklku4Zaj4y2fd span{fill:#333;color:#333;}#mermaid-svg-23Gklku4Zaj4y2fd .node rect,#mermaid-svg-23Gklku4Zaj4y2fd .node circle,#mermaid-svg-23Gklku4Zaj4y2fd .node ellipse,#mermaid-svg-23Gklku4Zaj4y2fd .node polygon,#mermaid-svg-23Gklku4Zaj4y2fd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-23Gklku4Zaj4y2fd .rough-node .label text,#mermaid-svg-23Gklku4Zaj4y2fd .node .label text,#mermaid-svg-23Gklku4Zaj4y2fd .image-shape .label,#mermaid-svg-23Gklku4Zaj4y2fd .icon-shape .label{text-anchor:middle;}#mermaid-svg-23Gklku4Zaj4y2fd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-23Gklku4Zaj4y2fd .rough-node .label,#mermaid-svg-23Gklku4Zaj4y2fd .node .label,#mermaid-svg-23Gklku4Zaj4y2fd .image-shape .label,#mermaid-svg-23Gklku4Zaj4y2fd .icon-shape .label{text-align:center;}#mermaid-svg-23Gklku4Zaj4y2fd .node.clickable{cursor:pointer;}#mermaid-svg-23Gklku4Zaj4y2fd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-23Gklku4Zaj4y2fd .arrowheadPath{fill:#333333;}#mermaid-svg-23Gklku4Zaj4y2fd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-23Gklku4Zaj4y2fd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-23Gklku4Zaj4y2fd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-23Gklku4Zaj4y2fd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-23Gklku4Zaj4y2fd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-23Gklku4Zaj4y2fd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-23Gklku4Zaj4y2fd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-23Gklku4Zaj4y2fd .cluster text{fill:#333;}#mermaid-svg-23Gklku4Zaj4y2fd .cluster span{color:#333;}#mermaid-svg-23Gklku4Zaj4y2fd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-23Gklku4Zaj4y2fd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-23Gklku4Zaj4y2fd rect.text{fill:none;stroke-width:0;}#mermaid-svg-23Gklku4Zaj4y2fd .icon-shape,#mermaid-svg-23Gklku4Zaj4y2fd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-23Gklku4Zaj4y2fd .icon-shape p,#mermaid-svg-23Gklku4Zaj4y2fd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-23Gklku4Zaj4y2fd .icon-shape .label rect,#mermaid-svg-23Gklku4Zaj4y2fd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-23Gklku4Zaj4y2fd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-23Gklku4Zaj4y2fd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-23Gklku4Zaj4y2fd :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
注册的原语
MCP Server
stdin/stdout
Transport 层stdio / HTTP+SSE
JSON-RPC 路由方法分发
方法处理器
Tool 注册表
Resource 注册表
Prompt 注册表
MCP Client
1.2 JSON-RPC 消息处理流
Handler
Router
Transport (stdio)
Client
Handler
Router
Transport (stdio)
Client
#mermaid-svg-wDs50V9lQZMM0Qpo{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wDs50V9lQZMM0Qpo .error-icon{fill:#552222;}#mermaid-svg-wDs50V9lQZMM0Qpo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wDs50V9lQZMM0Qpo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wDs50V9lQZMM0Qpo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wDs50V9lQZMM0Qpo .marker.cross{stroke:#333333;}#mermaid-svg-wDs50V9lQZMM0Qpo svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wDs50V9lQZMM0Qpo p{margin:0;}#mermaid-svg-wDs50V9lQZMM0Qpo .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wDs50V9lQZMM0Qpo text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wDs50V9lQZMM0Qpo .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wDs50V9lQZMM0Qpo .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wDs50V9lQZMM0Qpo #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wDs50V9lQZMM0Qpo .sequenceNumber{fill:white;}#mermaid-svg-wDs50V9lQZMM0Qpo #sequencenumber{fill:#333;}#mermaid-svg-wDs50V9lQZMM0Qpo #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wDs50V9lQZMM0Qpo .messageText{fill:#333;stroke:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wDs50V9lQZMM0Qpo .labelText,#mermaid-svg-wDs50V9lQZMM0Qpo .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .loopText,#mermaid-svg-wDs50V9lQZMM0Qpo .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wDs50V9lQZMM0Qpo .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wDs50V9lQZMM0Qpo .noteText,#mermaid-svg-wDs50V9lQZMM0Qpo .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wDs50V9lQZMM0Qpo .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wDs50V9lQZMM0Qpo .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wDs50V9lQZMM0Qpo .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wDs50V9lQZMM0Qpo .actorPopupMenu{position:absolute;}#mermaid-svg-wDs50V9lQZMM0Qpo .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wDs50V9lQZMM0Qpo .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wDs50V9lQZMM0Qpo .actor-man circle,#mermaid-svg-wDs50V9lQZMM0Qpo line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wDs50V9lQZMM0Qpo :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
写入 JSON-RPC 请求到 stdin
按行读取 + JSON 反序列化
路由到对应方法
dispatch(method, params)
执行业务逻辑
返回 result
包装 JSON-RPC response
写入 stdout
二、核心类型定义
2.1 JSON-RPC 消息类型
// internal/mcp/types.go
package mcp
import "encoding/json"
// JSONRPCMessage JSON-RPC 2.0 消息
type JSONRPCMessage struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
}
// RPCError JSON-RPC 错误
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// 标准错误码
const (
ErrParseError = –32700
ErrInvalidRequest = –32600
ErrMethodNotFound = –32601
ErrInvalidParams = –32602
ErrInternal = –32603
)
// Tool MCP 工具定义
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
}
// Resource MCP 资源定义
type Resource struct {
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description"`
MimeType string `json:"mimeType,omitempty"`
}
// Prompt MCP 提示模板定义
type Prompt struct {
Name string `json:"name"`
Description string `json:"description"`
Arguments []PromptArg `json:"arguments,omitempty"`
}
// PromptArg 提示模板参数
type PromptArg struct {
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
}
// CallToolResult 工具调用结果
type CallToolResult struct {
Content []ContentBlock `json:"content"`
IsError bool `json:"isError,omitempty"`
}
// ContentBlock 内容块
type ContentBlock struct {
Type string `json:"type"` // text / image / resource
Text string `json:"text,omitempty"`
}
三、Server 核心实现
3.1 Server 结构
// internal/mcp/server.go
package mcp
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"sync"
)
// ServerInfo Server 元信息
type ServerInfo struct {
Name string `json:"name"`
Version string `json:"version"`
}
// ServerCapabilities Server 能力声明
type ServerCapabilities struct {
Tools bool `json:"tools,omitempty"`
Resources bool `json:"resources,omitempty"`
Prompts bool `json:"prompts,omitempty"`
}
// MCPServer MCP Server
type MCPServer struct {
info ServerInfo
capabilities ServerCapabilities
tools map[string]*ToolHandler
resources map[string]*ResourceHandler
prompts map[string]*PromptHandler
mu sync.RWMutex
reader *bufio.Reader
writer io.Writer
initialized bool
}
// ToolHandler 工具处理器
type ToolHandler struct {
Tool Tool
Execute func(args map[string]interface{}) (string, error)
}
// ResourceHandler 资源处理器
type ResourceHandler struct {
Resource Resource
Read func(uri string) (string, error)
}
// PromptHandler 提示处理器
type PromptHandler struct {
Prompt Prompt
Render func(args map[string]string) (string, error)
}
// NewMCPServer 创建 Server
func NewMCPServer(name, version string) *MCPServer {
return &MCPServer{
info: ServerInfo{Name: name, Version: version},
capabilities: ServerCapabilities{
Tools: true,
Resources: true,
Prompts: true,
},
tools: make(map[string]*ToolHandler),
resources: make(map[string]*ResourceHandler),
prompts: make(map[string]*PromptHandler),
reader: bufio.NewReader(os.Stdin),
writer: os.Stdout,
}
}
3.2 注册方法
// RegisterTool 注册工具
func (s *MCPServer) RegisterTool(name, description string, schema json.RawMessage, execute func(map[string]interface{}) (string, error)) {
s.mu.Lock()
defer s.mu.Unlock()
s.tools[name] = &ToolHandler{
Tool: Tool{
Name: name,
Description: description,
InputSchema: schema,
},
Execute: execute,
}
}
// RegisterResource 注册资源
func (s *MCPServer) RegisterResource(uri, name, description, mimeType string, read func(string) (string, error)) {
s.mu.Lock()
defer s.mu.Unlock()
s.resources[uri] = &ResourceHandler{
Resource: Resource{
URI: uri,
Name: name,
Description: description,
MimeType: mimeType,
},
Read: read,
}
}
// RegisterPrompt 注册提示模板
func (s *MCPServer) RegisterPrompt(name, description string, args []PromptArg, render func(map[string]string) (string, error)) {
s.mu.Lock()
defer s.mu.Unlock()
s.prompts[name] = &PromptHandler{
Prompt: Prompt{
Name: name,
Description: description,
Arguments: args,
},
Render: render,
}
}
3.3 消息处理循环
// Serve 启动 Server,监听 stdin
func (s *MCPServer) Serve(ctx context.Context) error {
scanner := bufio.NewScanner(s.reader)
// 增大缓冲区以支持长消息
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 {
continue
}
var msg JSONRPCMessage
if err := json.Unmarshal(line, &msg); err != nil {
s.sendError(nil, ErrParseError, "JSON 解析失败: "+err.Error())
continue
}
// 分发到对应处理器
s.handleMessage(ctx, &msg)
}
return scanner.Err()
}
// handleMessage 消息分发
func (s *MCPServer) handleMessage(ctx context.Context, msg *JSONRPCMessage) {
switch msg.Method {
case "initialize":
s.handleInitialize(msg)
case "initialized":
s.initialized = true
case "tools/list":
s.handleToolsList(msg)
case "tools/call":
s.handleToolsCall(msg)
case "resources/list":
s.handleResourcesList(msg)
case "resources/read":
s.handleResourcesRead(msg)
case "prompts/list":
s.handlePromptsList(msg)
case "prompts/get":
s.handlePromptsGet(msg)
default:
s.sendError(msg.ID, ErrMethodNotFound, "未知方法: "+msg.Method)
}
}
3.4 核心方法实现
// handleInitialize 处理握手
func (s *MCPServer) handleInitialize(msg *JSONRPCMessage) {
result := map[string]interface{}{
"protocolVersion": "2024-11-05",
"capabilities": map[string]interface{}{
"tools": map[string]bool{"listChanged": true},
"resources": map[string]bool{"listChanged": true},
"prompts": map[string]bool{"listChanged": true},
},
"serverInfo": s.info,
}
s.sendResult(msg.ID, result)
}
// handleToolsList 列出所有工具
func (s *MCPServer) handleToolsList(msg *JSONRPCMessage) {
s.mu.RLock()
defer s.mu.RUnlock()
var tools []Tool
for _, handler := range s.tools {
tools = append(tools, handler.Tool)
}
s.sendResult(msg.ID, map[string]interface{}{
"tools": tools,
})
}
// handleToolsCall 执行工具调用
func (s *MCPServer) handleToolsCall(msg *JSONRPCMessage) {
var params struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
}
if err := json.Unmarshal(msg.Params, ¶ms); err != nil {
s.sendError(msg.ID, ErrInvalidParams, "参数解析失败")
return
}
s.mu.RLock()
handler, ok := s.tools[params.Name]
s.mu.RUnlock()
if !ok {
s.sendError(msg.ID, ErrMethodNotFound, fmt.Sprintf("工具不存在: %s", params.Name))
return
}
// 执行工具
result, err := handler.Execute(params.Arguments)
if err != nil {
s.sendResult(msg.ID, CallToolResult{
Content: []ContentBlock{{Type: "text", Text: fmt.Sprintf("错误: %v", err)}},
IsError: true,
})
return
}
s.sendResult(msg.ID, CallToolResult{
Content: []ContentBlock{{Type: "text", Text: result}},
})
}
// sendResult 发送成功响应
func (s *MCPServer) sendResult(id json.RawMessage, result interface{}) {
resultJSON, _ := json.Marshal(result)
resp := JSONRPCMessage{
JSONRPC: "2.0",
ID: id,
Result: resultJSON,
}
s.send(resp)
}
// sendError 发送错误响应
func (s *MCPServer) sendError(id json.RawMessage, code int, message string) {
resp := JSONRPCMessage{
JSONRPC: "2.0",
ID: id,
Error: &RPCError{Code: code, Message: message},
}
s.send(resp)
}
// send 写入 stdout
func (s *MCPServer) send(msg JSONRPCMessage) {
data, _ := json.Marshal(msg)
fmt.Fprintln(s.writer, string(data))
}
四、注册 AgentForge 工具为 MCP Tools
4.1 创建 AgentForge MCP Server
// cmd/mcp_server.go
package cmd
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
"github.com/yourname/agentforge/internal/mcp"
)
func startMCPServer() {
server := mcp.NewMCPServer("agentforge", "0.6.0")
// === 注册 Tools ===
// 天气工具
weatherSchema := json.RawMessage(`{
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}`)
server.RegisterTool("get_weather", "查询城市天气", weatherSchema,
func(args map[string]interface{}) (string, error) {
city := args["city"].(string)
return fmt.Sprintf("%s: 28°C, 晴", city), nil
},
)
// 计算工具
calcSchema := json.RawMessage(`{
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}`)
server.RegisterTool("calculate", "数学计算", calcSchema,
func(args map[string]interface{}) (string, error) {
expr := args["expression"].(string)
return fmt.Sprintf("%s = 42(示例结果)", expr), nil
},
)
// 代码执行工具(沙箱)
execSchema := json.RawMessage(`{
"type": "object",
"properties": {
"language": {"type": "string", "enum": ["go", "python"]},
"code": {"type": "string", "description": "要执行的代码"}
},
"required": ["language", "code"]
}`)
server.RegisterTool("execute_code", "在沙箱中执行代码", execSchema,
func(args map[string]interface{}) (string, error) {
lang := args["language"].(string)
code := args["code"].(string)
return executeInSandbox(lang, code)
},
)
// === 注册 Resources ===
server.RegisterResource(
"agentforge://config",
"AgentForge 配置",
"当前 AgentForge 的配置信息",
"application/json",
func(uri string) (string, error) {
return `{"version": "0.6.0", "provider": "ollama", "model": "qwen2.5:14b"}`, nil
},
)
server.RegisterResource(
"agentforge://tools",
"已注册工具列表",
"AgentForge 所有已注册工具的元信息",
"application/json",
func(uri string) (string, error) {
tools := server.ListToolNames()
return fmt.Sprintf(`{"tools": %v}`, tools), nil
},
)
// === 注册 Prompts ===
server.RegisterPrompt(
"code_review",
"代码审查模板",
[]mcp.PromptArg{
{Name: "language", Description: "编程语言", Required: true},
{Name: "code", Description: "待审查代码", Required: true},
{Name: "focus", Description: "审查重点(安全/性能/可读性)", Required: false},
},
func(args map[string]string) (string, error) {
focus := args["focus"]
if focus == "" {
focus = "综合"
}
return fmt.Sprintf(`请审查以下 %s 代码,重点关注 %s。
代码:
%s
请给出:
1. 潜在问题(按严重程度排序)
2. 改进建议
3. 总体评分(1-10)`, args["language"], focus, args["code"]), nil
},
)
// 启动
ctx := context.Background()
server.Serve(ctx)
}
// executeInSandbox 沙箱执行(简化版)
func executeInSandbox(lang, code string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var cmd *exec.Cmd
switch lang {
case "go":
cmd = exec.CommandContext(ctx, "go", "run", "-")
cmd.Stdin = strings.NewReader(code)
case "python":
cmd = exec.CommandContext(ctx, "python3", "-c", code)
default:
return "", fmt.Errorf("不支持的语言: %s", lang)
}
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), fmt.Errorf("执行失败: %w", err)
}
return string(output), nil
}
4.2 启动 Server
# AgentForge 作为 MCP Server 运行
$ go run main.go mcp-server
# 等待 MCP Client 通过 stdin 连接
# 所有日志输出到 stderr(stdout 专用于 JSON-RPC)
五、MCP Server 验证
5.1 手动测试
# 用 echo 模拟 Client 发送请求
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | go run main.go mcp-server
# 响应:
# {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":true},…},"serverInfo":{"name":"agentforge","version":"0.6.0"}}}
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | go run main.go mcp-server
# 响应:
# {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_weather","description":"查询城市天气",…},…]}}
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"北京"}}}' | go run main.go mcp-server
# 响应:
# {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"北京: 28°C, 晴"}],"isError":false}}
5.2 交互全景
工具执行
AgentForge MCP Server
测试终端
工具执行
AgentForge MCP Server
测试终端
#mermaid-svg-6Jn1e22QqSWXkUFG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6Jn1e22QqSWXkUFG .error-icon{fill:#552222;}#mermaid-svg-6Jn1e22QqSWXkUFG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6Jn1e22QqSWXkUFG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6Jn1e22QqSWXkUFG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6Jn1e22QqSWXkUFG .marker.cross{stroke:#333333;}#mermaid-svg-6Jn1e22QqSWXkUFG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6Jn1e22QqSWXkUFG p{margin:0;}#mermaid-svg-6Jn1e22QqSWXkUFG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6Jn1e22QqSWXkUFG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6Jn1e22QqSWXkUFG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6Jn1e22QqSWXkUFG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6Jn1e22QqSWXkUFG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6Jn1e22QqSWXkUFG .sequenceNumber{fill:white;}#mermaid-svg-6Jn1e22QqSWXkUFG #sequencenumber{fill:#333;}#mermaid-svg-6Jn1e22QqSWXkUFG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6Jn1e22QqSWXkUFG .messageText{fill:#333;stroke:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6Jn1e22QqSWXkUFG .labelText,#mermaid-svg-6Jn1e22QqSWXkUFG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .loopText,#mermaid-svg-6Jn1e22QqSWXkUFG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6Jn1e22QqSWXkUFG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6Jn1e22QqSWXkUFG .noteText,#mermaid-svg-6Jn1e22QqSWXkUFG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6Jn1e22QqSWXkUFG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6Jn1e22QqSWXkUFG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6Jn1e22QqSWXkUFG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6Jn1e22QqSWXkUFG .actorPopupMenu{position:absolute;}#mermaid-svg-6Jn1e22QqSWXkUFG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6Jn1e22QqSWXkUFG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6Jn1e22QqSWXkUFG .actor-man circle,#mermaid-svg-6Jn1e22QqSWXkUFG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6Jn1e22QqSWXkUFG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
initialize
能力声明 + ServerInfo
tools/list
[get_weather, calculate, execute_code]
tools/call (get_weather, {city:北京})
执行 get_weather("北京")
"28°C, 晴"
CallToolResult
resources/read (agentforge://config)
配置 JSON
prompts/get (code_review, {language:Go,…})
渲染后的审查 Prompt
本篇小结
| MCP Server 实现 | JSON-RPC 2.0 over stdio,按行读取 stdin,JSON 响应写 stdout |
| 三大原语注册 | RegisterTool / RegisterResource / RegisterPrompt |
| 消息分发 | handleMessage 根据 method 路由到对应 handler |
| 沙箱执行 | os/exec + context.WithTimeout 实现 10 秒超时控制 |
| stdout 专用 | JSON-RPC 响应必须走 stdout,日志走 stderr 避免污染协议 |
| AgentForge 作为 Server | 天气/计算/代码执行工具暴露为标准 MCP 服务 |
下篇预告
第 32 篇:Agent 与数据库交互 — NL2SQL 工具设计 自然语言转 SQL 的完整工具设计——Schema 感知、SQL 安全、结果格式化。
如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。
网硕互联帮助中心



评论前必须登录!
注册