mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-28 09:57:29 +08:00
refactor api server into focused modules
This commit is contained in:
216
pkg/api/server_tools_mcp.go
Normal file
216
pkg/api/server_tools_mcp.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
cfgpkg "github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func (s *Server) handleWebUITools(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
toolsList := []map[string]interface{}{}
|
||||
if s.onToolsCatalog != nil {
|
||||
if items, ok := s.onToolsCatalog().([]map[string]interface{}); ok && items != nil {
|
||||
toolsList = items
|
||||
}
|
||||
}
|
||||
mcpItems := make([]map[string]interface{}, 0)
|
||||
for _, item := range toolsList {
|
||||
if strings.TrimSpace(fmt.Sprint(item["source"])) == "mcp" {
|
||||
mcpItems = append(mcpItems, item)
|
||||
}
|
||||
}
|
||||
serverChecks := []map[string]interface{}{}
|
||||
if strings.TrimSpace(s.configPath) != "" {
|
||||
if cfg, err := cfgpkg.LoadConfig(s.configPath); err == nil {
|
||||
serverChecks = buildMCPServerChecks(cfg)
|
||||
}
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"tools": toolsList,
|
||||
"mcp_tools": mcpItems,
|
||||
"mcp_server_checks": serverChecks,
|
||||
})
|
||||
}
|
||||
|
||||
func buildMCPServerChecks(cfg *cfgpkg.Config) []map[string]interface{} {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
names := make([]string, 0, len(cfg.Tools.MCP.Servers))
|
||||
for name := range cfg.Tools.MCP.Servers {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
items := make([]map[string]interface{}, 0, len(names))
|
||||
for _, name := range names {
|
||||
server := cfg.Tools.MCP.Servers[name]
|
||||
transport := strings.ToLower(strings.TrimSpace(server.Transport))
|
||||
if transport == "" {
|
||||
transport = "stdio"
|
||||
}
|
||||
command := strings.TrimSpace(server.Command)
|
||||
status := "missing_command"
|
||||
message := "command is empty"
|
||||
resolved := ""
|
||||
missingCommand := false
|
||||
if !server.Enabled {
|
||||
status = "disabled"
|
||||
message = "server is disabled"
|
||||
} else if transport != "stdio" {
|
||||
status = "not_applicable"
|
||||
message = "command check not required for non-stdio transport"
|
||||
} else if command != "" {
|
||||
if filepath.IsAbs(command) {
|
||||
if info, err := os.Stat(command); err == nil && !info.IsDir() {
|
||||
status = "ok"
|
||||
message = "command found"
|
||||
resolved = command
|
||||
} else {
|
||||
status = "missing_command"
|
||||
message = fmt.Sprintf("command not found: %s", command)
|
||||
missingCommand = true
|
||||
}
|
||||
} else if path, err := exec.LookPath(command); err == nil {
|
||||
status = "ok"
|
||||
message = "command found"
|
||||
resolved = path
|
||||
} else {
|
||||
status = "missing_command"
|
||||
message = fmt.Sprintf("command not found in PATH: %s", command)
|
||||
missingCommand = true
|
||||
}
|
||||
}
|
||||
installSpec := inferMCPInstallSpec(server)
|
||||
items = append(items, map[string]interface{}{
|
||||
"name": name,
|
||||
"enabled": server.Enabled,
|
||||
"transport": transport,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"command": command,
|
||||
"resolved": resolved,
|
||||
"package": installSpec.Package,
|
||||
"installer": installSpec.Installer,
|
||||
"installable": missingCommand && installSpec.AutoInstallSupported,
|
||||
"missing_command": missingCommand,
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
type mcpInstallSpec struct {
|
||||
Installer string
|
||||
Package string
|
||||
AutoInstallSupported bool
|
||||
}
|
||||
|
||||
func inferMCPInstallSpec(server cfgpkg.MCPServerConfig) mcpInstallSpec {
|
||||
if pkgName := strings.TrimSpace(server.Package); pkgName != "" {
|
||||
return mcpInstallSpec{Installer: "npm", Package: pkgName, AutoInstallSupported: true}
|
||||
}
|
||||
command := strings.TrimSpace(server.Command)
|
||||
args := make([]string, 0, len(server.Args))
|
||||
for _, arg := range server.Args {
|
||||
if v := strings.TrimSpace(arg); v != "" {
|
||||
args = append(args, v)
|
||||
}
|
||||
}
|
||||
base := filepath.Base(command)
|
||||
switch base {
|
||||
case "npx":
|
||||
return mcpInstallSpec{Installer: "npm", Package: firstNonFlagArg(args), AutoInstallSupported: firstNonFlagArg(args) != ""}
|
||||
case "uvx":
|
||||
pkgName := firstNonFlagArg(args)
|
||||
return mcpInstallSpec{Installer: "uv", Package: pkgName, AutoInstallSupported: pkgName != ""}
|
||||
case "bunx":
|
||||
pkgName := firstNonFlagArg(args)
|
||||
return mcpInstallSpec{Installer: "bun", Package: pkgName, AutoInstallSupported: pkgName != ""}
|
||||
case "python", "python3":
|
||||
if len(args) >= 2 && args[0] == "-m" {
|
||||
return mcpInstallSpec{Installer: "pip", Package: strings.TrimSpace(args[1]), AutoInstallSupported: false}
|
||||
}
|
||||
}
|
||||
return mcpInstallSpec{}
|
||||
}
|
||||
|
||||
func firstNonFlagArg(args []string) string {
|
||||
for _, arg := range args {
|
||||
item := strings.TrimSpace(arg)
|
||||
if item == "" || strings.HasPrefix(item, "-") {
|
||||
continue
|
||||
}
|
||||
return item
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Server) handleWebUIMCPInstall(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Package string `json:"package"`
|
||||
Installer string `json:"installer"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
pkgName := strings.TrimSpace(body.Package)
|
||||
if pkgName == "" {
|
||||
http.Error(w, "package required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
out, binName, binPath, err := ensureMCPPackageInstalledWithInstaller(r.Context(), pkgName, body.Installer)
|
||||
if err != nil {
|
||||
msg := err.Error()
|
||||
if strings.TrimSpace(out) != "" {
|
||||
msg = strings.TrimSpace(out) + "\n" + msg
|
||||
}
|
||||
http.Error(w, strings.TrimSpace(msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
"package": pkgName,
|
||||
"output": out,
|
||||
"bin_name": binName,
|
||||
"bin_path": binPath,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleWebUIToolAllowlistGroups(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
"groups": tools.ToolAllowlistGroups(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user