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:
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 aprefers-color-schemevariant), 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, andneonis on theskiplist inscripts/catalog_icon_overrides.jsonso--forcedoes 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, andtweetsaveis on theskiplist so--forcedoes 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 theskiplist so--forcedoes 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_backdroplooks 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.iowas also unreachable when this was bundled, sozip1is on theskiplist so--forcedoes 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.icofed through the ordinarystrip_pale_backdropsweep 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, sometro-mcpis on theskiplist 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:
- Reads the catalog file on startup
- Registers all enabled servers
- Starts health checks (if enabled)
- 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:
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:
-
Check that catalog is enabled:
-
Verify catalog file path:
-
Check gateway logs for parsing errors:
-
Validate YAML syntax:
Servers Not Appearing in Catalog¶
Symptoms: Catalog servers don't appear in the Admin UI
Solutions:
-
Verify server URLs are accessible:
-
Check server entry has all required fields:
-
Validate YAML syntax:
Authentication Errors¶
Symptoms: 401/403 errors when accessing catalog servers after registration
Solutions:
-
Verify the
auth_typematches the server's requirements: -
"Open"- No authentication required "API Key"- Requires API key (setrequires_api_key: true)-
"OAuth2.1"- Requires OAuth configuration -
For OAuth servers, ensure you complete the OAuth flow after registration via the Admin UI
- For API Key servers, provide the API key during registration
Transport Type Issues¶
Symptoms: WebSocket servers fail to connect after registration
Solutions:
-
Explicitly specify the
transportfield in your catalog YAML: -
Verify URL scheme matches transport type:
-
WebSocket:
ws://orwss:// - SSE:
http://orhttps://with/ssepath - HTTP:
http://orhttps://with/mcppath
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
transportfield in catalog YAML (SSE,WEBSOCKET,STREAMABLEHTTP) -
Auto-Detection: Automatically detects transport from URL if not specified
-
ws://orwss://→WEBSOCKET - URLs ending in
/sse→SSE - URLs with
/mcppath →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¶
- Configuration Reference - Complete configuration guide
- SSO Configuration - Authentication and SSO setup
- Export/Import - Bulk operations and data migration
- Observability - Monitoring and tracing