Skip to content

Local Kubernetes deployment for MCP Registry with PostgreSQL database and web UI

Notifications You must be signed in to change notification settings

san360/mcp-registry

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MCP Registry - Local Development Guide

Complete guide for setting up and using a self-hosted Model Context Protocol (MCP) Registry on Kubernetes for local development and testing.

Table of Contents

Overview

This implementation provides:

  • Self-hosted MCP Registry (v1.3.10) on Kubernetes with PostgreSQL 16 backend
  • Anonymous Authentication for local development (no GitHub/DNS auth required)
  • Local MCP Server execution via Docker packages
  • Remote MCP Server deployment with HTTP/SSE transport protocols
  • Server Publishing with PowerShell automation scripts
  • Multi-transport Support for stdio, Docker, streamable-http, and SSE

Solution in Action

Local MCP Registry

Local MCP Registry Self-hosted registry running on Kubernetes with published servers

MCP AI Control Panel

MCP AI Control Managing MCP servers in VS Code Copilot settings

Allowed MCP Servers

Allowed MCP Servers Configured and enabled MCP servers ready for use

Disabled MCP Servers

Disabled MCP Servers MCP servers that can be enabled when needed

Key Features

Local Development Focus

  • Anonymous authentication enabled (no production auth complexity)
  • Registry validation disabled (allows non-existent packages)
  • Persistent PostgreSQL storage
  • Ingress routing with friendly hostname

Publishing Workflow

  • Automated PowerShell scripts for publishing
  • Version management and history
  • Support for both package-based and remote servers
  • Namespace-based access control

Kubernetes Native

  • Containerized deployment
  • Service discovery
  • ConfigMap and Secret management
  • Health checks and readiness probes

Quick Start

Prerequisites

  • Kubernetes cluster (Docker Desktop, Minikube, or kind)
  • kubectl CLI configured
  • PowerShell 7+ or PowerShell Core
  • Administrator access (for hosts file modification)

1. Deploy Infrastructure

# Deploy everything (registry + PKI + optional example servers)
.\scripts\deploy-all.ps1 -Wait

2. Setup Host Resolution

Windows (requires Administrator):

  1. Open Notepad as Administrator
  2. Open file: C:\Windows\System32\drivers\etc\hosts
  3. Add these lines at the end:
    127.0.0.1 mcp-registry.local
    

127.0.0.1 mcp-devtools.local

4. Save and close

**macOS/Linux:**

