Skip to content

MCP Server Catalog

🆕 Introduced in v0.7.0: The MCP Server Catalog feature allows you to define a catalog of pre-configured MCP servers in a YAML file for easy discovery, registration, and management via the Admin UI and API.

Overview

The MCP Server Catalog provides a declarative way to define and manage MCP servers, reducing manual configuration and enabling automated server registration and health monitoring.

Key Features:

  • 📝 Declarative Configuration: Define servers in YAML format
  • 🔍 Automatic Discovery: Servers are automatically registered on startup
  • 💚 Health Monitoring: Automatic health checks for catalog servers
  • 🗂️ Categorization: Organize servers with tags and descriptions
  • ⚡ Fast Onboarding: Quickly add new MCP servers without API calls

Configuration

Environment Variables

Configure the catalog feature using these environment variables:

# Enable MCP server catalog feature (default: true)
MCPGATEWAY_CATALOG_ENABLED=true

# Path to catalog configuration file (default: mcp-catalog.yml)
MCPGATEWAY_CATALOG_FILE=mcp-catalog.yml

# Automatically health check catalog servers (default: true)
MCPGATEWAY_CATALOG_AUTO_HEALTH_CHECK=true

# Catalog cache TTL in seconds (default: 3600)
MCPGATEWAY_CATALOG_CACHE_TTL=3600

# Number of catalog servers to display per page (default: 12)
MCPGATEWAY_CATALOG_PAGE_SIZE=12

Catalog File Format

The catalog file uses YAML format with the following structure:

Basic Example

# mcp-catalog.yml
catalog_servers:

  - id: "time-server"
    name: "Time Server"
    category: "Utilities"
    url: "http://localhost:9000/sse"
    auth_type: "Open"
    provider: "Local"
    description: "Fast time server providing current time utilities"
    requires_api_key: false
    tags:

      - "utilities"
      - "time"
      - "development"

  - id: "git-server"
    name: "Git Server"
    category: "Software Development"
    url: "http://localhost:9001/sse"
    auth_type: "Open"
    provider: "Local"
    description: "Git repository MCP server"
    requires_api_key: false
    tags:

      - "git"
      - "version-control"
      - "development"

# Optional: Categories for UI filtering
categories:

  - Utilities
  - Software Development

# Optional: Auth types for UI filtering
auth_types:

  - Open
  - OAuth2.1
  - API Key

Full Example with All Fields

# Production MCP Server Catalog
catalog_servers:

  - id: "production-time-server"
    name: "Production Time Server"
    category: "Utilities"
    url: "https://time.api.example.com/sse"
    transport: "SSE"  # Optional: Explicitly specify transport type
    auth_type: "OAuth2.1"
    provider: "Internal Platform"
    description: "Production time server with geo-replication"
    requires_api_key: false
    secure: true
    tags:

      - "production"
      - "utilities"
      - "time"
      - "geo-replicated"
    logo_url: "https://static.example.com/time-server-logo.png"
    documentation_url: "https://docs.example.com/time-server"

  - id: "websocket-server"
    name: "WebSocket MCP Server"
    category: "Development Tools"
    url: "wss://api.example.com/mcp"
    transport: "WEBSOCKET"  # DEPRECATED: sunsets 2027-01-18
    auth_type: "API Key"
    provider: "Internal Platform"
    description: "Real-time MCP server using WebSocket protocol"
    requires_api_key: true
    secure: true
    tags:

      - "production"
      - "websocket"
      - "real-time"

  - id: "database-server"
    name: "Database Server"
    category: "Database"
    url: "https://db.api.example.com/sse"
    auth_type: "OAuth2.1"
    provider: "Internal Platform"
    description: "Database query and management MCP server"
    requires_api_key: false
    secure: true
    tags:

      - "production"
      - "database"
      - "postgresql"
    documentation_url: "https://docs.example.com/db-server"

  - id: "github-api"
    name: "GitHub"
    category: "Software Development"
    url: "https://api.githubcopilot.com/mcp"
    auth_type: "OAuth2.1"
    provider: "GitHub"
    description: "Version control and collaborative software development"
    requires_api_key: false
    secure: true
    tags:

      - "development"
      - "git"
      - "version-control"
    logo_url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
    documentation_url: "https://docs.github.com"

