externalmcp

package
v0.0.0-...-805cb8e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: AGPL-3.0 Imports: 53 Imported by: 0

Documentation

Index

Constants

View Source
const (
	OAuthVersionNone = "none" // No OAuth required
	OAuthVersion21   = "2.1"  // MCP OAuth with RFC 8414 discovery + dynamic registration
	OAuthVersion20   = "2.0"  // Legacy OAuth 2.0 (no AS discovery, requires static client config)
)

OAuthVersion represents the detected OAuth version/capability level.

View Source
const ProxyToolNameDelimiter = "--"

ProxyToolNameDelimiter separates the slug from the tool name in proxy tool names.

Variables

View Source
var (
	ErrCatalogSourceNotFound = errors.New("catalog source not found")
	ErrCatalogSourceDisabled = errors.New("catalog source is not enabled and certified")
	ErrUnknownRegistrySource = errors.New("unknown registry source profile")
)
View Source
var ErrResponseTooLarge = errors.New("external mcp response body exceeds the configured read limit")

Functions

func Attach

func Attach(mux goahttp.Muxer, service *Service)

func BuildHeaders

func BuildHeaders(
	systemEnv *toolconfig.CaseInsensitiveEnv,
	userConfig *toolconfig.CaseInsensitiveEnv,
	headerDefs []HeaderDefinition,
	oauthToken string,
) map[string]string

BuildHeaders constructs HTTP headers from system environment variables and user configuration.

Logic:

  1. ALL system env values become headers using the appropriate header names.
  2. For keys with header definitions, use the definition's HeaderName.
  3. For keys without definitions, derive the header name using ToHTTPHeader.
  4. User config can override values (only for keys with header definitions).
  5. Empty values are skipped.
  6. If oauthToken is provided and no Authorization header was already set from config, sets Authorization: Bearer <token>.

Types

type AuthRejectedError

type AuthRejectedError struct {
	RemoteURL       string
	StatusCode      int
	WWWAuthenticate string
}

AuthRejectedError is returned when an MCP server rejects authentication (401 or 403). WWWAuthenticate is populated when the server provides a WWW-Authenticate header.

func (*AuthRejectedError) Error

func (e *AuthRejectedError) Error() string

type CachedListServers

type CachedListServers struct {
	Key     string
	Servers []*types.ExternalMCPServerEntry
}

CachedListServers wraps the full, deduplicated list of external MCP server summaries for a registry. The catalog is small and stable, so the whole list is cached under a single key per registry.

func (CachedListServers) CacheKey

func (c CachedListServers) CacheKey() string

func (CachedListServers) TTL

type CachedServerDetailsResponse

type CachedServerDetailsResponse struct {
	Key     string
	Details *ServerDetails
}

CachedServerDetailsResponse wraps server details for caching.

func (CachedServerDetailsResponse) CacheKey

func (c CachedServerDetailsResponse) CacheKey() string

func (CachedServerDetailsResponse) TTL

type CallToolResult

type CallToolResult struct {
	Content []json.RawMessage
	IsError bool
}

CallToolResult represents the result of a tool call.

type CatalogService

type CatalogService struct {
	// contains filtered or unexported fields
}

CatalogService is the single source selection and aggregation boundary for dashboard and Platform MCP catalogue reads. It exposes only enabled and certified sources with a known, code-reviewed adapter/profile combination.

func NewCatalogService

func NewCatalogService(db *pgxpool.Pool, pulse RegistryReader, official RegistryReader) *CatalogService

func (*CatalogService) Details

func (s *CatalogService) Details(ctx context.Context, registryID uuid.UUID, serverName string, allowedRemoteURLs []string) (*ServerDetails, error)

Details always re-fetches the selected server through its source-specific adapter. Catalogue discovery is not readiness evidence and cached list data is never used to materialize a registration.

func (*CatalogService) List

