Files
sproutclaw-web/backend/internal/services/mcp.go
2026-06-13 20:16:09 +08:00

134 lines
3.1 KiB
Go

package services
import (
"encoding/json"
"os"
"sproutclaw-web/internal/models"
)
type mcpConfig struct {
MCPServers map[string]any `json:"mcpServers"`
MCPServersDisabled []string `json:"mcpServersDisabled,omitempty"`
ExcludeTools []string `json:"excludeTools,omitempty"`
}
// ReadMCPServers loads mcp.json and returns the list of servers with their tools.
func ReadMCPServers(mcpConfigPath, mcpCachePath string) []models.MCPServer {
cfg := loadMCPConfig(mcpConfigPath)
cache := loadMCPCache(mcpCachePath)
disabledSet := map[string]bool{}
for _, s := range cfg.MCPServersDisabled {
disabledSet[s] = true
}
excludeSet := map[string]bool{}
for _, t := range cfg.ExcludeTools {
excludeSet[t] = true
}
var servers []models.MCPServer
for name := range cfg.MCPServers {
srv := models.MCPServer{
Name: name,
Disabled: disabledSet[name],
}
// add tools from cache
if toolNames, ok := cache[name]; ok {
for _, t := range toolNames {
srv.Tools = append(srv.Tools, models.MCPTool{
Name: t,
Disabled: excludeSet[name+"/"+t] || excludeSet[t],
})
}
}
servers = append(servers, srv)
}
return servers
}
// ToggleMCPServer enables or disables a server.
func ToggleMCPServer(mcpConfigPath, serverName string, enable bool) error {
cfg := loadMCPConfig(mcpConfigPath)
set := map[string]bool{}
for _, s := range cfg.MCPServersDisabled {
set[s] = true
}
if enable {
delete(set, serverName)
} else {
set[serverName] = true
}
cfg.MCPServersDisabled = keys(set)
return saveMCPConfig(mcpConfigPath, cfg)
}
// ToggleMCPTool enables or disables a specific tool on a server.
func ToggleMCPTool(mcpConfigPath, serverName, toolName string, enable bool) error {
cfg := loadMCPConfig(mcpConfigPath)
key := serverName + "/" + toolName
set := map[string]bool{}
for _, t := range cfg.ExcludeTools {
set[t] = true
}
if enable {
delete(set, key)
} else {
set[key] = true
}
cfg.ExcludeTools = keys(set)
return saveMCPConfig(mcpConfigPath, cfg)
}
func loadMCPConfig(path string) mcpConfig {
data, err := os.ReadFile(path)
if err != nil {
return mcpConfig{MCPServers: map[string]any{}}
}
var cfg mcpConfig
_ = json.Unmarshal(data, &cfg)
if cfg.MCPServers == nil {
cfg.MCPServers = map[string]any{}
}
return cfg
}
func saveMCPConfig(path string, cfg mcpConfig) error {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, append(data, '\n'), 0o644)
}
func loadMCPCache(path string) map[string][]string {
data, err := os.ReadFile(path)
if err != nil {
return nil
}
// cache format: { "serverName": { "tools": [{ "name": "..." }] } }
var raw map[string]struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return nil
}
result := map[string][]string{}
for srv, info := range raw {
for _, t := range info.Tools {
result[srv] = append(result[srv], t.Name)
}
}
return result
}
func keys(m map[string]bool) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}