Files
yunzerwebsiteallinone/go/cmd/demo-mcp/main.go
T

244 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 内置演示 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
}