Refactor MCP tool handling by introducing tool code normalization and parsing, enhancing request and response structures for AIAgentMCPTool
This commit is contained in:
@@ -2,13 +2,12 @@ package factory
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"cs-agent/internal/ai/mcps"
|
"cs-agent/internal/ai/mcps"
|
||||||
impladapter "cs-agent/internal/ai/runtime/internal/impl/adapter"
|
impladapter "cs-agent/internal/ai/runtime/internal/impl/adapter"
|
||||||
"cs-agent/internal/models"
|
"cs-agent/internal/models"
|
||||||
"cs-agent/internal/pkg/dto/request"
|
"cs-agent/internal/pkg/toolx"
|
||||||
|
|
||||||
einotool "github.com/cloudwego/eino/components/tool"
|
einotool "github.com/cloudwego/eino/components/tool"
|
||||||
)
|
)
|
||||||
@@ -23,13 +22,16 @@ func (f *ToolFactory) BuildMCPTools(aiAgent *models.AIAgent) ([]impladapter.MCPT
|
|||||||
if aiAgent == nil || strings.TrimSpace(aiAgent.AllowedMCPTools) == "" {
|
if aiAgent == nil || strings.TrimSpace(aiAgent.AllowedMCPTools) == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var raw []request.AIAgentMCPToolRequest
|
raw, err := toolx.ParseAgentMCPToolsJSON(aiAgent.AllowedMCPTools)
|
||||||
if err := json.Unmarshal([]byte(aiAgent.AllowedMCPTools), &raw); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ret := make([]impladapter.MCPToolDefinition, 0, len(raw))
|
ret := make([]impladapter.MCPToolDefinition, 0, len(raw))
|
||||||
for _, item := range raw {
|
for _, item := range raw {
|
||||||
toolCode := strings.TrimSpace(item.ServerCode) + "/" + strings.TrimSpace(item.ToolName)
|
toolCode := strings.TrimSpace(item.ToolCode)
|
||||||
|
if toolCode == "" {
|
||||||
|
toolCode = toolx.BuildMCPToolCode(item.ServerCode, item.ToolName)
|
||||||
|
}
|
||||||
definition := impladapter.MCPToolDefinition{
|
definition := impladapter.MCPToolDefinition{
|
||||||
ToolCode: toolCode,
|
ToolCode: toolCode,
|
||||||
ServerCode: strings.TrimSpace(item.ServerCode),
|
ServerCode: strings.TrimSpace(item.ServerCode),
|
||||||
@@ -80,7 +82,7 @@ func (f *ToolFactory) loadToolMetadata(ctx context.Context, definitions []implad
|
|||||||
}
|
}
|
||||||
for i := range toolInfos {
|
for i := range toolInfos {
|
||||||
toolInfo := toolInfos[i]
|
toolInfo := toolInfos[i]
|
||||||
toolCode := strings.TrimSpace(serverCode) + "/" + strings.TrimSpace(toolInfo.Name)
|
toolCode := toolx.BuildMCPToolCode(serverCode, toolInfo.Name)
|
||||||
toolInfoCopy := toolInfo
|
toolInfoCopy := toolInfo
|
||||||
toolsByCode[toolCode] = &toolInfoCopy
|
toolsByCode[toolCode] = &toolInfoCopy
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"cs-agent/internal/pkg/dto/request"
|
"cs-agent/internal/pkg/dto/request"
|
||||||
"cs-agent/internal/pkg/dto/response"
|
"cs-agent/internal/pkg/dto/response"
|
||||||
"cs-agent/internal/pkg/enums"
|
"cs-agent/internal/pkg/enums"
|
||||||
|
"cs-agent/internal/pkg/toolx"
|
||||||
"cs-agent/internal/pkg/utils"
|
"cs-agent/internal/pkg/utils"
|
||||||
"cs-agent/internal/services"
|
"cs-agent/internal/services"
|
||||||
|
|
||||||
@@ -117,15 +118,15 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse {
|
|||||||
StatusName: enums.GetStatusLabel(item.Status),
|
StatusName: enums.GetStatusLabel(item.Status),
|
||||||
AIConfigID: item.AIConfigID,
|
AIConfigID: item.AIConfigID,
|
||||||
ServiceMode: item.ServiceMode,
|
ServiceMode: item.ServiceMode,
|
||||||
ServiceModeName: enums.GetIMConversationServiceModeLabel(enums.IMConversationServiceMode(item.ServiceMode)),
|
ServiceModeName: enums.GetIMConversationServiceModeLabel(item.ServiceMode),
|
||||||
SystemPrompt: item.SystemPrompt,
|
SystemPrompt: item.SystemPrompt,
|
||||||
WelcomeMessage: item.WelcomeMessage,
|
WelcomeMessage: item.WelcomeMessage,
|
||||||
ReplyTimeoutSeconds: item.ReplyTimeoutSeconds,
|
ReplyTimeoutSeconds: item.ReplyTimeoutSeconds,
|
||||||
HandoffMode: item.HandoffMode,
|
HandoffMode: item.HandoffMode,
|
||||||
HandoffModeName: enums.GetAIAgentHandoffModeLabel(enums.AIAgentHandoffMode(item.HandoffMode)),
|
HandoffModeName: enums.GetAIAgentHandoffModeLabel(item.HandoffMode),
|
||||||
MaxAIReplyRounds: item.MaxAIReplyRounds,
|
MaxAIReplyRounds: item.MaxAIReplyRounds,
|
||||||
FallbackMode: item.FallbackMode,
|
FallbackMode: item.FallbackMode,
|
||||||
FallbackModeName: enums.GetAIAgentFallbackModeLabel(enums.AIAgentFallbackMode(item.FallbackMode)),
|
FallbackModeName: enums.GetAIAgentFallbackModeLabel(item.FallbackMode),
|
||||||
FallbackMessage: item.FallbackMessage,
|
FallbackMessage: item.FallbackMessage,
|
||||||
KnowledgeIDs: utils.SplitInt64s(item.KnowledgeIDs),
|
KnowledgeIDs: utils.SplitInt64s(item.KnowledgeIDs),
|
||||||
SkillIDs: utils.SplitInt64s(item.SkillIDs),
|
SkillIDs: utils.SplitInt64s(item.SkillIDs),
|
||||||
@@ -169,7 +170,12 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse {
|
|||||||
var directTools []request.AIAgentMCPToolRequest
|
var directTools []request.AIAgentMCPToolRequest
|
||||||
if err := json.Unmarshal([]byte(raw), &directTools); err == nil {
|
if err := json.Unmarshal([]byte(raw), &directTools); err == nil {
|
||||||
for _, tool := range directTools {
|
for _, tool := range directTools {
|
||||||
|
toolCode := strings.TrimSpace(tool.ToolCode)
|
||||||
|
if toolCode == "" {
|
||||||
|
toolCode = toolx.BuildMCPToolCode(tool.ServerCode, tool.ToolName)
|
||||||
|
}
|
||||||
ret.DirectTools = append(ret.DirectTools, response.AIAgentMCPToolResponse{
|
ret.DirectTools = append(ret.DirectTools, response.AIAgentMCPToolResponse{
|
||||||
|
ToolCode: toolCode,
|
||||||
ServerCode: strings.TrimSpace(tool.ServerCode),
|
ServerCode: strings.TrimSpace(tool.ServerCode),
|
||||||
ToolName: strings.TrimSpace(tool.ToolName),
|
ToolName: strings.TrimSpace(tool.ToolName),
|
||||||
Title: strings.TrimSpace(tool.Title),
|
Title: strings.TrimSpace(tool.Title),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package request
|
|||||||
import "cs-agent/internal/pkg/enums"
|
import "cs-agent/internal/pkg/enums"
|
||||||
|
|
||||||
type AIAgentMCPToolRequest struct {
|
type AIAgentMCPToolRequest struct {
|
||||||
|
ToolCode string `json:"toolCode"`
|
||||||
ServerCode string `json:"serverCode"`
|
ServerCode string `json:"serverCode"`
|
||||||
ToolName string `json:"toolName"`
|
ToolName string `json:"toolName"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type AIAgentSkillResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AIAgentMCPToolResponse struct {
|
type AIAgentMCPToolResponse struct {
|
||||||
|
ToolCode string `json:"toolCode"`
|
||||||
ServerCode string `json:"serverCode"`
|
ServerCode string `json:"serverCode"`
|
||||||
ToolName string `json:"toolName"`
|
ToolName string `json:"toolName"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package toolx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"cs-agent/internal/pkg/dto/request"
|
||||||
|
"cs-agent/internal/pkg/errorsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildMCPToolCode(serverCode, toolName string) string {
|
||||||
|
serverCode = strings.TrimSpace(serverCode)
|
||||||
|
toolName = strings.TrimSpace(toolName)
|
||||||
|
if serverCode == "" || toolName == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return serverCode + "/" + toolName
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitMCPToolCode(toolCode string) (string, string) {
|
||||||
|
toolCode = strings.TrimSpace(toolCode)
|
||||||
|
if toolCode == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
idx := strings.Index(toolCode, "/")
|
||||||
|
if idx <= 0 || idx >= len(toolCode)-1 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(toolCode[:idx]), strings.TrimSpace(toolCode[idx+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgentMCPToolRequest, error) {
|
||||||
|
toolCode := strings.TrimSpace(item.ToolCode)
|
||||||
|
serverCode := strings.TrimSpace(item.ServerCode)
|
||||||
|
toolName := strings.TrimSpace(item.ToolName)
|
||||||
|
if toolCode != "" {
|
||||||
|
parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode)
|
||||||
|
if parsedServerCode == "" || parsedToolName == "" {
|
||||||
|
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 格式不合法")
|
||||||
|
}
|
||||||
|
if serverCode != "" && !strings.EqualFold(serverCode, parsedServerCode) {
|
||||||
|
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 serverCode 不一致")
|
||||||
|
}
|
||||||
|
if toolName != "" && !strings.EqualFold(toolName, parsedToolName) {
|
||||||
|
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 toolName 不一致")
|
||||||
|
}
|
||||||
|
serverCode = parsedServerCode
|
||||||
|
toolName = parsedToolName
|
||||||
|
} else {
|
||||||
|
toolCode = BuildMCPToolCode(serverCode, toolName)
|
||||||
|
}
|
||||||
|
if toolCode == "" || serverCode == "" || toolName == "" {
|
||||||
|
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode、serverCode 和 toolName 不能为空")
|
||||||
|
}
|
||||||
|
ret := request.AIAgentMCPToolRequest{
|
||||||
|
ToolCode: toolCode,
|
||||||
|
ServerCode: serverCode,
|
||||||
|
ToolName: toolName,
|
||||||
|
Title: strings.TrimSpace(item.Title),
|
||||||
|
Description: strings.TrimSpace(item.Description),
|
||||||
|
}
|
||||||
|
if len(item.Arguments) > 0 {
|
||||||
|
ret.Arguments = make(map[string]string, len(item.Arguments))
|
||||||
|
for key, value := range item.Arguments {
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if key == "" || value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret.Arguments[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAgentMCPToolsJSON(raw string) ([]request.AIAgentMCPToolRequest, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var ret []request.AIAgentMCPToolRequest
|
||||||
|
if err := json.Unmarshal([]byte(raw), &ret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"cs-agent/internal/pkg/dto/request"
|
"cs-agent/internal/pkg/dto/request"
|
||||||
"cs-agent/internal/pkg/enums"
|
"cs-agent/internal/pkg/enums"
|
||||||
"cs-agent/internal/pkg/errorsx"
|
"cs-agent/internal/pkg/errorsx"
|
||||||
|
"cs-agent/internal/pkg/toolx"
|
||||||
"cs-agent/internal/pkg/utils"
|
"cs-agent/internal/pkg/utils"
|
||||||
"cs-agent/internal/repositories"
|
"cs-agent/internal/repositories"
|
||||||
|
|
||||||
@@ -294,37 +295,20 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ
|
|||||||
ret := make([]request.AIAgentMCPToolRequest, 0, len(input))
|
ret := make([]request.AIAgentMCPToolRequest, 0, len(input))
|
||||||
seen := make(map[string]struct{})
|
seen := make(map[string]struct{})
|
||||||
for _, item := range input {
|
for _, item := range input {
|
||||||
serverCode := strings.TrimSpace(item.ServerCode)
|
normalized, err := toolx.NormalizeMCPToolRequest(item)
|
||||||
toolName := strings.TrimSpace(item.ToolName)
|
if err != nil {
|
||||||
if serverCode == "" || toolName == "" {
|
return nil, err
|
||||||
return nil, errorsx.InvalidParam("Direct Tool 的 serverCode 和 toolName 不能为空")
|
|
||||||
}
|
}
|
||||||
|
serverCode := strings.TrimSpace(normalized.ServerCode)
|
||||||
server, ok := cfg.MCP.Servers[serverCode]
|
server, ok := cfg.MCP.Servers[serverCode]
|
||||||
if !ok || !server.Enabled {
|
if !ok || !server.Enabled {
|
||||||
return nil, errorsx.InvalidParam("Direct Tool 绑定的 MCP 服务不存在或未启用")
|
return nil, errorsx.InvalidParam("Direct Tool 绑定的 MCP 服务不存在或未启用")
|
||||||
}
|
}
|
||||||
key := serverCode + "/" + toolName
|
key := strings.TrimSpace(normalized.ToolCode)
|
||||||
if _, exists := seen[key]; exists {
|
if _, exists := seen[key]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[key] = struct{}{}
|
seen[key] = struct{}{}
|
||||||
normalized := request.AIAgentMCPToolRequest{
|
|
||||||
ServerCode: serverCode,
|
|
||||||
ToolName: toolName,
|
|
||||||
Title: strings.TrimSpace(item.Title),
|
|
||||||
Description: strings.TrimSpace(item.Description),
|
|
||||||
}
|
|
||||||
if len(item.Arguments) > 0 {
|
|
||||||
normalized.Arguments = make(map[string]string, len(item.Arguments))
|
|
||||||
for key, value := range item.Arguments {
|
|
||||||
key = strings.TrimSpace(key)
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
if key == "" || value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
normalized.Arguments[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = append(ret, normalized)
|
ret = append(ret, normalized)
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user