apigateway

package
v1.61.2 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Overview

Package apigateway provides an HTTP API gateway toolkit that proxies authenticated REST API calls through the platform's auth, persona, and audit pipeline. Sibling to pkg/toolkits/gateway, which proxies upstream MCP servers; this toolkit proxies arbitrary HTTP/JSON APIs.

The toolkit exposes a small fixed set of MCP tools regardless of how many connections are registered or how many endpoints each upstream API has. v1 ships api_invoke_endpoint; api_list_endpoints and api_get_endpoint_schema follow once OpenAPI ingestion lands (see the RFC at issue #364).

Index

Constants

View Source
const (
	// Kind is the connection-instance kind discriminator. Operators see
	// this in the admin UI's connection picker.
	Kind = "api"

	// AuthModeNone disables outbound authentication.
	AuthModeNone = "none"
	// AuthModeBearer sends "Authorization: Bearer <credential>".
	AuthModeBearer = "bearer"
	// AuthModeAPIKey sends the credential as a header (default
	// "X-API-Key") or as a query parameter; placement and key name are
	// per-connection so APIs that use non-standard schemes (e.g. an
	// "api_key" query parameter, or a custom "X-Api-Token" header) can
	// be onboarded without code changes.
	AuthModeAPIKey = "api_key"

	// AuthModeOAuth2ClientCredentials acquires a bearer token via
	// the OAuth 2.1 client_credentials grant — server-to-server,
	// no human in the loop. The platform exchanges the configured
	// client_id + client_secret for a token at OAuth.TokenURL and
	// applies it as "Authorization: Bearer <token>" on outbound
	// calls. Tokens are cached + refreshed automatically by the
	// underlying golang.org/x/oauth2 library; no DB state is
	// required because every restart can re-acquire from credentials.
	// The authorization_code grant (which DOES require DB-persisted
	// refresh tokens + a browser flow) is its own follow-up issue.
	AuthModeOAuth2ClientCredentials = "oauth2_client_credentials" // #nosec G101 -- mode name, not a credential

	// AuthModeOAuth2AuthorizationCode runs the user-driven OAuth 2.1
	// authorization-code grant: an admin completes a one-time browser
	// flow at connection setup; the resulting refresh token is
	// persisted (encrypted) so subsequent platform restarts and
	// background workloads keep working without further interaction.
	// Tokens are refreshed automatically before expiry. Requires the
	// platform's database (refresh-token state survives restarts).
	AuthModeOAuth2AuthorizationCode = "oauth2_authorization_code" // #nosec G101 -- mode name, not a credential

	// APIKeyPlacementHeader (default) sends the credential as an HTTP
	// header named by APIKeyHeader.
	APIKeyPlacementHeader = "header"
	// APIKeyPlacementQuery sends the credential as a URL query parameter
	// named by APIKeyParam.
	APIKeyPlacementQuery = "query"

	// DefaultAPIKeyHeader is the conventional API-key header name when
	// the connection does not specify one.
	DefaultAPIKeyHeader = "X-API-Key" // #nosec G101 -- header name, not a credential

	// TrustLevelUntrusted is the default. Advisory only in v1: the
	// field is parsed and validated but no platform code reads it.
	// Reserved for future response-shaping enforcement (see issue
	// #373) — operators setting this today get the same behavior as
	// not setting it.
	TrustLevelUntrusted = "untrusted"
	// TrustLevelTrusted is advisory only in v1; see TrustLevelUntrusted.
	TrustLevelTrusted = "trusted"

	// DefaultConnectTimeout caps the time spent establishing the
	// outbound connection (TCP + TLS handshake) on each invocation.
	DefaultConnectTimeout = 10 * time.Second
	// DefaultCallTimeout caps the total per-call time including
	// upstream processing and response read.
	DefaultCallTimeout = 60 * time.Second

	// DefaultMaxResponseBytes caps how much of the upstream response
	// body the toolkit will return to the model. Larger payloads are
	// truncated; the response envelope flags truncation so the model
	// can react. Operators with a need for large bodies can raise this
	// per-connection or, when #372 lands, use the streaming-to-S3
	// variant to bypass the model entirely.
	DefaultMaxResponseBytes = int64(10 * 1024 * 1024)
)
View Source
const (
	OAuth2AuthStyleHeader = "header"
	OAuth2AuthStyleParams = "params"
)

EndpointAuthStyle values.

View Source
const (
	// ToolInvokeEndpoint is the MCP tool name for the invoke
	// operation. Exported so audit code and tests reference the same
	// literal as the registration site.
	ToolInvokeEndpoint = "api_invoke_endpoint"

	// ToolListEndpoints names the tool that returns OperationSummary
	// candidates from a connection's parsed OpenAPI spec. Companion
	// to ToolInvokeEndpoint: the model uses list to discover what's
	// available, then invoke to call it.
	ToolListEndpoints = "api_list_endpoints"
)

Variables

View Source
var ErrConnectionExists = errors.New("apigateway: connection already exists")

ErrConnectionExists is returned when AddConnection is called with a name already registered in the toolkit.

View Source
var ErrConnectionNotFound = errors.New("apigateway: connection not found")

ErrConnectionNotFound is returned when an operation is requested against a connection that has not been registered.

View Source
var ErrNeedsReauth = errors.New("apigateway: oauth2 connection needs admin reconnect")

ErrNeedsReauth is the structured error api_invoke_endpoint surfaces when an authorization_code connection's stored refresh token is missing, expired beyond refresh_expires_at, or definitively rejected by the IdP (RFC 6749 §5.2 invalid_grant on the refresh_token grant). Transient failures (network, 5xx, request cancellation) DO NOT produce this error — see Apply for the distinction.

The error message intentionally points the operator at the platform's reauth path rather than echoing the underlying IdP response (which can include sensitive material from a partial grant exchange). See tokenFetchError for the parallel scrubber on transport failures.

View Source
var ErrTokenNotFound = errors.New("apigateway: oauth token not found")

ErrTokenNotFound is returned by TokenStore.Get when no row exists for a connection. Distinct from a transport error so callers can distinguish a never-authorized connection from a database that is merely unreachable.

Functions

This section is empty.

Types

type Authenticator

type Authenticator interface {
	Apply(req *http.Request) error
}

Authenticator applies a connection's authentication scheme to an outbound HTTP request before it is sent. Implementations must be safe for concurrent use — a single Authenticator is shared across all in-flight invocations of a connection.

Implementations MUST NOT log credential material. The toolkit's audit pipeline expects no Authorization or X-API-Key value to ever appear in slog output, error messages, or audit rows; carelessly formatted error strings are the most common leak path.

func NewAuthenticator

func NewAuthenticator(c Config) (Authenticator, error)

NewAuthenticator returns the Authenticator implementation for a validated Config. ParseConfig has already rejected unknown auth modes, so the default branch only fires if a future mode is added without a matching case here.

type Config

type Config struct {
	// BaseURL is the upstream API root (e.g. "https://api.example.com").
	// Required. Trailing slash is stripped at parse time.
	BaseURL string
	// AuthMode is "none", "bearer", or "api_key" in v1. OAuth modes
	// land with #368.
	AuthMode string
	// Credential is the bearer token or API key. Ignored when AuthMode
	// is "none". Encrypted at rest via the platform's FieldEncryptor.
	Credential string
	// APIKeyPlacement is "header" (default) or "query" — only consulted
	// when AuthMode is "api_key".
	APIKeyPlacement string
	// APIKeyHeader is the header name to set when APIKeyPlacement is
	// "header". Defaults to "X-API-Key".
	APIKeyHeader string
	// APIKeyParam is the query parameter name when APIKeyPlacement is
	// "query". No default — required when placement is "query".
	APIKeyParam string
	// ConnectionName is the audit-visible connection identifier and the
	// value passed in the tool's `connection` argument. Defaults to
	// the toolkit instance name when unset.
	ConnectionName string
	// ConnectTimeout caps the dial step on each invocation.
	ConnectTimeout time.Duration
	// CallTimeout caps the total per-invocation time.
	CallTimeout time.Duration
	// TrustLevel is "untrusted" (default) or "trusted".
	TrustLevel string
	// MaxResponseBytes caps how much of an upstream response body is
	// returned to the model. Defaults to DefaultMaxResponseBytes.
	MaxResponseBytes int64
	// OpenAPISpec is the raw OpenAPI 3.x document (YAML or JSON)
	// for this connection. Optional. When non-empty the toolkit
	// parses it at AddConnection time and exposes its operations
	// via api_list_endpoints; an unparseable spec fails the
	// connection with a clear error rather than silently dropping.
	// Inline-only in v1.
	OpenAPISpec string
	// OAuth2 carries the OAuth 2.1 parameters used when AuthMode
	// is oauth2_client_credentials. Empty for non-OAuth modes.
	OAuth2 OAuth2Config
}

Config holds api-gateway toolkit configuration for a single upstream HTTP API connection.

func ParseConfig

func ParseConfig(cfg map[string]any) (Config, error)

ParseConfig parses a Config from a generic map (the form admin-saved connections take in the connection_instances table) and applies defaults. The returned Config is fully validated.

func (Config) Validate

func (c Config) Validate() error

Validate returns an error if the configuration is missing required fields or contains invalid values.

type ExportAsset

type ExportAsset struct {
	ID             string
	OwnerID        string
	OwnerEmail     string
	Name           string
	Description    string
	ContentType    string
	S3Bucket       string
	S3Key          string
	SizeBytes      int64
	Tags           []string
	Provenance     ExportProvenance
	SessionID      string
	IdempotencyKey string
}

ExportAsset is the row inserted into portal_assets when an api_export call succeeds. Field shape mirrors trinokit.ExportAsset so the platform-side adapter can reuse its conversion logic.

type ExportAssetRef

type ExportAssetRef struct {
	ID        string
	SizeBytes int64
}

ExportAssetRef is returned by idempotency-key lookup. We only need the id + size for the response — the model doesn't see the existing asset's full row.

type ExportAssetStore

type ExportAssetStore interface {
	InsertExportAsset(ctx context.Context, asset ExportAsset) error
	GetByIdempotencyKey(ctx context.Context, ownerID, key string) (*ExportAssetRef, error)
}

ExportAssetStore is the subset of portal.AssetStore needed by api_export. Defined locally to avoid an import cycle (portal → registry → apigateway). Mirrors trinokit.ExportAssetStore.

type ExportConfig

type ExportConfig struct {
	MaxBytes       int64
	DefaultTimeout time.Duration
	MaxTimeout     time.Duration
}

ExportConfig holds platform-level limits for api_export. MaxBytes caps any single export's size (above which the call returns an error rather than a truncated asset — partial-data assets would be misleading). DefaultTimeout / MaxTimeout bound how long a single call may run.

type ExportDeps

type ExportDeps struct {
	AssetStore     ExportAssetStore
	VersionStore   ExportVersionStore
	S3Client       ExportS3Client
	ShareCreator   ExportShareCreator
	S3Bucket       string
	S3Prefix       string
	BaseURL        string
	Config         ExportConfig
	GetUserContext func(ctx context.Context) *ExportUserContext
}

ExportDeps holds platform-side dependencies injected into the api gateway toolkit. All types are defined locally to avoid import cycles. Mirrors trinokit.ExportDeps so the platform-side wiring can stay symmetric.

type ExportProvenance

type ExportProvenance struct {
	ToolCalls []ExportProvenanceCall
	SessionID string
	UserID    string
}

ExportProvenance captures the chain of tool calls that produced an asset so portal viewers can render "exported via api_export from <connection> <method> <path>".

type ExportProvenanceCall

type ExportProvenanceCall struct {
	ToolName   string
	Timestamp  string
	Parameters map[string]any
}

ExportProvenanceCall is one step in the provenance chain.

type ExportS3Client

type ExportS3Client interface {
	PutObject(ctx context.Context, bucket, key string, data []byte, contentType string) error
}

ExportS3Client is the subset of portal.S3Client needed by api_export. Note: this is the same shape as trinokit's; the platform adapter implementing it can serve both toolkits.

type ExportShareCreator

type ExportShareCreator interface {
	CreatePublicShare(ctx context.Context, assetID, createdBy string) (shareURL string, err error)
}

ExportShareCreator creates public share links for exported assets. nil disables public-link creation.

type ExportUserContext

type ExportUserContext struct {
	UserID    string
	UserEmail string
	SessionID string
}

ExportUserContext holds user identity extracted from the request context. Populated by the GetUserContext callback provided in ExportDeps so the toolkit doesn't import middleware directly.

type ExportVersion

type ExportVersion struct {
	ID            string
	AssetID       string
	S3Key         string
	S3Bucket      string
	ContentType   string
	SizeBytes     int64
	CreatedBy     string
	ChangeSummary string
}

ExportVersion is the row inserted into portal_asset_versions.

type ExportVersionStore

type ExportVersionStore interface {
	CreateExportVersion(ctx context.Context, version ExportVersion) (int, error)
}

ExportVersionStore is the subset of portal.VersionStore needed by api_export.

type FieldEncryptor

type FieldEncryptor interface {
	Encrypt(plaintext string) (string, error)
	Decrypt(ciphertext string) (string, error)
}

FieldEncryptor abstracts the platform's at-rest field encryption. Used here so this package doesn't import pkg/platform (which would create a cycle via the registry's factory wiring). The concrete implementation comes from pkg/platform.FieldEncryptor at startup.

type InvokeInput

type InvokeInput struct {
	Connection     string            `json:"connection"`
	Method         string            `json:"method"`
	Path           string            `json:"path"`
	Query          map[string]any    `json:"query_params,omitempty"`
	Headers        map[string]string `json:"headers,omitempty"`
	Body           any               `json:"body,omitempty"`
	TimeoutSeconds int               `json:"timeout_seconds,omitempty"`
}

InvokeInput is the parsed argument shape for api_invoke_endpoint. Field names match the JSON schema.

type InvokeOutput

type InvokeOutput struct {
	Status        int                 `json:"status"`
	Headers       map[string][]string `json:"headers,omitempty"`
	Body          any                 `json:"body,omitempty"`
	BodyTruncated bool                `json:"body_truncated,omitempty"`
	// Pagination is populated when the upstream response carries a
	// recognizable cursor (RFC 5988 Link rel="next", @odata.nextLink,
	// next_cursor, etc). The model uses this to decide whether to
	// issue a follow-up call. The gateway does NOT auto-follow so
	// each loop iteration stays observable in audit + conversation.
	Pagination *PaginationInfo `json:"pagination,omitempty"`
	// Hint surfaces operator-actionable advice to the model when the
	// response itself can't carry it — most importantly the "use
	// api_export instead" suggestion when the body exceeded
	// max_response_bytes. Distinct from Error: Hint is informational,
	// the call still succeeded.
	Hint       string `json:"hint,omitempty"`
	DurationMs int64  `json:"duration_ms"`
	Error      string `json:"error,omitempty"`
}

InvokeOutput is the structured result returned to the model. Errors reaching the upstream (DNS, connection refused, TLS failure, timeout) are surfaced via Error rather than as MCP tool errors so the model can branch on them without losing the rest of the response envelope.

type ListEndpointsInput

type ListEndpointsInput struct {
	Connection string `json:"connection"`
	Query      string `json:"query,omitempty"`
	Limit      int    `json:"limit,omitempty"`
	Ranking    string `json:"ranking,omitempty"`
}

ListEndpointsInput is the parsed argument shape for api_list_endpoints. Field names match the JSON schema.

type ListEndpointsOutput

type ListEndpointsOutput struct {
	Operations []OperationSummary `json:"operations"`
	Note       string             `json:"note,omitempty"`
}

ListEndpointsOutput is the structured result. Empty + Note when the connection has no OpenAPI spec configured (so the model can distinguish "no spec" from "no matches").

type MultiConfig

type MultiConfig struct {
	DefaultName string
	Instances   map[string]Config
}

MultiConfig holds parsed per-connection configs plus the aggregate toolkit's default connection name.

func ParseMultiConfig

func ParseMultiConfig(defaultName string, raw map[string]map[string]any) (MultiConfig, error)

ParseMultiConfig validates and returns the parsed config for every instance. Per-instance parse errors fail the platform startup; HTTP connectivity failures are handled at invocation time, not here.

type OAuth2Config

type OAuth2Config struct {
	// TokenURL is the upstream's token endpoint. Required.
	TokenURL string
	// ClientID is the platform's registered client id. Required.
	ClientID string
	// ClientSecret is the platform's registered client secret.
	// Required. Encrypted at rest via the platform's
	// FieldEncryptor (sensitive-key list already includes
	// "client_secret"; the nested map's value is encrypted before
	// storage in connection_instances.config).
	ClientSecret string
	// Scopes is an optional list of OAuth scopes to request.
	Scopes []string
	// EndpointAuthStyle controls how the client credentials are
	// transmitted at token-fetch time. "header" (default) sends
	// them as HTTP Basic auth on the token request; "params"
	// sends them as POST body parameters. Some IdPs require one
	// or the other; "header" is the OAuth 2.1 default.
	EndpointAuthStyle string
	// AuthorizationURL is the upstream's authorization endpoint.
	// Required only for the authorization_code grant — that's
	// where the platform redirects the admin's browser to start
	// the flow.
	AuthorizationURL string
	// Prompt is an optional OIDC prompt parameter (RFC OIDC
	// §3.1.2.1). Common values: "login" (force credential prompt),
	// "consent" (force consent screen), "select_account",
	// "none" (silent auth). Empty by default — the IdP decides.
	// Operators of strict OIDC realms (Keycloak, Auth0, Okta)
	// typically set this to "login" so admin Reconnect actions
	// always re-prompt the user. Pure-OAuth (non-OIDC) providers
	// often reject unknown parameters with invalid_request, so
	// leave empty for those.
	Prompt string
}

OAuth2Config describes the OAuth 2.1 client_credentials grant parameters. The platform exchanges ClientID + ClientSecret at TokenURL for an access token (cached + refreshed by the golang.org/x/oauth2 library) and applies it as "Authorization: Bearer <token>" on outbound calls.

Authorization-code (browser-driven, refresh-token-persisting) grants are deferred to a follow-up — they require DB state (PKCE verifier table, refresh-token cache) and an admin reauth callback handler that this PR intentionally does not bring in.

type OAuthKindHandler added in v1.60.1

type OAuthKindHandler struct{}

OAuthKindHandler adapts the HTTP API gateway toolkit to the unified connoauth flow. The admin layer's unified OAuth handler dispatches on the connection_kind path parameter ("api" for HTTP API gateway connections) and invokes this implementation for config extraction and post-auth side effects.

AfterConnect is a no-op for this kind: the toolkit's Authenticator reads the persisted token from the store lazily on every outbound request, so once the token is persisted the connection is immediately usable without further toolkit-side work.

func NewOAuthKindHandler added in v1.60.1

func NewOAuthKindHandler(_ *Toolkit) *OAuthKindHandler

NewOAuthKindHandler returns the API gateway adapter. The toolkit argument is accepted for symmetry with the MCP gateway adapter but is intentionally unused — the API gateway needs no post-auth side effect.

func (*OAuthKindHandler) AfterConnect added in v1.60.1

func (*OAuthKindHandler) AfterConnect(_ context.Context, _ string, _ map[string]any) error

AfterConnect is a no-op. The API gateway's Authenticator reads the persisted token from the store on every outbound request via connoauth.Source, so once the token is persisted by the unified callback handler the connection is immediately usable. The MCP gateway, in contrast, caches an in-memory client per connection and must rebuild it after Connect; that's the MCP-side reason AfterConnect exists on the interface at all.

func (*OAuthKindHandler) ParseOAuthConfig added in v1.60.1

func (*OAuthKindHandler) ParseOAuthConfig(connConfig map[string]any) (connoauth.Config, error)

ParseOAuthConfig validates the connection's stored config and maps the HTTP API gateway's per-kind OAuth shape into a connoauth.Config. Returns an error when the connection is not configured for the authorization_code grant — the unified handler maps that to HTTP 409 Conflict, matching the prior per-kind handler's response code.

type OperationSummary

type OperationSummary struct {
	OperationID string   `json:"operation_id"`
	Method      string   `json:"method"`
	Path        string   `json:"path"`
	Summary     string   `json:"summary,omitempty"`
	Tags        []string `json:"tags,omitempty"`
}

OperationSummary is the slim per-operation view returned by api_list_endpoints. Designed to be cheap on context: the model gets enough to decide whether an operation is relevant (operation_id, method, path, summary, tags) without paying for the full request / response schema. Schemas are fetched on demand via a follow-up api_get_endpoint_schema tool (deferred — see RFC #364).

type PaginationInfo

type PaginationInfo struct {
	HasMore    bool   `json:"has_more,omitempty"`
	NextCursor string `json:"next_cursor,omitempty"`
	NextURL    string `json:"next_url,omitempty"`
	Source     string `json:"source,omitempty"`
}

PaginationInfo is the structured pagination state api_invoke_endpoint surfaces to the model on every response. The model uses HasMore + NextCursor (or NextURL) to decide whether to issue a follow-up call; the gateway does NOT auto-follow so each loop iteration stays observable in the conversation and audit log.

Fields are populated only when the upstream response carries a recognizable pagination signal. When none are populated the field is omitted from the JSON response — the model sees no pagination envelope and treats the response as terminal.

type PersistedToken

type PersistedToken struct {
	ConnectionName   string
	AccessToken      string
	RefreshToken     string
	ExpiresAt        time.Time
	RefreshExpiresAt time.Time
	Scope            string
	AuthenticatedBy  string
	AuthenticatedAt  time.Time
	UpdatedAt        time.Time
}

PersistedToken is the row shape stored in apigateway_oauth_tokens. Mirror of the MCP gateway's PersistedToken — kept separate so the two systems remain independent and changes to one don't ripple.

RefreshExpiresAt is optional — only populated when the IdP returned refresh_expires_in (Keycloak does, others may not). Zero means the database column is NULL; callers must NOT interpret a zero value as "no expiry"; it means the IdP did not disclose one.

type PostgresTokenStore

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

PostgresTokenStore is a sql-backed TokenStore against the apigateway_oauth_tokens table (migration #38).

func NewPostgresTokenStore

func NewPostgresTokenStore(db *sql.DB, enc FieldEncryptor) *PostgresTokenStore

NewPostgresTokenStore builds a TokenStore against the supplied database. enc may be nil; if so, refresh tokens are stored unencrypted (callers that want at-rest encryption should pass the platform's FieldEncryptor).

func (*PostgresTokenStore) Delete

func (s *PostgresTokenStore) Delete(ctx context.Context, connection string) error

Delete removes the token row, forcing the next call to surface a "needs reauth" signal. Used by the admin reauth path and by the Authenticator when the IdP rejects a refresh token (revoked or expired beyond refresh_expires_at).

func (*PostgresTokenStore) Get

func (s *PostgresTokenStore) Get(ctx context.Context, connection string) (*PersistedToken, error)

Get returns the persisted token for connection or ErrTokenNotFound when no row matches. Refresh and access tokens are decrypted via the configured FieldEncryptor.

func (*PostgresTokenStore) Set

Set inserts or replaces the token row for a connection. Access and refresh tokens are encrypted via the configured FieldEncryptor before reaching the database.

type RankingMode

type RankingMode string

RankingMode selects the algorithm api_list_endpoints uses to score candidate operations against the model's query.

Lexical (default) is the substring-match filter that v1 shipped: fast, deterministic, no embedding-provider dependency. Misses on natural-language queries when the model's phrasing doesn't share vocabulary with the spec author's (e.g. query "create order" vs summary "Place a new order").

Semantic uses cosine similarity between the query embedding and each operation's pre-computed embedding. Best for free-form intent queries; needs an embedding provider wired via SetEmbeddingProvider.

Hybrid blends a lexical signal (substring match) with the semantic cosine score. The blend recovers the precision of substring match for queries that DO share vocabulary while still returning semantically-related results when they don't. The blend weight (alpha) is fixed at hybridSemanticWeight; tuning is deferred to a config knob if a real-world deployment needs it.

const (
	// RankingLexical is the v1 substring-match filter (default).
	RankingLexical RankingMode = "lexical"
	// RankingSemantic ranks by embedding cosine similarity only.
	RankingSemantic RankingMode = "semantic"
	// RankingHybrid blends a lexical signal with the cosine score.
	RankingHybrid RankingMode = "hybrid"
)

RankingMode values exposed on the api_list_endpoints schema.

type RoutePolicy

type RoutePolicy interface {
	Allow(ctx context.Context, connection, method, path string) (allowed bool, reason string)
}

RoutePolicy gates an api_invoke_endpoint call by (connection, method, path) on top of the platform's existing tool/connection authorization. Layered design: the MCP middleware's Authorizer.IsAuthorized check covers "may this user call api_invoke_endpoint at all?" and "on this connection at all?". RoutePolicy answers the more specific question "may this user call THIS method on THIS path of this connection?".

Reason is included for audit/log clarity when Allowed is false. Implementations must read the caller's roles from ctx (typically via the middleware's pre-authenticated user or an Authenticator) and resolve them to a persona's APIRoutes rules.

type TokenStore

type TokenStore interface {
	Get(ctx context.Context, connection string) (*PersistedToken, error)
	Set(ctx context.Context, t PersistedToken) error
	Delete(ctx context.Context, connection string) error
}

TokenStore persists OAuth tokens for the authorization_code grant so a one-time browser-based authentication grants long-running background access. v1 stores a single shared identity per connection (matches the MCP gateway's design).

func NewConnOAuthTokenStore added in v1.60.1

func NewConnOAuthTokenStore(store connoauth.Store) TokenStore

NewConnOAuthTokenStore returns a TokenStore implementation backed by the unified connoauth.Store (connection_oauth_tokens table). Used by the platform's WireAPIGatewayTokenStore when a database is configured — replaces the per-kind apigateway_oauth_tokens path so the admin layer's unified OAuth handler and the toolkit's Authenticator agree on the underlying storage row.

This adapter exists because the toolkit's existing TokenStore interface keys on a bare connection name while connoauth.Store keys on (kind, name). Wrapping the unified store keeps the toolkit code unchanged during the rollout — a follow-up may replace the in-toolkit oauth2AuthorizationCodeAuth with a direct connoauth.Source so this shim becomes unnecessary.

func NewMemoryTokenStore

func NewMemoryTokenStore() TokenStore

NewMemoryTokenStore returns an in-process TokenStore. Not safe across process restarts — refresh tokens are lost.

type Toolkit

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

Toolkit is the api-gateway toolkit. A single Toolkit manages multiple named connections, each addressing a different upstream HTTP API. Connections are added either at startup (from the platform's merged YAML+DB config) or at runtime via AddConnection (used by the admin REST handler when an operator saves a new connection through the portal).

func New

func New(name string) *Toolkit

New builds an empty toolkit. Connections are added later via AddConnection (used both by NewMulti at startup and by the admin hot-add path at runtime).

func NewMulti

func NewMulti(cfg MultiConfig) *Toolkit

NewMulti builds a Toolkit and pre-loads the given parsed connection configs. Per-connection materialization failures are logged and skipped so a single bad connection does not block platform startup.

func (*Toolkit) AddConnection

func (t *Toolkit) AddConnection(name string, config map[string]any) error

AddConnection parses a raw config map, materializes the per- connection auth + HTTP client, and registers the connection. Used both at startup (via NewMulti) and at runtime via the admin hot-add path.

func (*Toolkit) Close

func (t *Toolkit) Close() error

Close releases per-connection HTTP client resources.

func (*Toolkit) Connection

func (t *Toolkit) Connection() string

Connection returns the default connection name for audit attribution when a tool call does not carry one. Empty string when no default is configured (multi-connection deployments typically require the model to pass `connection` explicitly).

func (*Toolkit) HasConnection

func (t *Toolkit) HasConnection(name string) bool

HasConnection reports whether a connection with the given name is registered.

func (*Toolkit) Kind

func (*Toolkit) Kind() string

Kind returns the toolkit kind discriminator.

func (*Toolkit) ListConnections

func (t *Toolkit) ListConnections() []toolkit.ConnectionDetail

ListConnections returns details for every registered connection, in name-sorted order. Implements toolkit.ConnectionLister so the platform's unified list_connections tool surfaces api connections alongside trino, s3, and mcp.

func (*Toolkit) Name

func (t *Toolkit) Name() string

Name returns the toolkit instance name.

func (*Toolkit) RegisterTools

func (t *Toolkit) RegisterTools(s *mcp.Server)

RegisterTools registers the api gateway's MCP tools. api_get_endpoint_schema (the third tool from RFC #364) lands in a follow-up PR; for v1 the model gets the operation summaries via api_list_endpoints and constructs invoke calls from there.

func (*Toolkit) RemoveConnection

func (t *Toolkit) RemoveConnection(name string) error

RemoveConnection drops a registered connection. Used by the admin hot-remove path when an operator deletes the connection in the portal. Idle keepalive sockets on the per-connection HTTP client are closed so they don't linger up to idleConnectionTimeout after the connection is gone.

func (*Toolkit) RoutePolicy

func (t *Toolkit) RoutePolicy() RoutePolicy

RoutePolicy returns the currently installed route policy, or nil if none has been wired. Exposed so platform-side tests can verify that WireAPIGatewayRoutePolicy actually installed a policy and exercise it directly without spinning up a full MCP server.

func (*Toolkit) SetAuthEvents added in v1.61.0

func (t *Toolkit) SetAuthEvents(w *authevents.Writer)

SetAuthEvents wires the audit-event writer to the toolkit and into every already-materialized authorization_code authenticator. Called by the platform alongside SetTokenStore so every outbound refresh emits its lifecycle event. The writer itself is nil-safe — passing nil silences events at the toolkit level (e.g., dev with no DB).

func (*Toolkit) SetEmbeddingProvider

func (t *Toolkit) SetEmbeddingProvider(p embedding.Provider)

SetEmbeddingProvider wires the embedding model used by the "semantic" and "hybrid" ranking modes of api_list_endpoints. nil (the default) disables non-lexical ranking; calls that request it fall back to lexical with a note. Per-connection embedding vectors are computed lazily on the first non-lexical call so an unreachable embedding service does not block platform startup.

func (*Toolkit) SetExportDeps

func (t *Toolkit) SetExportDeps(deps ExportDeps)

SetExportDeps wires platform-side dependencies for api_export. Calling with nil-AssetStore deps is treated as "export disabled": registerExportTool checks t.exportDeps and skips registration so the model doesn't see a tool that would always fail.

func (*Toolkit) SetQueryProvider

func (t *Toolkit) SetQueryProvider(provider query.Provider)

SetQueryProvider stores the query provider. Reserved for future warehouse-bridging features (see issue #372); not consumed in v1.

func (*Toolkit) SetRoutePolicy

func (t *Toolkit) SetRoutePolicy(p RoutePolicy)

SetRoutePolicy installs a per-(connection, method, path) authorization gate. When set, api_invoke_endpoint consults the policy after the connection lookup and before the upstream call. A nil policy means no per-route gating — the platform's existing tool/connection authorization is the sole gate (backward-compatible).

func (*Toolkit) SetSemanticProvider

func (t *Toolkit) SetSemanticProvider(provider semantic.Provider)

SetSemanticProvider stores the semantic provider. Reserved for future enrichment (e.g. response shaping driven by DataHub PII tags, see issue #373); not consumed in v1.

func (*Toolkit) SetTokenStore

func (t *Toolkit) SetTokenStore(s TokenStore)

SetTokenStore wires the persistent OAuth token store. Required for the authorization_code grant: the Authenticator reads existing tokens at first call and writes back rotated tokens after refresh. Connections registered before SetTokenStore is called will pick up the store on first use; the toolkit re-runs the wire step in addParsedConnection to keep startup-order independent (mirrors how the MCP gateway threads its TokenStore).

func (*Toolkit) TokenStore

func (t *Toolkit) TokenStore() TokenStore

TokenStore returns the OAuth token store wired into this toolkit, or nil when the toolkit was constructed without one. The admin OAuth-callback handler calls this to persist tokens after the authorization-code exchange.

func (*Toolkit) Tools

func (t *Toolkit) Tools() []string

Tools returns the list of tool names this toolkit registers. api_export is included only when the toolkit was constructed with ExportDeps wired so callers (audit / introspection) see the tool list that actually exists at runtime.

Jump to

Keyboard shortcuts

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