244 lines
6.2 KiB
Go
244 lines
6.2 KiB
Go
// 内置演示 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
|
||
}
|