package mcp import ( "context" "errors" "fmt" "log/slog" "sync" "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry" ) // Registry connects an MCP client and auto-registers its discovered tools into // the shared capability registry, making them available to every character. type Registry struct { reg *registry.Registry mcp *Client log *slog.Logger mu sync.Mutex names []string closed bool } // NewRegistry builds a Registry that registers cfg's tools into reg. Options // apply to the underlying Client (logger, injected transport). It is named // NewRegistry rather than New because this package already exports New for // Client. func NewRegistry(reg *registry.Registry, cfg MCPConfig, opts ...Option) *Registry { c := New(cfg, opts...) return &Registry{reg: reg, mcp: c, log: c.log} } // Register connects the MCP server, discovers its tools, and registers each one // into the underlying registry. func (r *Registry) Register(ctx context.Context) error { if r.reg == nil { return fmt.Errorf("mcp registry: nil registry") } if err := r.mcp.Connect(ctx); err != nil { return fmt.Errorf("mcp registry %q: %w", r.mcp.cfg.Name, err) } tools, err := r.mcp.DiscoverTools(ctx) if err != nil { return fmt.Errorf("mcp registry %q: %w", r.mcp.cfg.Name, err) } r.mu.Lock() defer r.mu.Unlock() if r.closed { return fmt.Errorf("mcp registry %q: %w", r.mcp.cfg.Name, ErrClosed) } for _, t := range tools { if err := r.reg.RegisterTool(t); err != nil { return fmt.Errorf("mcp register %q: %w", t.Name(), err) } r.names = append(r.names, t.Name()) } r.log.Info("mcp tools registered", "name", r.mcp.cfg.Name, "count", len(tools)) return nil } // Unregister removes every tool this registry previously registered and closes // the underlying client. It is safe to call multiple times. func (r *Registry) Unregister(ctx context.Context) error { r.mu.Lock() names := r.names r.names = nil r.closed = true r.mu.Unlock() var errs error for _, name := range names { if err := r.reg.Remove(name); err != nil && !errors.Is(err, registry.ErrNotFound) { errs = errors.Join(errs, fmt.Errorf("mcp unregister %q: %w", name, err)) } } if err := r.mcp.Close(); err != nil { errs = errors.Join(errs, fmt.Errorf("mcp close %q: %w", r.mcp.cfg.Name, err)) } return errs }