diff --git a/cmd/bugseti/frontend/src/app/settings/settings.component.ts b/cmd/bugseti/frontend/src/app/settings/settings.component.ts
index f144af15..7447d3fa 100644
--- a/cmd/bugseti/frontend/src/app/settings/settings.component.ts
+++ b/cmd/bugseti/frontend/src/app/settings/settings.component.ts
@@ -9,6 +9,7 @@ interface Config {
notificationsEnabled: boolean;
notificationSound: boolean;
workspaceDir: string;
+ marketplaceMcpRoot: string;
theme: string;
autoSeedContext: boolean;
workHours?: {
@@ -161,6 +162,13 @@ interface Config {
+
+
@@ -308,6 +316,7 @@ export class SettingsComponent implements OnInit {
notificationsEnabled: true,
notificationSound: true,
workspaceDir: '',
+ marketplaceMcpRoot: '',
theme: 'dark',
autoSeedContext: true,
workHours: {
diff --git a/cmd/bugseti/main.go b/cmd/bugseti/main.go
index 4e23dbab..a7c2493e 100644
--- a/cmd/bugseti/main.go
+++ b/cmd/bugseti/main.go
@@ -37,7 +37,7 @@ func main() {
}
// Initialize core services
- notifyService := bugseti.NewNotifyService()
+ notifyService := bugseti.NewNotifyService(configService)
statsService := bugseti.NewStatsService(configService)
fetcherService := bugseti.NewFetcherService(configService, notifyService)
queueService := bugseti.NewQueueService(configService)
diff --git a/internal/bugseti/config.go b/internal/bugseti/config.go
index f5c9b301..3a8af7b5 100644
--- a/internal/bugseti/config.go
+++ b/internal/bugseti/config.go
@@ -37,6 +37,8 @@ type Config struct {
// Workspace
WorkspaceDir string `json:"workspaceDir,omitempty"`
DataDir string `json:"dataDir,omitempty"`
+ // Marketplace MCP
+ MarketplaceMCPRoot string `json:"marketplaceMcpRoot,omitempty"`
// Onboarding
Onboarded bool `json:"onboarded"`
@@ -96,6 +98,7 @@ func NewConfigService() *ConfigService {
MaxConcurrentIssues: 1,
AutoSeedContext: true,
DataDir: bugsetiDir,
+ MarketplaceMCPRoot: "",
UpdateChannel: "stable",
AutoUpdate: false,
UpdateCheckInterval: 6, // Check every 6 hours
@@ -181,6 +184,13 @@ func (c *ConfigService) GetConfig() Config {
return *c.config
}
+// GetMarketplaceMCPRoot returns the configured marketplace MCP root path.
+func (c *ConfigService) GetMarketplaceMCPRoot() string {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.config.MarketplaceMCPRoot
+}
+
// SetConfig updates the configuration and saves it.
func (c *ConfigService) SetConfig(config Config) error {
c.mu.Lock()
diff --git a/internal/bugseti/ethics_guard.go b/internal/bugseti/ethics_guard.go
index 3a0a3a44..8a267a7a 100644
--- a/internal/bugseti/ethics_guard.go
+++ b/internal/bugseti/ethics_guard.go
@@ -26,19 +26,32 @@ type EthicsGuard struct {
}
var (
- ethicsGuardOnce sync.Once
+ ethicsGuardMu sync.Mutex
ethicsGuard *EthicsGuard
+ ethicsGuardRoot string
)
func getEthicsGuard(ctx context.Context) *EthicsGuard {
- ethicsGuardOnce.Do(func() {
- guard := loadEthicsGuard(ctx)
- if guard == nil {
- guard = &EthicsGuard{}
- }
- ethicsGuard = guard
- })
+ return getEthicsGuardWithRoot(ctx, "")
+}
+func getEthicsGuardWithRoot(ctx context.Context, rootHint string) *EthicsGuard {
+ rootHint = strings.TrimSpace(rootHint)
+
+ ethicsGuardMu.Lock()
+ defer ethicsGuardMu.Unlock()
+
+ if ethicsGuard != nil && ethicsGuardRoot == rootHint {
+ return ethicsGuard
+ }
+
+ guard := loadEthicsGuard(ctx, rootHint)
+ if guard == nil {
+ guard = &EthicsGuard{}
+ }
+
+ ethicsGuard = guard
+ ethicsGuardRoot = rootHint
if ethicsGuard == nil {
return &EthicsGuard{}
}
@@ -67,14 +80,14 @@ func guardFromMarketplace(ctx context.Context, client marketplaceClient) *Ethics
}
}
-func loadEthicsGuard(ctx context.Context) *EthicsGuard {
+func loadEthicsGuard(ctx context.Context, rootHint string) *EthicsGuard {
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
- client, err := newMarketplaceClient(ctx)
+ client, err := newMarketplaceClient(ctx, rootHint)
if err != nil {
return &EthicsGuard{}
}
diff --git a/internal/bugseti/mcp_marketplace.go b/internal/bugseti/mcp_marketplace.go
index 7364f1d1..9f379dfd 100644
--- a/internal/bugseti/mcp_marketplace.go
+++ b/internal/bugseti/mcp_marketplace.go
@@ -60,12 +60,12 @@ type mcpMarketplaceClient struct {
client *client.Client
}
-func newMarketplaceClient(ctx context.Context) (marketplaceClient, error) {
+func newMarketplaceClient(ctx context.Context, rootHint string) (marketplaceClient, error) {
if ctx == nil {
ctx = context.Background()
}
- command, args, err := resolveMarketplaceCommand()
+ command, args, err := resolveMarketplaceCommand(rootHint)
if err != nil {
return nil, err
}
@@ -180,12 +180,17 @@ func toolResultMessage(result *mcp.CallToolResult) string {
return "unknown error"
}
-func resolveMarketplaceCommand() (string, []string, error) {
+func resolveMarketplaceCommand(rootHint string) (string, []string, error) {
if command := strings.TrimSpace(os.Getenv("BUGSETI_MCP_COMMAND")); command != "" {
args := strings.Fields(os.Getenv("BUGSETI_MCP_ARGS"))
return command, args, nil
}
+ if root := strings.TrimSpace(rootHint); root != "" {
+ path := filepath.Join(root, "mcp")
+ return "go", []string{"run", path}, nil
+ }
+
if root := strings.TrimSpace(os.Getenv("BUGSETI_MCP_ROOT")); root != "" {
path := filepath.Join(root, "mcp")
return "go", []string{"run", path}, nil
diff --git a/internal/bugseti/notify.go b/internal/bugseti/notify.go
index 097704d4..c467c1b2 100644
--- a/internal/bugseti/notify.go
+++ b/internal/bugseti/notify.go
@@ -14,13 +14,15 @@ import (
type NotifyService struct {
enabled bool
sound bool
+ config *ConfigService
}
// NewNotifyService creates a new NotifyService.
-func NewNotifyService() *NotifyService {
+func NewNotifyService(config *ConfigService) *NotifyService {
return &NotifyService{
enabled: true,
sound: true,
+ config: config,
}
}
@@ -45,7 +47,7 @@ func (n *NotifyService) Notify(title, message string) error {
return nil
}
- guard := getEthicsGuard(context.Background())
+ guard := getEthicsGuardWithRoot(context.Background(), n.getMarketplaceRoot())
safeTitle := guard.SanitizeNotification(title)
safeMessage := guard.SanitizeNotification(message)
@@ -72,6 +74,13 @@ func (n *NotifyService) Notify(title, message string) error {
return err
}
+func (n *NotifyService) getMarketplaceRoot() string {
+ if n == nil || n.config == nil {
+ return ""
+ }
+ return n.config.GetMarketplaceMCPRoot()
+}
+
// NotifyIssue sends a notification about a new issue.
func (n *NotifyService) NotifyIssue(issue *Issue) error {
title := "New Issue Available"
diff --git a/internal/bugseti/seeder.go b/internal/bugseti/seeder.go
index 579ad53b..52f9a8b7 100644
--- a/internal/bugseti/seeder.go
+++ b/internal/bugseti/seeder.go
@@ -48,7 +48,7 @@ func (s *SeederService) SeedIssue(issue *Issue) (*IssueContext, error) {
if err != nil {
log.Printf("Seed skill failed, using fallback: %v", err)
// Fallback to basic context preparation
- guard := getEthicsGuard(context.Background())
+ guard := getEthicsGuardWithRoot(context.Background(), s.config.GetMarketplaceMCPRoot())
ctx = s.prepareBasicContext(issue, guard)
}
@@ -95,7 +95,7 @@ func (s *SeederService) runSeedSkill(issue *Issue, workDir string) (*IssueContex
mcpCtx, mcpCancel := context.WithTimeout(ctx, 20*time.Second)
defer mcpCancel()
- marketplace, err := newMarketplaceClient(mcpCtx)
+ marketplace, err := newMarketplaceClient(mcpCtx, s.config.GetMarketplaceMCPRoot())
if err != nil {
return nil, err
}
diff --git a/internal/bugseti/submit.go b/internal/bugseti/submit.go
index a61c8282..fb15234e 100644
--- a/internal/bugseti/submit.go
+++ b/internal/bugseti/submit.go
@@ -67,7 +67,7 @@ func (s *SubmitService) Submit(submission *PRSubmission) (*PRResult, error) {
return nil, fmt.Errorf("work directory not specified")
}
- guard := getEthicsGuard(context.Background())
+ guard := getEthicsGuardWithRoot(context.Background(), s.config.GetMarketplaceMCPRoot())
issueTitle := guard.SanitizeTitle(issue.Title)
// Step 1: Ensure we have a fork