Skip to content

Commit 1e5d8d2

Browse files
committed
feat: 任务链路切换到 MCP Hub
1 parent e20e193 commit 1e5d8d2

File tree

4 files changed

+193
-18
lines changed

4 files changed

+193
-18
lines changed

backend/biz/task/usecase/task.go

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -392,24 +392,7 @@ func (a *TaskUsecase) Create(ctx context.Context, user *domain.User, req domain.
392392
}
393393
createdVm = vm
394394

395-
mcps := []taskflow.McpServerConfig{
396-
{
397-
Type: "http",
398-
Name: "mcaiBuiltin",
399-
Url: proto.String(fmt.Sprintf("http://127.0.0.1:65510/mcp?task_id=%s", t.ID.String())),
400-
},
401-
{
402-
Type: "http",
403-
Name: "context7",
404-
Url: proto.String("https://mcp.context7.com/mcp"),
405-
Headers: []*taskflow.McpHttpHeader{
406-
{
407-
Name: "CONTEXT7_API_KEY",
408-
Value: a.cfg.Context7ApiKey,
409-
},
410-
},
411-
},
412-
}
395+
mcps := a.buildMCPConfigs(t.ID)
413396

414397
// 存储 CreateTaskReq 到 Redis(10 分钟过期),供 Lifecycle Manager 消费
415398
createTaskReq := &taskflow.CreateTaskReq{
@@ -478,6 +461,45 @@ func (a *TaskUsecase) Create(ctx context.Context, user *domain.User, req domain.
478461
return result, nil
479462
}
480463

464+
func (a *TaskUsecase) buildMCPConfigs(taskID uuid.UUID) []taskflow.McpServerConfig {
465+
if a.cfg.MCPHub.Enabled && strings.TrimSpace(a.cfg.MCPHub.URL) != "" {
466+
headers := make([]*taskflow.McpHttpHeader, 0, 1)
467+
if strings.TrimSpace(a.cfg.MCPHub.Token) != "" {
468+
headers = append(headers, &taskflow.McpHttpHeader{
469+
Name: "Authorization",
470+
Value: fmt.Sprintf("Bearer %s", a.cfg.MCPHub.Token),
471+
})
472+
}
473+
return []taskflow.McpServerConfig{
474+
{
475+
Type: "http",
476+
Name: "mcphub",
477+
Url: proto.String(a.cfg.MCPHub.URL),
478+
Headers: headers,
479+
},
480+
}
481+
}
482+
483+
return []taskflow.McpServerConfig{
484+
{
485+
Type: "http",
486+
Name: "mcaiBuiltin",
487+
Url: proto.String(fmt.Sprintf("http://127.0.0.1:65510/mcp?task_id=%s", taskID.String())),
488+
},
489+
{
490+
Type: "http",
491+
Name: "context7",
492+
Url: proto.String("https://mcp.context7.com/mcp"),
493+
Headers: []*taskflow.McpHttpHeader{
494+
{
495+
Name: "CONTEXT7_API_KEY",
496+
Value: a.cfg.Context7ApiKey,
497+
},
498+
},
499+
},
500+
}
501+
}
502+
481503
func (a *TaskUsecase) getCodingConfigs(cli consts.CliName, m *db.Model, skillIDs []string) (taskflow.CodingAgent, []taskflow.ConfigFile, error) {
482504
var tmp string
483505
var path string
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package usecase
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/google/uuid"
8+
9+
"github.com/chaitin/MonkeyCode/backend/config"
10+
)
11+
12+
func TestBuildMCPConfigsWithHubEnabled(t *testing.T) {
13+
taskID := uuid.New()
14+
uc := &TaskUsecase{
15+
cfg: &config.Config{
16+
MCPHub: config.MCPHub{
17+
Enabled: true,
18+
URL: "http://mcp-hub:8897/mcp",
19+
Token: "shared-token",
20+
},
21+
Context7ApiKey: "context7-key",
22+
},
23+
}
24+
25+
mcps := uc.buildMCPConfigs(taskID)
26+
if len(mcps) != 1 {
27+
t.Fatalf("expected one mcp config, got %d", len(mcps))
28+
}
29+
if mcps[0].Name != "mcphub" {
30+
t.Fatalf("expected mcp name mcphub, got %s", mcps[0].Name)
31+
}
32+
if mcps[0].Type != "http" {
33+
t.Fatalf("expected mcp type http, got %s", mcps[0].Type)
34+
}
35+
if mcps[0].Url == nil || *mcps[0].Url != "http://mcp-hub:8897/mcp" {
36+
t.Fatalf("unexpected mcp url: %v", mcps[0].Url)
37+
}
38+
if len(mcps[0].Headers) != 1 {
39+
t.Fatalf("expected one mcp header, got %d", len(mcps[0].Headers))
40+
}
41+
if mcps[0].Headers[0].Name != "Authorization" {
42+
t.Fatalf("expected Authorization header, got %s", mcps[0].Headers[0].Name)
43+
}
44+
if mcps[0].Headers[0].Value != "Bearer shared-token" {
45+
t.Fatalf("unexpected Authorization header value: %s", mcps[0].Headers[0].Value)
46+
}
47+
}
48+
49+
func TestBuildMCPConfigsWithHubEnabledButEmptyURLFallback(t *testing.T) {
50+
taskID := uuid.New()
51+
uc := &TaskUsecase{
52+
cfg: &config.Config{
53+
MCPHub: config.MCPHub{
54+
Enabled: true,
55+
URL: "",
56+
Token: "shared-token",
57+
},
58+
Context7ApiKey: "context7-key",
59+
},
60+
}
61+
62+
mcps := uc.buildMCPConfigs(taskID)
63+
if len(mcps) != 2 {
64+
t.Fatalf("expected fallback two mcp configs, got %d", len(mcps))
65+
}
66+
if mcps[0].Name != "mcaiBuiltin" {
67+
t.Fatalf("expected fallback first mcp name mcaiBuiltin, got %s", mcps[0].Name)
68+
}
69+
if mcps[1].Name != "context7" {
70+
t.Fatalf("expected fallback second mcp name context7, got %s", mcps[1].Name)
71+
}
72+
}
73+
74+
func TestBuildMCPConfigsWithHubEnabledButEmptyTokenNoAuthHeader(t *testing.T) {
75+
taskID := uuid.New()
76+
uc := &TaskUsecase{
77+
cfg: &config.Config{
78+
MCPHub: config.MCPHub{
79+
Enabled: true,
80+
URL: "http://mcp-hub:8897/mcp",
81+
Token: "",
82+
},
83+
Context7ApiKey: "context7-key",
84+
},
85+
}
86+
87+
mcps := uc.buildMCPConfigs(taskID)
88+
if len(mcps) != 1 {
89+
t.Fatalf("expected one mcp config, got %d", len(mcps))
90+
}
91+
if len(mcps[0].Headers) != 0 {
92+
t.Fatalf("expected no headers when token is empty, got %d", len(mcps[0].Headers))
93+
}
94+
}
95+
96+
func TestBuildMCPConfigsWithHubDisabled(t *testing.T) {
97+
taskID := uuid.New()
98+
uc := &TaskUsecase{
99+
cfg: &config.Config{
100+
Context7ApiKey: "context7-key",
101+
},
102+
}
103+
104+
mcps := uc.buildMCPConfigs(taskID)
105+
if len(mcps) != 2 {
106+
t.Fatalf("expected two mcp configs, got %d", len(mcps))
107+
}
108+
if mcps[0].Name != "mcaiBuiltin" {
109+
t.Fatalf("expected first mcp name mcaiBuiltin, got %s", mcps[0].Name)
110+
}
111+
if mcps[0].Type != "http" {
112+
t.Fatalf("expected first mcp type http, got %s", mcps[0].Type)
113+
}
114+
expectBuiltinURL := fmt.Sprintf("http://127.0.0.1:65510/mcp?task_id=%s", taskID.String())
115+
if mcps[0].Url == nil || *mcps[0].Url != expectBuiltinURL {
116+
t.Fatalf("unexpected mcaiBuiltin url: %v", mcps[0].Url)
117+
}
118+
if mcps[1].Name != "context7" {
119+
t.Fatalf("expected second mcp name context7, got %s", mcps[1].Name)
120+
}
121+
if mcps[1].Type != "http" {
122+
t.Fatalf("expected second mcp type http, got %s", mcps[1].Type)
123+
}
124+
if mcps[1].Url == nil || *mcps[1].Url != "https://mcp.context7.com/mcp" {
125+
t.Fatalf("unexpected context7 url: %v", mcps[1].Url)
126+
}
127+
if len(mcps[1].Headers) != 1 {
128+
t.Fatalf("expected one context7 header, got %d", len(mcps[1].Headers))
129+
}
130+
if mcps[1].Headers[0].Name != "CONTEXT7_API_KEY" {
131+
t.Fatalf("unexpected context7 header name: %s", mcps[1].Headers[0].Name)
132+
}
133+
if mcps[1].Headers[0].Value != "context7-key" {
134+
t.Fatalf("unexpected context7 header value: %s", mcps[1].Headers[0].Value)
135+
}
136+
}

backend/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Config struct {
4444
Proxies []string `mapstructure:"proxies"`
4545

4646
TaskFlow TaskFlow `mapstructure:"taskflow"`
47+
MCPHub MCPHub `mapstructure:"mcp_hub"`
4748
PublicHost PublicHost `mapstructure:"public_host"`
4849
Task Task `mapstructure:"task"`
4950
TaskSummary TaskSummary `mapstructure:"task_summary"`
@@ -86,6 +87,12 @@ type TaskFlow struct {
8687
GrpcURL string `mapstructure:"grpc_url"`
8788
}
8889

90+
type MCPHub struct {
91+
Enabled bool `mapstructure:"enabled"`
92+
URL string `mapstructure:"url"`
93+
Token string `mapstructure:"token"`
94+
}
95+
8996
// PublicHost 公共主机配置(可选,内部项目通过 WithPublicHost 注入时生效)
9097
type PublicHost struct {
9198
CountLimit int `mapstructure:"count_limit"` // 每用户公共主机 VM 数量限制,0 表示不限制
@@ -192,6 +199,11 @@ func Init(dir string) (*Config, error) {
192199
v.SetDefault("init_team.name", "")
193200
v.SetDefault("init_team.password", "")
194201
v.SetDefault("taskflow.grpc_url", "")
202+
v.SetDefault("task.at_keyword", "")
203+
v.SetDefault("task.host_ids", []string{})
204+
v.SetDefault("mcp_hub.enabled", false)
205+
v.SetDefault("mcp_hub.url", "")
206+
v.SetDefault("mcp_hub.token", "")
195207

196208
v.SetConfigType("yaml")
197209
v.AddConfigPath(dir)

backend/config/server/config.yaml.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ admin_token: "change-me"
3434
logger:
3535
level: "info"
3636

37+
mcp_hub:
38+
enabled: false
39+
url: "http://mcp-hub:8897/mcp"
40+
token: ""
41+
3742
vm_idle:
3843
sleep_seconds: 600
3944
recycle_seconds: 604800

0 commit comments

Comments
 (0)