diff --git a/cmd/sin-code/internal/mcpclient/registry.go b/cmd/sin-code/internal/mcpclient/registry.go index 6fbba0c..7b2b706 100644 --- a/cmd/sin-code/internal/mcpclient/registry.go +++ b/cmd/sin-code/internal/mcpclient/registry.go @@ -26,9 +26,28 @@ func DefaultServers() []ServerConfig { } return cfg } + // goNative returns a ServerConfig for a Go-native skill. It prefers the + // binary built inside SIN_SKILLS_DIR//sin-websearch so that skillmgr + // can install and run the skill without requiring the user to put the binary + // on PATH. Falls back to the binary name on PATH if no local checkout exists. + goNative := func(repo, binary string, args ...string) ServerConfig { + name := shortName(repo) + cfg := ServerConfig{Name: name, Transport: "stdio", Args: args} + if skillsDir != "" { + localBin := filepath.Join(skillsDir, repo, binary) + if _, err := os.Stat(localBin); err == nil { + cfg.Command = localBin + } else { + cfg.Command = binary + } + } else { + cfg.Command = binary + } + return cfg + } return []ServerConfig{ // web_search_bundle is the Go-native successor to SIN-Code-Websearch-Skill. - {Name: "websearch", Transport: "stdio", Command: "sin-websearch", Args: []string{"serve"}}, + goNative("web_search_bundle", "sin-websearch", "serve"), py("SIN-Code-Scheduler-Skill"), py("SIN-Code-Goal-Mode-Skill"), py("SIN-Code-Grill-Me-Skill"), diff --git a/cmd/sin-code/internal/mcpclient/registry_test.go b/cmd/sin-code/internal/mcpclient/registry_test.go new file mode 100644 index 0000000..07d26a7 --- /dev/null +++ b/cmd/sin-code/internal/mcpclient/registry_test.go @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +// Purpose: tests for the built-in ecosystem registry in mcpclient. +package mcpclient + +import ( + "os" + "path/filepath" + "testing" +) + +func TestDefaultServersWebsearchUsesLocalBinaryWhenPresent(t *testing.T) { + dir := t.TempDir() + bin := filepath.Join(dir, "web_search_bundle", "sin-websearch") + if err := os.MkdirAll(filepath.Dir(bin), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(bin, []byte("#!/bin/sh\necho fake"), 0o755); err != nil { + t.Fatal(err) + } + + t.Setenv("SIN_SKILLS_DIR", dir) + for _, s := range DefaultServers() { + if s.Name != "websearch" { + continue + } + if s.Command != bin { + t.Fatalf("websearch command should use local binary %q, got %q", bin, s.Command) + } + if len(s.Args) != 1 || s.Args[0] != "serve" { + t.Fatalf("websearch args should be [serve], got %v", s.Args) + } + return + } + t.Fatal("websearch server not found in DefaultServers") +} + +func TestDefaultServersWebsearchFallsBackToPathBinary(t *testing.T) { + t.Setenv("SIN_SKILLS_DIR", "") + for _, s := range DefaultServers() { + if s.Name != "websearch" { + continue + } + if s.Command != "sin-websearch" { + t.Fatalf("websearch command should fall back to %q, got %q", "sin-websearch", s.Command) + } + return + } + t.Fatal("websearch server not found in DefaultServers") +} diff --git a/cmd/sin-code/internal/skillmgr/manager.go b/cmd/sin-code/internal/skillmgr/manager.go index 9b96af3..74f8343 100644 --- a/cmd/sin-code/internal/skillmgr/manager.go +++ b/cmd/sin-code/internal/skillmgr/manager.go @@ -125,8 +125,9 @@ func verifyEntrypoint(ctx context.Context, dir string) (bool, string) { return true, "node entrypoint (package.json)" } if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { - // Go-native skill: verify it compiles. - cmd := exec.CommandContext(ctx, "go", "build", "./cmd/sin-websearch") + // Go-native skill: build the binary into the repo root so the MCP + // registry can use the full path (SIN_SKILLS_DIR//). + cmd := exec.CommandContext(ctx, "go", "build", "-o", "sin-websearch", "./cmd/sin-websearch") cmd.Dir = dir if _, err := cmd.CombinedOutput(); err != nil { return false, fmt.Sprintf("go entrypoint exists but build failed: %v", err)