categories:

  - Utilities
  - Database
  - Software Development

auth_types:

  - OAuth2.1
  - API Key
  - Open

Bundled Icon Maintenance

Catalog entries with a bundled logo use a local path such as /static/catalog-icons/github.png. These assets are generated by maintainers before release; gateway requests never fetch remote icon URLs.

To reduce excess transparent padding in existing bundled assets without any network access, run:

python scripts/fetch_catalog_icons.py --normalize-existing

The command crops visible content, upscales source artwork by at most 8×, and centers it on a 128px transparent canvas. It skips missing assets and reports fully transparent assets as unresolved. Do not combine it with --force, which refreshes existing assets from remote candidates.

Catalog ids on the scale_boost list in scripts/catalog_icon_overrides.json get one further, deliberate step: some source artwork carries a lot of visual weight in a small area of its own bounding box (a thin-stroked mark, say), so even a full-bleed crop still reads smaller than its peers at tile size. For these ids, normalization zooms in past the natural fit and crops the overflow, the same way CSS background-size: cover does. Only add an id here after checking the result does not clip legible content (a wordmark's outer letters, for instance) — it is not a substitute for strip_pale_backdrop on artwork that is merely padded.

The scale_shrink list is the opposite adjustment: some source artwork fills its full bounding box on every side (no padding to trim) but still reads visually heavier than its peers at tile size once every other icon has breathing room. These ids render at SCALE_SHRINK_FACTOR of the natural fit instead, leaving margin on all sides — the safe direction, since shrinking never clips content the way scale_boost can.

SVG override sources are rasterized via resvg-py before normalization, so --force reproduces them like any raster source (linear, supabase, and the microsoft-*/azure-*/google-* overrides all resolve this way). One exception remains:

  • neon: its favicon SVG sets fill through a <style> rule (including a prefers-color-scheme variant), which resvg does not evaluate, so a fresh fetch renders blank. The bundled PNG was hand-rendered once, offline, from a variant with the fill on the path itself, and neon is on the skip list in scripts/catalog_icon_overrides.json so --force does not overwrite it with the blank render.
  • tweetsave: the only brand asset is a wide icon+wordmark lockup (https://tweetsave.org/logoType.webp), not a standalone icon. The bundled PNG was hand-cropped to the mark once, offline, and tweetsave is on the skip list so --force does not overwrite it with the full lockup (wordmark included) squeezed into a square canvas.
  • rube, waystation: both upstream sites (rube.app, waystation.ai) were unreachable when the assets were bundled (DNS failure and a disabled Vercel deployment, respectively). Both PNGs were rendered once, offline, from a Wayback Machine snapshot of the site's own favicon/logo asset, and both ids are on the skip list so --force does not fail trying to reach the live, currently-down source.
  • zip1: the source badge is a near-full-bleed rounded square whose only pale pixels are the four small corner triangles outside the rounding — the perimeter majority _strip_pale_backdrop looks for is the badge's own dark fill, not those corners, so it correctly declines to touch it (a majority vote is not the right tool for a minority pale region). The corners were cleared once, offline, by flood-filling from each corner pixel independently instead. zip1.io was also unreachable when this was bundled, so zip1 is on the skip list so --force does not fail trying to reach it or, if it comes back with the same source asset, reintroduce the corners.
  • metro-mcp: unlike the other entries here, the bundled PNG is currently reproducible — metro-mcp.anuragd.me/favicon.ico fed through the ordinary strip_pale_backdrop sweep reproduces it byte for byte. But that endpoint is a single maintainer's personal deployment with no stability guarantee, not a brand's own domain, so metro-mcp is on the skip list to keep a future favicon change on that site from silently replacing the catalog icon via --force.

Re-derive these by hand if their upstream source changes; do not expect --force to pick them up automatically.


Field Reference

Root Fields

Field Type Required Description
catalog_servers array Yes List of MCP server definitions
categories array No List of available categories for UI filtering
auth_types array No List of available auth types for UI filtering

Catalog Server Fields

Based on the CatalogServer schema (schemas.py:5371-5387):

Field Type Required Description
id string Yes Unique identifier for the catalog server
name string Yes Display name of the server
category string Yes Server category (e.g., "Project Management", "Software Development")
url string Yes Server endpoint URL
auth_type string Yes Authentication type (e.g., "OAuth2.1", "API Key", "Open")
provider string Yes Provider/vendor name
description string Yes Server description
requires_api_key boolean No Whether API key is required (default: false)
secure boolean No Whether additional security is required (default: false)
tags array No Tags for categorization (default: [])
transport string No Transport type: SSE, STREAMABLEHTTP, or WEBSOCKET (auto-detected if not specified). The WEBSOCKET value is deprecated. It sunsets on 2027-01-18.
logo_url string No URL to server logo/icon
documentation_url string No URL to server documentation
is_registered boolean No Whether server is already registered (set by system)
is_available boolean No Whether server is currently available (default: true)

Usage

Automatic Registration on Startup

When MCPGATEWAY_CATALOG_ENABLED=true, the gateway automatically:

  1. Reads the catalog file on startup
  2. Registers all enabled servers
  3. Starts health checks (if enabled)
  4. Makes servers available via the Admin UI and API

Manual Catalog Reload

Reload the catalog without restarting the gateway:

# Using the CLI
mcpgateway catalog reload

# Or via API
curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:4444/admin/catalog/reload

Listing Catalog Servers

# Via CLI
mcpgateway catalog list

# Via API
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:4444/admin/catalog/servers

Registering a Catalog Server (API)

Registration requires both servers.create and gateways.create (a catalog registration creates a federated gateway, the same capability as POST /v1/gateways). Admin bypass is disabled: platform admins need the permissions granted through their roles.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"My Asana\", \"api_key\": \"$ASANA_API_KEY\"}" \
  http://localhost:4444/v1/catalog/asana/register

The body is optional; name and api_key override the catalog defaults. Responses: 404 unknown catalog id (or catalog feature disabled), 409 already registered, 200 with success: false and a descriptive message for connectivity or auth failures, 200 with success: true on registration (OAuth servers register disabled until configured).

For catalog entries with an OAuth auth_type, submit oauth_credentials instead of api_key. Whatever isn't supplied - client ID/secret, token/authorization URLs - is filled in later, automatically where the provider supports discovery/DCR, via the OAuth authorization flow:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"oauth_credentials": {"issuer": "https://github.com", "scopes": ["repo", "read:user"]}}' \
  http://localhost:4444/v1/catalog/github/register

oauth_credentials accepts issuer, client_id, client_secret, token_url, authorization_url, redirect_uri, audience, scopes, and resource; any other key is silently ignored. grant_type is always forced to authorization_code regardless of what is submitted - it cannot be overridden through this endpoint, including for the privileged token-exchange grant. Each string value is capped at 4096 characters. For an "OAuth2.1 & API Key" entry, submitting api_key and oauth_credentials together registers the gateway with the API key only; oauth_credentials is ignored (logged) rather than persisted, since a gateway can't correctly carry both an active bearer auth and an unused OAuth config at once - switch to OAuth explicitly via PUT /gateways/{id} afterward if needed.

The admin endpoint POST /admin/mcp-registry/{server_id}/register accepts the same oauth_credentials field with the same validation.

Visibility and ownership

Catalog registrations default to private when visibility is omitted. To create a team-scoped gateway, send visibility: "team" together with a team_id; the authenticated caller must be an active member of that team. Public visibility for a team-scoped registration is rejected when ALLOW_PUBLIC_VISIBILITY=false.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"team","team_id":"TEAM_ID"}' \
  http://localhost:4444/v1/catalog/asana/register

Changing these defaults does not rewrite existing catalog gateways. Review existing registrations before relying on their visibility or ownership.

Administrators can transfer a gateway and its linked tools, resources, and prompts with:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_owner_email":"owner@example.com","target_team_id":"TEAM_ID"}' \
  http://localhost:4444/admin/gateways/GATEWAY_ID/transfer-ownership

The target user must be active. When target_team_id is supplied, the target user must also be an active member of that team. Private gateways and linked resources become team-visible when assigned to a team. Missing gateways return 404; invalid targets or team membership return 400; insufficient permission returns 403.

Filtering by Tags

# List all production servers
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:4444/admin/catalog/servers?tag=production"

# List all database servers
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:4444/admin/catalog/servers?tag=database"

Best Practices

1. Use Consistent Naming

Use clear, descriptive IDs and names:

catalog_servers:

  - id: "github-production"  # ✅ Good: Clear and unique
    name: "GitHub Production"
    # ... other fields

2. Organize with Tags

Use consistent tagging for easier filtering and management:

catalog_servers:

  - id: "prod-db-server"
    name: "Production Database"
    category: "Database"
    tags:

      - "production"      # Environment
      - "postgresql"      # Technology
      - "critical"        # Priority

3. Categorize Clearly

Use standard categories that match your organization:

categories:

  - "Software Development"
  - "Database"
  - "Productivity"
  - "Project Management"

4. Document Server Metadata

Include logo and documentation URLs for better UX:

catalog_servers:

  - id: "time-server"
    name: "Time Server"
    description: "Provides current time utilities with geo-replication"
    documentation_url: "https://docs.example.com/time-server"
    logo_url: "https://static.example.com/logos/time-server.png"

Examples

Development Environment

# mcp-catalog.dev.yml
catalog_servers:

  - id: "local-time"
    name: "Local Time Server"
    category: "Utilities"
    url: "http://localhost:9000/sse"
    auth_type: "Open"
    provider: "Local"
    description: "Local development time server"
    requires_api_key: false
    tags: ["dev", "utilities", "time"]

  - id: "local-git"
    name: "Local Git Server"
    category: "Software Development"
    url: "http://localhost:9001/sse"
    auth_type: "Open"
    provider: "Local"
    description: "Local Git MCP server"
    requires_api_key: false
    tags: ["dev", "git", "version-control"]

categories:

  - "Utilities"
  - "Software Development"

auth_types:

  - "Open"

Production Environment

# mcp-catalog.prod.yml
catalog_servers:

  - id: "prod-time-api"
    name: "Production Time API"
    category: "Utilities"
    url: "https://time.api.example.com/sse"
    auth_type: "OAuth2.1"
    provider: "Platform Engineering"
    description: "Production time API with geo-replication and high availability"
    requires_api_key: false
    secure: true
    tags: ["production", "critical", "utilities"]
    documentation_url: "https://docs.example.com/time-api"

  - id: "prod-database-api"
    name: "Production Database API"
    category: "Database"
    url: "https://db.api.example.com/sse"
    auth_type: "OAuth2.1"
    provider: "Platform Engineering"
    description: "Production PostgreSQL database API with RBAC"
    requires_api_key: false
    secure: true
    tags: ["production", "critical", "database", "postgresql"]
    documentation_url: "https://docs.example.com/db-api"

  - id: "stripe-payments"
    name: "Stripe Payments"
    category: "Payments"
    url: "https://mcp.stripe.com/"
    auth_type: "API Key"
    provider: "Stripe"
    description: "Payment processing and financial infrastructure"
    requires_api_key: true
    secure: true
    tags: ["production", "payments", "finance"]
    logo_url: "https://stripe.com/img/v3/home/social.png"
    documentation_url: "https://stripe.com/docs"

categories:

  - "Utilities"
  - "Database"
  - "Payments"

auth_types:

  - "OAuth2.1"
  - "API Key"

Troubleshooting

Catalog File Not Loading

Symptoms: Servers from catalog don't appear in the Admin UI

Solutions:

  1. Check that catalog is enabled:

    echo $MCPGATEWAY_CATALOG_ENABLED  # Should be "true"
    

  2. Verify catalog file path:

    ls -la mcp-catalog.yml  # Or your configured path
    

  3. Check gateway logs for parsing errors:

    docker logs mcpgateway | grep -i catalog
    

  4. Validate YAML syntax:

    python3 -c "import yaml; yaml.safe_load(open('mcp-catalog.yml'))"
    

Servers Not Appearing in Catalog

Symptoms: Catalog servers don't appear in the Admin UI

Solutions:

  1. Verify server URLs are accessible:

    curl -v http://localhost:9000/sse
    

  2. Check server entry has all required fields:

    catalog_servers:
    
      - id: "my-server"          # Required
        name: "My Server"        # Required
        category: "Utilities"    # Required
        url: "http://..."        # Required
        auth_type: "Open"        # Required
        provider: "MyProvider"   # Required
        description: "..."       # Required
    

  3. Validate YAML syntax:

    python3 -c "import yaml; print(yaml.safe_load(open('mcp-catalog.yml')))"
    

Authentication Errors

Symptoms: 401/403 errors when accessing catalog servers after registration

Solutions:

  1. Verify the auth_type matches the server's requirements:

  2. "Open" - No authentication required

  3. "API Key" - Requires API key (set requires_api_key: true)
  4. "OAuth2.1" - Requires OAuth configuration

  5. For OAuth servers, ensure you complete the OAuth flow after registration via the Admin UI

  6. For API Key servers, provide the API key during registration

Transport Type Issues

Symptoms: WebSocket servers fail to connect after registration

Solutions:

  1. Explicitly specify the transport field in your catalog YAML:

    catalog_servers:
    
      - id: "websocket-server"
        url: "wss://api.example.com/mcp"
        transport: "WEBSOCKET"  # Explicitly set transport
    

  2. Verify URL scheme matches transport type:

  3. WebSocket: ws:// or wss://

  4. SSE: http:// or https:// with /sse path
  5. HTTP: http:// or https:// with /mcp path

Recent Improvements (introduced in v0.9.0)

Enhanced UI Features

The catalog UI now includes several UX improvements:

  • 🔄 Refresh Button: Manually refresh the catalog without page reload
  • 🔍 Debounced Search: 300ms debounce on search input for better performance
  • 📝 Custom Server Names: Ability to specify custom names when registering servers
  • 📄 Pagination with Filters: Filter parameters preserved when navigating pages
  • ⚡ Better Error Messages: User-friendly error messages for common issues (connection, auth, SSL, etc.)
  • 🔐 OAuth Support: OAuth servers can be registered without credentials and configured later

Transport Type Detection

The catalog now supports:

  • Explicit Transport: Specify transport field in catalog YAML (SSE, WEBSOCKET, STREAMABLEHTTP)
  • Auto-Detection: Automatically detects transport from URL if not specified

  • ws:// or wss:// → WEBSOCKET

  • URLs ending in /sse → SSE
  • URLs with /mcp path → STREAMABLEHTTP
  • Default fallback → SSE

Authentication Improvements

  • Custom Auth Headers: Properly mapped as list of header key-value pairs
  • OAuth Registration: OAuth servers can be registered in "disabled" state until OAuth flow is completed
  • API Key Modal: Enhanced modal with custom name field and proper authorization headers

See Also