Skip to content

Commit bc99b89

Browse files
authored
Merge pull request #43 from DEEIX-AI/tool_group_display
feat: implement MCP tool grouping and search functionality
2 parents 08d7254 + bc7f5e3 commit bc99b89

30 files changed

Lines changed: 1779 additions & 1252 deletions

File tree

backend/docs/docs.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6525,6 +6525,30 @@ const docTemplate = `{
65256525
}
65266526
}
65276527
},
6528+
"/settings/mcp-policy": {
6529+
"get": {
6530+
"security": [
6531+
{
6532+
"BearerAuth": []
6533+
}
6534+
],
6535+
"produces": [
6536+
"application/json"
6537+
],
6538+
"tags": [
6539+
"settings"
6540+
],
6541+
"summary": "查询 MCP 工具运行策略",
6542+
"responses": {
6543+
"200": {
6544+
"description": "OK",
6545+
"schema": {
6546+
"$ref": "#/definitions/github_com_DEEIX-AI_DEEIX-Chat_backend_internal_shared_response.Envelope"
6547+
}
6548+
}
6549+
}
6550+
}
6551+
},
65286552
"/settings/model-option-policy": {
65296553
"get": {
65306554
"security": [
@@ -11713,7 +11737,7 @@ const docTemplate = `{
1171311737
},
1171411738
"selectedToolIDs": {
1171511739
"type": "array",
11716-
"maxItems": 32,
11740+
"maxItems": 128,
1171711741
"items": {
1171811742
"type": "integer"
1171911743
}

backend/docs/swagger.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6518,6 +6518,30 @@
65186518
}
65196519
}
65206520
},
6521+
"/settings/mcp-policy": {
6522+
"get": {
6523+
"security": [
6524+
{
6525+
"BearerAuth": []
6526+
}
6527+
],
6528+
"produces": [
6529+
"application/json"
6530+
],
6531+
"tags": [
6532+
"settings"
6533+
],
6534+
"summary": "查询 MCP 工具运行策略",
6535+
"responses": {
6536+
"200": {
6537+
"description": "OK",
6538+
"schema": {
6539+
"$ref": "#/definitions/github_com_DEEIX-AI_DEEIX-Chat_backend_internal_shared_response.Envelope"
6540+
}
6541+
}
6542+
}
6543+
}
6544+
},
65216545
"/settings/model-option-policy": {
65226546
"get": {
65236547
"security": [
@@ -11706,7 +11730,7 @@
1170611730
},
1170711731
"selectedToolIDs": {
1170811732
"type": "array",
11709-
"maxItems": 32,
11733+
"maxItems": 128,
1171011734
"items": {
1171111735
"type": "integer"
1171211736
}

backend/docs/swagger.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3278,7 +3278,7 @@ definitions:
32783278
selectedToolIDs:
32793279
items:
32803280
type: integer
3281-
maxItems: 32
3281+
maxItems: 128
32823282
type: array
32833283
sourceMessagePublicID:
32843284
maxLength: 32
@@ -7628,6 +7628,20 @@ paths:
76287628
summary: 查询公开登录页配置
76297629
tags:
76307630
- settings
7631+
/settings/mcp-policy:
7632+
get:
7633+
produces:
7634+
- application/json
7635+
responses:
7636+
"200":
7637+
description: OK
7638+
schema:
7639+
$ref: '#/definitions/github_com_DEEIX-AI_DEEIX-Chat_backend_internal_shared_response.Envelope'
7640+
security:
7641+
- BearerAuth: []
7642+
summary: 查询 MCP 工具运行策略
7643+
tags:
7644+
- settings
76317645
/settings/model-option-policy:
76327646
get:
76337647
produces:

backend/internal/application/conversation/errs.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ var (
3939
ErrEmbeddingUnavailable = errors.New("embedding unavailable")
4040
// ErrTooManyMessageFiles 单条消息文件数超限。
4141
ErrTooManyMessageFiles = errors.New("too many message files")
42+
// ErrTooManySelectedTools 单条消息选择的 MCP 工具数超限。
43+
ErrTooManySelectedTools = errors.New("too many selected tools")
4244
// ErrInvalidMessageBranch 消息分支参数无效。
4345
ErrInvalidMessageBranch = errors.New("invalid message branch")
4446
// ErrMessageNotFound 消息不存在或无权限。

backend/internal/application/conversation/service_message_send.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func (s *Service) sendMessageInternal(
150150
if len(input.FileIDs) > maxFiles {
151151
return nil, ErrTooManyMessageFiles
152152
}
153+
// application 层保留兜底校验,保证非 HTTP 调用路径也遵守同一 MCP 工具数量策略。
154+
if err := s.ValidateSelectedToolIDs(input.SelectedToolIDs); err != nil {
155+
return nil, err
156+
}
153157

154158
startedAt := time.Now()
155159
runID := normalizeRunID(input.ClientRunID)

backend/internal/application/conversation/service_tool.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
"time"
88

9+
"github.com/DEEIX-AI/DEEIX-Chat/backend/internal/infra/config"
910
"github.com/DEEIX-AI/DEEIX-Chat/backend/internal/infra/mcp"
1011
)
1112

@@ -59,6 +60,25 @@ func (s *Service) resolveMaxToolCallsPerRun() int {
5960
return maxCalls
6061
}
6162

63+
func (s *Service) resolveMaxSelectedToolsPerMessage() int {
64+
maxTools := s.cfg.Snapshot().MCPMaxSelectedToolsPerMessage
65+
if maxTools <= 0 {
66+
maxTools = config.DefaultMCPMaxSelectedToolsPerMessage
67+
}
68+
if maxTools > config.MaxMCPSelectedToolsPerMessage {
69+
maxTools = config.MaxMCPSelectedToolsPerMessage
70+
}
71+
return maxTools
72+
}
73+
74+
// ValidateSelectedToolIDs 校验单次消息选择的 MCP 工具数量。
75+
func (s *Service) ValidateSelectedToolIDs(toolIDs []uint) error {
76+
if len(toolIDs) > s.resolveMaxSelectedToolsPerMessage() {
77+
return ErrTooManySelectedTools
78+
}
79+
return nil
80+
}
81+
6282
func (s *Service) resolveMaxLLMCallsPerRun() int {
6383
maxCalls := s.cfg.Snapshot().MCPMaxLLMCallsPerRun
6484
if maxCalls <= 0 {

backend/internal/application/conversation/service_tool_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,14 @@ func TestResolveMaxLLMCallsPerRunRequiresFollowUpRound(t *testing.T) {
5050
t.Fatalf("expected minimum LLM calls per run to be 2, got %d", got)
5151
}
5252
}
53+
54+
func TestValidateSelectedToolIDsUsesRuntimeLimit(t *testing.T) {
55+
service := &Service{cfg: config.NewRuntime(config.Config{MCPMaxSelectedToolsPerMessage: 2})}
56+
57+
if err := service.ValidateSelectedToolIDs([]uint{1, 2}); err != nil {
58+
t.Fatalf("expected two selected tools to pass, got %v", err)
59+
}
60+
if err := service.ValidateSelectedToolIDs([]uint{1, 2, 3}); err != ErrTooManySelectedTools {
61+
t.Fatalf("expected ErrTooManySelectedTools, got %v", err)
62+
}
63+
}

backend/internal/application/settings/runtime_settings.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ func (r *RuntimeSettings) applyItem(cfg *config.Config, item domainsettings.Syst
332332
cfg.MCPToolRetryCount = toInt(item.Value, cfg.MCPToolRetryCount)
333333
case "mcp:mcp_max_concurrent_calls":
334334
cfg.MCPMaxConcurrentCalls = toInt(item.Value, cfg.MCPMaxConcurrentCalls)
335+
case "mcp:mcp_max_selected_tools_per_message":
336+
cfg.MCPMaxSelectedToolsPerMessage = toInt(item.Value, cfg.MCPMaxSelectedToolsPerMessage)
335337
case "mcp:mcp_max_llm_calls_per_run":
336338
cfg.MCPMaxLLMCallsPerRun = toInt(item.Value, cfg.MCPMaxLLMCallsPerRun)
337339
case "mcp:mcp_max_tool_calls_per_run":
@@ -372,6 +374,12 @@ func (r *RuntimeSettings) normalizeConfig(cfg *config.Config) {
372374
if strings.TrimSpace(cfg.NativeToolAllowedTypes) == "" {
373375
cfg.NativeToolAllowedTypes = config.DefaultNativeToolAllowedTypesJSON()
374376
}
377+
if cfg.MCPMaxSelectedToolsPerMessage <= 0 {
378+
cfg.MCPMaxSelectedToolsPerMessage = config.DefaultMCPMaxSelectedToolsPerMessage
379+
}
380+
if cfg.MCPMaxSelectedToolsPerMessage > config.MaxMCPSelectedToolsPerMessage {
381+
cfg.MCPMaxSelectedToolsPerMessage = config.MaxMCPSelectedToolsPerMessage
382+
}
375383
if !cfg.FileFullContextLimitEnabled {
376384
cfg.FileFullContextMaxBytes = 0
377385
cfg.FileFullContextMaxTokens = 0

backend/internal/application/settings/seed.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ func defaultSettings() []domainsettings.SystemSetting {
164164
{Namespace: "mcp", Key: "mcp_tool_timeout_seconds", Value: "10", ValueType: "int", Description: "MCP Tool Call 超时(秒)"},
165165
{Namespace: "mcp", Key: "mcp_tool_retry_count", Value: "0", ValueType: "int", Description: "MCP Tool Call 重试次数"},
166166
{Namespace: "mcp", Key: "mcp_max_concurrent_calls", Value: "8", ValueType: "int", Description: "MCP Tool Call 并发上限"},
167+
{Namespace: "mcp", Key: "mcp_max_selected_tools_per_message", Value: "32", ValueType: "int", Description: "单次消息最多可选择的 MCP 工具数量"},
167168
{Namespace: "mcp", Key: "mcp_max_llm_calls_per_run", Value: "5", ValueType: "int", Description: "单次 MCP 工具运行最大 LLM 请求次数(最小 2,首次请求 + 工具后续请求 + 最终总结)"},
168169
{Namespace: "mcp", Key: "mcp_max_tool_calls_per_run", Value: "8", ValueType: "int", Description: "单次 MCP 工具运行最大 MCP Tool Call 次数"},
169170

backend/internal/application/settings/service.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,8 @@ func validatePatchItem(item PatchItem) error {
390390
return validateIntMinMax(value, 1, 64, key)
391391
case "mcp:mcp_max_concurrent_calls":
392392
return validateIntMinMax(value, 1, 64, key)
393+
case "mcp:mcp_max_selected_tools_per_message":
394+
return validateIntMinMax(value, 1, config.MaxMCPSelectedToolsPerMessage, key)
393395
case "mcp:mcp_tool_timeout_seconds":
394396
return validateIntMinMax(value, 1, 120, key)
395397
case "mcp:mcp_tool_retry_count":

0 commit comments

Comments
 (0)