agentic-community / agentic-community/mcp-gateway-registry

Standardize MCP Server Schema to Official Anthropic Format

Abierto
#264 0 comentarios 1 reacción 1 asignado Reclamado por @aarora79 Ver en GitHub
enhancement
Lenguaje dominante
Python
Estrellas
912
Forks
234
Merge medio
1 d 11 h
PR fusionados (30 d)
62

Descripción

## 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:
```json
{
"server_name": "Current Time API",
"description": "...",
"path": "/currenttime/",
"proxy_pass_url": "http://server:8000/",
"tool_list": [...]
}
```

To unified MCP-extended format:
```json
{
"$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

- 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:
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)
```json
{
"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)
```json
{
"$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[]`)

```json
{
"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)

```json
{
"$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)

```json
{
"$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)

```json
{
"$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:

```python
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

```python
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

```json
{
"$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:
- [x] `server_name` -> `name`
- [x] `description` -> `description`
- [x] `path` -> `_meta...path`
- [x] `proxy_pass_url` -> `remotes[0].url`
- [x] `supported_transports` -> `remotes[].type`
- [x] `auth_type` -> `_meta...authType`
- [x] `auth_provider` -> `_meta...authProvider`
- [x] `tags` -> `_meta...tags`
- [x] `num_tools` -> computed from `toolList.length`
- [x] `num_stars` -> `_meta...numStars`
- [x] `is_python` -> `_meta...isPython`
- [x] `license` -> `_meta...license`
- [x] `headers` -> `_meta...headers`
- [x] `tool_list` -> `_meta...toolList`
- [x] `tool_list[].name` -> `toolList[].name`
- [x] `tool_list[].parsed_description` -> `toolList[].parsedDescription`
- [x] `tool_list[].parsed_description.main` -> preserved
- [x] `tool_list[].parsed_description.args` -> preserved
- [x] `tool_list[].parsed_description.returns` -> preserved
- [x] `tool_list[].parsed_description.raises` -> preserved
- [x] `tool_list[].schema` -> `toolList[].inputSchema`

New fields from MCP standard:
- [x] `$schema` - schema validation URL
- [x] `version` - semantic version (REQUIRED)
- [x] `repository` - source code metadata
- [x] `websiteUrl` - homepage/docs
- [x] `packages` - distribution info
- [x] `_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

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.