yao/registry/manager/agent/scan.go
Max 44af6ba759 Enhance command structure with MCP and Robot functionalities
- Introduce new MCP and Robot command groups in the CLI, allowing for better organization of package management commands.
- Add corresponding command descriptions for MCP and Robot functionalities to improve user guidance.
- Update the agent command group to include additional commands for enhanced agent management.
- Modify .gitignore to exclude specific design markdown files from version control.
- Add environment variable for test application path in GitHub workflows to streamline testing setup.
2026-03-03 08:11:43 +08:00

89 lines
2.5 KiB
Go

package agent
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
goujson "github.com/yaoapp/gou/json"
"github.com/yaoapp/yao/registry/manager/common"
)
// packageYao represents the assistant's package.yao DSL (subset of fields we care about).
type packageYao struct {
MCP *mcpConfig `json:"mcp,omitempty"`
Agents []string `json:"agents,omitempty"`
Uses json.RawMessage `json:"uses,omitempty"`
}
type mcpConfig struct {
Servers map[string]mcpServerEntry `json:"servers,omitempty"`
}
type mcpServerEntry struct {
ServerID string `json:"server_id,omitempty"`
}
// ScanDependencies scans an assistant directory's package.yao for external dependencies.
// It finds MCP dependencies from mcp.servers and returns them as "@scope/name" → "*" entries.
// Only MCPs with a scope directory (mcps/{scope}/) are included; top-level mcps/ are skipped.
func ScanDependencies(assistantDir, appRoot string) (map[string]string, error) {
pkgPath := filepath.Join(assistantDir, "package.yao")
data, err := os.ReadFile(pkgPath)
if err != nil {
return nil, fmt.Errorf("read package.yao: %w", err)
}
var pkg packageYao
if err := goujson.ParseFile("package.yao", data, &pkg); err != nil {
return nil, fmt.Errorf("parse package.yao: %w", err)
}
deps := map[string]string{}
// Scan MCP servers
if pkg.MCP != nil {
for _, entry := range pkg.MCP.Servers {
serverID := entry.ServerID
if serverID == "" {
continue
}
pkgID, err := resolveMCPDep(serverID, appRoot)
if err != nil {
continue
}
if pkgID != "" {
deps[pkgID] = "*"
}
}
}
return deps, nil
}
// resolveMCPDep resolves an MCP server_id to a package ID if it lives under a scoped directory.
// Returns empty string for non-scoped (local) MCPs.
func resolveMCPDep(serverID, appRoot string) (string, error) {
// server_id like "yao.rag-tools" → scope=yao, name=rag-tools
// Check if mcps/yao/rag-tools/ exists (has scope directory)
scope, name, err := common.IDFromYaoID(serverID)
if err != nil {
return "", nil
}
mcpDir := filepath.Join(appRoot, "mcps", scope, strings.ReplaceAll(name, ".", "/"))
if _, err := os.Stat(mcpDir); err == nil {
return common.FormatPackageID(scope, name), nil
}
// Also check for single-file MCP: mcps/{scope}/{name}.mcp.yao
// This is less common but possible
mcpFile := filepath.Join(appRoot, "mcps", scope, name+".mcp.yao")
if _, err := os.Stat(mcpFile); err == nil {
return common.FormatPackageID(scope, name), nil
}
return "", nil
}