// 内置演示 MCP 服务(stdio 传输) // 提供 get_time / calculate / weather_query / echo 等工具,用于验证「MCP 市场 → 会话工具调用」全链路。 // 运行:go run ./cmd/demo-mcp 或直接运行已编译二进制 bin/demo-mcp package main import ( "context" "encoding/json" "fmt" "math" "strings" "time" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) func toolResultText(text string) *mcp.CallToolResult { return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.TextContent{Type: "text", Text: text}, }, } } func main() { srv := server.NewMCPServer("demo-mcp", "1.0.0") // 1. 当前时间 srv.AddTool(mcp.NewTool( "get_time", mcp.WithDescription("获取服务器当前日期和时间(含星期),不需要参数"), ), func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { now := time.Now() weekdays := []string{"日", "一", "二", "三", "四", "五", "六"} return toolResultText(fmt.Sprintf("当前时间:%s(星期%s)", now.Format("2006-01-02 15:04:05"), weekdays[int(now.Weekday())])), nil }) // 2. 计算器 srv.AddTool(mcp.NewTool( "calculate", mcp.WithDescription("计算一个数学表达式,支持 + - * / % 与括号。例如:2+3*4"), mcp.WithString("expression", mcp.Required(), mcp.Description("数学表达式字符串")), ), func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { expr := req.GetArguments()["expression"] exprStr, _ := expr.(string) exprStr = strings.TrimSpace(exprStr) if exprStr == "" { return toolResultText("错误:expression 参数不能为空"), nil } val, err := safeEval(exprStr) if err != nil { return toolResultText("错误:" + err.Error()), nil } return toolResultText(fmt.Sprintf("%s = %v", exprStr, val)), nil }) // 3. 模拟天气 srv.AddTool(mcp.NewTool( "weather_query", mcp.WithDescription("查询指定城市的模拟天气信息(演示数据,非真实天气)"), mcp.WithString("city", mcp.Required(), mcp.Description("城市名称,如 北京、上海、连云港")), ), func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { city, _ := req.GetArguments()["city"].(string) city = strings.TrimSpace(city) if city == "" { city = "未知城市" } conditions := []string{"晴", "多云", "小雨", "阴", "晴转多云"} cond := conditions[int(time.Now().Unix())%len(conditions)] temp := 18 + int(time.Now().Unix())%15 return toolResultText(fmt.Sprintf("【%s】今天 %s,气温 %d℃,空气质量:良(模拟数据)", city, cond, temp)), nil }) // 4. 回声 srv.AddTool(mcp.NewTool( "echo", mcp.WithDescription("原样返回你输入的文字,用于测试"), mcp.WithString("text", mcp.Required(), mcp.Description("要回显的文字")), ), func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { text, _ := req.GetArguments()["text"].(string) return toolResultText("echo: " + text), nil }) // 5. 获取用户信息(演示带对象参数的 JSON 参数) srv.AddTool(mcp.NewTool( "get_user_profile", mcp.WithDescription("获取用户画像信息(演示),可指定姓名与年龄"), mcp.WithString("name", mcp.Required(), mcp.Description("姓名")), mcp.WithNumber("age", mcp.Description("年龄")), ), func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := req.GetArguments() name, _ := args["name"].(string) age, _ := args["age"].(float64) out, _ := json.Marshal(map[string]interface{}{ "name": name, "age": int(age), "role": "演示用户", "note": "这是一条来自内置演示 MCP 服务的测试数据", }) return toolResultText(string(out)), nil }) if err := server.ServeStdio(srv); err != nil { panic(err) } } // safeEval 极简安全表达式求值(仅支持数字与 + - * / % 和括号) func safeEval(expr string) (float64, error) { parser := &exprParser{s: expr} parser.skipSpace() v, err := parser.parseExpr() if err != nil { return 0, err } parser.skipSpace() if parser.pos < len(parser.s) { return 0, fmt.Errorf("无法解析的表达式(非法字符)") } if math.IsInf(v, 0) || math.IsNaN(v) { return 0, fmt.Errorf("计算结果无效") } return v, nil } type exprParser struct { s string pos int } func (p *exprParser) skipSpace() { for p.pos < len(p.s) && (p.s[p.pos] == ' ' || p.s[p.pos] == '\t') { p.pos++ } } func (p *exprParser) peek() byte { if p.pos < len(p.s) { return p.s[p.pos] } return 0 } func (p *exprParser) parseExpr() (float64, error) { v, err := p.parseTerm() if err != nil { return 0, err } for { p.skipSpace() c := p.peek() if c == '+' || c == '-' { p.pos++ rhs, err := p.parseTerm() if err != nil { return 0, err } if c == '+' { v += rhs } else { v -= rhs } } else { return v, nil } } } func (p *exprParser) parseTerm() (float64, error) { v, err := p.parseFactor() if err != nil { return 0, err } for { p.skipSpace() c := p.peek() if c == '*' || c == '/' || c == '%' { p.pos++ rhs, err := p.parseFactor() if err != nil { return 0, err } switch c { case '*': v *= rhs case '/': if rhs == 0 { return 0, fmt.Errorf("除数不能为 0") } v /= rhs case '%': if rhs == 0 { return 0, fmt.Errorf("取模除数不能为 0") } v = math.Mod(v, rhs) } } else { return v, nil } } } func (p *exprParser) parseFactor() (float64, error) { p.skipSpace() c := p.peek() if c == '(' { p.pos++ v, err := p.parseExpr() if err != nil { return 0, err } p.skipSpace() if p.peek() != ')' { return 0, fmt.Errorf("缺少右括号") } p.pos++ return v, nil } return p.parseNumber() } func (p *exprParser) parseNumber() (float64, error) { start := p.pos for p.pos < len(p.s) { c := p.s[p.pos] if (c >= '0' && c <= '9') || c == '.' { p.pos++ } else { break } } if start == p.pos { return 0, fmt.Errorf("表达式格式错误(无法识别的字符)") } raw := p.s[start:p.pos] var v float64 if _, err := fmt.Sscanf(raw, "%g", &v); err != nil { return 0, fmt.Errorf("数字格式错误: %s", raw) } return v, nil }