func (s *CatalogService) List(ctx context.Context, search *string, registryID *uuid.UUID) ([]*types.ExternalMCPServerEntry, error)

List returns a deterministic merged catalogue. A same-specifier entry from two sources remains distinct because source identity is part of its provenance; only duplicates within a source are collapsed by its adapter.

func (*CatalogService) ReaderFor

func (s *CatalogService) ReaderFor(source CatalogSource) (RegistryReader, error)

ReaderFor resolves the reviewed adapter/profile for a source returned by Sources. It deliberately accepts no caller-supplied URL or profile.

func (*CatalogService) Source

func (s *CatalogService) Source(ctx context.Context, registryID uuid.UUID) (CatalogSource, error)

Source resolves one enabled and certified source by its opaque database ID. It is used by detail paths that must preserve their surface-specific response projection while sharing source admission with the aggregate catalogue.

func (*CatalogService) Sources

func (s *CatalogService) Sources(ctx context.Context) ([]CatalogSource, error)

Sources returns only registry rows allowed to participate in the shared catalogue. Consumers that need their own projection can retain the source provenance while delegating fetches back through ReaderFor.

type CatalogSource

type CatalogSource struct {
	Registry             Registry
	SourceType           string
	AuthProfile          string
	CertificationVersion string
	Priority             int32
	SourceKey            string
	Legacy               bool
}

CatalogSource is an operator-owned, reviewed registry configuration. It is loaded from mcp_registries; request callers never provide a source URL, adapter, or auth profile.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents an active connection to an external MCP server.

func NewClient

func NewClient(ctx context.Context, logger *slog.Logger, guardianPolicy *guardian.Policy, remoteURL string, transportType types.TransportType, opts *ClientOptions) (*Client, error)

NewClient creates a new client connection to an external MCP server. This performs the MCP protocol initialization internally.

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, toolName string, arguments json.RawMessage, schema json.RawMessage) (*CallToolResult, error)

CallTool calls a tool on the external MCP server. A nil schema deliberately sends an ordinary call first, without discovery. Only conformant pre-execution HeaderMismatch responses permit one discovery and replay. Proxy schemas are not persisted, so later isolated calls may need the same recovery again.

func (*Client) Close

func (c *Client) Close() error

Close closes the client connection.

func (*Client) ListTools

func (c *Client) ListTools(ctx context.Context) ([]Tool, error)

ListTools lists available tools from the external MCP server.

type ClientOptions

type ClientOptions struct {
	// Metrics optionally injects recovery instruments; nil uses the process OTel provider.
	Metrics *Metrics
	// Authorization is the value for the Authorization header (e.g., "Bearer token").
	// If empty, no Authorization header is sent.
	Authorization string
	// Headers contains additional HTTP headers to send with each request.
	// Keys are header names, values are header values.
	Headers map[string]string
	// DisableRetries skips both the HTTP-transport retry layer and the MCP
	// transport's own connection retries. The two compound (up to 4 HTTP
	// attempts per MCP-level retry, each with its own backoff), which is
	// fine for the resilient gateway proxy path but defeats a caller-imposed
	// context deadline meant to bound a one-shot interactive probe — without
	// this, an unreachable server can take minutes to report as such instead
	// of the ~10s the probe intends.
	DisableRetries bool
	// MaxResponseBytes caps each HTTP response when greater than zero. It is
	// intended for short-lived, untrusted probes; the long-lived gateway path
	// leaves this at zero.
	MaxResponseBytes int64
}

ClientOptions contains options for creating an MCP client.

type ExternalMCPOAuthConfig

