proxy

package
v1.0.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Package proxy contains the core domain logic for the MCP proxy.

Index

Constants

View Source
const (
	// ErrCodeMethodNotFound is returned when a tool is not found in any upstream.
	ErrCodeMethodNotFound int64 = -32601
	// ErrCodeInternal is returned when an upstream connection fails.
	ErrCodeInternal int64 = -32603
	// ErrCodeNoUpstreams is returned when no upstreams are available (503-equivalent).
	ErrCodeNoUpstreams int64 = -32000
)

JSON-RPC error codes used by the router.

View Source
const ConnectionIDKey contextKey = "connection_id"

ConnectionIDKey is the context key for connection ID.

Variables

View Source
var (
	ErrUnauthenticated = errors.New("authentication required")
	ErrInvalidAPIKey   = errors.New("invalid API key")
	ErrSessionExpired  = errors.New("session expired")
	ErrInternalError   = errors.New("internal error")
)

Error types for authentication failures.

View Source
var APIKeyContextKey = apiKeyContextKey{}

APIKeyContextKey is the context key for API key. HTTP transport should set this value in context before calling ProxyService.Run(). Example: ctx = context.WithValue(ctx, proxy.APIKeyContextKey, "my-api-key")

View Source
var ErrMissingSession = errors.New("missing session context")

ErrMissingSession indicates a tool call was received without session context.

View Source
var ErrPolicyDenied = errors.New("policy denied")

Error types for policy evaluation failures.

View Source
var IPAddressKey = ipAddressContextKey{}

IPAddressKey is the context key for IP address. Transports should set this value in context before calling ProxyService.Run(). Example: ctx = context.WithValue(ctx, proxy.IPAddressKey, "192.168.1.1")

Functions

func CreateJSONRPCError

func CreateJSONRPCError(id interface{}, code int, message string) []byte

CreateJSONRPCError creates a JSON-RPC 2.0 error response. id: request ID (may be nil for notifications) code: JSON-RPC error code (e.g., -32600 for invalid request) message: human-readable error message

func LogDevModeWarning

func LogDevModeWarning(logger *slog.Logger, devMode bool) error

LogDevModeWarning logs prominent security warnings when DevMode is enabled. If SENTINELGATE_ALLOW_DEVMODE env var is set to "false", this function logs an error and returns an error to block startup. Returns nil if DevMode warnings were logged successfully or DevMode is disabled.

func SafeErrorMessage

func SafeErrorMessage(err error) string

SafeErrorMessage returns a client-safe error message. Internal error details are logged but not exposed to clients. SECURITY: This function MUST be used for all client-facing error responses to prevent information leakage (stack traces, internal paths, credentials).

Types

type AuditInterceptor

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

AuditInterceptor logs tool call decisions to the audit system. It wraps the PolicyInterceptor to capture allow/deny outcomes. Chain order: Auth -> Audit -> Policy -> Passthrough

func NewAuditInterceptor

func NewAuditInterceptor(
	recorder AuditRecorder,
	stats StatsRecorder,
	next MessageInterceptor,
	logger *slog.Logger,
) *AuditInterceptor

NewAuditInterceptor creates a new AuditInterceptor.

func (*AuditInterceptor) Intercept

