Adds an optional `subfolder` field to the Repository object in server.json schema to support MCP servers located within subdirectories of monorepo structures. ## Motivation and Context Addresses issue #360 . The current MCP registry schema doesn't explicitly define how to reference servers that are located within a subdirectory of a Git repository (a "monorepo" structure). This is a common pattern (e.g., [microsoft/mcp](https://github.com/microsoft/mcp) & [awslabs/mcp](https://github.com/awslabs/mcp). ## How Has This Been Tested? - **Validator tests**: Added 6 new test cases covering valid subfolders and security validation (path traversal, absolute paths, invalid characters, etc.) - **Schema validation**: All 11 examples in `examples.md` validate successfully against both `server.schema.json` and `registry-schema.json` - **End-to-end validation**: Publisher tool (`mcp-publisher init`) works correctly and generates valid templates without subfolder - **Backward compatibility**: Existing server.json files without subfolder continue to work unchanged ## Breaking Changes **None.** This is a fully backward-compatible change: - The `subfolder` field is optional (uses `omitempty` JSON tag) - Existing server.json files work unchanged - Publisher tools generate valid templates without subfolder by default - All existing validation and schemas remain compatible ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ### Implementation Details **Schema Changes:** - Added `subfolder` property to Repository definition in `server.schema.json` and `openapi.yaml` - Field is optional with description and example (`"src/everything"`) **Go Model:** - Added `Subfolder string` field to Repository struct with `json:"subfolder,omitempty"` tag - Maintains backward compatibility through omitempty behavior **Security-Focused Validation:** - Validates subfolder is a clean relative path (no leading `/`) - Prevents path traversal attacks (no `..` sequences) - Blocks malformed paths (no trailing `/`, empty segments, invalid characters) - Uses regex validation for allowed characters: `^[a-zA-Z0-9\-_./]+$` **Documentation:** - Added monorepo example to `examples.md` demonstrating subfolder usage - Updated `repository_references.md` with concise subfolder documentation - Maintained consistency across all documentation with same example path **Testing:** - 6 new integration tests in validator covering security and format validation - Updated example count validation tool for new example - All existing tests continue to pass ### Design Decisions 1. **Optional by design**: Most servers don't need subfolder (single-repo pattern is common) 2. **Clean separation**: Repository URL stays clean, subfolder is separate metadata 3. **Security first**: Comprehensive validation prevents common path-based attacks 4. **Publisher tool unchanged**: Default templates don't include subfolder to avoid confusion for typical use cases This enables publishers with monorepo structures to clearly specify server locations while maintaining simplicity for the common single-repository case.
467 lines
No EOL
15 KiB
JSON
467 lines
No EOL
15 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
"title": "MCP Server Detail",
|
|
"$ref": "#/$defs/ServerDetail",
|
|
"$defs": {
|
|
"Repository": {
|
|
"type": "object",
|
|
"required": [
|
|
"url",
|
|
"source"
|
|
],
|
|
"properties": {
|
|
"url": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"example": "https://github.com/modelcontextprotocol/servers"
|
|
},
|
|
"source": {
|
|
"type": "string",
|
|
"description": "Repository hosting service",
|
|
"example": "github"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"example": "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9"
|
|
},
|
|
"subfolder": {
|
|
"type": "string",
|
|
"description": "Optional relative path from repository root to the server location within a monorepo or nested package structure",
|
|
"example": "src/everything"
|
|
}
|
|
}
|
|
},
|
|
"VersionDetail": {
|
|
"type": "object",
|
|
"description": "Version information for this server. Defined as an object to allow for downstream extensibility (e.g. release_date)",
|
|
"required": [
|
|
"version"
|
|
],
|
|
"properties": {
|
|
"version": {
|
|
"type": "string",
|
|
"maxLength": 255,
|
|
"example": "1.0.2",
|
|
"description": "Version string for this server. SHOULD follow semantic versioning (e.g., '1.0.2', '2.1.0-alpha'). Equivalent of Implementation.version in MCP specification. Non-semantic versions are allowed but may not sort predictably."
|
|
}
|
|
}
|
|
},
|
|
"Server": {
|
|
"type": "object",
|
|
"required": [
|
|
"name",
|
|
"description",
|
|
"version_detail"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Server name/identifier",
|
|
"example": "io.modelcontextprotocol/filesystem",
|
|
"minLength": 1,
|
|
"maxLength": 200
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Human-readable description of the server's functionality",
|
|
"example": "Node.js server implementing Model Context Protocol (MCP) for filesystem operations.",
|
|
"minLength": 1,
|
|
"maxLength": 100
|
|
},
|
|
"status": {
|
|
"type": "string",
|
|
"enum": ["active", "deprecated", "deleted"],
|
|
"default": "active",
|
|
"description": "Server lifecycle status. 'deprecated' indicates the server is no longer recommended for new usage. 'deleted' indicates the server should never be installed and existing installations should be uninstalled - this is rare, and usually indicates malware or a legal takedown."
|
|
},
|
|
"repository": {
|
|
"$ref": "#/$defs/Repository"
|
|
},
|
|
"version_detail": {
|
|
"$ref": "#/$defs/VersionDetail"
|
|
}
|
|
}
|
|
},
|
|
"Package": {
|
|
"type": "object",
|
|
"properties": {
|
|
"registry_type": {
|
|
"type": "string",
|
|
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
|
|
"examples": ["npm", "pypi", "oci", "nuget", "mcpb"]
|
|
},
|
|
"registry_base_url": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Base URL of the package registry",
|
|
"examples": ["https://registry.npmjs.org", "https://pypi.org", "https://docker.io", "https://api.nuget.org", "https://github.com", "https://gitlab.com"]
|
|
},
|
|
"identifier": {
|
|
"type": "string",
|
|
"description": "Package identifier - either a package name (for registries) or URL (for direct downloads)",
|
|
"examples": ["@modelcontextprotocol/server-brave-search", "https://github.com/example/releases/download/v1.0.0/package.mcpb"]
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"description": "Package version",
|
|
"example": "1.0.2",
|
|
"minLength": 1
|
|
},
|
|
"file_sha256": {
|
|
"type": "string",
|
|
"description": "SHA-256 hash of the package file for integrity verification.",
|
|
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
|
|
},
|
|
"runtime_hint": {
|
|
"type": "string",
|
|
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtime_arguments` are present.",
|
|
"examples": [
|
|
"npx",
|
|
"uvx",
|
|
"docker",
|
|
"dnx"
|
|
]
|
|
},
|
|
"transport": {
|
|
"anyOf": [
|
|
{
|
|
"$ref": "#/$defs/StdioTransport"
|
|
},
|
|
{
|
|
"$ref": "#/$defs/StreamableHttpTransport"
|
|
},
|
|
{
|
|
"$ref": "#/$defs/SseTransport"
|
|
}
|
|
],
|
|
"description": "Transport protocol configuration for the package"
|
|
},
|
|
"runtime_arguments": {
|
|
"type": "array",
|
|
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtime_hint` field should be provided when `runtime_arguments` are present.",
|
|
"items": {
|
|
"$ref": "#/$defs/Argument"
|
|
}
|
|
},
|
|
"package_arguments": {
|
|
"type": "array",
|
|
"description": "A list of arguments to be passed to the package's binary.",
|
|
"items": {
|
|
"$ref": "#/$defs/Argument"
|
|
}
|
|
},
|
|
"environment_variables": {
|
|
"type": "array",
|
|
"description": "A mapping of environment variables to be set when running the package.",
|
|
"items": {
|
|
"$ref": "#/$defs/KeyValueInput"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"Input": {
|
|
"type": "object",
|
|
"properties": {
|
|
"description": {
|
|
"description": "A description of the input, which clients can use to provide context to the user.",
|
|
"type": "string"
|
|
},
|
|
"is_required": {
|
|
"type": "boolean",
|
|
"default": false
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"description": "Specifies the input format. Supported values include `filepath`, which should be interpreted as a file on the user's filesystem.\n\nWhen the input is converted to a string, booleans should be represented by the strings \"true\" and \"false\", and numbers should be represented as decimal values.",
|
|
"enum": [
|
|
"string",
|
|
"number",
|
|
"boolean",
|
|
"filepath"
|
|
],
|
|
"default": "string"
|
|
},
|
|
"value": {
|
|
"type": "string",
|
|
"description": "The default value for the input. If this is not set, the user may be prompted to provide a value. If a value is set, it should not be configurable by end users.\n\nIdentifiers wrapped in `{curly_braces}` will be replaced with the corresponding properties from the input `variables` map. If an identifier in braces is not found in `variables`, or if `variables` is not provided, the `{curly_braces}` substring should remain unchanged.\n"
|
|
},
|
|
"is_secret": {
|
|
"type": "boolean",
|
|
"description": "Indicates whether the input is a secret value (e.g., password, token). If true, clients should handle the value securely.",
|
|
"default": false
|
|
},
|
|
"default": {
|
|
"type": "string",
|
|
"description": "The default value for the input."
|
|
},
|
|
"choices": {
|
|
"type": "array",
|
|
"description": "A list of possible values for the input. If provided, the user must select one of these values.",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"example": []
|
|
}
|
|
}
|
|
},
|
|
"InputWithVariables": {
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/$defs/Input"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"variables": {
|
|
"type": "object",
|
|
"description": "A map of variable names to their values. Keys in the input `value` that are wrapped in `{curly_braces}` will be replaced with the corresponding variable values.",
|
|
"additionalProperties": {
|
|
"$ref": "#/$defs/Input"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"PositionalArgument": {
|
|
"description": "A positional input is a value inserted verbatim into the command line.",
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/$defs/InputWithVariables"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"required": [
|
|
"type"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"enum": [
|
|
"positional"
|
|
],
|
|
"example": "positional"
|
|
},
|
|
"value_hint": {
|
|
"type": "string",
|
|
"description": "An identifier-like hint for the value. This is not part of the command line, but can be used by client configuration and to provide hints to users.",
|
|
"example": "file_path"
|
|
},
|
|
"is_repeated": {
|
|
"type": "boolean",
|
|
"description": "Whether the argument can be repeated multiple times in the command line.",
|
|
"default": false
|
|
}
|
|
},
|
|
"anyOf": [
|
|
{
|
|
"required": [
|
|
"value_hint"
|
|
]
|
|
},
|
|
{
|
|
"required": [
|
|
"value"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"NamedArgument": {
|
|
"description": "A command-line `--flag={value}`.",
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/$defs/InputWithVariables"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"required": [
|
|
"type",
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"enum": [
|
|
"named"
|
|
],
|
|
"example": "named"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "The flag name, including any leading dashes.",
|
|
"example": "--port"
|
|
},
|
|
"is_repeated": {
|
|
"type": "boolean",
|
|
"description": "Whether the argument can be repeated multiple times.",
|
|
"default": false
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"KeyValueInput": {
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/$defs/InputWithVariables"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"required": [
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Name of the header or environment variable.",
|
|
"example": "SOME_VARIABLE"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"Argument": {
|
|
"anyOf": [
|
|
{
|
|
"$ref": "#/$defs/PositionalArgument"
|
|
},
|
|
{
|
|
"$ref": "#/$defs/NamedArgument"
|
|
}
|
|
]
|
|
},
|
|
"StdioTransport": {
|
|
"type": "object",
|
|
"required": [
|
|
"type"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"enum": [
|
|
"stdio"
|
|
],
|
|
"description": "Transport type",
|
|
"example": "stdio"
|
|
}
|
|
}
|
|
},
|
|
"StreamableHttpTransport": {
|
|
"type": "object",
|
|
"required": [
|
|
"type",
|
|
"url"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"enum": [
|
|
"streamable-http"
|
|
],
|
|
"description": "Transport type",
|
|
"example": "streamable-http"
|
|
},
|
|
"url": {
|
|
"type": "string",
|
|
"description": "URL template for the streamable-http transport. Variables in {curly_braces} reference argument value_hints, argument names, or environment variable names. After variable substitution, this should produce a valid URI.",
|
|
"example": "https://api.example.com/mcp"
|
|
},
|
|
"headers": {
|
|
"type": "array",
|
|
"description": "HTTP headers to include",
|
|
"items": {
|
|
"$ref": "#/$defs/KeyValueInput"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"SseTransport": {
|
|
"type": "object",
|
|
"required": [
|
|
"type",
|
|
"url"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"enum": [
|
|
"sse"
|
|
],
|
|
"description": "Transport type",
|
|
"example": "sse"
|
|
},
|
|
"url": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Server-Sent Events endpoint URL",
|
|
"example": "https://mcp-fs.example.com/sse"
|
|
},
|
|
"headers": {
|
|
"type": "array",
|
|
"description": "HTTP headers to include",
|
|
"items": {
|
|
"$ref": "#/$defs/KeyValueInput"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ServerDetail": {
|
|
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
|
|
"allOf": [
|
|
{
|
|
"$ref": "#/$defs/Server"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"$schema": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "JSON Schema URI for this server.json format",
|
|
"example": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json"
|
|
},
|
|
"packages": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/Package"
|
|
}
|
|
},
|
|
"remotes": {
|
|
"type": "array",
|
|
"items": {
|
|
"anyOf": [
|
|
{
|
|
"$ref": "#/$defs/StreamableHttpTransport"
|
|
},
|
|
{
|
|
"$ref": "#/$defs/SseTransport"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"_meta": {
|
|
"type": "object",
|
|
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"publisher": {
|
|
"type": "object",
|
|
"description": "Publisher-provided metadata for downstream registries",
|
|
"additionalProperties": true
|
|
},
|
|
"io.modelcontextprotocol.registry": {
|
|
"type": "object",
|
|
"description": "Official MCP registry metadata (read-only, added by registry)",
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
} |