yao/registry/manager/mcp/push.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

125 lines
3 KiB
Go

package mcp
import (
"fmt"
"os"
"path/filepath"
"github.com/yaoapp/yao/registry/manager/common"
)
// PushOptions configures the Push operation.
type PushOptions struct {
Version string
}
// Push packages and uploads an MCP to the registry.
// Per DESIGN-MCP.md:
// 1. ID → path
// 2. Validate .mcp.yao exists
// 3. Derive scope/name, reject @local
// 4. Validate scripts are in scripts/{scope}/
// 5. Pack MCP dir + collect scripts from project root
// 6. Generate pkg.yao
// 7. Push to registry
func (m *Manager) Push(yaoID string, opts PushOptions) error {
if opts.Version == "" {
return fmt.Errorf("--version is required for push")
}
scope, name, err := common.IDFromYaoID(yaoID)
if err != nil {
return fmt.Errorf("invalid MCP ID %q: %w", yaoID, err)
}
if common.IsLocalScope(scope) {
return fmt.Errorf("cannot push @local packages. Fork to your own scope first")
}
mcpDir := common.PackageDir(common.TypeMCP, scope, name, m.appRoot)
// Find .mcp.yao files
mcpYaoFiles, err := FindMCPYaoFiles(mcpDir)
if err != nil || len(mcpYaoFiles) == 0 {
return fmt.Errorf(".mcp.yao not found in %s", mcpDir)
}
// Validate script scope and collect scripts
allScripts := map[string]string{}
for _, mcpFile := range mcpYaoFiles {
if err := ValidateScriptScope(mcpFile, scope, m.appRoot); err != nil {
return err
}
scripts, err := CollectScripts(mcpFile, m.appRoot)
if err != nil {
return err
}
for k, v := range scripts {
allScripts[k] = v
}
}
manifest := &common.PkgManifest{
Type: common.TypeMCP,
Scope: "@" + scope,
Name: name,
Version: opts.Version,
}
zipData, err := common.PackDir(mcpDir, manifest, allScripts)
if err != nil {
return fmt.Errorf("pack: %w", err)
}
regType := common.TypeToRegistryType(common.TypeMCP)
result, err := m.client.Push(regType, "@"+scope, name, opts.Version, zipData)
if err != nil {
return fmt.Errorf("push: %w", err)
}
fmt.Printf("✓ Pushed %s@%s (digest: %s)\n", common.FormatPackageID(scope, name), result.Version, result.Digest)
// Report packed scripts
if len(allScripts) > 0 {
fmt.Printf(" Scripts packed:\n")
for scriptPath := range allScripts {
fmt.Printf(" %s\n", scriptPath)
}
}
return nil
}
// pushValidateMCPDir checks that the MCP directory structure is valid for push.
func pushValidateMCPDir(mcpDir string) error {
if _, err := os.Stat(mcpDir); err != nil {
return fmt.Errorf("MCP directory %s not found", mcpDir)
}
mcpFiles, err := FindMCPYaoFiles(mcpDir)
if err != nil || len(mcpFiles) == 0 {
return fmt.Errorf("no .mcp.yao files found in %s", mcpDir)
}
return nil
}
// listDirFiles lists all files under a directory relative to root.
func listDirFiles(dir string) ([]string, error) {
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
files = append(files, filepath.ToSlash(rel))
return nil
})
return files, err
}