Ctrl K
ComunidadGuías…

internal/mcpgateway/store_virtual.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package mcpgateway

import (
	"context"
	"crypto/rand"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/enterpilot/gomodel/config"
)

// VirtualStore persists admin-managed virtual servers. The SQL and MongoDB
// stores implement it next to Store.
type VirtualStore interface {
	ListVirtual(ctx context.Context) ([]ManagedVirtualServer, error)
	GetVirtual(ctx context.Context, name string) (*ManagedVirtualServer, error)
	UpsertVirtual(ctx context.Context, virtual ManagedVirtualServer) error
	// UpdateVirtual rewrites an existing row and returns ErrNotFound when the
	// row is gone, so an edit racing a delete cannot recreate it.
	UpdateVirtual(ctx context.Context, virtual ManagedVirtualServer) error
	// DeleteVirtual removes a row and returns it as it was deleted, in the
	// same operation, so a caller learns which incarnation it removed even
	// when another instance replaced the row just before.
	DeleteVirtual(ctx context.Context, name string) (*ManagedVirtualServer, error)
}

// ManagedVirtualServer is one admin-managed virtual server row.
type ManagedVirtualServer struct {
	Name          string    `json:"name"`
	Description   string    `json:"description,omitempty"`
	Servers       []string  `json:"servers"`
	ToolDiscovery string    `json:"tool_discovery,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
	// Generation is a random token the store sets when it creates the row
	// and keeps across edits, so it tells one creation of a name from the
	// next. Rows created before it have none; see incarnation.
	Generation string `json:"-"`
}

// Validate applies the declarative-config rules and normalizes the row in
// place. Member existence and name clashes depend on the running gateway, so
// Service.UpsertVirtual checks them.
func (v *ManagedVirtualServer) Validate() error {
	name := strings.ToLower(strings.TrimSpace(v.Name))
	if err := config.ValidateMCPVirtualServerName(name); err != nil {
		return err
	}
	cfg := v.config()
	if err := config.NormalizeMCPVirtualServer(&cfg); err != nil {
		return err
	}
	v.Name = name
	v.Description = cfg.Description
	v.Servers = cfg.Servers
	v.ToolDiscovery = cfg.ToolDiscovery
	return nil
}

func (v ManagedVirtualServer) config() config.MCPVirtualServerConfig {
	return config.MCPVirtualServerConfig{
		Description:   v.Description,
		Servers:       slices.Clone(v.Servers),
		ToolDiscovery: v.ToolDiscovery,
	}
}

// Spec converts the row into a runtime spec.
func (v ManagedVirtualServer) Spec() VirtualServerSpec {
	spec := VirtualFromConfig(v.Name, v.config())
	spec.Managed = false
	spec.Incarnation = v.incarnation()
	return spec
}

// incarnation identifies this creation of the row: its Generation, or for a
// row created before generations its creation time. Those times are whole
// seconds in SQL, but every row created since has a Generation, so two
// incarnations of one name never share one.
func (v ManagedVirtualServer) incarnation() string {
	if v.Generation != "" {
		return v.Generation
	}
	if v.CreatedAt.IsZero() {
		return ""
	}
	return "created-" + strconv.FormatInt(v.CreatedAt.UnixNano(), 10)
}

// stampVirtualUpsert sets timestamps, and the generation an insert stores;
// an update keeps the row's own.
func stampVirtualUpsert(virtual *ManagedVirtualServer) {
	now := time.Now().UTC()
	if virtual.CreatedAt.IsZero() {
		virtual.CreatedAt = now
	}
	virtual.UpdatedAt = now
	if virtual.Generation == "" {
		virtual.Generation = rand.Text()
	}
}