分享
  1. 首页
  2. 文章

GFast 开发 MCP 服务器之AI对话

kocie · · 494 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

支持配置不同平台大模型接口(如:openAI 或兼容 openAI 接口,DeepSeek, 通义千问)或者本地搭建接口(如:ollama), 并支持配置 mcp 服务调用 mcp 工具。

AI 对话聊天功能的核心特点:

1、自然语言理解 (NLU)

理解用户输入的意思,包括意图识别、实体提取等。 例如:用户说 “明天北京天气怎么样?”AI 能识别出意图是 “查天气”,地点是 “北京”,时间是 “明天”。

2、对话管理

维持多轮对话的连贯性,记住上下文。 比如在一轮对话中,用户说 “我想订机票”,然后接着说 “去北京”,系统要能理解这是继续订票流程。 不同用户对话历史记录存入数据库,永久保存,由用户自行管理,方便查阅

3、自然语言生成 (NLG)

将系统的回应内容用自然流畅的语言表达出来。 比如根据查询结果,可自定义查询天气的 mpc 工具调用天气接口,对话时 AI 自动调用工具查询天气,生成 “明天北京最高气温 20°C,最低 12°C。”

4、MCP 工具掉用

系统内置了几个基本示例 MCP 工具,用户可自行扩展工具,系统已对 mcp 扩展功能进行了封装,扩展非常方便。

插件下载:

https://plugins.g-fast.cn/store#/showModule?id=22

插件安装

一、覆盖下载插件后端代码,并安装相关依赖

将下载插件中 go 文件夹下的文件覆盖到后端,后端代码为一个新的目录,不会覆盖项目已有代码,可放心覆盖到项目根目录下,覆盖后修改以下几个配置:

internal/app/system/consts/consts.go 文件添加常量:

20250529171357

// WebSocketTypeChat websocket通知类型-AI对话
WebSocketTypeChat = "chat"
WebSocketTypeLLMTools = "LLMTools"

main.go 文件中注册 mcp hook:

20250529171603

import (
	...
	_ "github.com/tiger1103/gfast/v3/internal/app/mcp/hook"
	...
)

安装相关依赖:

go get github.com/mark3labs/mcp-go
go get github.com/cloudwego/eino
go get github.com/cloudwego/eino-ext/components/model/openai
go get github.com/cloudwego/eino-ext/components/model/deepseek
go get github.com/cloudwego/eino-ext/components/model/ollama
go get github.com/cloudwego/eino-ext/components/tool/mcp
go get github.com/cloudwego/eino-ext/libs/acl/openai

二、覆盖下载插件前端代码,并安装相关依赖

将下载插件中 vue 文件夹下的文件覆盖到前端

添加前端依赖 通过修改 package.json 文件添加依赖,在 dependencies 块中添加(推荐使用此方式):

"dependencies": {
	"dompurify": "^3.2.5",
	"marked": "^15.0.11",
	"@types/highlight.js": "^9.12.4",
	"highlight.js": "^11.11.1",
}
"devDependencies":{
	"@types/dompurify": "^3.0.5",
 "@types/marked": "^5.0.2",
}

添加后运行:npm install --registry=https://registry.npmmirror.com 或者通过命令行安装(如果使用了上面方式安装,跳过,不需重复安装):

npm install dompurify@3.2.5 marked@15.0.11 @types/highlight.js@9.12.4 highlight.js@11.11.1 --registry=https://registry.npmmirror.com
npm install @types/dompurify@3.0.5 @types/marked@5.0.2 --save-dev --registry=https://registry.npmmirror.com

修改 src/utils/websocket.ts 文件,添加 AI 对话相关处理代码:

20250529174021

import { ToolCall, useChatStore } from '/@/stores/chatStore';

20250529174122

if(message.event ==='chat'){
	useChatStore().setChunkMessage(message.data)
	return
}
if(message.event ==='LLMTools'){
	const tools = message.data as ToolCall[]
	useChatStore().setToolCall(tools)
	return
}

三、导入数据库文件

1.1 字典数据导入

导入字典数据文件字典.sql

1.2 流程相关数据表导入