```bash
sudo sh -c 'echo "127.0.0.1 mcp-registry.local" >> /etc/hosts'
sudo sh -c 'echo "127.0.0.1 mcp-devtools.local" >> /etc/hosts'

Verify:

ping mcp-registry.local
# Should resolve to 127.0.0.1

3. Verify Deployment

# Check pods
kubectl get pods -n mcp-registry

# Expected output:
# NAME                            READY   STATUS    RESTARTS   AGE
# mcp-registry-xxxxx-xxxxx        1/1     Running   0          1m
# postgres-xxxxx-xxxxx            1/1     Running   0          1m

# Test health endpoint (HTTPS)
curl https://mcp-registry.local/v0.1/health

# List servers
curl https://mcp-registry.local/v0.1/servers

4. Enable Local HTTPS (Recommended)

This repo is set up for HTTPS everywhere (registry + remote MCP servers). For local development, the key is making your client trust the local CA.

# Create a local Root CA inside the cluster, create the CA issuer, and export the CA to .\.certs\...
.\scripts\setup-local-ssl.ps1

# Recommended on Windows: trust the CA and enable Node to use the Windows trust store
.\scripts\trust-local-ca-windows.ps1

# Quick validation (Node fetch against devtools + registry)
node -e "fetch('https://mcp-registry.local/v0.1/health').then(r=>console.log('registry',r.status)).catch(e=>console.error(e.cause||e))"
node -e "fetch('https://mcp-devtools.local/').then(r=>console.log('devtools',r.status)).catch(e=>console.error(e.cause||e))"

5. Test MCP Servers (Local + Remote)

This repo includes both:

  • Local MCP server (runs on your machine via Docker + stdio): time-local
  • Remote MCP server (runs in Kubernetes via HTTPS): devtools-remote

5.1 Test via VS Code (recommended)

  1. Ensure your workspace MCP config includes both servers:
  • Local: Docker command (stdio)
  • Remote: https://mcp-devtools.local

See .vscode/mcp.json.

  1. In VS Code, restart MCP servers (or reload the window).

  2. Open Copilot Chat and copy/paste these test prompts:

Test local server (time-local):

Call time-local.get_current_time with timezone = UTC
Convert 13:37 from UTC to America/Los_Angeles using time-local.convert_time

Test remote server (devtools-remote):

Call devtools-remote.get_timestamp with format = iso
Call devtools-remote.generate_uuid with version = v4
Call devtools-remote.hash_text with text = hello and algorithm = sha256
Call devtools-remote.encode_decode with text = hello and operation = encode

If the remote calls fail with fetch failed, it usually means TLS trust is not set up. Run:

.\scripts\setup-local-ssl.ps1
.\scripts\trust-local-ca-windows.ps1

5.2 Quick remote TLS sanity check (outside VS Code)

This is not a full MCP protocol test, but it confirms HTTPS + DNS + certificate trust are working:

node -e "fetch('https://mcp-devtools.local/').then(r=>console.log('devtools',r.status)).catch(e=>console.error(e.cause||e))"

Tip: a plain GET may return 406 (expected) because MCP endpoints expect specific headers; the important part is that TLS completes without certificate errors.

6. Publish Example Servers

This repository includes two pre-configured example servers:

  1. mcp-time-local - Package-based server using Docker stdio transport
  2. mcp-devtools-remote - Remote server with streamable-http transport
# Publish all example servers
.\scripts\publish-servers.ps1

# Verify publication
curl https://mcp-registry.local/v0.1/servers | ConvertFrom-Json | Select-Object -ExpandProperty servers

7. Deploy MCP DevTools Server (Optional)

The MCP DevTools server provides developer utility tools accessible via remote HTTP transport.

Features:

  • get_timestamp - Current timestamps in multiple formats
  • encode_decode - Base64 encoding/decoding
  • generate_uuid - UUID v4/v5 generation
  • hash_text - MD5, SHA1, SHA256, SHA512 hashing

Deploy to Kubernetes:

# Apply deployment
kubectl apply -f .\servers\mcp-devtools\k8s\deployment.yaml

# Add to hosts file (Windows - requires admin):
# C:\Windows\System32\drivers\etc\hosts
127.0.0.1 mcp-devtools.local

# Verify deployment
kubectl wait --for=condition=ready pod -l app=mcp-devtools -n mcp-devtools --timeout=60s

# TLS sanity check (a plain GET typically returns 406 because MCP expects specific Accept headers)
curl -i https://mcp-devtools.local/

# View logs
kubectl logs -n mcp-devtools -l app=mcp-devtools

The server will be accessible at https://mcp-devtools.local and can be used directly with VS Code MCP client:

// .vscode/mcp.json
{
  "servers": {
    "devtools-remote": {
      "url": "https://mcp-devtools.local"
    }
  }
}

Important: HTTPS + Trust

This repo assumes HTTPS everywhere. If your MCP client shows fetch failed, it’s usually not reachability — it’s that Node/VS Code does not trust the local CA. Run the scripts in “Enable Local HTTPS (Recommended)” above.

See servers/mcp-devtools/README.md for full documentation.

Manual Publishing (Alternative):

# Get authentication token and publish
$authResp = curl -s https://mcp-registry.local/v0/auth/none -X POST | ConvertFrom-Json
$token = $authResp.registry_token
$headers = @{"Authorization" = "Bearer $token"}
Invoke-RestMethod -Uri "https://mcp-registry.local/v0.1/publish" `
    -Method POST `
    -ContentType "application/json" `
    -Headers $headers `
    -Body (Get-Content .\servers\your-server.json -Raw)

Scripts

PowerShell scripts are in scripts/.

Architecture

Diagrams

Solution Flow (HTTPS + Trust)

flowchart TD
  A[Deploy cluster resources] --> B[cert-manager issues local Root CA]
  B --> C[Ingress certs signed by local CA]
  C --> D[Export Root CA PEM to .certs/]
  D --> E[Trust Root CA on developer machine]
  E --> F[Node/VS Code accepts TLS chain]
  F --> G[VS Code MCP connects to https://mcp-devtools.local]
  F --> H[Publisher uses https://mcp-registry.local]

  classDef deploy fill:#e1f5ff,stroke:#0078d4,stroke-width:2px,color:#000
  classDef cert fill:#fff4ce,stroke:#ca5010,stroke-width:2px,color:#000
  classDef trust fill:#dff6dd,stroke:#107c10,stroke-width:2px,color:#000
  classDef client fill:#f4ecff,stroke:#8764b8,stroke-width:2px,color:#000

  class A deploy
  class B,C,D cert
  class E,F trust
  class G,H client
Loading

User Flow (Local + Remote MCP Testing)

flowchart LR
  U[Developer] --> HOSTS[Add hosts entries]
  HOSTS --> DEP["Run scripts/deploy-all.ps1 -Wait"]
  DEP --> SSL["Run scripts/setup-local-ssl.ps1"]
  SSL --> TRUST["Run scripts/trust-local-ca-windows.ps1"]
  TRUST --> VS["Open VS Code workspace"]
  VS --> MCP["Use .vscode/mcp.json servers"]
  MCP --> LOCAL["Test time-local<br/>Docker/stdio"]
  MCP --> REMOTE["Test devtools-remote<br/>HTTPS"]
  VS --> PUB["Run scripts/publish-servers.ps1"]

  classDef user fill:#f3f2f1,stroke:#323130,stroke-width:2px,color:#000
  classDef setup fill:#fff4ce,stroke:#ca5010,stroke-width:2px,color:#000
  classDef ssl fill:#dff6dd,stroke:#107c10,stroke-width:2px,color:#000
  classDef vscode fill:#e1f5ff,stroke:#0078d4,stroke-width:2px,color:#000
  classDef test fill:#f4ecff,stroke:#8764b8,stroke-width:2px,color:#000

  class U user
  class HOSTS,DEP setup
  class SSL,TRUST ssl
  class VS,MCP vscode
  class LOCAL,REMOTE,PUB test
Loading

Architecture Flow (Components + Requests)

flowchart TB
  subgraph DevMachine["Developer Machine"]
    VSCode["VS Code MCP Client<br/>(Node extension host)"]
    Docker["Docker<br/>(time-local package)"]
    VSCode -->|stdio| Docker
  end

  subgraph Cluster["Kubernetes Cluster"]
    CM["cert-manager<br/>local Root CA + ClusterIssuer"]
    NGINX["Ingress Controller<br/>(mcp-*.local)"]
    REG["MCP Registry<br/>Service/Pod"]
    PG["PostgreSQL<br/>Service/Pod"]
    DEV["MCP DevTools<br/>Service/Pod"]

    REG --> PG
    NGINX --> REG
    NGINX --> DEV
    CM -->|"signs TLS certs"| NGINX
  end

  VSCode -->|HTTPS| NGINX

  classDef client fill:#e1f5ff,stroke:#0078d4,stroke-width:2px,color:#000
  classDef security fill:#dff6dd,stroke:#107c10,stroke-width:2px,color:#000
  classDef network fill:#fff4ce,stroke:#ca5010,stroke-width:2px,color:#000
  classDef service fill:#f4ecff,stroke:#8764b8,stroke-width:2px,color:#000
  classDef data fill:#fef0f1,stroke:#d13438,stroke-width:2px,color:#000

  class VSCode,Docker client
  class CM security
  class NGINX network
  class REG,DEV service
  class PG data
Loading

Component Overview

┌─────────────────────────────────────────────────────────┐
│                  Kubernetes Cluster                     │
│              Namespace: mcp-registry                    │
│                                                         │
│  ┌────────────────────────────────────────────────┐   │
│  │          Nginx Ingress Controller               │   │
│  │         mcp-registry.local (127.0.0.1)         │   │
│  └──────────────────┬─────────────────────────────┘   │
│                     │                                   │
│  ┌──────────────────▼─────────────────────────────┐   │
│  │           MCP Registry Service                  │   │
│  │              Port 8080                          │   │
│  │  ┌──────────────────────────────────────────┐  │   │
│  │  │     MCP Registry Pod                     │  │   │
│  │  │  ghcr.io/modelcontextprotocol/registry  │  │   │
│  │  │           v1.3.10                        │  │   │
│  │  │                                          │  │   │
│  │  │  Environment:                            │  │   │
│  │  │  - ENABLE_ANONYMOUS_AUTH=true           │  │   │
│  │  │  - ENABLE_REGISTRY_VALIDATION=false     │  │   │
│  │  │  - PUBLIC_BASE_URL=https://...          │  │   │
│  │  └──────────────────────────────────────────┘  │   │
│  └──────────────────┬─────────────────────────────┘   │
│                     │                                   │
│  ┌──────────────────▼─────────────────────────────┐   │
│  │        PostgreSQL Service (Port 5432)          │   │
│  │  ┌──────────────────────────────────────────┐  │   │
│  │  │       PostgreSQL 16 Pod                  │  │   │
│  │  │      Database: mcp_registry              │  │   │
│  │  │                                          │  │   │
│  │  │  Storage: PersistentVolumeClaim (2Gi)   │  │   │
│  │  └──────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Data Flow

Publishing a Server:

Client → POST /v0/auth/none → JWT Token
     ↓
Client → POST /v0.1/publish (with token) → Registry Pod
     ↓
Registry Pod → Validate Schema & Permissions
     ↓
Registry Pod → Store in PostgreSQL
     ↓
Return Success Response

Listing Servers:

Client → GET /v0.1/servers → Registry Pod
     ↓
Registry Pod → Query PostgreSQL
     ↓
Return Server List (JSON)

Configuration

Environment Variables (mcp-registry.yaml)

env:
  # Database connection
  - name: MCP_REGISTRY_DATABASE_URL
    value: "postgresql://postgres:postgres@postgres.mcp-registry.svc.cluster.local:5432/mcp_registry?sslmode=disable"
    
  # JWT signing key (32 bytes hex)
  - name: MCP_REGISTRY_JWT_PRIVATE_KEY
    value: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"
    
  # Public URL for API
  - name: PUBLIC_BASE_URL
    value: "https://mcp-registry.local"
    
  # Enable anonymous auth (LOCAL DEV ONLY!)
  - name: MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH
    value: "true"
    
  # Disable package validation (allows fake packages)
  - name: MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION
    value: "false"

Key Settings Explained

Variable Value Purpose Production?
MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH "true" Enables /v0/auth/none endpoint ❌ Must be false
MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION "false" Skip npm/PyPI/Docker validation ❌ Should be true
PUBLIC_BASE_URL "https://mcp-registry.local" Base URL for API ✅ Update for prod
MCP_REGISTRY_JWT_PRIVATE_KEY 64-char hex Ed25519 seed for JWT signing ✅ Use secrets

Validation Checklist

# Check anonymous auth is enabled
kubectl -n mcp-registry get deploy mcp-registry -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH")].value}'
# Expected: true

# Check validation is disabled
kubectl -n mcp-registry get deploy mcp-registry -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION")].value}'
# Expected: false

# Check pods are running
kubectl -n mcp-registry get pods
# Expected: Both pods Running 1/1

# Test API
curl https://mcp-registry.local/v0.1/health
# Expected: {"status":"ok"}

Authentication

Anonymous Authentication (Local Dev Only)

The registry supports anonymous authentication for local development:

Endpoint: POST /v0/auth/none

Response:

{
  "registry_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "expires_at": 1765643590
}

Token Claims:

{
  "auth_method": "none",
  "auth_method_sub": "anonymous",
  "permissions": [
    {
      "action": "publish",
      "resource": "io.modelcontextprotocol.anonymous/*"
    },
    {
      "action": "edit",
      "resource": "io.modelcontextprotocol.anonymous/*"
    }
  ]
}

Important: Tokens are scoped to the io.modelcontextprotocol.anonymous/* namespace only.

Getting a Token

$authResp = curl -s https://mcp-registry.local/v0/auth/none -X POST | ConvertFrom-Json
$token = $authResp.registry_token
Write-Host "Token expires at: $(Get-Date -UnixTimeSeconds $authResp.expires_at)"
# Bash/curl
curl -X POST https://mcp-registry.local/v0/auth/none | jq -r '.registry_token'

Using the Token

$headers = @{"Authorization" = "Bearer $token"}
Invoke-RestMethod -Uri "https://mcp-registry.local/v0.1/publish" `
    -Method POST `
    -Headers $headers `
    -Body $jsonBody

