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
- Variables
- func CreateJSONRPCError(id interface{}, code int, message string) []byte
- func LogDevModeWarning(logger *slog.Logger, devMode bool) error
- func SafeErrorMessage(err error) string
- type AuditInterceptor
- type AuditRecorder
- type AuthInterceptor
- func (a *AuthInterceptor) CacheSize() int
- func (a *AuthInterceptor) ClearSession(connID string)
- func (a *AuthInterceptor) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)
- func (a *AuthInterceptor) SetTestCacheEntry(connID, sessionID string)
- func (a *AuthInterceptor) SetTestCacheEntryWithTime(connID, sessionID string, lastAccess time.Time)
- func (a *AuthInterceptor) StartCleanup(ctx context.Context)
- func (a *AuthInterceptor) Stop()
- type IPRateLimitInterceptor
- type MessageInterceptor
- type PassthroughInterceptor
- type PolicyDenyError
- type PolicyInterceptor
- type RateLimitError
- type RoutableTool
- type StatsRecorder
- type ToolCacheAdapter
- type ToolCacheReader
- type UpstreamConnectionProvider
- type UpstreamRouter
- type UserRateLimitInterceptor
- type ValidationInterceptor
Constants ¶
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.
const ConnectionIDKey contextKey = "connection_id"
ConnectionIDKey is the context key for connection ID.
Variables ¶
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.
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")
var ErrMissingSession = errors.New("missing session context")
ErrMissingSession indicates a tool call was received without session context.
var ErrPolicyDenied = errors.New("policy denied")
Error types for policy evaluation failures.
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 ¶
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 ¶
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 ¶
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.
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 ¶
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
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.
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.
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 ¶
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.