type ExternalMCPOAuthConfig struct {
	// RemoteURL is the parsed URL of the external MCP server
	RemoteURL *url.URL
	// RegistryID is the ID of the MCP registry the server belongs to
	RegistryID string
	// Slug is the tool prefix slug (e.g., "github")
	Slug string
	// Name is the reverse-DNS server name (e.g., "ai.exa/exa")
	Name string

	// OAuth metadata from the external server
	OAuthVersion          string   // "2.1", "2.0", or "none"
	AuthorizationEndpoint string   // OAuth authorization endpoint URL
	TokenEndpoint         string   // OAuth token endpoint URL
	RegistrationEndpoint  string   // OAuth dynamic client registration endpoint URL
	ScopesSupported       []string // OAuth scopes supported by the server
}

ExternalMCPOAuthConfig contains OAuth configuration extracted from an external MCP tool that requires OAuth authentication.

func ResolveOAuthConfig

func ResolveOAuthConfig(toolset *types.Toolset) *ExternalMCPOAuthConfig

ResolveOAuthConfig returns the OAuth configuration from the first external MCP tool in the toolset that requires OAuth, or nil if none found.

type HeaderDefinition

type HeaderDefinition struct {
	Name       string // Prefixed environment variable name (e.g., "SLACK_X_API_KEY")
	HeaderName string // HTTP header to send (e.g., "X-Api-Key")
}

HeaderDefinition maps an environment variable name to an HTTP header name.

type ListServersParams

type ListServersParams struct {
	Search *string
}

ListServersParams contains optional parameters for listing servers.

type ListServersResult

type ListServersResult struct {
	Servers []*types.ExternalMCPServerEntry
}

ListServersResult is the result of a ListServers call.

type Metrics

type Metrics struct {
	// contains filtered or unexported fields
}

Metrics records parameter-header recovery without request-derived dimensions. Share one instance across clients. These diagnostic counters do not meter logical tool calls or billable usage. The zero value and nil receiver are safe.

func NewMetrics

func NewMetrics(provider metric.MeterProvider, logger *slog.Logger) *Metrics

func (*Metrics) RecordHeaderMismatch

func (m *Metrics) RecordHeaderMismatch(ctx context.Context)

RecordHeaderMismatch counts each mismatch response, including on replay.

func (*Metrics) RecordRecoveryAttempt

func (m *Metrics) RecordRecoveryAttempt(ctx context.Context)

RecordRecoveryAttempt counts the start of the single permitted recovery.

func (*Metrics) RecordRecoveryExhaustion

func (m *Metrics) RecordRecoveryExhaustion(ctx context.Context)

RecordRecoveryExhaustion counts a repeated mismatch after the one replay. Exhaustion is a subset of failures, not an additional recovery attempt.

func (*Metrics) RecordRecoveryFailure

func (m *Metrics) RecordRecoveryFailure(ctx context.Context)

RecordRecoveryFailure counts an unsuccessful refresh or replay, including exhaustion. Every completed attempt records exactly one success or failure.

func (*Metrics) RecordRecoverySuccess

func (m *Metrics) RecordRecoverySuccess(ctx context.Context)

RecordRecoverySuccess counts a replay with neither a protocol nor tool error.

type OAuthDiscoveryResult

type OAuthDiscoveryResult struct {
	Version               string // "2.1", "2.0", or "none"
	Issuer                string // Authorization server issuer from RFC 8414 metadata.
	AuthorizationEndpoint string
	TokenEndpoint         string
	RegistrationEndpoint  string
	ScopesSupported       []string

	// ClientIDMetadataDocumentSupported reports whether the authorization
	// server advertises client_id_metadata_document_supported (OAuth CIMD
	// draft). When true, a client may send a metadata-document URL as
	// client_id instead of dynamically registering.
	ClientIDMetadataDocumentSupported bool

	// TokenEndpointAuthMethodsSupported is the authorization server's
	// advertised token_endpoint_auth_methods_supported. Empty means the
	// document omitted the field; CIMD public clients require "none" only
	// when the server enumerated methods.
	TokenEndpointAuthMethodsSupported []string

	// ProbeIncomplete reports that at least one discovery request failed
	// without the server cleanly saying the document is not there (only a
	// 404 or 410 says that): unreachable host, TLS failure, auth or
	// rate-limit refusal, 5xx, or invalid published metadata. A Version of
	// "none" with ProbeIncomplete set means discovery could not run to
	// completion — callers that treat "none" as "publishes no OAuth
	// metadata" must keep that case distinct.
	ProbeIncomplete bool
}

