Refactor database implementation to use PostgreSQL instead of MongoDB (#289)

Fixes #228

## Motivation and Context

See #19

## How Has This Been Tested?

- Run locally
- Run in docker compose
- Run infra in minikube
- Unit and integration tests all passing :)

## Breaking Changes

Yes: users will not be able to access any data they used to have in
custom mongodb. We are accepting this breaking change, given the
registry is in experimental development status.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context

I have not implemented connection pooling support for simplicity, and
because cloudnative-pg already provides built-in connection pooling with
pgBouncer so if we want it later we can switch to this endpoint easily.
This commit is contained in:
adam jones 2025-08-22 01:40:28 +01:00 • committed by GitHub
commit ebaf4c9c99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 846 additions and 564 deletions

View file

@ -69,7 +69,7 @@ func SetupCertManager(ctx *pulumi.Context, cluster *providers.ProviderInfo) erro
},
},
},
}, pulumi.Provider(cluster.Provider), pulumi.DependsOn([]pulumi.Resource{certManager}))
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(certManager.Ready))
if err != nil {
return err
}

View file

@ -16,19 +16,19 @@ func DeployAll(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment
}
// Setup ingress controller
err = SetupIngressController(ctx, cluster, environment)
ingressNginx, err := SetupIngressController(ctx, cluster, environment)
if err != nil {
return nil, err
}
// Deploy MongoDB
err = DeployMongoDB(ctx, cluster, environment)
// Deploy PostgreSQL databases
pgCluster, err := DeployPostgresDatabases(ctx, cluster, environment)
if err != nil {
return nil, err
}
// Deploy MCP Registry
service, err = DeployMCPRegistry(ctx, cluster, environment)
service, err = DeployMCPRegistry(ctx, cluster, environment, ingressNginx, pgCluster)
if err != nil {
return nil, err
}

View file

@ -13,7 +13,7 @@ import (
)
// SetupIngressController sets up the NGINX Ingress Controller
func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) error {
func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*helm.Chart, error) {
conf := config.New(ctx, "mcp-registry")
provider := conf.Get("provider")
if provider == "" {
@ -27,7 +27,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return err
return nil, err
}
// Install NGINX Ingress Controller
@ -60,7 +60,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return err
return nil, err
}
// Extract ingress IPs from the Helm chart's controller service
@ -90,5 +90,5 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
})
ctx.Export("ingressIps", ingressIps)
return nil
return ingressNginx, nil
}

View file

@ -1,141 +0,0 @@
package k8s
import (
"fmt"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/modelcontextprotocol/registry/deploy/infra/pkg/providers"
)
// DeployMongoDB deploys MongoDB to the Kubernetes cluster
func DeployMongoDB(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) error {
// Create PersistentVolumeClaim for MongoDB data
_, err := corev1.NewPersistentVolumeClaim(ctx, "mongodb-pvc", &corev1.PersistentVolumeClaimArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mongodb-pvc"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mongodb"),
"environment": pulumi.String(environment),
},
},
Spec: &corev1.PersistentVolumeClaimSpecArgs{
AccessModes: pulumi.StringArray{
pulumi.String("ReadWriteOnce"),
},
Resources: &corev1.VolumeResourceRequirementsArgs{
Requests: pulumi.StringMap{
"storage": pulumi.String("10Gi"),
},
},
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return err
}
// Create MongoDB Deployment
_, err = v1.NewDeployment(ctx, "mongodb", &v1.DeploymentArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mongodb"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mongodb"),
"environment": pulumi.String(environment),
},
},
Spec: &v1.DeploymentSpecArgs{
Selector: &metav1.LabelSelectorArgs{
MatchLabels: pulumi.StringMap{
"app": pulumi.String("mongodb"),
},
},
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: pulumi.StringMap{
"app": pulumi.String("mongodb"),
},
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
&corev1.ContainerArgs{
Name: pulumi.String("mongodb"),
Image: pulumi.String("mongo:7.0"),
Ports: corev1.ContainerPortArray{
&corev1.ContainerPortArgs{
ContainerPort: pulumi.Int(27017),
Name: pulumi.String("mongodb"),
},
},
VolumeMounts: corev1.VolumeMountArray{
&corev1.VolumeMountArgs{
Name: pulumi.String("mongodb-data"),
MountPath: pulumi.String("/data/db"),
},
},
LivenessProbe: &corev1.ProbeArgs{
TcpSocket: &corev1.TCPSocketActionArgs{
Port: pulumi.Int(27017),
},
InitialDelaySeconds: pulumi.Int(30),
TimeoutSeconds: pulumi.Int(5),
},
ReadinessProbe: &corev1.ProbeArgs{
TcpSocket: &corev1.TCPSocketActionArgs{
Port: pulumi.Int(27017),
},
InitialDelaySeconds: pulumi.Int(5),
TimeoutSeconds: pulumi.Int(1),
},
},
},
Volumes: corev1.VolumeArray{
&corev1.VolumeArgs{
Name: pulumi.String("mongodb-data"),
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSourceArgs{
ClaimName: pulumi.String("mongodb-pvc"),
},
},
},
},
},
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return err
}
// Create MongoDB Service
_, err = corev1.NewService(ctx, "mongodb", &corev1.ServiceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mongodb"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mongodb"),
"environment": pulumi.String(environment),
},
},
Spec: &corev1.ServiceSpecArgs{
Selector: pulumi.StringMap{
"app": pulumi.String("mongodb"),
},
Ports: corev1.ServicePortArray{
&corev1.ServicePortArgs{
Port: pulumi.Int(27017),
TargetPort: pulumi.Int(27017),
Name: pulumi.String("mongodb"),
},
},
Type: pulumi.String("ClusterIP"),
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return fmt.Errorf("failed to create MongoDB service: %w", err)
}
return nil
}