Note: on Windows, PowerShell HTTPS requests use the Windows certificate trust store. If you see certificate errors, run .\scripts\trust-local-ca-windows.ps1 first.

Common Authentication Mistakes

# ❌ WRONG - Wrong field name
$token = $authResp.registryToken  # Should be registry_token

# ❌ WRONG - Missing Bearer prefix
-Headers @{"Authorization" = "$token"}

# ❌ WRONG - Extra quotes
-Headers @{"Authorization" = "'Bearer $token'"}

# ✅ CORRECT
$token = $authResp.registry_token
-Headers @{"Authorization" = "Bearer $token"}

Server Publishing

Server Definition Format

Package-Based Server (Docker example)

{
  "$schema": "2025-10-17",
  "name": "io.modelcontextprotocol.anonymous/my-server",
  "title": "My MCP Server",
  "description": "Server description here",
  "version": "1.0.0",
  "packages": [
    {
      "registryType": "docker",
      "identifier": "myorg/my-mcp-server",
      "version": "1.0.0",
      "transport": {
        "type": "stdio"
      }
    }
  ]
}

Remote Server (HTTP/SSE Transports)

⚠️ CRITICAL: Remote URLs must point to ACTUAL MCP server endpoints!

Remote servers MUST:

  • Be publicly accessible via HTTPS (for production)
  • Implement the MCP protocol over HTTP or SSE
  • NOT use placeholder URLs like httpbin.org in production