OAuthDiscoveryResult contains the OAuth metadata discovered for an external MCP server.

func DiscoverOAuthMetadata

func DiscoverOAuthMetadata(ctx context.Context, logger *slog.Logger, guardianPolicy *guardian.Policy, wwwAuthenticate string, remoteURL string) (*OAuthDiscoveryResult, error)

DiscoverOAuthMetadata discovers OAuth configuration for an external MCP server. It parses the WWW-Authenticate header and fetches metadata from discovered URLs. If no metadata URLs are in the header, it probes standard well-known locations.

type OfficialRegistryAdapter

type OfficialRegistryAdapter struct {
	// contains filtered or unexported fields
}

OfficialRegistryAdapter implements the public read-only v0.1 Official MCP Registry contract. It is deliberately not composed into production until its source profile is certified and enabled by a later rollout.

func NewOfficialRegistryAdapter

func NewOfficialRegistryAdapter(logger *slog.Logger, policy *guardian.Policy) *OfficialRegistryAdapter

func (*OfficialRegistryAdapter) GetServerDetails

func (a *OfficialRegistryAdapter) GetServerDetails(ctx context.Context, registry Registry, serverName string, allowedRemoteURLs []string) (*ServerDetails, error)

func (*OfficialRegistryAdapter) ListServers

func (a *OfficialRegistryAdapter) ListServers(ctx context.Context, registry Registry, params ListServersParams) (ListServersResult, error)

type PlanResolver

type PlanResolver func(ctx context.Context, toolURN urn.Tool, projectID uuid.UUID) (*ToolCallPlan, error)

PlanResolver resolves a tool URN to a ToolCallPlan.

type ProxyToolEntry

type ProxyToolEntry struct {
	SourceSlug string   // e.g., "slack" - used for matching
	URN        urn.Tool // URN of the proxy tool (e.g., tools:ext-mcp:slack:proxy)
}

ProxyToolEntry contains metadata needed for matching incoming tool calls to proxy tools.

type ProxyToolExecutor

type ProxyToolExecutor struct {
	// contains filtered or unexported fields
}

ProxyToolExecutor provides matching for external MCP tool names. The executor matches tool names against MCP server slugs and returns the external tool name. Actual execution happens elsewhere using the returned URN to get a plan and create a client.

func BuildProxyToolExecutor

func BuildProxyToolExecutor(logger *slog.Logger, guardianPolicy *guardian.Policy, tools []*types.Tool) *ProxyToolExecutor

BuildProxyToolExecutor creates a ProxyToolExecutor from a list of tools. Filters internally to only include external MCP tools with Type "proxy".

func (*ProxyToolExecutor) DoList

func (e *ProxyToolExecutor) DoList(
	ctx context.Context,
	projectID uuid.UUID,
	userConfig *toolconfig.CaseInsensitiveEnv,
	oauthToken string,
	loadSystemEnv SystemEnvLoader,
	resolve PlanResolver,
) ([]Tool, error)

DoList lists tools from all proxy tools in this executor. For each entry, loads system env, resolves the plan, connects to the MCP server, lists tools, and prefixes tool names with the source slug.

func (*ProxyToolExecutor) HasEntries

func (e *ProxyToolExecutor) HasEntries() bool

HasEntries returns true if this executor has any proxy tool entries.

func (*ProxyToolExecutor) MatchPlanInputs

func (e *ProxyToolExecutor) MatchPlanInputs(ctx context.Context, toolName string, projectID uuid.UUID, resolve PlanResolver) (*ToolCallPlan, error)