View file

@ -0,0 +1,70 @@
package k8s
import (
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/modelcontextprotocol/registry/deploy/infra/pkg/providers"
)
// DeployPostgresDatabases deploys the CloudNative PostgreSQL operator and PostgreSQL cluster
func DeployPostgresDatabases(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*apiextensions.CustomResource, error) {
// Create cnpg-system namespace
cnpgNamespace, err := corev1.NewNamespace(ctx, "cnpg-system", &corev1.NamespaceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("cnpg-system"),
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
// Install cloudnative-pg Helm chart
cloudNativePG, err := helm.NewChart(ctx, "cloudnative-pg", helm.ChartArgs{
Chart: pulumi.String("cloudnative-pg"),
Version: pulumi.String("v0.26.0"),
FetchArgs: helm.FetchArgs{
Repo: pulumi.String("https://cloudnative-pg.github.io/charts"),
},
Namespace: cnpgNamespace.Metadata.Name().Elem(),
Values: pulumi.Map{
"webhooks": pulumi.Map{
"replicaCount": pulumi.Int(1),
},
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
// Create PostgreSQL cluster with proper timeout handling
// Note: This may fail on first run until CloudNativePG operator is fully ready
pgCluster, err := apiextensions.NewCustomResource(ctx, "registry-pg", &apiextensions.CustomResourceArgs{
ApiVersion: pulumi.String("postgresql.cnpg.io/v1"),
Kind: pulumi.String("Cluster"),
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("registry-pg"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("registry-pg"),
"environment": pulumi.String(environment),
},
},
OtherFields: map[string]any{
"spec": map[string]any{
"instances": 1,
"storage": map[string]any{
"size": "50Gi",
},
},
},
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(cloudNativePG.Ready))
if err != nil {
return nil, err
}
return pgCluster, nil
}

View file

@ -4,8 +4,10 @@ import (
"os/exec"
"strings"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
v1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
@ -26,7 +28,7 @@ func getGitCommitHash() string {
}
// DeployMCPRegistry deploys the MCP Registry to the Kubernetes cluster
func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*corev1.Service, error) {
func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string, ingressNginx *helm.Chart, pgCluster *apiextensions.CustomResource) (*corev1.Service, error) {
conf := config.New(ctx, "mcp-registry")
githubClientId := conf.Require("githubClientId")
@ -91,8 +93,18 @@ func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, env
},
Env: corev1.EnvVarArray{
&corev1.EnvVarArgs{
Name: pulumi.String("MCP_REGISTRY_DATABASE_URL"),
Value: pulumi.String("mongodb://mongodb.default.svc.cluster.local:27017"),
Name: pulumi.String("MCP_REGISTRY_DATABASE_URL"),
ValueFrom: &corev1.EnvVarSourceArgs{
SecretKeyRef: &corev1.SecretKeySelectorArgs{
Name: pgCluster.Metadata.Name().ApplyT(func(name *string) string {
if name == nil {
return "registry-pg-app"
}
return *name + "-app"
}).(pulumi.StringOutput),
Key: pulumi.String("uri"),
},
},
},
&corev1.EnvVarArgs{
Name: pulumi.String("MCP_REGISTRY_GITHUB_CLIENT_ID"),
@ -227,7 +239,7 @@ func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, env
return rules
}).(networkingv1.IngressRuleArrayOutput),
},
}, pulumi.Provider(cluster.Provider))
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(ingressNginx.Ready))
if err != nil {
return nil, err
}