func (a *AuditInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept records tool call decisions and passes messages to the next interceptor. Non-tool-call messages are passed through without audit logging.

type AuditRecorder

type AuditRecorder interface {
	Record(record audit.AuditRecord)
}

AuditRecorder records audit events. This interface is satisfied by AuditService.

type AuthInterceptor

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

AuthInterceptor validates API keys and manages sessions. It wraps another MessageInterceptor (e.g., policy engine).

SECURITY: API keys are NEVER logged. Only connection_id, session_id, and identity_id are logged. Raw key material must never appear in log output.

func NewAuthInterceptor

func NewAuthInterceptor(
	apiKeyService *auth.APIKeyService,
	sessionService *session.SessionService,
	next MessageInterceptor,
	logger *slog.Logger,
	devMode bool,
) *AuthInterceptor

NewAuthInterceptor creates a new AuthInterceptor with default cleanup settings. Default cleanupInterval: 5 minutes, default cacheMaxAge: 30 minutes.

func NewAuthInterceptorWithConfig

func NewAuthInterceptorWithConfig(
	apiKeyService *auth.APIKeyService,
	sessionService *session.SessionService,
	next MessageInterceptor,
	logger *slog.Logger,
	devMode bool,
	cleanupInterval time.Duration,
	cacheMaxAge time.Duration,
) *AuthInterceptor

NewAuthInterceptorWithConfig creates a new AuthInterceptor with custom cleanup settings.

func (*AuthInterceptor) CacheSize

func (a *AuthInterceptor) CacheSize() int

CacheSize returns the current number of entries in the session cache. Primarily for testing purposes.

func (*AuthInterceptor) ClearSession

func (a *AuthInterceptor) ClearSession(connID string)

ClearSession removes a session from the cache. Used when a connection closes or session is invalidated.

func (*AuthInterceptor) Intercept

func (a *AuthInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept validates authentication before passing to next interceptor. Returns error to BLOCK message propagation - ProxyService MUST check error and send JSON-RPC error response back to client instead of forwarding.

func (*AuthInterceptor) SetTestCacheEntry

func (a *AuthInterceptor) SetTestCacheEntry(connID, sessionID string)

SetTestCacheEntry adds a cache entry directly for testing purposes. This method is intended only for tests and should not be used in production code.

func (*AuthInterceptor) SetTestCacheEntryWithTime

func (a *AuthInterceptor) SetTestCacheEntryWithTime(connID, sessionID string, lastAccess time.Time)

SetTestCacheEntryWithTime adds a cache entry with a specific lastAccess time for testing.

func (*AuthInterceptor) StartCleanup

func (a *AuthInterceptor) StartCleanup(ctx context.Context)

StartCleanup starts a background goroutine that periodically cleans up stale cache entries. The goroutine runs until ctx is canceled or Stop() is called.

func (*AuthInterceptor) Stop

func (a *AuthInterceptor) Stop()

Stop signals the cleanup goroutine to stop and waits for it to exit. Safe to call multiple times (uses sync.Once internally).

type IPRateLimitInterceptor

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

IPRateLimitInterceptor enforces IP-based rate limits on requests. It runs before authentication to prevent brute-force attacks.

Position in chain: After Validation, before Auth. Chain order: Validation -> IPRateLimit -> Auth -> UserRateLimit -> Audit -> Policy -> ...

func NewIPRateLimitInterceptor

func NewIPRateLimitInterceptor(
	limiter ratelimit.RateLimiter,
	ipConfig ratelimit.RateLimitConfig,
	next MessageInterceptor,
	logger *slog.Logger,
) *IPRateLimitInterceptor

NewIPRateLimitInterceptor creates a new IPRateLimitInterceptor.

Parameters:

  • limiter: The rate limiter implementation
  • ipConfig: Rate limit config for IP-based limiting
  • next: The next interceptor in the chain (typically AuthInterceptor)
  • logger: Logger for rate limit events

func (*IPRateLimitInterceptor) Intercept

func (r *IPRateLimitInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept checks IP rate limits before passing to the next interceptor. Returns RateLimitError if the request is rate limited.

type MessageInterceptor

type MessageInterceptor interface {
	// Intercept inspects a message and returns it (possibly modified).
	// Returns the message to forward, or an error to block/reject.
	// For passthrough, return the same message unchanged.
	Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)
}

MessageInterceptor inspects and optionally modifies messages. Phase 1: Passthrough only. Phase 3+: Policy evaluation.

type PassthroughInterceptor

type PassthroughInterceptor struct{}

PassthroughInterceptor forwards all messages unchanged. Used in Phase 1 before policy engine is implemented.

func NewPassthroughInterceptor

func NewPassthroughInterceptor() *PassthroughInterceptor

NewPassthroughInterceptor creates a passthrough interceptor.

func (*PassthroughInterceptor) Intercept

func (i *PassthroughInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept returns the message unchanged.

type PolicyDenyError

type PolicyDenyError struct {
	RuleID   string
	RuleName string
	Reason   string
	HelpURL  string
	HelpText string
}

PolicyDenyError wraps a policy denial with structured information. It includes rule details and human-readable guidance for resolving the denial.

func (*PolicyDenyError) Error

func (e *PolicyDenyError) Error() string

Error implements the error interface.

func (*PolicyDenyError) Unwrap

func (e *PolicyDenyError) Unwrap() error

Unwrap returns ErrPolicyDenied so errors.Is(err, ErrPolicyDenied) works.

type PolicyInterceptor

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

PolicyInterceptor evaluates tool calls against RBAC policies. It wraps another MessageInterceptor (e.g., PassthroughInterceptor).

func NewPolicyInterceptor

func NewPolicyInterceptor(
	engine policy.PolicyEngine,
	next MessageInterceptor,
	logger *slog.Logger,
) *PolicyInterceptor

NewPolicyInterceptor creates a new PolicyInterceptor.

func (*PolicyInterceptor) Intercept

func (p *PolicyInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept evaluates tool calls against policies before passing to next interceptor. Returns error to BLOCK message propagation - ProxyService MUST check error and send JSON-RPC error response back to client instead of forwarding.

type RateLimitError

type RateLimitError struct {
	// RetryAfter indicates how long to wait before retrying.
	RetryAfter time.Duration
}

RateLimitError is returned when a request is rate limited.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

Error implements the error interface.

type RoutableTool

type RoutableTool struct {
	// Name is the tool's unique name.
	Name string
	// UpstreamID identifies which upstream owns this tool.
	UpstreamID string
	// Description is the human-readable tool description.
	Description string
	// InputSchema is the JSON Schema for the tool's input parameters.
	InputSchema json.RawMessage
}

RoutableTool represents a tool that can be routed to a specific upstream. This is a minimal struct with just the fields the router needs, avoiding circular imports with the upstream package's DiscoveredTool type.

type StatsRecorder

type StatsRecorder interface {
	RecordAllow()
	RecordDeny()
	RecordRateLimited()
	RecordProtocol(protocol string)
	RecordFramework(framework string)
}

StatsRecorder records decision statistics. This interface is satisfied by service.StatsService.

type ToolCacheAdapter

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

ToolCacheAdapter wraps an upstream.ToolCache to satisfy the ToolCacheReader interface. It converts *upstream.DiscoveredTool to *RoutableTool.

func NewToolCacheAdapter

func NewToolCacheAdapter(cache *upstream.ToolCache) *ToolCacheAdapter

NewToolCacheAdapter creates a new ToolCacheAdapter wrapping the given ToolCache.

func (*ToolCacheAdapter) GetAllTools

func (a *ToolCacheAdapter) GetAllTools() []*RoutableTool

GetAllTools returns all discovered tools as RoutableTools.

func (*ToolCacheAdapter) GetTool

func (a *ToolCacheAdapter) GetTool(name string) (*RoutableTool, bool)

GetTool looks up a tool by name and converts to RoutableTool.

type ToolCacheReader

type ToolCacheReader interface {
	// GetTool looks up a tool by name. Returns the tool and true if found.
	GetTool(name string) (*RoutableTool, bool)
	// GetAllTools returns all discovered tools across all upstreams.
	GetAllTools() []*RoutableTool
}

ToolCacheReader provides read access to the shared tool cache. The ToolCache from the upstream package will satisfy this interface.

type UpstreamConnectionProvider

type UpstreamConnectionProvider interface {
	// GetConnection returns the stdin writer and stdout reader for an upstream.
	GetConnection(upstreamID string) (io.WriteCloser, io.ReadCloser, error)
	// AllConnected returns true if at least one upstream is connected.
	AllConnected() bool
}

UpstreamConnectionProvider provides access to upstream connections. The UpstreamManager will satisfy this interface.

type UpstreamRouter

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

UpstreamRouter routes MCP messages to the appropriate upstream based on tool name lookup in the shared ToolCache. It is the innermost interceptor in the chain for multi-upstream mode.

func NewUpstreamRouter

func NewUpstreamRouter(cache ToolCacheReader, manager UpstreamConnectionProvider, logger *slog.Logger) *UpstreamRouter

NewUpstreamRouter creates a new UpstreamRouter.

func (*UpstreamRouter) Intercept

func (r *UpstreamRouter) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept routes the message to the appropriate upstream based on method type. - tools/list: aggregates tools from all upstreams via the ToolCache - tools/call: routes to the correct upstream based on tool name lookup - other methods: forwards to the first connected upstream (primary)

type UserRateLimitInterceptor

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

UserRateLimitInterceptor enforces per-user rate limits on authenticated requests. It runs after authentication so msg.Session is populated with identity info.

Position in chain: After Auth, before Audit. Chain order: Validation -> IPRateLimit -> Auth -> UserRateLimit -> Audit -> Policy -> ...

func NewUserRateLimitInterceptor

func NewUserRateLimitInterceptor(
	limiter ratelimit.RateLimiter,
	userConfig ratelimit.RateLimitConfig,
	next MessageInterceptor,
	logger *slog.Logger,
) *UserRateLimitInterceptor

NewUserRateLimitInterceptor creates a new UserRateLimitInterceptor.

Parameters:

  • limiter: The rate limiter implementation
  • userConfig: Rate limit config for user-based limiting
  • next: The next interceptor in the chain (typically AuditInterceptor)
  • logger: Logger for rate limit events

func (*UserRateLimitInterceptor) Intercept

func (r *UserRateLimitInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept checks user rate limits for authenticated requests. If msg.Session is nil (unauthenticated), it passes through without checking. Returns RateLimitError if the request is rate limited.

type ValidationInterceptor

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

ValidationInterceptor validates incoming messages before authentication. It ensures JSON-RPC structure compliance, tracks pending requests for confused deputy protection, and sanitizes tool call arguments.

Must be first in the interceptor chain (before AuthInterceptor).

func NewValidationInterceptor

func NewValidationInterceptor(next MessageInterceptor, logger *slog.Logger) *ValidationInterceptor

NewValidationInterceptor creates a new ValidationInterceptor. It wraps the next interceptor in the chain (typically AuthInterceptor).

func (*ValidationInterceptor) Intercept

func (v *ValidationInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept validates the message based on direction. ClientToServer messages are validated and sanitized. ServerToClient messages are checked for confused deputy attacks.

Jump to

Keyboard shortcuts

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