MatchPlanInputs checks if the given tool name belongs to any proxy tool in this executor. If matched, resolves the ToolCallPlan inputs and sets ToolName to the external tool name. Returns nil if no match (not an error). Returns error if resolver fails. Expected format: <slug>--<toolName>

type PulseBackend

type PulseBackend struct {
	// contains filtered or unexported fields
}

func NewPulseBackend

func NewPulseBackend(registryURL *url.URL, tenantID string, api conv.Secret) *PulseBackend

func (*PulseBackend) Authorize

func (p *PulseBackend) Authorize(req *http.Request) error

func (*PulseBackend) Match

func (p *PulseBackend) Match(req *http.Request) bool

type Registry

type Registry struct {
	ID  uuid.UUID
	URL string
}

Registry represents an MCP registry endpoint.

type RegistryBackend

type RegistryBackend interface {
	Match(req *http.Request) bool
	Authorize(req *http.Request) error
}

type RegistryClient

type RegistryClient struct {
	// contains filtered or unexported fields
}

RegistryClient handles communication with external MCP registries.

func NewRegistryClient

func NewRegistryClient(logger *slog.Logger, tracerProvider trace.TracerProvider, guardianPolicy *guardian.Policy, backend RegistryBackend, cacheImpl cache.Cache) *RegistryClient

NewRegistryClient creates a new registry client.

func (*RegistryClient) ClearCache

func (c *RegistryClient) ClearCache(ctx context.Context, registryURL string) error

ClearCache removes all cached entries for the given registry URL.

func (*RegistryClient) GetServerDetails

func (c *RegistryClient) GetServerDetails(ctx context.Context, registry Registry, serverName string, allowedRemoteURLs []string) (*ServerDetails, error)

GetServerDetails fetches server details including the remote URL from the registry. If allowedRemoteURLs is provided and non-empty, only remotes with matching URLs are considered.

func (*RegistryClient) ListServers

func (c *RegistryClient) ListServers(ctx context.Context, registry Registry, params ListServersParams) (ListServersResult, error)

ListServers fetches every server from the given registry and applies an optional in-memory search filter. The catalog is small and stable, so the full list is fetched, deduplicated, and cached under a single key per registry; callers receive the whole result set and paginate client-side.

func (*RegistryClient) WithAllowedCIDRBlocks

func (c *RegistryClient) WithAllowedCIDRBlocks(cidrs ...string) *RegistryClient

WithAllowedCIDRBlocks returns a client whose registry requests may reach the supplied trusted CIDR blocks. Callers must use this only for code-defined, non-user-controlled registries such as the local fixture.

type RegistryReader

type RegistryReader interface {
	ListServers(ctx context.Context, registry Registry, params ListServersParams) (ListServersResult, error)
	GetServerDetails(ctx context.Context, registry Registry, serverName string, allowedRemoteURLs []string) (*ServerDetails, error)
}

RegistryReader is the narrow, normalized registry contract consumed by both dashboard and Platform MCP catalogue projections. RegistryClient remains the Pulse-compatible transport implementation; CatalogService selects an adapter before delegating to it.

type RemoteHeader

type RemoteHeader struct {
	Name        string  `json:"name"`
	IsSecret    bool    `json:"isSecret"`
	IsRequired  bool    `json:"isRequired"`
	Description *string `json:"description,omitempty"`
	Placeholder *string `json:"placeholder,omitempty"`
}

RemoteHeader represents a header requirement from the registry.

type RemoteVariable

type RemoteVariable struct {
	Description *string  `json:"description,omitempty"`
	IsSecret    bool     `json:"isSecret"`
	IsRequired  bool     `json:"isRequired"`
	Default     *string  `json:"default,omitempty"`
	Choices     []string `json:"choices,omitempty"`
}

RemoteVariable represents a URL template variable from the registry.

type ServerDetails

type ServerDetails struct {
	Name          string
	Description   string
	Version       string
	RemoteURL     string
	TransportType externalmcptypes.TransportType
	Tools         []serverTool
	Headers       []RemoteHeader
	Variables     map[string]RemoteVariable
}

