internal/mcpgateway/types.go
Rama main · réplica de lectura del repositorio original.
// Package mcpgateway aggregates upstream MCP servers behind GoModel's
// authenticated /mcp endpoints. The gateway terminates the MCP protocol on
// both legs: it is an MCP server to clients and an MCP client to upstreams.
// It is also the credential boundary — client bearer tokens never reach an
// upstream; upstream credentials come only from server configuration.
package mcpgateway
import (
"maps"
"slices"
"time"
"github.com/enterpilot/gomodel/config"
)
// ServerStatus describes the runtime connection state of one upstream server.
type ServerStatus string
const (
// StatusDisabled means the server is declared but switched off.
StatusDisabled ServerStatus = "disabled"
// StatusConnecting means no session has been established yet.
StatusConnecting ServerStatus = "connecting"
// StatusConnected means the session is live and the catalog is fresh.
StatusConnected ServerStatus = "connected"
// StatusDegraded means the last connect or listing failed; any previous
// catalog is kept (stale carry-forward) and the server is re-probed.
StatusDegraded ServerStatus = "degraded"
)
// ServerSpec is the runtime-normalized definition of one upstream server,
// merged from declarative config (Managed=true) and the admin store.
type ServerSpec struct {
// Name is the stable ASCII slug used for routing and namespacing.
Name string
DisplayName string
URL string
Transport string
// Headers are sent to the upstream, secret references resolved.
Headers map[string]string
// HeaderReferences holds the stored value of each header of an
// admin-managed server that holds a secret reference, so the admin API
// can show the reference instead of masking the header.
HeaderReferences map[string]string
Command string
Args []string
Env map[string]string
Description string
Enabled bool
AllowedTools []string
DisallowedTools []string
UserPaths []string
// DisallowedUserPaths hides the server from these subtrees; it wins over
// UserPaths.
DisallowedUserPaths []string
ToolTimeout time.Duration
// Managed marks specs declared in config.yaml / MCP_SERVERS. They override
// admin-store rows with the same name and are read-only in the dashboard.
Managed bool
// oauth supplies the access token of an operator OAuth sign-in; nil
// when the server is not signed in. A new sign-in is a new source, so
// the spec changes and the manager redials with it.
oauth *oauthSource
}
// SpecFromConfig converts one declarative config entry into a runtime spec.
func SpecFromConfig(name string, cfg config.MCPServerConfig) ServerSpec {
return ServerSpec{
Name: name,
DisplayName: name,
URL: cfg.URL,
Transport: cfg.Transport,
Headers: maps.Clone(cfg.Headers),
Command: cfg.Command,
Args: slices.Clone(cfg.Args),
Env: maps.Clone(cfg.Env),
Description: cfg.Description,
Enabled: config.MCPServerEnabled(cfg),
AllowedTools: slices.Clone(cfg.AllowedTools),
DisallowedTools: slices.Clone(cfg.DisallowedTools),
UserPaths: normalizeUserPaths(cfg.UserPaths),
DisallowedUserPaths: normalizeUserPaths(cfg.DisallowedUserPaths),
ToolTimeout: cfg.ToolTimeout,
Managed: true,
}
}
// equal reports whether two specs describe the same upstream configuration,
// so the manager can keep an existing session across no-op reloads.
func (s ServerSpec) equal(other ServerSpec) bool {
return s.Name == other.Name &&
s.DisplayName == other.DisplayName &&
s.URL == other.URL &&
s.Transport == other.Transport &&
maps.Equal(s.Headers, other.Headers) &&
maps.Equal(s.HeaderReferences, other.HeaderReferences) &&
s.Command == other.Command &&
slices.Equal(s.Args, other.Args) &&
maps.Equal(s.Env, other.Env) &&
s.Description == other.Description &&
s.Enabled == other.Enabled &&
slices.Equal(s.AllowedTools, other.AllowedTools) &&
slices.Equal(s.DisallowedTools, other.DisallowedTools) &&
slices.Equal(s.UserPaths, other.UserPaths) &&
slices.Equal(s.DisallowedUserPaths, other.DisallowedUserPaths) &&
s.ToolTimeout == other.ToolTimeout &&
s.Managed == other.Managed &&
s.oauth == other.oauth
}
// withoutAccessPolicy clears the gateway-side access policy (tool filters and
// user-path scopes), so callers can tell a policy-only edit, applied in place,
// from one that changes the upstream connection and needs a redial.
func (s ServerSpec) withoutAccessPolicy() ServerSpec {
s.AllowedTools = nil
s.DisallowedTools = nil
s.UserPaths = nil
s.DisallowedUserPaths = nil
return s
}
// visibleTo reports whether a caller with userPath may see and use this
// server: inside UserPaths (empty means everyone) and outside every
// DisallowedUserPaths subtree.
func (s ServerSpec) visibleTo(userPath string) bool {
return userPathAllowed(userPath, s.UserPaths) && !userPathWithin(userPath, s.DisallowedUserPaths)
}
// ServerView is a point-in-time snapshot of one upstream for admin and
// dashboard consumption.
type ServerView struct {
Spec ServerSpec
Status ServerStatus
LastError string
ToolCount int
// ExcludedToolCount counts discovered tools hidden by the tool filters.
ExcludedToolCount int
PromptCount int
// ResourceCount includes resource templates.
ResourceCount int
ConnectedAt time.Time
// AuthRequired reports that the last connect was answered 401
// Unauthorized: the server needs a credential or an OAuth sign-in.
AuthRequired bool
// OAuth is the operator sign-in state; nil when never signed in.
OAuth *OAuthView
}