For Local Testing Only: When MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION=false, you can use placeholder URLs for testing:

{
  "$schema": "2025-10-17",
  "name": "io.modelcontextprotocol.anonymous/test-remote-server",
  "title": "Test Remote Server (Local Dev Only)",
  "description": "⚠️ Uses placeholder URL - NOT for production use",
  "version": "0.0.1",
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://httpbin.org/status/200"
    }
  ]
}

For Production/Real Deployments:

{
  "$schema": "2025-10-17",
  "name": "io.github.username/my-remote-server",
  "title": "My Production Remote MCP Server",
  "description": "Cloud-hosted MCP server",
  "version": "1.0.0",
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://mcp.myservice.com/api/v1"
    }
  ]
}

With SSE Transport:

{
  "remotes": [
    {
      "type": "sse",
      "url": "https://mcp.myservice.com/events"
    }
  ]
}

Multi-transport Support:

{
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://mcp.myservice.com/http"
    },
    {
      "type": "sse",
      "url": "https://mcp.myservice.com/sse"
    }
  ]
}

With URL Variables (Multi-tenant):

{
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://{tenant_id}.mcp.myservice.com/api",
      "variables": {
        "tenant_id": {
          "description": "Your tenant identifier",
          "isRequired": true
        }
      }
    }
  ]
}

