|
| 1 | +// Package plugins provides interfaces and registry for MCP Gateway plugins. |
| 2 | +// |
| 3 | +// The plugin architecture supports two provider types: |
| 4 | +// - In-Memory Provider: Direct Go code execution (in-process) |
| 5 | +// - MCP Provider: JSON-RPC over HTTP to MCP servers (local containerized or remote) |
| 6 | +// |
| 7 | +// This design allows the same plugin interfaces to work across Desktop and Kubernetes |
| 8 | +// deployments, with different provider implementations for each environment. |
1 | 9 | package plugins |
2 | 10 |
|
3 | 11 | import ( |
4 | 12 | "context" |
5 | | - "io" |
6 | 13 | ) |
7 | 14 |
|
8 | | -// PluginClient defines the interface for communicating with plugins. |
9 | | -// This interface abstracts the transport layer, allowing the same plugin |
10 | | -// interface to work with both subprocess (Desktop) and sidecar (Kubernetes) |
11 | | -// deployment models. |
12 | | -type PluginClient interface { |
13 | | - // Call invokes a method on the plugin with the given parameters. |
14 | | - // The params are JSON-serializable and the result is returned as raw JSON. |
15 | | - Call(ctx context.Context, method string, params any) ([]byte, error) |
| 15 | +// Plugin Code Interfaces (The Stable Contract) |
| 16 | +// These interfaces define what plugins must implement. Gateway code depends on these |
| 17 | +// interfaces, not on specific implementations. |
16 | 18 |
|
17 | | - // Close shuts down the connection to the plugin. |
18 | | - Close() error |
| 19 | +// AuthProvider validates credentials and returns user principals. |
| 20 | +type AuthProvider interface { |
| 21 | + ValidateCredential(ctx context.Context, creds Credentials) (*UserPrincipal, error) |
19 | 22 | } |
20 | 23 |
|
21 | | -// PluginTransport defines how to establish a connection to a plugin. |
22 | | -// Different transports support different deployment models. |
23 | | -type PluginTransport interface { |
24 | | - // Connect establishes a connection to the plugin and returns a PluginClient. |
25 | | - Connect(ctx context.Context) (PluginClient, error) |
| 24 | +// Credentials represents authentication credentials to validate. |
| 25 | +type Credentials struct { |
| 26 | + Type string `json:"type"` // e.g., "api_key", "oauth_token", "jwt" |
| 27 | + Value string `json:"value"` // The credential value |
26 | 28 | } |
27 | 29 |
|
28 | | -// PluginInfo contains metadata about a plugin. |
29 | | -type PluginInfo struct { |
30 | | - // Name is the unique identifier for the plugin. |
31 | | - Name string |
| 30 | +// UserPrincipal represents an authenticated user. |
| 31 | +type UserPrincipal struct { |
| 32 | + UserID string `json:"user_id"` |
| 33 | + TenantID string `json:"tenant_id,omitempty"` |
| 34 | + Roles []string `json:"roles,omitempty"` |
| 35 | + Groups []string `json:"groups,omitempty"` |
| 36 | + Metadata map[string]string `json:"metadata,omitempty"` |
| 37 | +} |
32 | 38 |
|
33 | | - // Type indicates the plugin category (e.g., "telemetry", "auth", "audit"). |
34 | | - Type string |
| 39 | +// CredentialStorage stores and retrieves credentials for users. |
| 40 | +type CredentialStorage interface { |
| 41 | + Store(ctx context.Context, userID, server, credType, value string) error |
| 42 | + Retrieve(ctx context.Context, userID, server, credType string) (string, error) |
| 43 | + Delete(ctx context.Context, userID, server, credType string) error |
| 44 | + List(ctx context.Context, userID string) ([]CredentialInfo, error) |
| 45 | +} |
35 | 46 |
|
36 | | - // Version is the plugin version. |
37 | | - Version string |
| 47 | +// CredentialInfo contains metadata about a stored credential. |
| 48 | +type CredentialInfo struct { |
| 49 | + Server string `json:"server"` |
| 50 | + CredType string `json:"cred_type"` |
| 51 | + CreatedAt string `json:"created_at,omitempty"` |
| 52 | + ExpiresAt string `json:"expires_at,omitempty"` |
38 | 53 | } |
39 | 54 |
|
40 | | -// Plugin represents a loaded plugin instance. |
41 | | -type Plugin interface { |
42 | | - // Info returns metadata about the plugin. |
43 | | - Info() PluginInfo |
| 55 | +// AuthProxy injects credentials into outgoing requests. |
| 56 | +type AuthProxy interface { |
| 57 | + InjectCredentials(ctx context.Context, req *ProxyRequest) (*ProxyResponse, error) |
| 58 | +} |
44 | 59 |
|
45 | | - // Client returns the underlying client for making calls to the plugin. |
46 | | - Client() PluginClient |
| 60 | +// ProxyRequest represents a request that needs credential injection. |
| 61 | +type ProxyRequest struct { |
| 62 | + UserID string `json:"user_id"` |
| 63 | + TenantID string `json:"tenant_id,omitempty"` |
| 64 | + MCPServer string `json:"mcp_server"` |
| 65 | + TargetURL string `json:"target_url"` |
| 66 | + Method string `json:"method"` |
| 67 | + Headers map[string]string `json:"headers,omitempty"` |
| 68 | + Body string `json:"body,omitempty"` |
| 69 | +} |
47 | 70 |
|
48 | | - // Close shuts down the plugin and releases resources. |
49 | | - Close() error |
| 71 | +// ProxyResponse contains the modified request with injected credentials. |
| 72 | +type ProxyResponse struct { |
| 73 | + Headers map[string]string `json:"headers"` |
| 74 | + Body string `json:"body,omitempty"` |
50 | 75 | } |
51 | 76 |
|
52 | | -// PluginConfig defines the configuration for loading a plugin. |
53 | | -type PluginConfig struct { |
54 | | - // Name is the plugin name used for registration. |
55 | | - Name string `json:"name" yaml:"name"` |
| 77 | +// AuditSink logs audit events. |
| 78 | +type AuditSink interface { |
| 79 | + LogEvent(ctx context.Context, event *AuditEvent) error |
| 80 | +} |
56 | 81 |
|
57 | | - // Type is the transport type: "subprocess" or "sidecar". |
58 | | - Type string `json:"type" yaml:"type"` |
| 82 | +// AuditEvent represents an audit log entry. |
| 83 | +type AuditEvent struct { |
| 84 | + Timestamp string `json:"timestamp"` |
| 85 | + EventType string `json:"event_type"` |
| 86 | + TenantID string `json:"tenant_id,omitempty"` |
| 87 | + UserID string `json:"user_id,omitempty"` |
| 88 | + MCPServer string `json:"mcp_server,omitempty"` |
| 89 | + Tool string `json:"tool,omitempty"` |
| 90 | + Result string `json:"result,omitempty"` |
| 91 | + Metadata map[string]string `json:"metadata,omitempty"` |
| 92 | +} |
59 | 93 |
|
60 | | - // Subprocess configuration (used when Type is "subprocess"). |
61 | | - Subprocess *SubprocessConfig `json:"subprocess,omitempty" yaml:"subprocess,omitempty"` |
| 94 | +// PolicyEvaluator checks access permissions. |
| 95 | +type PolicyEvaluator interface { |
| 96 | + CheckAccess(ctx context.Context, principal *UserPrincipal, mcpServer string) error |
| 97 | +} |
62 | 98 |
|
63 | | - // Sidecar configuration (used when Type is "sidecar"). |
64 | | - Sidecar *SidecarConfig `json:"sidecar,omitempty" yaml:"sidecar,omitempty"` |
| 99 | +// MCPProvisioner provisions and manages MCP server instances. |
| 100 | +type MCPProvisioner interface { |
| 101 | + Provision(ctx context.Context, server *ServerDef, userID string) (*ProvisionedServer, error) |
| 102 | + Deprovision(ctx context.Context, serverID string) error |
| 103 | + List(ctx context.Context, userID string) ([]*ProvisionedServer, error) |
65 | 104 | } |
66 | 105 |
|
67 | | -// SubprocessConfig defines configuration for subprocess-based plugins. |
68 | | -type SubprocessConfig struct { |
69 | | - // Exec is the path to the plugin executable. |
70 | | - Exec string `json:"exec" yaml:"exec"` |
| 106 | +// ServerDef defines an MCP server to provision. |
| 107 | +type ServerDef struct { |
| 108 | + Name string `json:"name"` |
| 109 | + Type string `json:"type"` // "image", "remote", "registry" |
| 110 | + Image string `json:"image,omitempty"` |
| 111 | + Source string `json:"source,omitempty"` |
| 112 | + Endpoint string `json:"endpoint,omitempty"` |
| 113 | + Env map[string]string `json:"env,omitempty"` |
| 114 | +} |
71 | 115 |
|
72 | | - // Args are command-line arguments to pass to the plugin. |
73 | | - Args []string `json:"args,omitempty" yaml:"args,omitempty"` |
| 116 | +// ProvisionedServer represents a running MCP server instance. |
| 117 | +type ProvisionedServer struct { |
| 118 | + ID string `json:"id"` |
| 119 | + Name string `json:"name"` |
| 120 | + Endpoint string `json:"endpoint"` |
| 121 | + Status string `json:"status"` |
| 122 | + UserID string `json:"user_id"` |
| 123 | +} |
74 | 124 |
|
75 | | - // Env are environment variables to set for the plugin process. |
76 | | - Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` |
| 125 | +// TelemetryPlugin defines the interface for telemetry plugins. |
| 126 | +// This interface mirrors OpenTelemetry's metric instruments, allowing |
| 127 | +// plugins to receive telemetry data in a generic format. |
| 128 | +type TelemetryPlugin interface { |
| 129 | + RecordCounter(ctx context.Context, name string, value int64, attrs map[string]string) |
| 130 | + RecordHistogram(ctx context.Context, name string, value float64, attrs map[string]string) |
| 131 | + RecordGauge(ctx context.Context, name string, value int64, attrs map[string]string) |
| 132 | + Close() error |
| 133 | +} |
77 | 134 |
|
78 | | - // WorkDir is the working directory for the plugin process. |
79 | | - WorkDir string `json:"workdir,omitempty" yaml:"workdir,omitempty"` |
| 135 | +// Plugin Provider Types |
| 136 | +// Providers are responsible for creating plugin instances based on configuration. |
| 137 | + |
| 138 | +// PluginProvider creates plugin instances from configuration. |
| 139 | +type PluginProvider interface { |
| 140 | + // Name returns the provider name (e.g., "in-memory", "mcp"). |
| 141 | + Name() string |
| 142 | + |
| 143 | + // CreateAuthProvider creates an AuthProvider from configuration. |
| 144 | + CreateAuthProvider(ctx context.Context, config PluginConfig) (AuthProvider, error) |
| 145 | + |
| 146 | + // CreateCredentialStorage creates a CredentialStorage from configuration. |
| 147 | + CreateCredentialStorage(ctx context.Context, config PluginConfig) (CredentialStorage, error) |
| 148 | + |
| 149 | + // CreateAuthProxy creates an AuthProxy from configuration. |
| 150 | + CreateAuthProxy(ctx context.Context, config PluginConfig) (AuthProxy, error) |
| 151 | + |
| 152 | + // CreateAuditSink creates an AuditSink from configuration. |
| 153 | + CreateAuditSink(ctx context.Context, config PluginConfig) (AuditSink, error) |
| 154 | + |
| 155 | + // CreatePolicyEvaluator creates a PolicyEvaluator from configuration. |
| 156 | + CreatePolicyEvaluator(ctx context.Context, config PluginConfig) (PolicyEvaluator, error) |
| 157 | + |
| 158 | + // CreateMCPProvisioner creates an MCPProvisioner from configuration. |
| 159 | + CreateMCPProvisioner(ctx context.Context, config PluginConfig) (MCPProvisioner, error) |
| 160 | + |
| 161 | + // CreateTelemetryPlugin creates a TelemetryPlugin from configuration. |
| 162 | + CreateTelemetryPlugin(ctx context.Context, config PluginConfig) (TelemetryPlugin, error) |
80 | 163 | } |
81 | 164 |
|
82 | | -// SidecarConfig defines configuration for sidecar-based plugins. |
83 | | -type SidecarConfig struct { |
84 | | - // URL is the base URL for the plugin's HTTP endpoint. |
85 | | - URL string `json:"url" yaml:"url"` |
| 165 | +// Configuration Types |
86 | 166 |
|
87 | | - // Headers are additional HTTP headers to include in requests. |
88 | | - Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` |
| 167 | +// PluginConfig defines configuration for a plugin. |
| 168 | +type PluginConfig struct { |
| 169 | + // Provider is the provider type: "in-memory" or "mcp". |
| 170 | + Provider string `json:"provider" yaml:"provider"` |
| 171 | + |
| 172 | + // Implementation is the implementation name (for in-memory provider). |
| 173 | + // Examples: "desktop-implicit", "stdout", "always-allow" |
| 174 | + Implementation string `json:"implementation,omitempty" yaml:"implementation,omitempty"` |
| 175 | + |
| 176 | + // Server is the MCP server reference (for mcp provider). |
| 177 | + // Can be a catalog reference string or inline server definition. |
| 178 | + // Examples: |
| 179 | + // - "catalog://docker.io/docker/mcp-plugins:v1/auth-k8s-secret" |
| 180 | + // - ServerConfig object for inline definition |
| 181 | + Server any `json:"server,omitempty" yaml:"server,omitempty"` |
89 | 182 | } |
90 | 183 |
|
91 | | -// PluginLifecycleHooks provides callbacks for plugin lifecycle events. |
92 | | -type PluginLifecycleHooks struct { |
93 | | - // OnStart is called when a plugin starts successfully. |
94 | | - OnStart func(info PluginInfo) |
| 184 | +// ServerConfig defines an inline MCP server configuration. |
| 185 | +type ServerConfig struct { |
| 186 | + // Type is the server type: "image", "remote", or "registry". |
| 187 | + Type string `json:"type" yaml:"type"` |
| 188 | + |
| 189 | + // Image is the container image (for type: image). |
| 190 | + Image string `json:"image,omitempty" yaml:"image,omitempty"` |
| 191 | + |
| 192 | + // Endpoint is the HTTP endpoint (for type: remote). |
| 193 | + Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"` |
95 | 194 |
|
96 | | - // OnStop is called when a plugin stops. |
97 | | - OnStop func(info PluginInfo, err error) |
| 195 | + // Source is the registry source URL (for type: registry). |
| 196 | + Source string `json:"source,omitempty" yaml:"source,omitempty"` |
98 | 197 |
|
99 | | - // OnError is called when a plugin encounters an error. |
100 | | - OnError func(info PluginInfo, err error) |
| 198 | + // Port is the port the server listens on. |
| 199 | + Port int `json:"port,omitempty" yaml:"port,omitempty"` |
| 200 | + |
| 201 | + // Env are environment variables for the server. |
| 202 | + Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` |
| 203 | +} |
101 | 204 |
|
102 | | - // OnRestart is called when a plugin is restarted. |
103 | | - OnRestart func(info PluginInfo, attempt int) |
| 205 | +// PluginsConfig represents the plugins section of gateway configuration. |
| 206 | +type PluginsConfig struct { |
| 207 | + AuthProvider *PluginConfig `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` |
| 208 | + CredentialStorage *PluginConfig `json:"credential_storage,omitempty" yaml:"credential_storage,omitempty"` |
| 209 | + AuthProxy *PluginConfig `json:"auth_proxy,omitempty" yaml:"auth_proxy,omitempty"` |
| 210 | + AuditSink *PluginConfig `json:"audit_sink,omitempty" yaml:"audit_sink,omitempty"` |
| 211 | + PolicyEvaluator *PluginConfig `json:"policy_evaluator,omitempty" yaml:"policy_evaluator,omitempty"` |
| 212 | + MCPProvisioner *PluginConfig `json:"mcp_provisioner,omitempty" yaml:"mcp_provisioner,omitempty"` |
| 213 | + Telemetry *PluginConfig `json:"telemetry,omitempty" yaml:"telemetry,omitempty"` |
104 | 214 | } |
105 | 215 |
|
106 | | -// PluginOutput represents output streams from a subprocess plugin. |
107 | | -type PluginOutput struct { |
108 | | - // Stdout is the plugin's standard output stream. |
109 | | - Stdout io.Reader |
| 216 | +// ContainerManager manages plugin containers. |
| 217 | +// Desktop uses Docker Engine API, Kubernetes uses noop (sidecars pre-started). |
| 218 | +type ContainerManager interface { |
| 219 | + // EnsureRunning ensures the container is running and returns its endpoint. |
| 220 | + EnsureRunning(ctx context.Context, config ServerConfig) (endpoint string, err error) |
110 | 221 |
|
111 | | - // Stderr is the plugin's standard error stream. |
112 | | - Stderr io.Reader |
| 222 | + // Stop stops a container. |
| 223 | + Stop(ctx context.Context, endpoint string) error |
113 | 224 | } |
0 commit comments