-
Notifications
You must be signed in to change notification settings - Fork 61
Description
Summary
Adopt the official Anthropic MCP Server Schema as our unified server configuration format, extending it with gateway-specific fields via the _meta extension point. Use a one-time migration approach with no backward compatibility.
Motivation
- Interoperability: Enable importing servers directly from the official MCP registry (https://registry.modelcontextprotocol.io)
- Single Schema: Eliminate dual maintenance by merging our homegrown schema into the MCP standard
- UI Enhancement: Display rich "Server Cards" with all MCP metadata when users click the info icon
- Ecosystem Alignment: Follow the industry standard for MCP server definitions
- Simplicity: One-time migration means no adapter layer or dual-format code
Proposed Changes
Schema Migration
Migrate from our current format:
{
"server_name": "Current Time API",
"description": "...",
"path": "/currenttime/",
"proxy_pass_url": "http://server:8000/",
"tool_list": [...]
}To unified MCP-extended format:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.mcpgateway/currenttime",
"description": "...",
"version": "1.0.0",
"remotes": [{"type": "streamable-http", "url": "http://server:8000/"}],
"_meta": {
"io.mcpgateway/registry": {
"path": "/currenttime/",
"authType": "none",
"toolList": [...]
}
}
}Key Design Decisions
- Single File Per Server: All metadata and tools in one JSON file
- Standard Fields at Top Level:
name,description,version,remotes,repository - Gateway Extensions in
_meta: Our custom fields (path,authType,toolList, etc.) under_meta.io.mcpgateway/registry - No Fields Lost: Complete mapping of all existing fields (see design doc)
Implementation Plan
Phase 1: Create Migration Script
- Create
registry/utils/schema_migrator.py - Test with dry-run on existing servers
Phase 2: Run One-Time Migration
- Run migration with backups
- Verify all files converted correctly
Phase 3: Update Backend
- Create Pydantic models for new schema
- Update
server_service.pyto use new models - Remove old schema handling code
Phase 4: Update Frontend
- Create TypeScript types for new schema
- Update components to use new field names
- Create Server Card modal component
Phase 5: Add Import Feature
- Create
registry/utils/registry_fetcher.py - Add CLI command for importing servers
Field Mapping Summary
| Current | New Location |
|---|---|
server_name |
name |
proxy_pass_url |
remotes[0].url |
supported_transports |
remotes[].type |
path, auth_type, tags, etc. |
_meta.io.mcpgateway/registry.* |
tool_list |
_meta.io.mcpgateway/registry.toolList |
New fields from MCP: version, repository, websiteUrl, packages
References
- MCP Server Schema: https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json
- Official Registry API: https://registry.modelcontextprotocol.io/v0/servers
Labels
enhancement, schema, mcp-standard
Full Design Document
Click to expand full design document
MCP Server Schema Standardization Design
Created: 2025-11-30
Updated: 2025-11-30 - Revised to single unified schema
Overview
Standardize MCP server configurations by extending the official Anthropic MCP Server Schema with our gateway-specific fields. This enables:
- Import of servers from the official MCP registry
- Display of "Server Cards" in the UI
- Single unified schema - no dual maintenance
Design Principle: Single Unified Schema
Instead of maintaining two separate schemas (cards + tools), we extend the official MCP schema by:
- Using standard MCP fields for metadata
- Adding
tool_listinside_metafor our runtime tool definitions - One file per server - everything in one place
Current vs Proposed Schema
Current (Homegrown)
{
"server_name": "Current Time API",
"description": "A simple API that returns the current server time",
"path": "/currenttime/",
"proxy_pass_url": "http://currenttime-server:8000/",
"auth_type": "none",
"tags": [],
"num_tools": 1,
"num_stars": 0,
"is_python": true,
"license": "MIT-0",
"supported_transports": ["streamable-http"],
"tool_list": [...]
}Proposed (Unified MCP-Extended)
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.mcpgateway/currenttime",
"description": "A simple API that returns the current server time",
"version": "1.0.0",
"repository": {
"url": "https://github.com/agentic-community/mcp-gateway-registry",
"source": "github",
"subfolder": "mcp-servers/currenttime"
},
"remotes": [
{
"type": "streamable-http",
"url": "http://currenttime-server:8000/"
}
],
"_meta": {
"io.mcpgateway/registry": {
"path": "/currenttime/",
"authType": "none",
"tags": ["time", "utility"],
"numStars": 0,
"isPython": true,
"license": "MIT-0",
"source": "internal",
"toolList": [
{
"name": "current_time_by_timezone",
"description": "Get the current time for a specified timezone",
"inputSchema": {
"type": "object",
"properties": {
"tz_name": {
"type": "string",
"default": "America/New_York",
"description": "Name of the timezone"
}
},
"required": ["tz_name"]
}
}
]
}
}
}Unified Schema Structure
Standard MCP Fields (Top Level)
| Field | Type | Required | Description |
|---|---|---|---|
$schema |
string | No | Schema URL for validation |
name |
string | Yes | Reverse-DNS format: namespace/server-name |
description |
string | Yes | Human-readable description (max 100 chars) |
version |
string | Yes | Semantic version |
repository |
object | No | Source code repository info |
websiteUrl |
string | No | Homepage/documentation URL |
remotes |
array | No | Remote transport endpoints |
packages |
array | No | Package distribution info |
Gateway Extension Fields (_meta.io.mcpgateway/registry)
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Gateway routing path (e.g., /currenttime/) |
authType |
string | Yes | Authentication: none, api_key, oauth |
authProvider |
string | No | OAuth provider: keycloak, etc. |
tags |
array | No | Categorization tags |
numStars |
number | No | Popularity metric |
isPython |
boolean | No | Python implementation flag |
license |
string | No | License identifier |
source |
string | Yes | Origin: internal, anthropic-registry, smithery |
importedAt |
string | No | ISO timestamp when imported |
headers |
array | No | Custom headers for proxy |
toolList |
array | No | Runtime tool definitions |
Tool Definition Schema (toolList[])
{
"name": "tool_name",
"description": "What the tool does",
"inputSchema": {
"type": "object",
"properties": {...},
"required": [...]
}
}This follows MCP's tool schema format, making it compatible with the protocol.
Field Mapping (Current -> New)
| Current Field | New Location | Notes |
|---|---|---|
server_name |
name |
Convert to reverse-DNS |
description |
description |
Direct mapping |
path |
_meta...path |
Same value |
proxy_pass_url |
remotes[0].url |
Move to standard field |
supported_transports |
remotes[].type |
Move to standard field |
auth_type |
_meta...authType |
Same value |
auth_provider |
_meta...authProvider |
Same value |
tags |
_meta...tags |
Same value |
num_tools |
Computed | Count of toolList |
num_stars |
_meta...numStars |
Same value |
is_python |
_meta...isPython |
Same value |
license |
_meta...license |
Same value |
tool_list |
_meta...toolList |
Restructure to MCP format |
headers |
_meta...headers |
Same value |
| N/A | version |
NEW - add semantic version |
| N/A | repository |
NEW - add repo info |
Example: Complete Server Definitions
Internal Server (Self-Hosted)
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.mcpgateway/currenttime",
"description": "A simple API that returns the current server time in various formats",
"version": "1.0.0",
"repository": {
"url": "https://github.com/agentic-community/mcp-gateway-registry",
"source": "github",
"subfolder": "mcp-servers/currenttime"
},
"packages": [
{
"registryType": "oci",
"identifier": "015469603702.dkr.ecr.us-east-1.amazonaws.com/mcp-gateway-currenttime:latest",
"version": "1.0.0",
"transport": {"type": "stdio"}
}
],
"remotes": [
{
"type": "streamable-http",
"url": "http://currenttime-server:8000/"
}
],
"_meta": {
"io.mcpgateway/registry": {
"path": "/currenttime/",
"authType": "none",
"tags": ["time", "utility", "internal"],
"numStars": 0,
"isPython": true,
"license": "MIT-0",
"source": "internal",
"toolList": [
{
"name": "current_time_by_timezone",
"description": "Get the current time for a specified timezone using the timeapi.io API",
"inputSchema": {
"type": "object",
"properties": {
"tz_name": {
"type": "string",
"default": "America/New_York",
"description": "Name of the timezone for which to find out the current time"
}
},
"required": ["tz_name"]
}
}
]
}
}
}External Server (Imported from Anthropic Registry)
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "ai.exa/exa",
"description": "Fast, intelligent web search and web crawling",
"version": "3.1.0",
"repository": {
"url": "https://github.com/exa-labs/exa-mcp-server",
"source": "github"
},
"remotes": [
{
"type": "streamable-http",
"url": "https://mcp.exa.ai/mcp"
}
],
"_meta": {
"io.modelcontextprotocol.registry/official": {
"status": "active",
"publishedAt": "2025-11-20T18:00:55.174988Z",
"isLatest": true
},
"io.mcpgateway/registry": {
"path": "/exa/",
"authType": "none",
"tags": ["search", "web", "anthropic-registry"],
"source": "anthropic-registry",
"importedAt": "2025-11-30T16:00:00Z",
"toolList": []
}
}
}External Server with OAuth (Smithery)
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "ai.smithery/github",
"description": "Access the GitHub API, enabling file operations, repository management, and search",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://server.smithery.ai/@smithery-ai/github/mcp"
}
],
"_meta": {
"io.mcpgateway/registry": {
"path": "/smithery-github/",
"authType": "oauth",
"authProvider": "keycloak",
"tags": ["github", "smithery", "oauth"],
"source": "smithery",
"importedAt": "2025-11-30T16:00:00Z",
"headers": [
{"Authorization": "Bearer {access_token}"}
],
"toolList": []
}
}
}Import Flow
From Official MCP Registry
1. Fetch: GET https://registry.modelcontextprotocol.io/v0/servers/{name}
2. Extract server object from response
3. Add _meta.io.mcpgateway/registry:
- path: Generate from name (e.g., ai.exa/exa -> /exa/)
- authType: "none" (default)
- tags: [name parts, "anthropic-registry"]
- source: "anthropic-registry"
- importedAt: Current ISO timestamp
- toolList: [] (discover at runtime via /tools endpoint)
4. Save to servers/{sanitized-name}.json
Tool Discovery
For remote servers, tools are discovered dynamically:
- On first access or explicit refresh
- Call server's
/toolsendpoint (MCP protocol) - Update
toolListin the server JSON - Cache with TTL for performance
File Structure
/home/ubuntu/mcp-gateway/servers/
io.mcpgateway-currenttime.json # Internal servers
io.mcpgateway-mcpgw.json
io.mcpgateway-realserverfaketools.json
ai.exa-exa.json # Imported from Anthropic
ai.smithery-github.json # Imported from Smithery
io.cloudflare-docs.json # External remote
server_state.json # Runtime state (unchanged)
Gateway Runtime Behavior
The gateway reads the unified schema and extracts what it needs:
def load_server_config(server_json: dict) -> GatewayConfig:
meta = server_json.get("_meta", {}).get("io.mcpgateway/registry", {})
return GatewayConfig(
name=server_json["name"],
description=server_json["description"],
path=meta["path"],
proxy_url=server_json["remotes"][0]["url"],
transport=server_json["remotes"][0]["type"],
auth_type=meta.get("authType", "none"),
auth_provider=meta.get("authProvider"),
headers=meta.get("headers", []),
tools=meta.get("toolList", [])
)UI Server Card Display
When info icon clicked, display the full MCP card:
+----------------------------------------------------------+
| ai.exa/exa v3.1.0 |
+----------------------------------------------------------+
| Fast, intelligent web search and web crawling |
+----------------------------------------------------------+
| Repository: github.com/exa-labs/exa-mcp-server |
| Transport: streamable-http |
| Endpoint: https://mcp.exa.ai/mcp |
| Auth: none |
+----------------------------------------------------------+
| Tools (3): |
| - exa_search: Search the web intelligently |
| - exa_crawl: Crawl web pages |
| - exa_code: Code context tool |
+----------------------------------------------------------+
| Tags: search, web, anthropic-registry |
| Source: Anthropic Registry |
| Imported: 2025-11-30 |
+----------------------------------------------------------+
Migration Script
def migrate_server(old_json: dict) -> dict:
"""Convert old schema to unified MCP schema."""
# Extract remote URL and transport
proxy_url = old_json.get("proxy_pass_url", "")
transports = old_json.get("supported_transports", ["streamable-http"])
# Convert tool_list to MCP format
tool_list = []
for tool in old_json.get("tool_list", []):
tool_list.append({
"name": tool["name"],
"description": tool.get("parsed_description", {}).get("main", ""),
"inputSchema": tool.get("schema", {})
})
# Generate name in reverse-DNS format
server_name = old_json.get("server_name", "unknown")
name = f"io.mcpgateway/{_slugify(server_name)}"
return {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": name,
"description": old_json.get("description", "")[:100],
"version": "1.0.0",
"remotes": [
{
"type": transports[0] if transports else "streamable-http",
"url": proxy_url
}
],
"_meta": {
"io.mcpgateway/registry": {
"path": old_json.get("path", "/"),
"authType": old_json.get("auth_type", "none"),
"authProvider": old_json.get("auth_provider"),
"tags": old_json.get("tags", []),
"numStars": old_json.get("num_stars", 0),
"isPython": old_json.get("is_python", False),
"license": old_json.get("license", "N/A"),
"source": "migrated",
"headers": old_json.get("headers", []),
"toolList": tool_list
}
}
}Benefits of Unified Approach
- Single Source of Truth: One file per server, no sync issues
- Standard Compliance: Valid MCP schema, can be shared with ecosystem
- Extensible:
_metaallows our custom fields without breaking standard - Import Ready: Can directly import from official registry
- UI Ready: All info needed for server card in one place
- No Dual Maintenance: One schema to maintain and validate
Field Audit: No Fields Lost
Current Schema Fields (All Servers)
| Current Field | New Location | Status |
|---|---|---|
server_name |
name |
MAPPED (renamed to reverse-DNS) |
description |
description |
MAPPED (direct) |
path |
_meta...path |
MAPPED |
proxy_pass_url |
remotes[0].url |
MAPPED |
supported_transports |
remotes[].type |
MAPPED |
auth_type |
_meta...authType |
MAPPED |
auth_provider |
_meta...authProvider |
MAPPED |
tags |
_meta...tags |
MAPPED |
num_tools |
Computed from toolList.length |
COMPUTED |
num_stars |
_meta...numStars |
MAPPED |
is_python |
_meta...isPython |
MAPPED |
license |
_meta...license |
MAPPED |
headers |
_meta...headers |
MAPPED |
tool_list |
_meta...toolList |
MAPPED |
tool_list[].name |
toolList[].name |
MAPPED |
tool_list[].parsed_description |
toolList[].parsedDescription |
MAPPED |
tool_list[].parsed_description.main |
toolList[].parsedDescription.main |
MAPPED |
tool_list[].parsed_description.args |
toolList[].parsedDescription.args |
MAPPED |
tool_list[].parsed_description.returns |
toolList[].parsedDescription.returns |
MAPPED |
tool_list[].parsed_description.raises |
toolList[].parsedDescription.raises |
MAPPED |
tool_list[].schema |
toolList[].inputSchema |
MAPPED (renamed) |
Official MCP Schema Fields
| MCP Field | Status | Notes |
|---|---|---|
$schema |
NEW | Schema URL for validation |
name |
MAPPED | From server_name |
description |
MAPPED | Direct |
version |
NEW | Semantic version (required by MCP) |
repository |
NEW | Source code info |
repository.url |
NEW | Repo URL |
repository.source |
NEW | "github", "gitlab", etc. |
repository.id |
NEW | Repo ID (optional) |
repository.subfolder |
NEW | Monorepo path (optional) |
websiteUrl |
NEW | Homepage/docs URL |
remotes |
MAPPED | From proxy_pass_url + supported_transports |
remotes[].type |
MAPPED | Transport type |
remotes[].url |
MAPPED | Endpoint URL |
remotes[].headers |
NEW | Per-transport headers (optional) |
packages |
NEW | Package distribution (optional) |
packages[].registryType |
NEW | npm, pypi, oci, etc. |
packages[].identifier |
NEW | Package name/URL |
packages[].version |
NEW | Package version |
packages[].transport |
NEW | Transport config |
packages[].environmentVariables |
NEW | Env vars for package |
_meta |
EXTENSION | Vendor extensions |
Complete Unified Schema
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.mcpgateway/server-name",
"description": "Server description",
"version": "1.0.0",
"repository": {
"url": "https://github.com/org/repo",
"source": "github",
"subfolder": "path/to/server"
},
"websiteUrl": "https://example.com",
"remotes": [
{
"type": "streamable-http",
"url": "http://server:8000/"
}
],
"packages": [
{
"registryType": "oci",
"identifier": "registry/image:tag",
"version": "1.0.0",
"transport": {"type": "stdio"}
}
],
"_meta": {
"io.mcpgateway/registry": {
"path": "/server-path/",
"authType": "none",
"authProvider": null,
"tags": ["tag1", "tag2"],
"numStars": 0,
"isPython": true,
"license": "MIT",
"source": "internal",
"importedAt": "2025-11-30T00:00:00Z",
"headers": [
{"Authorization": "Bearer {token}"}
],
"toolList": [
{
"name": "tool_name",
"parsedDescription": {
"main": "What the tool does",
"args": "arg1: description, arg2: description",
"returns": "Return value description",
"raises": "Exception conditions"
},
"inputSchema": {
"type": "object",
"properties": {
"param1": {
"type": "string",
"description": "Parameter description"
}
},
"required": ["param1"]
}
}
]
}
}
}Verification Checklist
Current fields accounted for:
-
server_name->name -
description->description -
path->_meta...path -
proxy_pass_url->remotes[0].url -
supported_transports->remotes[].type -
auth_type->_meta...authType -
auth_provider->_meta...authProvider -
tags->_meta...tags -
num_tools-> computed fromtoolList.length -
num_stars->_meta...numStars -
is_python->_meta...isPython -
license->_meta...license -
headers->_meta...headers -
tool_list->_meta...toolList -
tool_list[].name->toolList[].name -
tool_list[].parsed_description->toolList[].parsedDescription -
tool_list[].parsed_description.main-> preserved -
tool_list[].parsed_description.args-> preserved -
tool_list[].parsed_description.returns-> preserved -
tool_list[].parsed_description.raises-> preserved -
tool_list[].schema->toolList[].inputSchema
New fields from MCP standard:
-
$schema- schema validation URL -
version- semantic version (REQUIRED) -
repository- source code metadata -
websiteUrl- homepage/docs -
packages- distribution info -
_meta- extension point
Open Questions
-
Name Prefix: Use
io.mcpgateway/for internal servers?- Decision: Yes, follows reverse-DNS convention
-
Tool Discovery Timing: When to fetch tools for imported servers?
- Decision: On-demand with caching (TTL 1 hour)
-
Version Updates: How to handle version bumps for imported servers?
- Decision: Manual refresh or periodic sync job
-
Backward Compatibility: Support old format during transition?
- Decision: No - one-time migration, no backward compatibility needed