feat: init sproutclaw-web — Go+Gin backend + React frontend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
133
backend/internal/services/mcp.go
Normal file
133
backend/internal/services/mcp.go
Normal file
@@ -0,0 +1,133 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user