Skip to content

Standardize MCP Server Schema to Official Anthropic Format #264

@aarora79

Description

@aarora79

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

  1. Single File Per Server: All metadata and tools in one JSON file
  2. Standard Fields at Top Level: name, description, version, remotes, repository
  3. Gateway Extensions in _meta: Our custom fields (path, authType, toolList, etc.) under _meta.io.mcpgateway/registry
  4. 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.py to 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

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:

  1. Import of servers from the official MCP registry
  2. Display of "Server Cards" in the UI
  3. 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:

  1. Using standard MCP fields for metadata
  2. Adding tool_list inside _meta for our runtime tool definitions
  3. 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:

  1. On first access or explicit refresh
  2. Call server's /tools endpoint (MCP protocol)
  3. Update toolList in the server JSON
  4. 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

  1. Single Source of Truth: One file per server, no sync issues
  2. Standard Compliance: Valid MCP schema, can be shared with ecosystem
  3. Extensible: _meta allows our custom fields without breaking standard
  4. Import Ready: Can directly import from official registry
  5. UI Ready: All info needed for server card in one place
  6. 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 from toolList.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

  1. Name Prefix: Use io.mcpgateway/ for internal servers?

    • Decision: Yes, follows reverse-DNS convention
  2. Tool Discovery Timing: When to fetch tools for imported servers?

    • Decision: On-demand with caching (TTL 1 hour)
  3. Version Updates: How to handle version bumps for imported servers?

    • Decision: Manual refresh or periodic sync job
  4. Backward Compatibility: Support old format during transition?

    • Decision: No - one-time migration, no backward compatibility needed

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions