Skip to content

Commit f95b69b

Browse files
authored
Merge branch 'main' into registry_cm
2 parents 500c5a5 + fc1aedf commit f95b69b

File tree

62 files changed

+4373
-518
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4373
-518
lines changed

Taskfile.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ tasks:
4949
- go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
5050
# we have to use ldflags to avoid the LC_DYSYMTAB linker error.
5151
# https://github.com/stacklok/toolhive/issues/1687
52-
- go test -ldflags=-extldflags=-Wl,-w -v -json -race $(go list ./... | grep -v '/test/e2e') | gotestfmt
52+
- go test -ldflags=-extldflags=-Wl,-w -v -json -race $(go list ./... | grep -v '/test/e2e') | gotestfmt -hide "all"
5353

5454
test-windows:
5555
desc: Run unit tests (excluding e2e tests) on Windows with race detection
@@ -61,11 +61,6 @@ tasks:
6161
cmds:
6262
- go test -v -race {{.DIR_LIST | catLines}}
6363

64-
test-silent:
65-
desc: Run unit tests, and only show failures.
66-
cmds:
67-
- go test -ldflags=-extldflags=-Wl,-w -json -race $(go list ./... | grep -vE '/test/e2e|/mocks') | gotestfmt -hide "all"
68-
6964
test:
7065
desc: Run unit tests (excluding e2e tests)
7166
deps: [gen]

cmd/thv-operator/api/v1alpha1/mcpserver_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,17 @@ type MCPServerSpec struct {
8888
Audit *AuditConfig `json:"audit,omitempty"`
8989

9090
// ToolsFilter is the filter on tools applied to the MCP server
91+
// Deprecated: Use ToolConfigRef instead
9192
// +optional
9293
ToolsFilter []string `json:"tools,omitempty"`
9394

95+
// ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.
96+
// The referenced MCPToolConfig must exist in the same namespace as this MCPServer.
97+
// Cross-namespace references are not supported for security and isolation reasons.
98+
// If specified, this takes precedence over the inline ToolsFilter field.
99+
// +optional
100+
ToolConfigRef *ToolConfigRef `json:"toolConfigRef,omitempty"`
101+
94102
// Telemetry defines observability configuration for the MCP server
95103
// +optional
96104
Telemetry *TelemetryConfig `json:"telemetry,omitempty"`
@@ -440,6 +448,14 @@ type ConfigMapAuthzRef struct {
440448
Key string `json:"key,omitempty"`
441449
}
442450

451+
// ToolConfigRef defines a reference to a MCPToolConfig resource.
452+
// The referenced MCPToolConfig must be in the same namespace as the MCPServer.
453+
type ToolConfigRef struct {
454+
// Name is the name of the MCPToolConfig resource in the same namespace
455+
// +kubebuilder:validation:Required
456+
Name string `json:"name"`
457+
}
458+
443459
// InlineAuthzConfig contains direct authorization configuration
444460
type InlineAuthzConfig struct {
445461
// Policies is a list of Cedar policy strings
@@ -543,6 +559,10 @@ type MCPServerStatus struct {
543559
// +optional
544560
Conditions []metav1.Condition `json:"conditions,omitempty"`
545561

562+
// ToolConfigHash stores the hash of the referenced ToolConfig for change detection
563+
// +optional
564+
ToolConfigHash string `json:"toolConfigHash,omitempty"`
565+
546566
// URL is the URL where the MCP server can be accessed
547567
// +optional
548568
URL string `json:"url,omitempty"`
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package v1alpha1
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
// MCPToolConfigSpec defines the desired state of MCPToolConfig.
8+
// MCPToolConfig resources are namespace-scoped and can only be referenced by
9+
// MCPServer resources in the same namespace.
10+
type MCPToolConfigSpec struct {
11+
// ToolsFilter is a list of tool names to filter (allow list).
12+
// Only tools in this list will be exposed by the MCP server.
13+
// If empty, all tools are exposed.
14+
// +optional
15+
ToolsFilter []string `json:"toolsFilter,omitempty"`
16+
17+
// ToolsOverride is a map from actual tool names to their overridden configuration.
18+
// This allows renaming tools and/or changing their descriptions.
19+
// +optional
20+
ToolsOverride map[string]ToolOverride `json:"toolsOverride,omitempty"`
21+
}
22+
23+
// ToolOverride represents a tool override configuration.
24+
// Both Name and Description can be overridden independently, but
25+
// they can't be both empty.
26+
type ToolOverride struct {
27+
// Name is the redefined name of the tool
28+
// +optional
29+
Name string `json:"name,omitempty"`
30+
31+
// Description is the redefined description of the tool
32+
// +optional
33+
Description string `json:"description,omitempty"`
34+
}
35+
36+
// MCPToolConfigStatus defines the observed state of MCPToolConfig
37+
type MCPToolConfigStatus struct {
38+
// ObservedGeneration is the most recent generation observed for this MCPToolConfig.
39+
// It corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server.
40+
// +optional
41+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
42+
43+
// ConfigHash is a hash of the current configuration for change detection
44+
// +optional
45+
ConfigHash string `json:"configHash,omitempty"`
46+
47+
// ReferencingServers is a list of MCPServer resources that reference this MCPToolConfig
48+
// This helps track which servers need to be reconciled when this config changes
49+
// +optional
50+
ReferencingServers []string `json:"referencingServers,omitempty"`
51+
}
52+
53+
// +kubebuilder:object:root=true
54+
// +kubebuilder:subresource:status
55+
// +kubebuilder:resource:shortName=tc;toolconfig
56+
// +kubebuilder:printcolumn:name="Filter Count",type=integer,JSONPath=`.spec.toolsFilter[*]`
57+
// +kubebuilder:printcolumn:name="Override Count",type=integer,JSONPath=`.spec.toolsOverride`
58+
// +kubebuilder:printcolumn:name="Referenced By",type=string,JSONPath=`.status.referencingServers`
59+
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
60+
61+
// MCPToolConfig is the Schema for the mcptoolconfigs API.
62+
// MCPToolConfig resources are namespace-scoped and can only be referenced by
63+
// MCPServer resources within the same namespace. Cross-namespace references
64+
// are not supported for security and isolation reasons.
65+
type MCPToolConfig struct {
66+
metav1.TypeMeta `json:",inline"` // nolint:revive
67+
metav1.ObjectMeta `json:"metadata,omitempty"`
68+
69+
Spec MCPToolConfigSpec `json:"spec,omitempty"`
70+
Status MCPToolConfigStatus `json:"status,omitempty"`
71+
}
72+
73+
// +kubebuilder:object:root=true
74+
75+
// MCPToolConfigList contains a list of MCPToolConfig
76+
type MCPToolConfigList struct {
77+
metav1.TypeMeta `json:",inline"` // nolint:revive
78+
metav1.ListMeta `json:"metadata,omitempty"`
79+
Items []MCPToolConfig `json:"items"`
80+
}
81+
82+
func init() {
83+
SchemeBuilder.Register(&MCPToolConfig{}, &MCPToolConfigList{})
84+
}

cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 141 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/thv-operator/controllers/mcpserver_controller.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ func (r *MCPServerReconciler) detectPlatform(ctx context.Context) (kubernetes.Pl
127127
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpservers,verbs=get;list;watch;create;update;patch;delete
128128
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpservers/status,verbs=get;update;patch
129129
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpservers/finalizers,verbs=update
130+
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcptoolconfigs,verbs=get;list;watch
130131
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=create;delete;get;list;patch;update;watch
131132
// +kubebuilder:rbac:groups="",resources=services,verbs=create;delete;get;list;patch;update;watch;apply
132133
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=create;delete;get;list;patch;update;watch
@@ -162,6 +163,17 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
162163
return ctrl.Result{}, err
163164
}
164165

166+
// Check if MCPToolConfig is referenced and handle it
167+
if err := r.handleToolConfig(ctx, mcpServer); err != nil {
168+
ctxLogger.Error(err, "Failed to handle MCPToolConfig")
169+
// Update status to reflect the error
170+
mcpServer.Status.Phase = mcpv1alpha1.MCPServerPhaseFailed
171+
if statusErr := r.Status().Update(ctx, mcpServer); statusErr != nil {
172+
ctxLogger.Error(statusErr, "Failed to update MCPServer status after MCPToolConfig error")
173+
}
174+
return ctrl.Result{}, err
175+
}
176+
165177
// Check if the MCPServer instance is marked to be deleted
166178
if mcpServer.GetDeletionTimestamp() != nil {
167179
// The object is being deleted
@@ -393,6 +405,50 @@ func (r *MCPServerReconciler) updateRBACResourceIfNeeded(
393405
}
394406

395407
// ensureRBACResources ensures that the RBAC resources are in place for the MCP server
408+
409+
// handleToolConfig handles MCPToolConfig reference for an MCPServer
410+
func (r *MCPServerReconciler) handleToolConfig(ctx context.Context, m *mcpv1alpha1.MCPServer) error {
411+
if m.Spec.ToolConfigRef == nil {
412+
// No MCPToolConfig referenced, clear any stored hash
413+
if m.Status.ToolConfigHash != "" {
414+
m.Status.ToolConfigHash = ""
415+
if err := r.Status().Update(ctx, m); err != nil {
416+
return fmt.Errorf("failed to clear MCPToolConfig hash from status: %w", err)
417+
}
418+
}
419+
return nil
420+
}
421+
422+
// Get the referenced MCPToolConfig
423+
toolConfig, err := GetToolConfigForMCPServer(ctx, r.Client, m)
424+
if err != nil {
425+
return err
426+
}
427+
428+
if toolConfig == nil {
429+
return fmt.Errorf("MCPToolConfig %s not found", m.Spec.ToolConfigRef.Name)
430+
}
431+
432+
// Check if the MCPToolConfig hash has changed
433+
if m.Status.ToolConfigHash != toolConfig.Status.ConfigHash {
434+
ctxLogger.Info("MCPToolConfig has changed, updating MCPServer",
435+
"mcpserver", m.Name,
436+
"toolconfig", toolConfig.Name,
437+
"oldHash", m.Status.ToolConfigHash,
438+
"newHash", toolConfig.Status.ConfigHash)
439+
440+
// Update the stored hash
441+
m.Status.ToolConfigHash = toolConfig.Status.ConfigHash
442+
if err := r.Status().Update(ctx, m); err != nil {
443+
return fmt.Errorf("failed to update MCPToolConfig hash in status: %w", err)
444+
}
445+
446+
// The change in hash will trigger a reconciliation of the RunConfig
447+
// which will pick up the new tool configuration
448+
}
449+
450+
return nil
451+
}
396452
func (r *MCPServerReconciler) ensureRBACResources(ctx context.Context, mcpServer *mcpv1alpha1.MCPServer) error {
397453
proxyRunnerNameForRBAC := proxyRunnerServiceAccountName(mcpServer.Name)
398454

0 commit comments

Comments
 (0)