Skip to content

Commit 417015b

Browse files
kgprsclaude
andcommitted
Refactor to in-memory/MCP provider pattern per design doc
Aligns with Design Decision 0000: Plugin Model and Sidecar Strategy. Changes: - Replace subprocess/sidecar transports with in-memory/mcp providers - Add all plugin interfaces: AuthProvider, CredentialStorage, AuthProxy, AuditSink, PolicyEvaluator, MCPProvisioner, TelemetryPlugin - Add in-memory provider with default implementations: - desktop-implicit auth (always returns desktop-user) - always-allow policy evaluator - stdout/stderr audit sinks - noop telemetry - Add MCP provider with tool adapters for all plugin types - Update PluginRegistry to use provider pattern - Support catalog:// references and inline server configs - Update architecture documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1ed9cd2 commit 417015b

11 files changed

Lines changed: 1449 additions & 1168 deletions

File tree

docs/architecture/plugin-architecture.md

Lines changed: 252 additions & 344 deletions
Large diffs are not rendered by default.

pkg/plugins/interface.go

Lines changed: 185 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,224 @@
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.
19
package plugins
210

311
import (
412
"context"
5-
"io"
613
)
714

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.
1618

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)
1922
}
2023

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
2628
}
2729

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+
}
3238

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+
}
3546

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"`
3853
}
3954

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+
}
4459

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+
}
4770

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"`
5075
}
5176

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+
}
5681

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+
}
5993

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+
}
6298

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)
65104
}
66105

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+
}
71115

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+
}
74124

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+
}
77134

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)
80163
}
81164

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
86166

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"`
89182
}
90183

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"`
95194

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"`
98197

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+
}
101204

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"`
104214
}
105215

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)
110221

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
113224
}

0 commit comments

Comments
 (0)