With Authentication Headers:

{
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://mcp.myservice.com/api",
      "headers": [
        {
          "name": "X-API-Key",
          "description": "API key for authentication",
          "isRequired": true,
          "isSecret": true
        }
      ]
    }
  ]
}

Key Requirements

  1. Schema Version: Use "2025-10-17" (string, not URL)
  2. Namespace: Must be io.modelcontextprotocol.anonymous/* for local dev
  3. Version: Use semantic versioning (e.g., "1.0.0"), not "latest"
  4. Remote URLs:
    • Production: HTTPS, publicly accessible, actual MCP endpoints
    • Local testing: Can use placeholders when validation is disabled
  5. Transport Types:
    • Packages: stdio
    • Remotes: streamable-http (recommended) or sse

Publishing Script

Use the provided script:

.\scripts\publish-servers.ps1

# With custom registry URL
.\scripts\publish-servers.ps1 -RegistryUrl "https://my-registry.local"

# Skip verification
.\scripts\publish-servers.ps1 -SkipVerification

Manual Publishing

# 1. Get token
$authResp = curl -s https://mcp-registry.local/v0/auth/none -X POST | ConvertFrom-Json
$token = $authResp.registry_token

# 2. Publish server
$headers = @{"Authorization" = "Bearer $token"}
$body = Get-Content .\servers\my-server.json -Raw
Invoke-RestMethod -Uri "https://mcp-registry.local/v0.1/publish" `
    -Method POST `
    -ContentType "application/json" `
    -Headers $headers `
    -Body $body

Server Management

Listing Servers

# List all servers
curl https://mcp-registry.local/v0.1/servers | ConvertFrom-Json | 
    Select-Object -ExpandProperty servers |
    Select-Object @{N='name';E={$_.server.name}}, 
                  @{N='version';E={$_.server.version}}, 
                  @{N='title';E={$_.server.title}} |
    Format-Table -AutoSize

# Get specific server
curl "https://mcp-registry.local/v0.1/servers?name=io.modelcontextprotocol.anonymous/my-server"

# Get specific version
curl "https://mcp-registry.local/v0.1/servers?name=io.modelcontextprotocol.anonymous/my-server&version=1.0.0"

Unpublishing Servers

Use the unpublish script:

# Unpublish specific server version
.\scripts\unpublish-servers.ps1 -ServerName "io.modelcontextprotocol.anonymous/my-server" -Version "1.0.0"

# Unpublish all versions of a server
.\scripts\unpublish-servers.ps1 -ServerName "io.modelcontextprotocol.anonymous/my-server" -AllVersions

# Unpublish ALL servers in namespace (requires 'yes' confirmation)
.\scripts\unpublish-servers.ps1 -UnpublishAll

# Unpublish ALL servers without confirmation (DANGEROUS - use with caution!)
.\scripts\unpublish-servers.ps1 -UnpublishAll -SkipConfirmation

⚠️ Warning: The -UnpublishAll option directly deletes ALL servers from the PostgreSQL database. Always verify what will be deleted before confirming!

Version Management

Best Practices:

  • Use semantic versioning: MAJOR.MINOR.PATCH
  • Increment version for each publish
  • Cannot publish duplicate versions (by design)
  • All versions are preserved in history

Example progression:

0.0.1 → Initial development
0.0.2 → Bug fix
0.1.0 → New features
1.0.0 → Stable release

Troubleshooting

Common Issues

Issue: HTTP 401 Unauthorized

Symptoms:

{"title":"Unauthorized","status":401,"detail":"Invalid Authorization header format"}

Cause: Token not properly extracted from auth response

Solution: Use correct field name registry_token (snake_case):

$authResp = curl -s https://mcp-registry.local/v0/auth/none -X POST | ConvertFrom-Json
$token = $authResp.registry_token  # NOT $authResp.registryToken

Issue: HTTP 400 Duplicate Version

Symptoms:

{"title":"Bad Request","detail":"invalid version: cannot publish duplicate version"}

Cause: Server with that name and version already exists

Solution: Increment the version number in your server.json

Issue: HTTP 403 Forbidden

Symptoms:

{"title":"Forbidden","detail":"Permission denied for namespace"}

Cause: Server name doesn't start with io.modelcontextprotocol.anonymous/

Solution: Fix the namespace:

{
  "name": "io.modelcontextprotocol.anonymous/my-server"  // ✅ Correct
  // NOT: "com.example/my-server"  // ❌ Wrong for anonymous auth
}

Issue: Anonymous Auth Endpoint Returns 404

Symptoms:

404 Not Found when calling /v0/auth/none

Cause: MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH not set to "true"

Solution:

# Verify setting
kubectl -n mcp-registry get deploy mcp-registry -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH")].value}'

# If not "true", edit k8s/mcp-registry.yaml and reapply
kubectl apply -f .\k8s\mcp-registry.yaml
kubectl -n mcp-registry rollout restart deployment/mcp-registry

Issue: Wrong Schema Version

Symptoms:

{"title":"Bad Request","detail":"schema version https://... is not supported"}

Cause: Using URL instead of version string

Solution: Use string version:

{
  "$schema": "2025-10-17"  // ✅ Correct
  // NOT: "$schema": "https://example.com/schema.json"  // ❌ Wrong
}

Issue: Remote URL Validation Fails

Symptoms:

{"title":"Bad Request","detail":"Remote URL not accessible or invalid"}

Causes:

  1. Using httpbin.org or other non-MCP endpoints with validation enabled
  2. URL not publicly accessible
  3. URL doesn't implement MCP protocol

Solutions:

  1. For local testing: Ensure validation is disabled

    kubectl -n mcp-registry get deploy mcp-registry -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION")].value}'
    # Should return: false
  2. For production: Use actual MCP server endpoint

    {
      "remotes": [{
        "type": "streamable-http",
        "url": "https://your-actual-mcp-server.com/mcp"
      }]
    }

Debugging Commands

View Registry Logs:

$podName = kubectl -n mcp-registry get pods -l app=mcp-registry -o jsonpath='{.items[0].metadata.name}'
kubectl -n mcp-registry logs $podName --tail=100 --follow

Check Database:

$pgPod = kubectl -n mcp-registry get pods -l app=postgres -o jsonpath='{.items[0].metadata.name}'
kubectl -n mcp-registry exec -it $pgPod -- psql -U postgres -d mcp_registry

# Inside psql:
\dt                           # List tables
SELECT * FROM servers;        # View servers
SELECT * FROM versions;       # View versions
\q                            # Exit

Port Forward (Bypass Ingress):

$podName = kubectl -n mcp-registry get pods -l app=mcp-registry -o jsonpath='{.items[0].metadata.name}'
kubectl -n mcp-registry port-forward $podName 8080:8080

# Test: curl http://localhost:8080/v0.1/servers

Check Ingress:

kubectl get ingress -n mcp-registry
kubectl describe ingress mcp-registry -n mcp-registry

Production Migration

⚠️ Anonymous auth is NOT available on the official registry (registry.modelcontextprotocol.io)

Steps for Production Publishing

  1. Install mcp-publisher CLI

    # Download from: https://github.com/modelcontextprotocol/registry/releases
  2. Publish Actual Package

    # npm
    npm publish
    
    # PyPI
    poetry publish
    
    # Docker
    docker push myorg/my-server:1.0.0
  3. Add Validation Metadata

    For npm (package.json):

    {
      "name": "my-mcp-server",
      "mcpName": "io.github.username/my-server"
    }

    For PyPI (README.md):

    mcp-name: io.github.username/my-server

    For Docker (Dockerfile):

    LABEL io.modelcontextprotocol.server.name="io.github.username/my-server"
  4. Authenticate

    # GitHub OAuth (interactive)
    mcp-publisher login github
    
    # GitHub OIDC (CI/CD)
    mcp-publisher login github-oidc
    
    # DNS verification (custom domain)
    mcp-publisher login dns --domain=example.com
  5. Update Namespace

    {
      "name": "io.github.username/my-server"  // For GitHub auth
      // OR
      "name": "com.example/my-server"  // For DNS auth
    }
  6. Publish to Official Registry

    mcp-publisher publish

Key Differences: Local vs Production

Aspect Local Development Production
Authentication Anonymous (/v0/auth/none) GitHub/DNS/HTTP
Namespace io.modelcontextprotocol.anonymous/* io.github.username/* or com.example/*
Validation Disabled (fake packages OK) Enabled (real packages required)
Registry URL https://mcp-registry.local https://registry.modelcontextprotocol.io
Publishing Tool PowerShell + curl mcp-publisher CLI
Package Metadata Not required mcpName field required
Remote URLs Placeholders OK Must be real MCP endpoints

Resources

Official Documentation

Repository Structure

mcp-registry/
├── k8s/                    # Kubernetes manifests
│   ├── mcp-registry.yaml  # Main registry deployment
│   └── mcp-remote.yaml    # Remote configuration
├── scripts/               # Automation scripts
│   ├── setup-local-ssl.ps1        # Create/export local Root CA
│   ├── trust-local-ca-windows.ps1 # Trust CA on Windows + Node system CA
│   ├── deploy-all.ps1             # One-command deploy
│   ├── publish-servers.ps1        # Publish server definitions
│   └── unpublish-servers.ps1      # Unpublish via direct DB delete (dangerous)
├── servers/               # Server definitions
│   ├── time-local.server.json           # Example package-based server
│   ├── mcp-devtools-remote.server.json  # Remote DevTools server
│   └── mcp-devtools/                    # DevTools server source
│       ├── src/server.py                # Python MCP server
│       ├── Dockerfile                   # Container build
│       └── k8s/deployment.yaml          # Kubernetes deployment
└── README.md              # This file

Schema Versions

  • Current: 2025-10-17 (working in this setup)
  • Latest Official: 2025-12-11 (check GitHub for updates)

Quick Reference

Essential Commands

# Deploy
kubectl apply -f .\k8s\mcp-registry.yaml

# Check Status
kubectl get pods -n mcp-registry
curl https://mcp-registry.local/v0.1/health

# Get Token
$token = (curl -s https://mcp-registry.local/v0/auth/none -X POST | ConvertFrom-Json).registry_token

# List Servers
curl https://mcp-registry.local/v0.1/servers | ConvertFrom-Json | Select-Object -ExpandProperty servers

# Publish Server
.\scripts\publish-servers.ps1

# Unpublish Server
.\scripts\unpublish-servers.ps1 -ServerName "io.modelcontextprotocol.anonymous/my-server" -Version "1.0.0"

# View Logs
kubectl -n mcp-registry logs -l app=mcp-registry --follow

# Restart Registry
kubectl -n mcp-registry rollout restart deployment/mcp-registry

# Clean Up
kubectl delete namespace mcp-registry

Note: This setup is for local development and testing only. For production deployment to the official MCP Registry, follow the production migration guide above.

About

Local Kubernetes deployment for MCP Registry with PostgreSQL database and web UI

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published