导入数据表文件表数据.sql

1.3 菜单数据导入

导入菜单数据文件菜单.sql

注意:菜单.sql 为脚本,需要在 mysql 命令中执行 。

四、使用说明

1、模型配置

20250530104802

例如添加通义千问:

20250530104852

填写模型和密钥

配置 ollama:

20250530104945

配置模型 deepseek-r1 和 接口地址

注意有的模型不支持函数调用,不能使用 mcp,则函数调用选择否。

配置完成后可进入 AI 对话:

选择对应模型

20250530105432

选择 ollama deepseek-r1 则有思考过程:

20250530105559

四、MCP 工具添加

系统默认已经有几个示例 MCP 工具:

20250530110153

对应代码目录在 internal/app/mcp/tools 下:

20250530110344

如果需要新增 MCP 工具,只需在该目录下添加对应方法即可,GFAST 已对该目录下工具进行封装自动注册,添加后会自动加载注入到系统中, 注意系统中自带数据库操作的工具只在开发环境下有作用,生产环境不会调用,防止生产故障和数据泄漏(例如用户对 AI 说请清空数据库...)。

以下我们做一个添加 mcp 工具实践,例如添加天气预报查询工具:

internal/app/mcp/tools 目录下新建文件:wether_query.go(文件名这些都是随意填写,没有限制)

20250530152927

添加以下代码:

/*
* @desc:天气预报查询tools
 */