ServerDetails contains detailed information about an MCP server including connection info. Headers and variables are copied from the selected server-owned remote. Callers must still validate configuration values against these declarations; they must never treat the registry response as permission to accept arbitrary endpoints.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(logger *slog.Logger, tracerProvider trace.TracerProvider, db *pgxpool.Pool, sessions *sessions.Manager, registryClient *RegistryClient, catalog *CatalogService, authzEngine *authz.Engine, serverURL *url.URL) *Service

func (*Service) APIKeyAuth

func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)

func (*Service) ClearCache

func (s *Service) ClearCache(ctx context.Context, payload *gen.ClearCachePayload) error

func (*Service) GetServerDetails

func (s *Service) GetServerDetails(ctx context.Context, payload *gen.GetServerDetailsPayload) (*types.ExternalMCPServer, error)

func (*Service) GetSetupDocs

func (s *Service) GetSetupDocs(ctx context.Context, payload *gen.GetSetupDocsPayload) (*gen.GetSetupDocsResult, error)

func (*Service) ListCatalog

func (s *Service) ListCatalog(ctx context.Context, payload *gen.ListCatalogPayload) (*gen.ListCatalogResult, error)

func (*Service) ListRegistries

func (s *Service) ListRegistries(ctx context.Context, payload *gen.ListRegistriesPayload) (*gen.ListRegistriesResult, error)

type SystemEnvLoader

type SystemEnvLoader func(ctx context.Context, toolURN urn.Tool) (*toolconfig.CaseInsensitiveEnv, error)

SystemEnvLoader loads system environment variables for a given tool URN.

type Tool

type Tool struct {
	Name        string
	Description string
	Schema      json.RawMessage
	Annotations *ToolAnnotations
}

Tool represents a tool discovered from an external MCP server.

type ToolAnnotations

type ToolAnnotations struct {
	Title           string `json:"title,omitempty"`
	ReadOnlyHint    *bool  `json:"readOnlyHint,omitempty"`
	DestructiveHint *bool  `json:"destructiveHint,omitempty"`
	IdempotentHint  *bool  `json:"idempotentHint,omitempty"`
	OpenWorldHint   *bool  `json:"openWorldHint,omitempty"`
}

ToolAnnotations contains MCP tool behavior hints.

type ToolCallEnv

type ToolCallEnv = toolconfig.ToolCallEnv

ToolCallEnv is an alias for toolconfig.ToolCallEnv.

type ToolCallPlan

type ToolCallPlan struct {
	RemoteURL         string
	ToolName          string          // The tool name to call on the MCP server
	InputSchema       json.RawMessage // Original upstream schema; nil for a proxy placeholder
	Slug              string
	RequiresOAuth     bool
	TransportType     externalmcptypes.TransportType
	HeaderDefinitions []HeaderDefinition
}

ToolCallPlan contains the execution plan for calling a tool on an external MCP server.

type ToolExtractor

type ToolExtractor struct {
	// contains filtered or unexported fields
}

func NewToolExtractor

func NewToolExtractor(
	logger *slog.Logger,
	guardianPolicy *guardian.Policy,
	db *pgxpool.Pool,
	registryClient *RegistryClient,
) *ToolExtractor

func (*ToolExtractor) Do

type ToolExtractorTask

type ToolExtractorTask struct {
	OrgSlug      string
	ProjectSlug  string
	ProjectID    uuid.UUID
	DeploymentID uuid.UUID
	MCP          ToolExtractorTaskMCPServer
}

type ToolExtractorTaskMCPServer

type ToolExtractorTaskMCPServer struct {
	AttachmentID            uuid.UUID
	RegistryID              uuid.NullUUID
	Name                    string
	Slug                    string
	RegistryServerSpecifier string
	SelectedRemotes         []string
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL