GFast开发MCP服务器之集成AI对话插件

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

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

1、自然语言理解 (NLU)

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

2、对话管理

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

3、自然语言生成 (NLG)

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

4、MCP工具掉用

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

插件下载:

plugins.g-fast.cn/store#/show...

插件安装

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

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

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

ini 复制代码
// WebSocketTypeChat websocket通知类型-AI对话
WebSocketTypeChat     = "chat"
WebSocketTypeLLMTools = "LLMTools"

main.go文件中注册 mcp hook:

go 复制代码
import (
	...
	_ "github.com/tiger1103/gfast/v3/internal/app/mcp/hook"
	...
)

安装相关依赖:

bash 复制代码
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块中添加(推荐使用此方式):

perl 复制代码
"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 或者通过命令行安装(如果使用了上面方式安装,跳过,不需重复安装):

kotlin 复制代码
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对话相关处理代码:

javascript 复制代码
import { ToolCall, useChatStore } from '/@/stores/chatStore';
scss 复制代码
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、模型配置

例如添加通义千问:

填写模型和密钥

配置ollama:

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

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

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

选择对应模型

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

四、MCP工具添加

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

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

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

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

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

添加以下代码:

go 复制代码
/*
* @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即可免费使用

完成后对话如下图:

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

相关推荐
counterxing8 小时前
Agent 跑起来之后,难的是复用、观测和评测
node.js·agent·ai编程
uccs8 小时前
大模型底层机制与Agent开发
agent·ai编程·claude
counterxing9 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
夜雪闻竹9 小时前
vectra 向量索引文件损坏怎么办
ai编程·向量·vectra
ZzT9 小时前
Harness 到底指什么
openai·ai编程·claude
宅小年9 小时前
AI 创业最危险的地方:太容易做出来
openai·ai编程·claude
麦客奥德彪9 小时前
Android Skills
架构·ai编程
言萧凡_CookieBoty10 小时前
一文讲清 RAG:让 AI 读懂业务知识库的核心方法
ai编程
kyriewen11 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
Patrick_Wilson11 小时前
知识沉淀的四层模型:从个人笔记到企业资产,让文档真正长出复利
面试·程序员·ai编程