package tools
import (
	"context"
	"errors"
	"fmt"
	"github.com/gogf/gf/v2/encoding/gurl"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"github.com/tiger1103/gfast/v3/internal/app/mcp/register"
	"github.com/tiger1103/gfast/v3/internal/consts"
	"github.com/tiger1103/gfast/v3/library/liberr"
)
func (r *Reg) RegisterWeatherQuery() {
	// 注册工具
	register.AddHandler(func(mcpServer *server.MCPServer) {
		// 添加工具
		var (
			tool = mcp.NewTool("weather_query", // 工具名称
				mcp.WithDescription("查询给定城市的天气预报信息"), // 工具描述
				mcp.WithString("city", // 参数名称
					mcp.Required(), // 参数是必需的
					mcp.Description("指定的城市名称"), // 参数描述
				),
				mcp.WithString("extensions", // 参数名称
					mcp.Description("气象类型实况天气(如现在,今天)为:base;预报天气(如这几天,明天等)为:all"), // 参数描述
				),
			)
			helloHandler = func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
				var (
					err error
					weather = new(Weather)
				)
				err = g.Try(ctx, func(ctx context.Context) {
					// 从请求参数中获取名字参数,并断言为字符串类型
					name, ok := request.GetArguments()["city"].(string)
					if !ok {
						// 如果断言失败,返回错误
						liberr.ErrIsNilCode(ctx, errors.New("请给我一个城市名称"), consts.CodeInfo)
					}
					extensions := request.GetArguments()["extensions"].(string)
					weather, err = weather.Query(ctx, name, extensions)
					liberr.ErrIsNilCode(ctx, err, consts.CodeInfo)
				})
				if err != nil {
					return r.returnRes(err)
				}
				// 返回结果
				return mcp.NewToolResultText(fmt.Sprintf("该城市的天气预报信息为: %s!", gconv.String(weather))), nil
			}
		)
		mcpServer.AddTool(tool, helloHandler)
	})
}
func (s *Weather) Query(ctx context.Context, city, extensions string) (info *Weather, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		var (
			key = "此处填写高德开发平台申请的接口key"
			cityInfo *AMapDistrictResponse
		)
		cityInfo, err = s.GetDistricts(ctx, city, key)
		liberr.ErrIsNil(ctx, err)
		if cityInfo == nil || len(cityInfo.Districts) == 0 {
			liberr.ErrIsNil(ctx, errors.New("未查询到对应城市行政编码信息,请核对城市是否正确"))
		}
		info, err = s.GetWeather(ctx, cityInfo.Districts[0].AdCode, extensions, key)
	})
	return
}
func (s *Weather) GetWeather(ctx context.Context, code, extensions, key string) (info *Weather, err error) {
	// 1. 构造请求 URL
	url := fmt.Sprintf(
		"https://restapi.amap.com/v3/weather/weatherInfo?city=%s&extensions=%s&key=%s",
		gurl.Encode(code),
		gurl.Encode(extensions),
		gurl.Encode(key),
	)
	// 2. 发起 GET 请求
	client := g.Client()
	resp, err := client.Get(ctx, url)
	if err != nil {
		return nil, fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Close()
	// 3. 解析 JSON 响应
	if err := gconv.Scan(resp.ReadAll(), &info); err != nil {
		return nil, fmt.Errorf("解析 JSON 失败: %v", err)
	}
	// 4. 检查接口返回状态
	if info.Status != "1" {
		return nil, fmt.Errorf("查询天气接口返回错误: %s,状态码:%s", info.Info, info.Infocode)
	}
	return
}
// GetDistricts 调用高德地图行政区域查询接口
func (s *Weather) GetDistricts(ctx context.Context, keywords string, key string) (*AMapDistrictResponse, error) {
	// 1. 构造请求 URL
	url := fmt.Sprintf(
		"https://restapi.amap.com/v3/config/district?keywords=%s&subdistrict=2&key=%s",
		gurl.Encode(keywords),
		gurl.Encode(key),
	)
	// 2. 发起 GET 请求
	client := g.Client()
	resp, err := client.Get(ctx, url)
	if err != nil {
		return nil, fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Close()
	// 3. 解析 JSON 响应
	var result *AMapDistrictResponse
	if err := gconv.Scan(resp.ReadAll(), &result); err != nil {
		return nil, fmt.Errorf("解析 JSON 失败: %v", err)
	}
	// 4. 检查接口返回状态
	if result.Status != "1" {
		return nil, fmt.Errorf("查询城市行政区划接口返回错误: %s,状态码:%s", result.Info, result.Infocode)
	}
	return result, nil
}
type Suggestion struct {
	Keywords []string `json:"keywords"`
	Cities []string `json:"cities"`
}
type District struct {
	CityCode string `json:"citycode"`
	AdCode string `json:"adcode"`
	Name string `json:"name"`
	Center string `json:"center"`
	Level string `json:"level"`
}
type AMapDistrictResponse struct {
	Status string `json:"status"`
	Info string `json:"info"`
	Infocode string `json:"infocode"`
	Count string `json:"count"`
	Suggestion Suggestion `json:"suggestion"`
	Districts []District `json:"districts"`
}
type Weather struct {
	Status string `json:"status"`
	Count string `json:"count"`
	Info string `json:"info"`
	Infocode string `json:"infocode"`
	Lives []struct {
		Province string `json:"province"`
		City string `json:"city"`
		Adcode string `json:"adcode"`
		Weather string `json:"weather"`
		Temperature string `json:"temperature"`
		Winddirection string `json:"winddirection"`
		Windpower string `json:"windpower"`
		Humidity string `json:"humidity"`
		Reporttime string `json:"reporttime"`
		TemperatureFloat string `json:"temperature_float"`
		HumidityFloat string `json:"humidity_float"`
	} `json:"lives"`
	Forecasts []struct {
		City string `json:"city"`
		Adcode string `json:"adcode"`
		Province string `json:"province"`
		Reporttime string `json:"reporttime"`
		Casts []struct {
			Date string `json:"date"`
			Week string `json:"week"`
			Dayweather string `json:"dayweather"`
			Nightweather string `json:"nightweather"`
			Daytemp string `json:"daytemp"`
			Nighttemp string `json:"nighttemp"`
			Daywind string `json:"daywind"`
			Nightwind string `json:"nightwind"`
			Daypower string `json:"daypower"`
			Nightpower string `json:"nightpower"`
			DaytempFloat string `json:"daytemp_float"`
			NighttempFloat string `json:"nighttemp_float"`
		}
	} `json:"forecasts"`
}

注意,天气查询接口用的是高德地图,需要在高德开放平台申请一个 key 即可免费使用

完成后对话如下图:

20250530153214

补充,使用 MCP Inspector 工具连接时,需要配置 token


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
494 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