Enhanced server.json validation (phase 1) (#636)

Added schema validation and support for exhaustive and more detailed
validation to existing validators. Added new `mcp-publisher validate`
command. No change to any existing tests or behavior other than the new
command.

## Motivation and Context

Many currently published servers fail schema validation. Of those that
pass, many have semantic/logical errors or other errors that make them
impossible to configure, or their configuration when applied doesn't
generate correct MCP server configuration (per their own documentation).

To address this, we need better tooling to validate servers and to
inform server publishers of errors, issues, and best practices in a way
that is clear and actionable, both during server development and at time
of publishing.

I have started a discussion about the broader topic:
https://github.com/modelcontextprotocol/registry/discussions/635

There is also a fairly detailed document describing the design,
architecture, implementation, future plans, etc. See [Enhanced
Validation Design](docs/explanations/proposed-enhanced-validation.md)

This PR is the first step in the process of improving validation. It
adds schema validation and updates all existing validators to use a new
model that allows them to track context and return rich and exhaustive
results. This has been done in a way that is backward compatible
(ValidateServerJSON is unchanged as are all existing validation unit
tests).

There is a new `mcp-publisher` command called `validate` that is
currenty the only place that schema validation and enhanced
(exhaustive/rich) validation results are exposed.

### Changes

Internal Validation improvements
- Schema validation has been implemented and is available as an option
in validation methods (defaults to off)
- Existing validators track the JSON path (context) of the attribute
they're evaluating, passing it to any child validators
- Existing validators are exhaustive (return all issues, not failing
fast on the first error)
- Existing validators return rich validation results, including:
  - type (schema, semantic, linter)
  - severity (error, warn, info)
  - path (JSON path to server.json element)
  - reference (to the schema element or rule that triggered the result)
  - message (currently the same as previous error message)

A new `validate` command has been added to `mcp-publisher` so publishers
can evaluate their server.json before publishing
- Includes new schema validation and previous semantic validation
- Shows full details of all issues encountered during validation

### Usage

Given a server.json that looks like this:

```json
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
  "name": "io.github.BobDickinson/registry",
  "description": "An MCP server that provides [describe what your server does]",
  "repository": {
    "url": "",
    "source": ""
  },
  "version": "=1.0.0",
  "packages": [
    {
      "registryType": "oci",
      "registryBaseUrl": "https://docker.io",
      "identifier": "registry",
      "version": "1.0.0",
      "transport": {
        "type": "sse"
      },
      "packageArguments": [
        {
          "type": "named",
          "name": "--mode",
          "description": "Operation mode",
          "format": "foo"
        }
      ],
      "environmentVariables": [
        {
          "description": "Your API key for the service",
          "isRequired": true,
          "format": "string",
          "isSecret": true,
          "name": "YOUR_API_KEY"
        }
      ]
    }
  ]
}
```

Run the command: 

`mcp-publisher validate server.json`

Which will produce the output:

```bash
❌ Validation failed with 5 issue(s):

1. [error] packages.0.packageArguments.0.format (schema)
   value must be one of "string", "number", "boolean", "filepath"
   Reference: #/definitions/Input/properties/format/enum from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/packageArguments/items/[#/definitions/Argument]/else/[#/definitions/NamedArgument]/allOf/0/[#/definitions/InputWithVariables]/allOf/0/[#/definitions/Input]/properties/format/enum

2. [error] packages.0.transport (schema)
   missing required fields: 'url'
   Reference: #/definitions/SseTransport/required from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/transport/else/else/[#/definitions/SseTransport]/required

3. [error] repository.url (schema)
   '' has invalid format 'uri'
   Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format

4. [error] version (semantic)
   version must be a specific version, not a range: "=1.0.0"
   Reference: version-looks-like-range

5. [error] packages[0].transport.url (semantic)
   url is required for sse transport type
   Reference: streamable-transport-url-required

Error: validation failed
```

Note in the results above:
- All errors contain the JSON path of the triggering element from
server.json.
- The first three errors are schema errors detected during schema
validation. The `Reference` provided contains an absolute path to the
schema rule that was violated, followed by the full schema path that
enforced that rule (with $ref redirect values substited and enclosed in
square brackets).
- The error messages provided by the existing semantic validators are
exactly the same as the previous error messages those validators
produced.

In the final solution we would remove semantic validators that are
redundant to schema validation (as in number 5 above). We have the
option to use the structured data produced by schema validation to
replace the generic schema validation message with a more descriptive
message if that is an issue (in particular, if the only argument for an
ad-hoc validator is that is produces a better/cleaner message, we would
just move that message creation code to the schema error result
formatter and special case it as opposed to having a redundant
validator).

### What's Next

If this general direction is supported and this PR is accepted, a fast
follow would include:

- Add new validators (infliuenced by observed issues) for both semantic
errors and best practices
- Require passing schema validation (in addition to improved semantic
validation) in order to publish
- Remove existing validation code that is redundant to schema validation
(ensuring error messages remain intelligible)
- Update validation client code and tests to handle the new rich return
types natively

### Issues

Schema validation requires embedding the `server.schema.json` file into
the validators package via `go:embed`, which is restricted from
embedding files outside of the package. For this reason we copy the
schema file into a schema subdir (via a prep target in the makefile) and
we .gitignore it. I tried cleaning up the copy in the make clean target,
but then the linter would complain about the embed target being missing
if it wasn't there during development. I also considered just checking
in the schema file and assuming a separate workflow for updating the
embedded schema file, but it's not clear what that workflow would be or
how it would be maintained. I'm open to a different approach, but the
schema file does need to be embedded somehow for schema validation to
work.

## How Has This Been Tested?
All existing validation tests pass, new tests implemented and all pass.

## Breaking Changes
No breaking changes, no behavior change except new `validate` command

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] 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)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [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

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Bob Dickinson 2026-01-15 14:49:39 -08:00 • committed by GitHub
commit fc88cf0870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 6107 additions and 179 deletions

69
.github/workflows/sync-schema.yml vendored Normal file
View file

@ -0,0 +1,69 @@
name: Sync Schema
on:
workflow_dispatch: # Manual trigger
# TODO: Add daily schedule later
# schedule:
# - cron: '0 2 * * *' # Run daily at 2 AM UTC
permissions:
contents: write
jobs:
sync-schema:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout static repo
uses: actions/checkout@v4
with:
repository: modelcontextprotocol/static
path: static-repo
- name: Sync schemas from static repo
run: |
echo "🔍 Syncing schemas from modelcontextprotocol/static..."
mkdir -p internal/validators/schemas
# Copy all versioned schema files
for dir in static-repo/schemas/*/; do
if [ -f "$dir/server.schema.json" ]; then
version=$(basename "$dir")
# Skip draft directory if it exists
if [ "$version" != "draft" ]; then
output_file="internal/validators/schemas/${version}.json"
if [ ! -f "$output_file" ] || ! cmp -s "$dir/server.schema.json" "$output_file"; then
echo "⬇ Adding/updating ${version}/server.schema.json -> ${version}.json"
cp "$dir/server.schema.json" "$output_file"
else
echo "✓ ${version} is already up to date"
fi
fi
fi
done
echo "✅ Schema sync complete"
- name: Check for changes
id: changes
run: |
# Check for both modified and untracked files
if [ -n "$(git status --porcelain internal/validators/schemas/)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
git status --porcelain internal/validators/schemas/
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "No changes to schemas"
fi
- name: Commit and push changes
if: steps.changes.outputs.changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add internal/validators/schemas/
git commit -m "Sync schemas from modelcontextprotocol/static [skip ci]"
git push

2
.gitignore vendored
View file

@ -12,4 +12,4 @@ validate-schemas
coverage.out
coverage.html
deploy/infra/infra
./registry
registry

View file

@ -58,7 +58,7 @@ func InitCommand() error {
// Create the server structure
server := createServerJSON(
name, description, version, repoURL, repoSource, subfolder,
model.CurrentSchemaURL, name, description, version, repoURL, repoSource, subfolder,
packageType, packageIdentifier, version, envVars,
)
@ -327,7 +327,7 @@ func detectPackageIdentifier(serverName string, packageType string) string {
}
func createServerJSON(
name, description, version, repoURL, repoSource, subfolder,
currentSchema, name, description, version, repoURL, repoSource, subfolder,
packageType, packageIdentifier, packageVersion string,
envVars []model.KeyValueInput,
) apiv0.ServerJSON {
@ -406,7 +406,7 @@ func createServerJSON(
// Create server structure
return apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Schema: currentSchema,
Name: name,
Description: description,
Repository: repo,

View file

@ -12,8 +12,8 @@ import (
"path/filepath"
"strings"
"github.com/modelcontextprotocol/registry/internal/validators"
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
"github.com/modelcontextprotocol/registry/pkg/model"
)
func PublishCommand(args []string) error {
@ -38,15 +38,20 @@ func PublishCommand(args []string) error {
return fmt.Errorf("invalid server.json: %w", err)
}
// Check for deprecated schema and recommend migration
// Allow empty schema (will use default) but reject old schemas
if serverJSON.Schema != "" && !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) {
return fmt.Errorf(`deprecated schema detected: %s.
Migrate to the current schema format for new servers.
📋 Migration checklist: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md#migration-checklist-for-publishers
📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema)
// Validate schema version (non-empty schema, valid schema, and current schema)
// This performs schema version checks without full schema validation
// Note: When we enable full validation, use validators.ValidationAll instead
result, formattedErrorMsg := runValidationAndPrintIssues(&serverJSON, validators.ValidationSchemaVersionOnly)
if !result.Valid {
// Return error after printing (all errors already printed by validateServerJSON)
// Prefer formatted error message for schema validation errors, otherwise use first error
if formattedErrorMsg != "" {
return fmt.Errorf("%s", formattedErrorMsg)
}
if firstErr := result.FirstError(); firstErr != nil {
return firstErr
}
return fmt.Errorf("validation failed")
}
// Load saved token

View file

@ -41,7 +41,7 @@ func TestPublishCommand_DeprecatedSchema(t *testing.T) {
name: "deprecated 2025-07-09 schema should show warning",
schema: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
expectError: true,
errorSubstr: "deprecated schema detected",
errorSubstr: "schema version 2025-07-09 is not the current version",
},
{
name: "current 2025-12-11 schema should pass validation",
@ -54,10 +54,10 @@ func TestPublishCommand_DeprecatedSchema(t *testing.T) {
expectError: false,
},
{
name: "custom schema without 2025-07-09 should pass validation",
name: "custom schema without 2025-07-09 should fail validation",
schema: "https://example.com/custom.schema.json",
expectError: true,
errorSubstr: "deprecated schema detected",
errorSubstr: "failed to extract schema version from URL",
},
}

View file

@ -0,0 +1,162 @@
package commands
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/modelcontextprotocol/registry/internal/validators"
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
"github.com/modelcontextprotocol/registry/pkg/model"
)
// printSchemaValidationErrors prints nicely formatted error messages for schema validation issues
// (empty schema or non-current schema) with migration guidance to stdout.
// Returns the formatted error message string if any schema errors were printed, empty string otherwise.
func printSchemaValidationErrors(result *validators.ValidationResult, serverJSON *apiv0.ServerJSON) string {
currentSchemaURL := model.CurrentSchemaURL
migrationURL := "https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md"
checklistURL := migrationURL + "#migration-checklist-for-publishers"
var formattedMsg strings.Builder
for _, issue := range result.Issues {
switch issue.Reference {
case "schema-field-required":
// Empty/missing schema
_, _ = fmt.Fprintf(os.Stdout, "$schema field is required.\n")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintf(os.Stdout, "Expected current schema: %s\n", currentSchemaURL)
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "Run 'mcp-publisher init' to create a new server.json with the correct schema, or update your existing server.json file.")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintf(os.Stdout, "📋 Migration checklist: %s\n", checklistURL)
_, _ = fmt.Fprintf(os.Stdout, "📖 Full changelog with examples: %s\n", migrationURL)
_, _ = fmt.Fprintln(os.Stdout)
// Build formatted error message
_, _ = fmt.Fprintf(&formattedMsg, "$schema field is required. Expected current schema: %s. 📋 Migration checklist: %s 📖 Full changelog with examples: %s", currentSchemaURL, checklistURL, migrationURL)
return formattedMsg.String() // Only one schema error at a time
case "schema-version-deprecated":
// Non-current schema
if issue.Severity == validators.ValidationIssueSeverityWarning {
// Warning format (for validate command)
_, _ = fmt.Fprintf(os.Stdout, "⚠️ Deprecated schema detected: %s\n", serverJSON.Schema)
} else {
// Error format (for publish command)
_, _ = fmt.Fprintf(os.Stdout, "deprecated schema detected: %s.\n", serverJSON.Schema)
}
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintf(os.Stdout, "Expected current schema: %s\n", currentSchemaURL)
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "Migrate to the current schema format for new servers.")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintf(os.Stdout, "📋 Migration checklist: %s\n", checklistURL)
_, _ = fmt.Fprintf(os.Stdout, "📖 Full changelog with examples: %s\n", migrationURL)
_, _ = fmt.Fprintln(os.Stdout)
// Build formatted error message - include the original issue message for test compatibility
_, _ = fmt.Fprintf(&formattedMsg, "%s. deprecated schema detected: %s. Expected current schema: %s. Migrate to the current schema format for new servers. 📋 Migration checklist: %s 📖 Full changelog with examples: %s", issue.Message, serverJSON.Schema, currentSchemaURL, checklistURL, migrationURL)
return formattedMsg.String() // Only one schema error at a time
case "schema-version-extraction-error":
// Invalid schema URL format - also include migration links for consistency
// Build formatted error message with migration links
_, _ = fmt.Fprintf(&formattedMsg, "%s. 📋 Migration checklist: %s 📖 Full changelog with examples: %s", issue.Message, checklistURL, migrationURL)
return formattedMsg.String()
}
}
return ""
}
// runValidationAndPrintIssues validates the server JSON, prints schema validation errors, and prints all issues.
// Validation failures are always printed (for both validate and publish commands).
// Returns the validation result and a formatted error message string for schema validation errors.
func runValidationAndPrintIssues(serverJSON *apiv0.ServerJSON, opts validators.ValidationOptions) (*validators.ValidationResult, string) {
result := validators.ValidateServerJSON(serverJSON, opts)
// Print schema validation errors/warnings with friendly messages
formattedErrorMsg := printSchemaValidationErrors(result, serverJSON)
if result.Valid {
return result, ""
}
// Print all issues
_, _ = fmt.Fprintf(os.Stdout, "❌ Validation failed with %d issue(s):\n", len(result.Issues))
_, _ = fmt.Fprintln(os.Stdout)
// Track which schema issues we've already printed to avoid duplicates
issueNum := 1
for _, issue := range result.Issues {
// Skip schema issues that were already printed (they're printed by printSchemaValidationErrors above)
if issue.Reference == "schema-field-required" || issue.Reference == "schema-version-deprecated" {
continue
}
// Print other issues normally
_, _ = fmt.Fprintf(os.Stdout, "%d. [%s] %s (%s)\n", issueNum, issue.Severity, issue.Path, issue.Type)
_, _ = fmt.Fprintf(os.Stdout, " %s\n", issue.Message)
if issue.Reference != "" {
_, _ = fmt.Fprintf(os.Stdout, " Reference: %s\n", issue.Reference)
}
_, _ = fmt.Fprintln(os.Stdout)
issueNum++
}
return result, formattedErrorMsg
}
func ValidateCommand(args []string) error {
// Parse arguments
serverFile := "server.json"
for _, arg := range args {
if arg == "--help" || arg == "-h" {
_, _ = fmt.Fprintln(os.Stdout, "Usage: mcp-publisher validate [file]")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "Validate a server.json file without publishing.")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "Arguments:")
_, _ = fmt.Fprintln(os.Stdout, " file Path to server.json file (default: ./server.json)")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "The validate command performs exhaustive validation, reporting all issues at once.")
_, _ = fmt.Fprintln(os.Stdout, "It validates JSON syntax, schema compliance, and semantic rules.")
return nil
}
if !strings.HasPrefix(arg, "-") {
serverFile = arg
}
}
// Read server file
serverData, err := os.ReadFile(serverFile)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("%s not found, please check the file path", serverFile)
}
return fmt.Errorf("failed to read %s: %w", serverFile, err)
}
// Validate JSON
var serverJSON apiv0.ServerJSON
if err := json.Unmarshal(serverData, &serverJSON); err != nil {
return fmt.Errorf("invalid JSON: %w", err)
}
// Run detailed validation (this is the whole point of the validate command)
// Include schema validation for comprehensive validation
// Warn about non-current schemas (don't error, just inform)
result, _ := runValidationAndPrintIssues(&serverJSON, validators.ValidationAll)
if result.Valid {
_, _ = fmt.Fprintln(os.Stdout, "✅ server.json is valid")
return nil
}
return fmt.Errorf("validation failed")
}

View file

@ -43,6 +43,8 @@ func main() {
err = commands.LogoutCommand()
case "publish":
err = commands.PublishCommand(os.Args[2:])
case "validate":
err = commands.ValidateCommand(os.Args[2:])
case "--version", "-v", "version":
log.Printf("mcp-publisher %s (commit: %s, built: %s)", Version, GitCommit, BuildTime)
return
@ -71,6 +73,7 @@ func printUsage() {
_, _ = fmt.Fprintln(os.Stdout, " login Authenticate with the registry")
_, _ = fmt.Fprintln(os.Stdout, " logout Clear saved authentication")
_, _ = fmt.Fprintln(os.Stdout, " publish Publish server.json to the registry")
_, _ = fmt.Fprintln(os.Stdout, " validate Validate server.json without publishing")
_, _ = fmt.Fprintln(os.Stdout)
_, _ = fmt.Fprintln(os.Stdout, "Use 'mcp-publisher <command> --help' for more information about a command.")
}

View file

@ -0,0 +1,912 @@
# Enhanced Server Validation Design
NOTE: This document describes a proposed direction for improving validation of server.json data in the Official Registry. This work is in progress (including open PRs and discussions) in a collaborative process and may change significantly or be abandoned.
## Overview
This document outlines the design for implementing comprehensive server validation in the MCP Registry, due to the following concerns:
- Currently, the MCP Registry project publishes a server.json schema but does not validate servers against it, allowing non-compliant servers to be published.
- There is existing ad-hoc validation that covers some schema compliance, but not all (there are logical errors not identifiable by schema validation and that are not covered by the existing ad hoc validation).
- Many servers that do pass validation do not represent best-practices for published servers.
This design implements a three-tier validation system: **Schema Validation**, **Semantic Validation**, and **Linter Validation**.
## Current State
### Problems with Current Validation
- **No schema validation**: Servers are published without validating against the published schema (and many violate it)
- **Incomplete validation**: Ad hoc validation covers only some schema constraints (many published servers have additional logical errors)
- **Best Practices not indicated**: Many servers that would pass schema and semantic validation do not represent best practices
- **Fail-fast behavior**: Legacy `ValidateServerJSON()` stopped at first error (now replaced with exhaustive validation)
- **No path information**: Errors don't specify where in JSON the problem occurs
## Three-Tier Validation System
### Schema Validation (Primary)
- **Validates against published schema**: Ensures servers comply with the official server.json schema
- **Exhaustive coverage**: Catches all structural and format violations defined in the schema
- **Detailed error references**: Shows exact schema rule locations with specific constraint and full path to constraint
### Semantic Validation (Secondary)
- **Business logic validation**: Validates only constraints not expressible in JSON Schema
- **Registry validation**: Enforce validitiy of registry references (as current)
- **Logical Errors**: Enforce logical consistency: format, choices, variable usage, etc
### Linter Validation (Tertiary)
- **Best practice recommendations**: Security concerns, style guidelines, naming conventions
- **Non-blocking**: Warnings and suggestions, not errors
- **Quality improvements**: Helps developers create better servers
- **Educational**: Teaches best practices for MCP server development
## Implementation Approach
The enhanced validation will be implemented in stages to minimize risk and allow for review and experimentation:
### **Stage 1: Schema Validation and Exhaustive Validation Results (Current)**
- Convert existing validators to use and track context and to return exhaustive results
- Add `mcp-publisher validate` command that performs exhaustive validation with full schema validation
- Implement schema validation with configurable policy for non-current schemas
- Schema version validation consolidated in `schema.go` with policy support (Allow/Warn/Error)
- `mcp-publisher publish` command validates schema version (rejects empty and non-current schemas) but does not perform full schema validation
- API `/v0/publish` endpoint uses `ValidatePublishRequest` which validates schema version and semantic validation, but not full schema validation
- **All callers migrated**: All code now uses `ValidateServerJSON()` with `ValidationOptions` directly; legacy wrapper removed
- **ValidationResult.FirstError()**: Backward compatibility maintained via `FirstError()` method for code expecting error return type
- This allows experimentation and validation of the new model (including schema validation) without impacting production API
### **Future Stages**
- Enable full schema validation in `mcp-publisher publish` command (currently only validates schema version)
- Enable full schema validation in the `/v0/publish` API endpoint (currently only validates schema version via `ValidatePublishRequest`)
- Add `/v0/validate` API endpoint for programmatic validation without publishing (see Validate API Endpoint section below)
- Enhance production code to use full validation results: Update `importer.go` and `validate-examples/main.go` to log all issues instead of just first error
- Build out comprehensive semantic and linter validation rules (with tests)
- Remove redundant manual validators that duplicate schema constraints
- Consider migrating tests to check all validation issues instead of just first error (where appropriate)
## Proposed Design
### Design Goals
1. **Exhaustive Feedback**: Collect all validation issues in a single pass, not just the first error
2. **Precise Location**: Provide exact JSON paths for every validation issue
3. **Structured Output**: Return machine-readable validation results with consistent format
4. **Backward Compatibility**: Use `ValidationResult.FirstError()` for code expecting error return type
5. **Extensible**: Support different validation types (json, schema, semantic, linter) and severity levels
### Core Types
```go
// Validation issue type with constrained values
type ValidationIssueType string
const (
ValidationIssueTypeJSON ValidationIssueType = "json"
ValidationIssueTypeSchema ValidationIssueType = "schema"
ValidationIssueTypeSemantic ValidationIssueType = "semantic"
ValidationIssueTypeLinter ValidationIssueType = "linter"
)
// Validation issue severity with constrained values
type ValidationIssueSeverity string
const (
ValidationIssueSeverityError ValidationIssueSeverity = "error"
ValidationIssueSeverityWarning ValidationIssueSeverity = "warning"
ValidationIssueSeverityInfo ValidationIssueSeverity = "info"
)
type ValidationIssue struct {
Type ValidationIssueType `json:"type"`
Path string `json:"path"` // JSON path like "packages[0].transport.url"
Message string `json:"message"` // Error description (extracted from error.Error())
Severity ValidationIssueSeverity `json:"severity"`
Reference string `json:"reference"` // Schema rule path or rule name like "prefer-transport-configuration"
}
type ValidationResult struct {
Valid bool `json:"valid"`
Issues []ValidationIssue `json:"issues"`
}
type ValidationContext struct {
path string
}
// SchemaVersionPolicy determines how non-current schema versions are handled
type SchemaVersionPolicy string
const (
SchemaVersionPolicyAllow SchemaVersionPolicy = "allow" // Allow non-current schemas silently
SchemaVersionPolicyWarn SchemaVersionPolicy = "warn" // Allow but generate warning
SchemaVersionPolicyError SchemaVersionPolicy = "error" // Reject non-current schemas
)
// Constructor functions following Go conventions
func NewValidationIssue(issueType ValidationIssueType, path, message string, severity ValidationIssueSeverity, reference string) ValidationIssue
func NewValidationIssueFromError(issueType ValidationIssueType, path string, err error, reference string) ValidationIssue
```
### Validation Types
The `Type` field categorizes validation issues by their source:
- **`ValidationIssueTypeJSON`**: JSON parsing errors (malformed JSON syntax)
- **`ValidationIssueTypeSchema`**: JSON Schema validation errors (structural/format violations)
- **`ValidationIssueTypeSemantic`**: Logical validation errors not enforceable in schema (business rules)
- **`ValidationIssueTypeLinter`**: Best practice recommendations, security concerns, style guidelines
The `Severity` field indicates the impact level:
- **`ValidationIssueSeverityError`**: Critical issues that must be fixed
- **`ValidationIssueSeverityWarning`**: Issues that should be addressed
- **`ValidationIssueSeverityInfo`**: Suggestions and recommendations
The `Reference` field provides context about what triggered the validation issue:
- **Schema validation**: Contains the resolved schema path with `$ref` resolution (e.g., `"#/definitions/SseTransport/properties/url/format from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/transport/properties/url/format"`)
- **Semantic validation**: Contains rule names for business logic (e.g., `"invalid-server-name"`, `"missing-transport-url"`)
- **Linter validation**: Contains rule names for best practices (e.g., `"descriptive-naming"`, `"security-recommendation"`)
- **JSON validation**: Contains error type identifiers (e.g., `"json-syntax-error"`, `"invalid-json-format"`)
### ValidationContext
The `ValidationContext` tracks the current JSON path during validation, allowing validators to report issues with precise location information. This is essential for providing users with exact paths to problematic fields.
#### **Purpose**
- **Path tracking**: Builds JSON paths like `"packages[0].transport.url"` as validation traverses nested structures
- **Precise error location**: Users can see exactly where validation issues occur
- **Immutable building**: Each method returns a new context, preventing accidental mutations
#### **Usage Example**
```go
// Navigate to packages array, first item, transport field
pkgCtx := ctx.Field("packages").Index(0).Field("transport")
// Validate transport - any issues will be reported at "packages[0].transport"
```
### Backward Compatibility Strategy
The design maintains perfect backward compatibility by leveraging Go's existing error handling patterns:
#### **Error Message Preservation**
- **Current validators** use `fmt.Errorf("%w: %s", ErrInvalidRepositoryURL, obj.URL)`
- **New validators** use `NewValidationIssueFromError()` which extracts `err.Error()`
- **Result**: Identical error messages, ensuring all existing tests pass
#### **Constructor Pattern**
Following Go conventions used throughout the project:
```go
// Standard constructor for manual field setting
issue := NewValidationIssue(ValidationIssueTypeLinter, "name", "message", ValidationIssueSeverityWarning, "rule-name")
// Constructor that preserves existing error formatting
issue := NewValidationIssueFromError(ValidationIssueTypeSemantic, "path", err, "rule-name")
```
#### **Error Interface Compatibility**
For code that needs an error return type, use `ValidationResult.FirstError()`:
```go
result := ValidateServerJSON(serverJSON, ValidationSchemaVersionAndSemantic)
if err := result.FirstError(); err != nil {
return err // Returns first error-level issue as error
}
```
This maintains compatibility with existing error handling code while providing access to all validation issues.
### New Validation Architecture
#### **All Validators Use Context and Return ValidationResult**
All existing validators are converted to use `ValidationContext` for precise error location tracking and return `ValidationResult` for comprehensive error collection:
```go
func ValidateServerJSON(serverJSON *apiv0.ServerJSON, opts ValidationOptions) *ValidationResult {
result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}}
// Schema validation based on options
if opts.ValidateSchemaVersion || opts.ValidateSchema {
schemaResult := validateServerJSONSchema(serverJSON, opts.ValidateSchema, opts.NonCurrentSchemaPolicy)
result.Merge(schemaResult)
}
// Semantic validation (if requested)
if opts.ValidateSemantic {
// Validate server name - using existing error logic
if _, err := parseServerName(*serverJSON); err != nil {
issue := NewValidationIssueFromError(
ValidationIssueTypeSemantic,
"name",
err,
"invalid-server-name",
)
result.AddIssue(issue)
}
// Validate repository with context
if repoResult := validateRepository(&ValidationContext{}, &serverJSON.Repository); !repoResult.Valid {
result.Merge(repoResult)
}
// ... more semantic validation ...
}
return result
}
```
For backward compatibility with code that expects an error return type, `ValidationResult.FirstError()` can be used:
```go
result := ValidateServerJSON(serverJSON, ValidationSchemaVersionAndSemantic)
if err := result.FirstError(); err != nil {
return err
}
```
## Schema Validation
The project uses `github.com/santhosh-tekuri/jsonschema/v5` for schema validation with an embedded schema approach. The schema is embedded at compile time using Go's `//go:embed` directive, eliminating the need for file system access and ensuring the schema is always available.
### Schema-First Validation Strategy
The enhanced validation system adopts a **schema-first approach** where JSON Schema validation serves as the primary and first validator. This strategy addresses the current duplication between manual/semantic validators and schema constraints.
#### **Current Problem: Validation Duplication**
The existing system has both:
- **Manual/semantic validators**: Custom Go code validating server name format, URL patterns, etc.
- **JSON Schema validation**: Structural validation of the same constraints
This creates redundancy and potential inconsistencies where:
- Manual validators provide friendly error messages
- Schema validation provides technical error messages
- Both validate the same underlying constraints
#### **Proposed Solution: Schema-First with Friendly Error Mapping**
1. **Schema validation runs first** and catches all structural/format issues
2. **Manual validators are eliminated** for constraints already specified in the schema
3. **Schema error messages are mapped to friendly messages** using deterministic schema rule references (if needed)
### Embedded Schema Benefits
#### **No File System Dependencies**
- **Embedded at compile time**: Schema is included in the binary using `//go:embed schema/*.json`
- **No external files**: Eliminates dependency on schema files being present at runtime
- **Portable**: Binary contains everything needed for validation
#### **Version Consistency**
- **Schema version tracking**: `model.CurrentSchemaURL` provides compile-time constant for current schema version
- **Version validation**: Schema version validation consolidated in `schema.go` with policy support (Allow/Warn/Error)
- **Empty schema handling**: Empty/missing schema fields always generate errors during validation
- **Compile-time validation**: Schema is validated when the binary is built
- **No version drift**: Schema version is locked to the binary version
#### **Performance Benefits**
- **No I/O operations**: Schema is already in memory
- **Faster startup**: No need to read schema files
- **Reduced complexity**: No file path resolution or error handling for missing files
### Rich Error Information
The `jsonschema.ValidationError` provides:
- **InstanceLocation**: JSON Pointer format (RFC 6901) path to the invalid field (e.g., `"/packages/0/transport/url"`)
- **Error**: Detailed error message from schema
- **KeywordLocation**: Schema path with $ref segments (e.g., `"/$ref/properties/transport/$ref/properties/url/format"`)
- **AbsoluteKeywordLocation**: Resolved schema path (e.g., `"file:///server.schema.json#/definitions/SseTransport/properties/url/format"`)
**Path Format Conversion**: JSON Pointer format paths from `InstanceLocation` are converted to bracket notation format to match semantic validation paths. The conversion transforms JSON Pointer paths like `"/packages/0/transport/url"` into bracket notation like `"packages[0].transport.url"`. This ensures consistent path formatting across all validation types (schema, semantic, and linter).
#### **Current Error Reference Format**
Schema validation errors now include detailed reference information:
```
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format
```
This format provides:
- **Absolute location**: `#/definitions/Repository/properties/url/format` - the final resolved schema location
- **Resolved path**: Shows the complete path with `$ref` segments replaced by their resolved values in square brackets
- **Full context**: Users can see exactly which schema rule triggered the error and how it was reached
#### **Error Message Quality**
The current schema validation errors are generally quite readable:
```
[error] repository.url (schema)
'' has invalid format 'uri'
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format
```
#### **Future Error Message Enhancement**
If we encounter situations where schema validation errors need to be more user-friendly, we have full access to:
- **`KeywordLocation`**: The schema path to the validating rule
- **`AbsoluteKeywordLocation`**: The absolute schema location after `$ref` resolution
- **`InstanceLocation`**: The JSON Pointer format path (e.g., `"/packages/0/transport/url"`) which is converted to bracket notation (e.g., `"packages[0].transport.url"`) for consistency with semantic validation
- **`Message`**: The original schema validation error message
- **Complete reference stack**: The entire resolved path showing how the error was reached
This allows us to build better, more descriptive error messages if needed, while maintaining the current high-quality error references.
### Integration with ValidateServerJSON
```go
// ValidationOptions configures which types of validation to perform
type ValidationOptions struct {
ValidateSchemaVersion bool // Check schema version (empty, non-current)
ValidateSchema bool // Perform full schema validation (implies ValidateSchemaVersion)
ValidateSemantic bool // Perform semantic validation
NonCurrentSchemaPolicy SchemaVersionPolicy // Policy for non-current schemas
}
// Common validation configurations
var (
ValidationSemanticOnly = ValidationOptions{
ValidateSemantic: true,
}
ValidationSchemaVersionOnly = ValidationOptions{
ValidateSchemaVersion: true,
NonCurrentSchemaPolicy: SchemaVersionPolicyError,
}
ValidationSchemaVersionAndSemantic = ValidationOptions{
ValidateSchemaVersion: true,
ValidateSemantic: true,
NonCurrentSchemaPolicy: SchemaVersionPolicyWarn,
}
ValidationAll = ValidationOptions{
ValidateSchema: true, // Implies ValidateSchemaVersion
ValidateSemantic: true,
NonCurrentSchemaPolicy: SchemaVersionPolicyWarn,
}
)
func ValidateServerJSON(serverJSON *apiv0.ServerJSON, opts ValidationOptions) *ValidationResult {
result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}}
ctx := &ValidationContext{}
// Schema validation (version check and/or full validation)
if opts.ValidateSchemaVersion || opts.ValidateSchema {
schemaResult := validateServerJSONSchema(serverJSON, opts.ValidateSchema, opts.NonCurrentSchemaPolicy)
result.Merge(schemaResult)
}
// Semantic validation (only if requested)
if !opts.ValidateSemantic {
return result
}
// ... semantic validation logic ...
return result
}
```
### Schema Version Validation
Schema version validation is consolidated in `validateServerJSONSchema()` (now private) in `schema.go`:
- **Empty schema check**: Always performed when schema validation is requested, always generates an error
- **Schema file existence check**: Always performed when schema validation is requested - verifies the schema file exists in embedded schemas, even when not performing full validation
- **Schema version policy**: Controls how non-current schemas are handled (via `ValidationOptions.NonCurrentSchemaPolicy`):
- `SchemaVersionPolicyAllow`: Non-current schemas are allowed with no warning
- `SchemaVersionPolicyWarn`: Non-current schemas are allowed but generate a warning
- `SchemaVersionPolicyError`: Non-current schemas are rejected with an error
- **Full schema validation**: Only performed if `performValidation` is `true`
The `mcp-publisher publish` command validates schema version (rejects empty, non-existent, and non-current schemas) but does not perform full schema validation. The `mcp-publisher validate` command performs full schema validation with `SchemaVersionPolicyWarn` (warns about non-current schemas but doesn't error).
### Request Validation Functions
Two consolidated validation functions in `validators` package handle publish and update requests:
- **`ValidatePublishRequest()`**: Validates publisher extensions, server JSON structure (via `ValidateServerJSON`), and registry ownership (if enabled)
- **`ValidateUpdateRequest()`**: Validates server JSON structure (via `ValidateServerJSON`) and registry ownership (if enabled), with option to skip registry validation for deleted servers
Both functions use `ValidateServerJSON()` with `ValidationSchemaVersionAndSemantic` and `FirstError()` for backward-compatible error handling. Registry ownership validation is extracted into a shared `validateRegistryOwnership()` helper function.
### Testing with Draft or Custom Schemas
The validation system supports testing against draft schemas or custom schema versions by embedding them in the validators package.
#### Setup Steps
1. **Copy the schema file**: Copy your schema file (e.g., `docs/reference/server-json/server.schema.json`) to `internal/validators/schemas/{version}.json`
- Example: Copy to `internal/validators/schemas/draft.json` for draft schema testing
- Ensure the schema file's `$id` field matches: `https://static.modelcontextprotocol.io/schemas/{version}/server.schema.json`
- For draft schema, the `$id` should be: `https://static.modelcontextprotocol.io/schemas/draft/server.schema.json`
2. **Rebuild**: Recompile the Go binary to embed the new schema file (schemas are embedded at compile time)
3. **Use in server.json**: Reference the schema version in your `server.json` file:
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json",
...
}
```
#### Schema Version Identifier Rules
Schema version identifiers can contain:
- **Letters**: A-Z, a-z
- **Digits**: 0-9
- **Special characters**: Hyphen (-), underscore (_), tilde (~), period (.)
Examples of valid identifiers: `2025-10-17`, `draft`, `test-v1.0`, `custom_schema~1.2.3`
#### Non-Current Schema Policy
When testing with draft or custom schemas, they will be treated as **non-current** schemas (since they don't match `model.CurrentSchemaURL`), which triggers the `NonCurrentSchemaPolicy` behavior:
- **`SchemaVersionPolicyAllow`**: Draft schemas are allowed with no warning
- **`SchemaVersionPolicyWarn`**: Draft schemas are allowed but generate a warning (default for `ValidationAll` and `ValidationSchemaVersionAndSemantic`)
- **`SchemaVersionPolicyError`**: Draft schemas are rejected with an error (default for `ValidationSchemaVersionOnly`)
#### Treating Draft as Current Schema
To test with a draft schema as if it were the current schema (no warnings/errors about non-current version):
1. Temporarily update `model.CurrentSchemaVersion` in `pkg/model/constants.go`:
```go
const (
CurrentSchemaVersion = "draft" // Temporarily set for testing
CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json"
)
```
2. Rebuild and test
3. **Important**: Revert the change before committing - `model.CurrentSchemaVersion` should always point to the latest official schema version
#### Example: Testing with Draft Schema
```bash
# 1. Copy draft schema
cp docs/reference/server-json/server.schema.json internal/validators/schemas/draft.json
# 2. Verify the $id field in draft.json is correct
# Should be: "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json"
# 3. Rebuild
go build ./...
# 4. Use in server.json
# Set "$schema": "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json"
# 5. Validate
mcp-publisher validate server.json
```
**Note**: The draft schema will be validated successfully, but you may see a warning about it not being the current schema version unless you temporarily update `model.CurrentSchemaVersion` as described above.
### Discriminated Union Error Consolidation
The schema uses `anyOf` for discriminated unions (transport, argument, remote), which causes noisy error messages when validation fails. When a transport/argument/remote doesn't match its specified type, `anyOf` validation tries all variants and reports errors for each one that doesn't match.
**Problem Example**: If you have an "sse" transport with no url, you get errors for all transport types:
1. [error] packages[0].transport.type (schema)
value must be "stdio"
Reference: #/definitions/StdioTransport/properties/type/enum
2. [error] packages[0].transport (schema)
missing required fields: 'url'
Reference: #/definitions/StreamableHttpTransport/required
3. [error] packages[0].transport.type (schema)
value must be "streamable-http"
Reference: #/definitions/StreamableHttpTransport/properties/type/enum
4. [error] packages[0].transport (schema)
missing required fields: 'url'
Reference: #/definitions/SseTransport/required
**Solution Strategy**: Since we cannot modify the schema (it's managed in the static repository), we'll detect and consolidate these `anyOf` error patterns in the validation error processing code (`addDetailedErrors` in `schema.go`).
**Detection Strategy**:
- Identify groups of errors at the same JSON path (e.g., `packages[0].transport`)
- Detect pattern of multiple "type must be X" errors or multiple "missing required fields" errors from different schema definitions
- Extract the actual `type` value from the JSON being validated
- Filter out errors from non-matching transport/argument/remote definitions
- Consolidate remaining errors into a single, actionable error message
**Implementation Approach**:
- Add logic in `addDetailedErrors()` or a post-processing function to detect `anyOf` error clusters
- Group errors by instance location and analyze error patterns
- Identify the intended type from the JSON data
- Filter/consolidate errors to only show relevant issues for the actual type specified
- Preserve all other validation errors unchanged
This approach allows us to provide clearer error messages without modifying the schema, and can be applied to transport, argument, and remote validation.
**Future Enhancement**: If the schema is updated to use `if/then/else` discriminated unions in the future, this consolidation logic can be removed, but it provides immediate value without requiring schema changes.
## Implementation Status
### ✅ Completed Features
#### **Core Validation System**
- [x] **ValidationIssue and ValidationResult types**: Complete with all required fields
- [x] **ValidationContext**: Immutable context building for JSON path tracking
- [x] **Constructor functions**: `NewValidationIssue()` and `NewValidationIssueFromError()` with consistent parameter naming
- [x] **Helper methods**: Context building, result merging, and path construction
#### **Schema Validation Integration**
- [x] **JSON Schema validation**: Using existing `jsonschema/v5` library
- [x] **Error conversion**: Schema errors converted to `ValidationIssue` format
- [x] **$ref resolution**: Sophisticated resolution showing complete schema path with resolved references
- [x] **Comprehensive testing**: Full test coverage for schema validation scenarios
- [x] **Embedded schema**: Schema embedded at compile time using `//go:embed` directive
- [x] **Path format normalization**: JSON Pointer paths converted to bracket notation to match semantic validation format (e.g., `/packages/0/transport` → `packages[0].transport`)
#### **Enhanced Error References**
- [x] **Resolved schema paths**: Shows complete path with `$ref` segments replaced by resolved values
- [x] **Incremental resolution**: Each `$ref` resolved in context of previous resolution
- [x] **Human-readable format**: Clear indication of schema rule location and resolution chain
- [x] **Consistent output**: All schema errors use the same reference format
#### **Testing and Quality**
- [x] **Unit tests**: Comprehensive test coverage for all new functionality
- [x] **Integration tests**: End-to-end validation testing
- [x] **Backward compatibility**: Existing validation continues to work
#### **Caller Migration**
- [x] **Function rename**: `ValidateServerJSONExhaustive` renamed to `ValidateServerJSON` (now takes `ValidationOptions` parameter)
- [x] **Legacy wrapper removed**: Old `ValidateServerJSON()` wrapper that returned `error` removed
- [x] **All callers migrated**: All production code and tests now use `ValidateServerJSON()` with `ValidationOptions` directly
- [x] **FirstError() helper**: `ValidationResult.FirstError()` method added for backward compatibility with error return types
- [x] **Request validators consolidated**: `ValidatePublishRequest` and `ValidateUpdateRequest` moved to validators package with shared `validateRegistryOwnership` helper
### 🔄 In Progress
#### **Schema-First Validation Strategy**
- [x] **Schema validation integration**: `ValidateServerJSON()` runs schema validation first
- [x] **CLI integration**: Schema validation enabled in `mcp-publisher validate` command
- [x] **Schema version validation**: Consolidated in `schema.go` with policy support (Allow/Warn/Error)
- [x] **Schema file existence check**: Schema version validation verifies schema file exists in embedded schemas
- [x] **Publish command schema checks**: `mcp-publisher publish` validates schema version (rejects empty, non-existent, and non-current schemas)
- [x] **API endpoint validation**: `/v0/publish` uses `ValidatePublishRequest` which validates schema version and semantic validation
- [ ] **Full schema validation in publish**: Enable full schema validation in `mcp-publisher publish` command
- [ ] **Full schema validation in API**: Enable full schema validation in `/v0/publish` API endpoint
- [ ] **Discriminated union error consolidation**: Detect and filter/consolidate noisy `anyOf` errors for transport, argument, and remote validation to show only relevant errors for the actual type
- [ ] **Error message mapping**: Map technical schema errors to user-friendly messages (if needed)
- [ ] **Validator migration**: Move from manual validators to schema-first approach
### 📋 Pending
#### **Migration Strategy**
- [ ] **Phase 1: Identify Schema Coverage**: Audit existing manual validators against schema constraints
- [ ] **Phase 2: Implement Error Mapping (Optional)**: Create mapping function for schema error messages (only if current messages are insufficient)
- [ ] **Phase 3: Error Consolidation**: Implement logic to detect and consolidate noisy `anyOf` errors from discriminated unions (transport, argument, remote)
- [ ] **Phase 4: Enable Schema-First Validation**: Update tests to expect schema validation errors instead of semantic errors; Enable schema validation in publish API
- [ ] **Phase 5: Clean Up Redundant Validators**: Remove manual validators that duplicate schema constraints
- [ ] **Phase 6: Add Enhanced Semantic and Linter Rules**: Review and implement specific rules from [MCP Registry Validator linter guidelines](https://github.com/TeamSparkAI/ToolCatalog/blob/main/packages/mcp-registry-validator/linter.md)
#### **Command Integration**
- [x] **CLI updates**: `mcp-publisher validate` command uses detailed validation with full schema validation
- [x] **Publish command**: `mcp-publisher publish` validates schema version (rejects empty, non-existent, and non-current schemas)
- [x] **Shared validation logic**: Both commands use `runValidationAndPrintIssues` to eliminate duplication
- [x] **Caller migration**: All callers migrated to use `ValidateServerJSON()` with `ValidationOptions` directly
- [x] **Request validation consolidation**: `ValidatePublishRequest` and `ValidateUpdateRequest` consolidated in validators package
- [ ] **Enhanced error reporting**: Update production code (importer, validate-examples tool) to log all issues instead of just first error
- [ ] **Output formatting**: Add JSON output format options
- [ ] **Filtering options**: Add severity and type filtering
#### **Validate API Endpoint**
- [ ] **POST /v0/validate endpoint**: API endpoint for validating server.json without publishing
#### **Documentation and Polish**
- [ ] **API documentation**: Update API documentation with new validation types
### 🎯 Key Achievements
1. **Comprehensive Error Collection**: All validation issues collected in single pass
2. **Precise Error Location**: Exact JSON paths for every validation issue
3. **Schema Integration**: Full JSON Schema validation with detailed error references
4. **Backward Compatibility**: Existing validation continues to work unchanged
5. **Type Safety**: Constrained types prevent invalid validation issue creation
6. **Extensible Architecture**: Easy to add new validation types and severity levels
The enhanced validation system is now production-ready with comprehensive schema validation, detailed error references, and full backward compatibility.
## Example Usage
### JSON Output Format
```json
{
"valid": false,
"issues": [
{
"type": "json",
"path": "",
"message": "invalid JSON syntax at line 5, column 12",
"severity": "error",
"reference": "json-syntax-error"
},
{
"type": "semantic",
"path": "name",
"message": "server name must be in format 'dns-namespace/name'",
"severity": "error",
"reference": "invalid-server-name"
},
{
"type": "semantic",
"path": "packages[0].transport.url",
"message": "url is required for streamable-http transport type",
"severity": "error",
"reference": "missing-transport-url"
},
{
"type": "schema",
"path": "packages[1].environmentVariables[0].name",
"message": "string does not match required pattern",
"severity": "error",
"reference": "#/definitions/EnvironmentVariable/properties/name/pattern from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/environmentVariables/items/[#/definitions/EnvironmentVariable]/properties/name/pattern"
},
{
"type": "linter",
"path": "packages[1].description",
"message": "consider adding a more descriptive package description",
"severity": "warning",
"reference": "descriptive-package-description"
}
]
}
```
**Note**: The JSON output still uses string values for `type` and `severity` fields for JSON serialization compatibility, but the Go code uses the typed constants for type safety.
### CLI Usage
```bash
# Basic validation
mcp-publisher validate server.json
# JSON output format
mcp-publisher validate --format json server.json
# Filter by severity
mcp-publisher validate --severity error server.json
# Include schema validation
mcp-publisher validate --schema server.json
```
## Benefits and Achievements
### ✅ Comprehensive Feedback
- **Exhaustive error collection**: See all validation issues at once, not just the first error
- **Better developer experience**: No need to fix errors one by one
- **Precise error location**: JSON paths show exactly where issues occur in large JSON files
- **Structured output**: JSON format for tooling integration and machine-readable error information
### ✅ Schema-First Validation
- **Primary validator**: Schema validation catches all structural and format violations defined in the schema
- **Semantic validation only for gaps**: Covers business logic that cannot be expressed in JSON Schema
- **Standards compliance**: Ensures server.json follows the official schema
- **Detailed error messages**: Exact JSON paths and resolved schema references
### ✅ Backward Compatibility
- **Backward compatibility**: Use `ValidationResult.FirstError()` for code expecting error return type
- **Error interface compatibility**: Leverages Go's error interface and existing error constants
- **Constructor pattern**: Follows established project conventions
- **No breaking changes**: All error handling code remains functional
### ✅ Extensible Architecture
- **Easy to add new validation types**: Schema, semantic, linter validation
- **Easy to add new severity levels**: Error, warning, info
- **Easy to add filtering and formatting options**: By type, severity, path pattern
- **Type safety**: Constrained types prevent invalid validation issue creation
### ✅ Schema-First Strategy Benefits
- **Eliminates duplication**: Single source of truth for structural constraints
- **Better error messages**: Schema validation provides precise JSON paths with deterministic mapping
- **Maintainability**: Schema changes automatically update validation
- **Standards compliance**: Ensures validation matches official schema exactly
## Technical Design
### Architecture Overview
The enhanced validation system uses a **schema-first approach** with comprehensive error collection and precise location tracking. The system is designed for maximum backward compatibility while providing extensive new capabilities.
#### **Error Interface Compatibility**
- **Leverages existing error constants**: `ErrInvalidRepositoryURL`, `ErrVersionLooksLikeRange`, etc.
- **Preserves error wrapping**: Uses `fmt.Errorf("%w: %s", err, context)` pattern
- **Maintains error.Is() compatibility**: Existing error checking continues to work
- **No breaking changes**: All error handling code remains functional
#### **Constructor Pattern**
Following established Go conventions in the project:
- **`NewValidationIssue()`**: Standard constructor following `NewXxx()` pattern
- **`NewValidationIssueFromError()`**: Specialized constructor for error conversion
- **Consistent with project**: Matches patterns used in `NewConfig()`, `NewServer()`, etc.
- **Type safety**: Compile-time validation of required fields
#### **Context Passing Architecture**
- **Immutable context building**: `ctx.Field("name").Index(0)` pattern
- **Clean composition**: Validators focus on validation, not path building
- **Reusable validators**: Same validator can be called with different contexts
- **No global state**: Thread-safe validation with explicit context
#### **Type Safety with Constrained Values**
Following Go best practices used throughout the project:
- **Typed string constants**: `ValidationIssueType`, `ValidationIssueSeverity` prevent invalid values
- **Compile-time validation**: IDE autocomplete and error checking
- **JSON compatibility**: Still serializes as strings for API compatibility
- **Refactoring safety**: Rename constants without breaking code
- **Consistent with project**: Matches patterns used in `Status`, `Format`, `ArgumentType`
### Performance Considerations
- **Slightly slower than fail-fast validation**: Acceptable trade-off for better user experience
- **Memory usage increases with error collection**: Manageable for typical server.json files
- **Schema validation performance**: Embedded schema eliminates I/O operations
### Testing Strategy
- **Unit tests**: Each validator with context
- **Integration tests**: End-to-end validation testing
- **Backward compatibility tests**: Ensure existing code continues to work
- **Performance benchmarks**: Validate acceptable performance characteristics
---
## Appendix: Future Enhancements
### Additional Validation Types
- **Linter rules**: Best practices and style guidelines
- **Warning level**: Non-critical issues
- **Info level**: Suggestions and improvements
### Advanced Features
- **Error filtering**: By type, severity, path pattern
- **Output formatting**: Human-readable, JSON, XML
- **Configuration**: Custom validation rules
- **IDE integration**: Real-time validation feedback
### Tooling Integration
- **WASM package**: Browser-based validation
- **VS Code extension**: Real-time validation
- **CI/CD integration**: Automated validation in pipelines
- **API endpoint**: Validation as a service (see Validate API Endpoint section below)
## Validate API Endpoint
### Overview
A REST API endpoint (`POST /v0/validate`) that validates `server.json` files without publishing them to the registry. This endpoint provides programmatic access to the same validation logic used by the CLI commands, returning structured validation results in JSON format.
### Use Cases
- **CI/CD Pipelines**: Validate server.json files before attempting to publish
- **Editor/IDE Integrations**: Real-time validation feedback in development tools
- **Web UIs**: Validate files in browser-based interfaces
- **Pre-publish Checks**: Validate before authentication/publishing workflow
- **Validation as a Service**: Allow external tools to validate server.json format
### Implementation
#### Endpoint Specification
**Endpoint**: `POST /v0/validate`
**Authentication**: None required (read-only operation)
**Content-Type**: `application/json`
#### Request
Request body should be a valid `ServerJSON` object:
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.example/server",
"version": "1.0.0",
...
}
```
#### Response
Returns a `ValidationResult` in JSON format:
```json
{
"valid": false,
"issues": [
{
"type": "schema",
"path": "packages[0].transport.url",
"message": "missing required field: 'url'",
"severity": "error",
"reference": "#/definitions/SseTransport/required"
},
{
"type": "semantic",
"path": "name",
"message": "server name must be in format 'dns-namespace/name'",
"severity": "error",
"reference": "invalid-server-name"
}
]
}
```
**HTTP Status Codes**:
- `200 OK`: Validation completed successfully (regardless of whether valid or invalid)
- `400 Bad Request`: Malformed JSON or invalid request format
Note: A `200 OK` status does not mean the server.json is valid - check the `valid` field in the response body.
#### Implementation Details
**Location**: `internal/api/handlers/v0/validate.go`
**Handler Function**:
- Accepts `ServerJSON` in request body
- Calls `validators.ValidateServerJSON(serverJSON, validators.ValidationAll)`
- Returns `ValidationResult` as JSON response
- Uses Huma framework (same as publish endpoint) for request/response handling
**Key Differences from Publish Endpoint**:
- No authentication required (read-only)
- Does not save to database
- Returns structured validation results instead of published server response
- Returns warnings, not just errors (useful for comprehensive feedback)
**Reuses Existing Infrastructure**:
- Same validation functions as CLI commands
- Same `ValidationResult` type
- Same issue types and severity levels
- Consistent validation behavior across CLI and API
### Testing Strategy
#### Unit Tests
Test handler function with mocked dependencies:
- Valid server.json → `valid: true, issues: []`
- Invalid server.json → `valid: false` with specific issues
- Schema errors → issues with `type: "schema"`
- Semantic errors → issues with `type: "semantic"`
- Empty schema → `schema-field-required` issue
- Non-current schema → `schema-version-deprecated` issue
- Multiple issues → all issues returned in response
- Malformed JSON → proper error handling
#### Integration Tests
Follow patterns from `publish_integration_test.go`:
- Start test server
- Send HTTP POST requests with various `server.json` payloads
- Assert response JSON matches expected `ValidationResult` structure
- Verify HTTP status codes (200 for valid requests, 400 for malformed)
- Test both valid and invalid inputs
- Reuse test fixtures from `validation_detailed_test.go`
#### Test Infrastructure
- Reuse existing test server setup
- Use same patterns as `test_endpoints.sh` for manual testing
- Leverage existing validation test cases
### Future Enhancements
- **Query Parameters**: Optional parameters to filter by issue type or severity
- **Partial Validation**: Validate specific sections (e.g., only schema, only semantic)
- **Format Options**: Request different output formats (detailed vs. summary)
- **Batch Validation**: Validate multiple server.json files in one request

View file

@ -155,7 +155,7 @@ The `remotes` property can coexist with the `packages` property in `server.json`
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"identifier": "@example/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"

View file

@ -207,6 +207,45 @@ mcp-publisher login none [--registry=URL]
- No authentication - for local testing only
- Only works with local registry instances
### `mcp-publisher validate`
Validate a `server.json` file without publishing.
**Usage:**
```bash
mcp-publisher validate [file]
```
**Arguments:**
- `file` - Path to server.json file (default: `./server.json`)
**Behavior:**
- Performs exhaustive validation, reporting all issues at once (not just the first error)
- Validates JSON syntax and schema compliance
- Runs semantic validation (business logic checks)
- Checks for deprecated schema versions and provides migration guidance
- Includes detailed error locations with JSON paths (e.g., `packages[0].transport.url`)
- Shows validation issue type (json, schema, semantic, linter)
- Displays severity level (error, warning, info)
- Provides schema references showing which validation rule triggered each error
**Example output:**
```bash
$ mcp-publisher validate
✅ server.json is valid
$ mcp-publisher validate custom-server.json
❌ Validation failed with 2 issue(s):
1. [error] repository.url (schema)
'' has invalid format 'uri'
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format
2. [error] name (semantic)
server name must be in format 'dns-namespace/name'
Reference: invalid-server-name
```
### `mcp-publisher publish`
Publish server to the registry.

View file

@ -99,7 +99,10 @@ func readSeedFile(ctx context.Context, path string) ([]*apiv0.ServerJSON, error)
var validationFailures []string
for _, response := range serverResponses {
if err := validators.ValidateServerJSON(&response); err != nil {
// ValidateServerJSON returns all validation results; using FirstError() to preserve existing behavior
// In future, consider logging all issues from result.Issues for better diagnostics
result := validators.ValidateServerJSON(&response, validators.ValidationSchemaVersionAndSemantic)
if err := result.FirstError(); err != nil {
// Log warning and track invalid server instead of failing
invalidServers = append(invalidServers, response.Name)
validationFailures = append(validationFailures, fmt.Sprintf("Server '%s': %v", response.Name, err))

View file

@ -209,7 +209,7 @@ func (s *registryServiceImpl) updateServerInTransaction(ctx context.Context, tx
skipRegistryValidation := currentlyDeleted || beingDeleted
// Validate the request, potentially skipping registry validation for deleted servers
if err := s.validateUpdateRequest(ctx, *req, skipRegistryValidation); err != nil {
if err := validators.ValidateUpdateRequest(ctx, *req, s.cfg, skipRegistryValidation); err != nil {
return nil, err
}
@ -243,25 +243,3 @@ func (s *registryServiceImpl) updateServerInTransaction(ctx context.Context, tx
return updatedServerResponse, nil
}
// validateUpdateRequest validates an update request with optional registry validation skipping
func (s *registryServiceImpl) validateUpdateRequest(ctx context.Context, req apiv0.ServerJSON, skipRegistryValidation bool) error {
// Always validate the server JSON structure
if err := validators.ValidateServerJSON(&req); err != nil {
return err
}
// Skip registry validation if requested (for deleted servers)
if skipRegistryValidation || !s.cfg.EnableRegistryValidation {
return nil
}
// Perform registry validation for all packages
for i, pkg := range req.Packages {
if err := validators.ValidatePackage(ctx, pkg, req.Name); err != nil {
return fmt.Errorf("registry validation failed for package %d (%s): %w", i, pkg.Identifier, err)
}
}
return nil
}

View file

@ -0,0 +1,476 @@
package validators
import (
"bytes"
"embed"
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
"github.com/modelcontextprotocol/registry/pkg/model"
"github.com/santhosh-tekuri/jsonschema/v5"
)
//go:embed schemas/*.json
var schemaFS embed.FS
// extractVersionFromSchemaURL extracts the version identifier from a schema URL
// e.g., "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json" -> "2025-10-17"
// e.g., "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json" -> "draft"
// Version identifier can contain: A-Z, a-z, 0-9, hyphen (-), underscore (_), tilde (~), and period (.)
func extractVersionFromSchemaURL(schemaURL string) (string, error) {
// Pattern: /schemas/{identifier}/server.schema.json
// Identifier allowed characters: A-Z, a-z, 0-9, -, _, ~, .
re := regexp.MustCompile(`/schemas/([A-Za-z0-9_~.-]+)/server\.schema\.json`)
matches := re.FindStringSubmatch(schemaURL)
if len(matches) < 2 {
return "", fmt.Errorf("invalid schema URL format: %s", schemaURL)
}
return matches[1], nil
}
// loadSchemaByVersion loads a schema file from the embedded filesystem by version
func loadSchemaByVersion(version string) ([]byte, error) {
filename := fmt.Sprintf("schemas/%s.json", version)
data, err := schemaFS.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("schema version %s not found in embedded schemas: %w", version, err)
}
return data, nil
}
// GetCurrentSchemaVersion returns the current schema URL from constants
func GetCurrentSchemaVersion() (string, error) {
return model.CurrentSchemaURL, nil
}
// validateServerJSONSchema validates the server JSON against the schema version specified in $schema using jsonschema
// Empty/missing schema always produces an error.
// If performValidation is true, performs full JSON Schema validation.
// If performValidation is false, only checks for empty schema (always an error) and handles non-current schemas per policy.
// nonCurrentPolicy determines how non-current (but valid) schema versions are handled when performValidation is true.
func validateServerJSONSchema(serverJSON *apiv0.ServerJSON, performValidation bool, nonCurrentPolicy SchemaVersionPolicy) *ValidationResult {
result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}}
ctx := &ValidationContext{}
// Empty/missing schema is always an error
if serverJSON.Schema == "" {
issue := NewValidationIssue(
ValidationIssueTypeSemantic,
ctx.Field("schema").String(),
"$schema field is required",
ValidationIssueSeverityError,
"schema-field-required",
)
result.AddIssue(issue)
return result
}
// Extract version from the schema URL
version, err := extractVersionFromSchemaURL(serverJSON.Schema)
if err != nil {
issue := NewValidationIssue(
ValidationIssueTypeSchema,
ctx.Field("schema").String(),
fmt.Sprintf("failed to extract schema version from URL: %v", err),
ValidationIssueSeverityError,
"schema-version-extraction-error",
)
result.AddIssue(issue)
return result
}
// Check if the schema version is the current one and handle based on policy
currentSchemaURL, err := GetCurrentSchemaVersion()
if err == nil && serverJSON.Schema != currentSchemaURL {
// Extract current version for the message
currentVersion, _ := extractVersionFromSchemaURL(currentSchemaURL)
switch nonCurrentPolicy {
case SchemaVersionPolicyError:
issue := NewValidationIssue(
ValidationIssueTypeSemantic,
ctx.Field("schema").String(),
fmt.Sprintf("schema version %s is not the current version (%s). Use the current schema version", version, currentVersion),
ValidationIssueSeverityError,
"schema-version-deprecated",
)
result.AddIssue(issue)
case SchemaVersionPolicyWarn:
issue := NewValidationIssue(
ValidationIssueTypeSemantic,
ctx.Field("schema").String(),
fmt.Sprintf("schema version %s is not the current version (%s). Consider updating to the latest schema version", version, currentVersion),
ValidationIssueSeverityWarning,
"schema-version-deprecated",
)
result.AddIssue(issue)
case SchemaVersionPolicyAllow:
// No issue added - allow non-current schemas silently
}
}
// Load the appropriate schema file to verify it exists (required for schema version validation)
// This ensures that the specified schema version is available, even when not performing full validation
schemaData, err := loadSchemaByVersion(version)
if err != nil {
issue := NewValidationIssue(
ValidationIssueTypeSchema,
ctx.Field("schema").String(),
fmt.Sprintf("schema version %s not available: %v", version, err),
ValidationIssueSeverityError,
"schema-version-not-available",
)
result.AddIssue(issue)
return result
}
// If not performing validation, return after performing schema version checks (done above)
if !performValidation {
return result
}
// Parse the schema
var schema map[string]any
if err := json.Unmarshal(schemaData, &schema); err != nil {
// If we can't parse the schema, return an error
issue := NewValidationIssue(
ValidationIssueTypeSchema,
ctx.Field("schema").String(),
fmt.Sprintf("failed to parse schema file: %v", err),
ValidationIssueSeverityError,
"schema-parse-error",
)
result.AddIssue(issue)
return result
}
// Convert the server JSON to a map for validation
serverData, err := json.Marshal(serverJSON)
if err != nil {
issue := NewValidationIssue(
ValidationIssueTypeJSON,
"",
fmt.Sprintf("failed to marshal server JSON for schema validation: %v", err),
ValidationIssueSeverityError,
"json-marshal-error",
)
result.AddIssue(issue)
return result
}
var serverMap map[string]any
if err := json.Unmarshal(serverData, &serverMap); err != nil {
issue := NewValidationIssue(
ValidationIssueTypeJSON,
"",
fmt.Sprintf("failed to unmarshal server JSON for schema validation: %v", err),
ValidationIssueSeverityError,
"json-unmarshal-error",
)
result.AddIssue(issue)
return result
}
// Get the schema $id for proper reference resolution
// Schema files must have $id (required by JSON Schema spec and verified by sync process)
// However, we check here in case a schema file exists but is malformed or missing $id
schemaID, ok := schema["$id"].(string)
if !ok {
issue := NewValidationIssue(
ValidationIssueTypeSchema,
ctx.Field("schema").String(),
fmt.Sprintf("schema file for version %s exists but is missing or has invalid $id field (required by JSON Schema spec)", version),
ValidationIssueSeverityError,
"schema-missing-id",
)
result.AddIssue(issue)
return result
}
// Validate against schema using jsonschema library
compiler := jsonschema.NewCompiler()
if err := compiler.AddResource(schemaID, bytes.NewReader(schemaData)); err != nil {
// If we can't add the schema resource, return an error
issue := NewValidationIssue(
ValidationIssueTypeSchema,
ctx.Field("schema").String(),
fmt.Sprintf("failed to add schema resource: %v", err),
ValidationIssueSeverityError,
"schema-resource-error",
)
result.AddIssue(issue)
return result
}
schemaInstance, err := compiler.Compile(schemaID)
if err != nil {
// If we can't compile the schema, return an error
issue := NewValidationIssue(
ValidationIssueTypeSchema,
"",
fmt.Sprintf("failed to compile schema: %v", err),
ValidationIssueSeverityError,
"schema-compile-error",
)
result.AddIssue(issue)
return result
}
// Perform validation
if err := schemaInstance.Validate(serverMap); err != nil {
// Convert validation error to our issue format
var validationErr *jsonschema.ValidationError
if errors.As(err, &validationErr) {
// Process the validation error and its causes
addValidationError(result, validationErr, schema)
} else {
// Fallback for other error types
issue := NewValidationIssue(
ValidationIssueTypeSchema,
"",
fmt.Sprintf("schema validation failed: %v", err),
ValidationIssueSeverityError,
"schema-validation-error",
)
result.AddIssue(issue)
}
}
return result
}
// addValidationError processes validation errors and extracts useful information
func addValidationError(result *ValidationResult, validationErr *jsonschema.ValidationError, schema map[string]any) {
// Use DetailedOutput to get the nested error details
detailed := validationErr.DetailedOutput()
// Process the detailed error structure
addDetailedErrors(result, detailed, schema)
}
// ConvertJSONPointerToBracketNotation converts a JSON Pointer path (RFC 6901) to bracket notation
// format to match the format used by semantic validation (ValidationContext).
// The transformation includes:
// 1. Remove leading slash from JSON Pointer format
// 2. Convert path separators from "/" to "."
// 3. Convert numeric array indices from dot notation to bracket notation
// Example: "/packages/0/transport" -> "packages[0].transport"
// Example: "/0/name" -> "[0].name"
// Example: "/packages/0/transport/1/url" -> "packages[0].transport[1].url"
func ConvertJSONPointerToBracketNotation(jsonPointer string) string {
if jsonPointer == "" {
return ""
}
// Step 1: Convert JSON Pointer to dot notation (remove leading slash, convert / to .)
path := strings.TrimPrefix(jsonPointer, "/")
path = strings.ReplaceAll(path, "/", ".")
// Step 2: Convert dot notation array indices to bracket notation
if path == "" {
return ""
}
parts := strings.Split(path, ".")
var result strings.Builder
for i, part := range parts {
// Check if part is a pure number (array index)
if _, err := strconv.Atoi(part); err == nil {
// It's a numeric index - use bracket notation
result.WriteString(fmt.Sprintf("[%s]", part))
// Add dot after bracket if next part exists and is a field name (not a number)
if i < len(parts)-1 {
nextPart := parts[i+1]
if _, err := strconv.Atoi(nextPart); err != nil {
// Next part is a field name, add dot separator
result.WriteString(".")
}
// If next part is a number, no dot needed (brackets will connect: [0][1])
}
} else {
// It's a field name
// Add dot separator before field name if previous part was also a field name
if i > 0 {
prevPart := parts[i-1]
if _, err := strconv.Atoi(prevPart); err != nil {
// Previous was not a number (it's a field), need dot separator
result.WriteString(".")
}
// If previous was a number, brackets already written, dot added after bracket above
}
result.WriteString(part)
}
}
return result.String()
}
// addDetailedErrors recursively processes detailed validation errors
func addDetailedErrors(result *ValidationResult, detailed jsonschema.Detailed, schema map[string]any) {
// Only process errors that have specific field paths and meaningful messages
if detailed.InstanceLocation != "" && detailed.Error != "" {
// Convert JSON Pointer format to bracket notation to match semantic validation format
path := ConvertJSONPointerToBracketNotation(detailed.InstanceLocation)
// Clean up the error message
message := detailed.Error
// Make messages more user-friendly
if strings.Contains(message, "missing properties:") {
message = strings.ReplaceAll(message, "missing properties:", "missing required fields:")
}
if strings.Contains(message, "is not valid") {
message = strings.ReplaceAll(message, "is not valid", "has invalid format")
}
// Build the full resolved reference path
reference := buildResolvedReference(detailed.KeywordLocation, detailed.AbsoluteKeywordLocation, schema)
issue := NewValidationIssue(
ValidationIssueTypeSchema,
path,
message,
ValidationIssueSeverityError,
reference, // cleaned schema rule path for deterministic mapping
)
result.AddIssue(issue)
}
// Process nested errors
for _, nested := range detailed.Errors {
addDetailedErrors(result, nested, schema)
}
}
// buildResolvedReference extracts the resolved reference path by resolving $ref segments
func buildResolvedReference(keywordLocation, absoluteKeywordLocation string, schema map[string]any) string {
if keywordLocation == "" || absoluteKeywordLocation == "" {
return ""
}
// Clean up the absolute location by removing file:// prefix
absolute := absoluteKeywordLocation
if strings.HasPrefix(absolute, "file://") {
absolute = strings.TrimPrefix(absolute, "file://")
if idx := strings.Index(absolute, "#"); idx != -1 {
absolute = absolute[idx:] // Keep only the #/path part
}
}
// Parse the keyword location to understand the $ref chain
keyword := strings.TrimPrefix(keywordLocation, "/")
keywordParts := strings.Split(keyword, "/")
// Build the path showing $ref resolution
pathSegments := make([]string, 0)
// Track the resolved path so far (starts empty, gets built up as we resolve $refs)
resolvedPath := ""
// Process each part of the keyword path
for i, part := range keywordParts {
if part == "" {
continue // Skip empty parts
}
if part == "$ref" {
// This is a $ref - we need to look up what it resolves to
// For the first $ref, use the path from the root
// For subsequent $refs, use the resolved path from the previous $ref plus the current segment
var refPath string
if resolvedPath == "" {
// First $ref - use the path from the root
refPath = strings.Join(keywordParts[:i+1], "/")
refPath = "/" + refPath
} else {
// Subsequent $ref - use the resolved path plus the current segment
refPath = resolvedPath + "/" + part
}
// Look up the $ref value in the schema
refValue := resolveRefInSchema(schema, refPath)
if refValue != "" {
pathSegments = append(pathSegments, fmt.Sprintf("[%s]", refValue))
// Update the resolved path for the next $ref
resolvedPath = refValue
} else {
pathSegments = append(pathSegments, "[$ref]")
}
} else {
// Regular path segment
pathSegments = append(pathSegments, part)
// Add this segment to the resolved path for the next $ref
if resolvedPath != "" {
resolvedPath = resolvedPath + "/" + part
} else {
resolvedPath = part
}
}
}
// Build the final reference string
if len(pathSegments) > 0 {
pathStr := strings.Join(pathSegments, "/")
return fmt.Sprintf("%s from: %s", absolute, pathStr)
}
// Fallback: return the absolute location with context
return absolute + " (from: " + keywordLocation + ")"
}
// resolveRefInSchema looks up a $ref value in the schema
func resolveRefInSchema(schema map[string]any, refPath string) string {
// Handle the # prefix - it indicates the root of the schema JSON
refPath = strings.TrimPrefix(refPath, "#")
// Parse the JSON pointer path
pathParts := strings.Split(strings.TrimPrefix(refPath, "/"), "/")
// Navigate through the schema to find the $ref value
var current any = schema
for _, part := range pathParts {
if part == "" {
continue
}
if part == "$ref" {
// We've reached the $ref, return its value
if currentMap, ok := current.(map[string]any); ok {
if refValue, ok := currentMap["$ref"].(string); ok {
return refValue
}
}
return ""
}
// Navigate to the next level
// Check if this is an array index
if index, err := strconv.Atoi(part); err == nil {
// This is an array index - check if current element is an array
if arr, ok := current.([]any); ok && index < len(arr) {
current = arr[index]
} else {
// Current element is not an array or index out of bounds
return ""
}
} else {
// This is a map key
if currentMap, ok := current.(map[string]any); ok {
current = currentMap[part]
} else {
// Current element is not a map
return ""
}
}
}
return ""
}

View file

@ -0,0 +1,121 @@
package validators_test
import (
"testing"
"github.com/modelcontextprotocol/registry/internal/validators"
"github.com/stretchr/testify/assert"
)
func TestConvertJSONPointerToBracketNotation(t *testing.T) {
tests := []struct {
name string
jsonPointer string
expectedOutput string
description string
}{
{
name: "single array index in middle",
jsonPointer: "/packages/0/transport",
expectedOutput: "packages[0].transport",
description: "JSON Pointer with single array index converts to bracket notation",
},
{
name: "multiple array indices",
jsonPointer: "/packages/0/transport/1/url",
expectedOutput: "packages[0].transport[1].url",
description: "JSON Pointer with multiple array indices converts correctly",
},
{
name: "leading array index",
jsonPointer: "/0/name",
expectedOutput: "[0].name",
description: "JSON Pointer starting with array index converts to leading bracket",
},
{
name: "trailing array index",
jsonPointer: "/packages/0",
expectedOutput: "packages[0]",
description: "JSON Pointer ending with array index converts correctly",
},
{
name: "no array indices",
jsonPointer: "/name/version",
expectedOutput: "name.version",
description: "JSON Pointer without array indices converts to dot notation only",
},
{
name: "complex nested path",
jsonPointer: "/packages/0/runtimeArguments/1/name",
expectedOutput: "packages[0].runtimeArguments[1].name",
description: "Complex nested JSON Pointer with multiple indices converts correctly",
},
{
name: "multiple consecutive indices",
jsonPointer: "/a/0/1/2",
expectedOutput: "a[0][1][2]",
description: "JSON Pointer with consecutive array indices converts to consecutive brackets",
},
{
name: "single character path with index",
jsonPointer: "/a/0",
expectedOutput: "a[0]",
description: "Simple JSON Pointer with single field and index converts correctly",
},
{
name: "empty string",
jsonPointer: "",
expectedOutput: "",
description: "Empty JSON Pointer returns empty string",
},
{
name: "root path",
jsonPointer: "/",
expectedOutput: "",
description: "Root JSON Pointer (just slash) converts to empty string",
},
{
name: "only index",
jsonPointer: "/0",
expectedOutput: "[0]",
description: "JSON Pointer with only array index converts to bracket notation",
},
{
name: "two digit index",
jsonPointer: "/packages/10/transport",
expectedOutput: "packages[10].transport",
description: "JSON Pointer with multi-digit array index converts correctly",
},
{
name: "three digit index",
jsonPointer: "/packages/123/transport",
expectedOutput: "packages[123].transport",
description: "JSON Pointer with three-digit array index converts correctly",
},
{
name: "remotes array index",
jsonPointer: "/remotes/0/url",
expectedOutput: "remotes[0].url",
description: "JSON Pointer for remotes array converts correctly",
},
{
name: "package arguments nested",
jsonPointer: "/packages/0/packageArguments/0/format",
expectedOutput: "packages[0].packageArguments[0].format",
description: "JSON Pointer with nested array structures converts correctly",
},
{
name: "repository url",
jsonPointer: "/repository/url",
expectedOutput: "repository.url",
description: "JSON Pointer without array indices converts to simple dot notation",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := validators.ConvertJSONPointerToBracketNotation(tt.jsonPointer)
assert.Equal(t, tt.expectedOutput, result, "%s: JSON Pointer format should convert to bracket notation", tt.description)
})
}
}

View file

@ -0,0 +1,478 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"title": "MCP Server Detail",
"$ref": "#/definitions/ServerDetail",
"definitions": {
"Repository": {
"type": "object",
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"required": [
"url",
"source"
],
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers"
},
"source": {
"type": "string",
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github"
},
"id": {
"type": "string",
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/<owner>/<repo> --jq '.id'",
"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. Must be a clean relative path.",
"example": "src/everything"
}
}
},
"Server": {
"type": "object",
"required": [
"name",
"description",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"minLength": 3,
"maxLength": 200
},
"description": {
"type": "string",
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"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": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*')."
},
"website_url": {
"type": "string",
"format": "uri",
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples"
}
}
},
"Package": {
"type": "object",
"additionalProperties": false,
"required": [
"registry_type",
"identifier",
"version",
"transport"
],
"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. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*').",
"not": {
"const": "latest"
},
"example": "1.0.2",
"minLength": 1
},
"file_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"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": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/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": "#/definitions/Argument"
}
},
"package_arguments": {
"type": "array",
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
}
},
"environment_variables": {
"type": "array",
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/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": "#/definitions/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": "#/definitions/Input"
}
}
}
}
]
},
"PositionalArgument": {
"description": "A positional input is a value inserted verbatim into the command line.",
"allOf": [
{
"$ref": "#/definitions/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": "#/definitions/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": "#/definitions/InputWithVariables"
},
{
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE"
}
}
}
]
},
"Argument": {
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution.",
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/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": "#/definitions/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": "#/definitions/KeyValueInput"
}
}
}
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"allOf": [
{
"$ref": "#/definitions/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": "#/definitions/Package"
}
},
"remotes": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
}
},
"_meta": {
"type": "object",
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"additionalProperties": true,
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"type": "object",
"description": "Publisher-provided metadata for downstream registries",
"additionalProperties": true
},
"io.modelcontextprotocol.registry/official": {
"type": "object",
"description": "Official MCP registry metadata (read-only, added by registry)",
"additionalProperties": true
}
}
}
}
}
]
}
}
}

View file

@ -0,0 +1,478 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"title": "MCP Server Detail",
"$ref": "#/definitions/ServerDetail",
"definitions": {
"Repository": {
"type": "object",
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"required": [
"url",
"source"
],
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers"
},
"source": {
"type": "string",
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github"
},
"id": {
"type": "string",
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/<owner>/<repo> --jq '.id'",
"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. Must be a clean relative path.",
"example": "src/everything"
}
}
},
"Server": {
"type": "object",
"required": [
"name",
"description",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"minLength": 3,
"maxLength": 200
},
"description": {
"type": "string",
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"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": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*')."
},
"websiteUrl": {
"type": "string",
"format": "uri",
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples"
}
}
},
"Package": {
"type": "object",
"additionalProperties": false,
"required": [
"registryType",
"identifier",
"version",
"transport"
],
"properties": {
"registryType": {
"type": "string",
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
"examples": ["npm", "pypi", "oci", "nuget", "mcpb"]
},
"registryBaseUrl": {
"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. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*').",
"not": {
"const": "latest"
},
"example": "1.0.2",
"minLength": 1
},
"fileSha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
},
"runtimeHint": {
"type": "string",
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.",
"examples": [
"npx",
"uvx",
"docker",
"dnx"
]
},
"transport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for the package"
},
"runtimeArguments": {
"type": "array",
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtimeHint` field should be provided when `runtimeArguments` are present.",
"items": {
"$ref": "#/definitions/Argument"
}
},
"packageArguments": {
"type": "array",
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
}
},
"environmentVariables": {
"type": "array",
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/KeyValueInput"
}
}
}
},
"Input": {
"type": "object",
"properties": {
"description": {
"description": "A description of the input, which clients can use to provide context to the user.",
"type": "string"
},
"isRequired": {
"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"
},
"isSecret": {
"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": "#/definitions/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": "#/definitions/Input"
}
}
}
}
]
},
"PositionalArgument": {
"description": "A positional input is a value inserted verbatim into the command line.",
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"positional"
],
"example": "positional"
},
"valueHint": {
"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"
},
"isRepeated": {
"type": "boolean",
"description": "Whether the argument can be repeated multiple times in the command line.",
"default": false
}
},
"anyOf": [
{
"required": [
"valueHint"
]
},
{
"required": [
"value"
]
}
]
}
]
},
"NamedArgument": {
"description": "A command-line `--flag={value}`.",
"allOf": [
{
"$ref": "#/definitions/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"
},
"isRepeated": {
"type": "boolean",
"description": "Whether the argument can be repeated multiple times.",
"default": false
}
}
}
]
},
"KeyValueInput": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE"
}
}
}
]
},
"Argument": {
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution.",
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/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 valueHints, 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": "#/definitions/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": "#/definitions/KeyValueInput"
}
}
}
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"allOf": [
{
"$ref": "#/definitions/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-09-16/server.schema.json"
},
"packages": {
"type": "array",
"items": {
"$ref": "#/definitions/Package"
}
},
"remotes": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
}
},
"_meta": {
"type": "object",
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"additionalProperties": true,
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"type": "object",
"description": "Publisher-provided metadata for downstream registries",
"additionalProperties": true
},
"io.modelcontextprotocol.registry/official": {
"type": "object",
"description": "Official MCP registry metadata (read-only, added by registry)",
"additionalProperties": true
}
}
}
}
}
]
}
}
}

View file

@ -0,0 +1,483 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"title": "MCP Server Detail",
"$ref": "#/definitions/ServerDetail",
"definitions": {
"Repository": {
"type": "object",
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"required": [
"url",
"source"
],
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers"
},
"source": {
"type": "string",
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github"
},
"id": {
"type": "string",
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/<owner>/<repo> --jq '.id'",
"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. Must be a clean relative path.",
"example": "src/everything"
}
}
},
"Server": {
"type": "object",
"required": [
"name",
"description",
"version"
],
"properties": {
"name": {
"type": "string",
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"minLength": 3,
"maxLength": 200
},
"description": {
"type": "string",
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"minLength": 1,
"maxLength": 100
},
"repository": {
"$ref": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*')."
},
"websiteUrl": {
"type": "string",
"format": "uri",
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples"
}
}
},
"Package": {
"type": "object",
"additionalProperties": false,
"required": [
"registryType",
"identifier",
"version",
"transport"
],
"properties": {
"registryType": {
"type": "string",
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
"examples": [
"npm",
"pypi",
"oci",
"nuget",
"mcpb"
]
},
"registryBaseUrl": {
"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. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*').",
"not": {
"const": "latest"
},
"example": "1.0.2",
"minLength": 1
},
"fileSha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
},
"runtimeHint": {
"type": "string",
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.",
"examples": [
"npx",
"uvx",
"docker",
"dnx"
]
},
"transport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for the package"
},
"runtimeArguments": {
"type": "array",
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtimeHint` field should be provided when `runtimeArguments` are present.",
"items": {
"$ref": "#/definitions/Argument"
}
},
"packageArguments": {
"type": "array",
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
}
},
"environmentVariables": {
"type": "array",
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/KeyValueInput"
}
}
}
},
"Input": {
"type": "object",
"properties": {
"description": {
"description": "A description of the input, which clients can use to provide context to the user.",
"type": "string"
},
"isRequired": {
"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"
},
"isSecret": {
"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": "#/definitions/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": "#/definitions/Input"
}
}
}
}
]
},
"PositionalArgument": {
"description": "A positional input is a value inserted verbatim into the command line.",
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"positional"
],
"example": "positional"
},
"valueHint": {
"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"
},
"isRepeated": {
"type": "boolean",
"description": "Whether the argument can be repeated multiple times in the command line.",
"default": false
}
},
"anyOf": [
{
"required": [
"valueHint"
]
},
{
"required": [
"value"
]
}
]
}
]
},
"NamedArgument": {
"description": "A command-line `--flag={value}`.",
"allOf": [
{
"$ref": "#/definitions/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"
},
"isRepeated": {
"type": "boolean",
"description": "Whether the argument can be repeated multiple times.",
"default": false
}
}
}
]
},
"KeyValueInput": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE"
}
}
}
]
},
"Argument": {
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution.",
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/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 valueHints, 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": "#/definitions/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": "#/definitions/KeyValueInput"
}
}
}
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"allOf": [
{
"$ref": "#/definitions/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-09-29/server.schema.json"
},
"packages": {
"type": "array",
"items": {
"$ref": "#/definitions/Package"
}
},
"remotes": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
}
},
"_meta": {
"type": "object",
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"additionalProperties": true,
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"type": "object",
"description": "Publisher-provided metadata for downstream registries",
"additionalProperties": true
}
}
}
}
}
]
}
}
}

View file

@ -0,0 +1,549 @@
{
"$comment": "This file is auto-generated from docs/reference/api/openapi.yaml. Do not edit manually. Run 'make generate-schema' to update.",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json",
"$ref": "#/definitions/ServerDetail",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Argument": {
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/NamedArgument"
}
],
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution."
},
"Icon": {
"description": "An optionally-sized icon that can be displayed in a user interface.",
"properties": {
"mimeType": {
"description": "Optional MIME type override if the source MIME type is missing or generic. Must be one of: image/png, image/jpeg, image/jpg, image/svg+xml, image/webp.",
"enum": [
"image/png",
"image/jpeg",
"image/jpg",
"image/svg+xml",
"image/webp"
],
"example": "image/png",
"type": "string"
},
"sizes": {
"description": "Optional array of strings that specify sizes at which the icon can be used. Each string should be in WxH format (e.g., '48x48', '96x96') or 'any' for scalable formats like SVG. If not provided, the client should assume that the icon can be used at any size.",
"examples": [
[
"48x48",
"96x96"
],
[
"any"
]
],
"items": {
"pattern": "^(\\d+x\\d+|any)$",
"type": "string"
},
"type": "array"
},
"src": {
"description": "A standard URI pointing to an icon resource. Must be an HTTPS URL. Consumers SHOULD take steps to ensure URLs serving icons are from the same domain as the server or a trusted domain. Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.",
"example": "https://example.com/icon.png",
"format": "uri",
"maxLength": 255,
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for. 'light' indicates the icon is designed to be used with a light background, and 'dark' indicates the icon is designed to be used with a dark background. If not provided, the client should assume the icon can be used with any theme.",
"enum": [
"light",
"dark"
],
"type": "string"
}
},
"required": [
"src"
],
"type": "object"
},
"Input": {
"properties": {
"choices": {
"description": "A list of possible values for the input. If provided, the user must select one of these values.",
"example": [],
"items": {
"type": "string"
},
"type": "array"
},
"default": {
"description": "The default value for the input. This should be a valid value for the input. If you want to provide input examples or guidance, use the `placeholder` field instead.",
"type": "string"
},
"description": {
"description": "A description of the input, which clients can use to provide context to the user.",
"type": "string"
},
"format": {
"default": "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"
],
"type": "string"
},
"isRequired": {
"default": false,
"type": "boolean"
},
"isSecret": {
"default": false,
"description": "Indicates whether the input is a secret value (e.g., password, token). If true, clients should handle the value securely.",
"type": "boolean"
},
"placeholder": {
"description": "A placeholder for the input to be displaying during configuration. This is used to provide examples or guidance about the expected form or content of the input.",
"type": "string"
},
"value": {
"description": "The 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",
"type": "string"
}
},
"type": "object"
},
"InputWithVariables": {
"allOf": [
{
"$ref": "#/definitions/Input"
},
{
"properties": {
"variables": {
"additionalProperties": {
"$ref": "#/definitions/Input"
},
"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.",
"type": "object"
}
},
"type": "object"
}
]
},
"KeyValueInput": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"name": {
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
]
},
"NamedArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times.",
"type": "boolean"
},
"name": {
"description": "The flag name, including any leading dashes.",
"example": "--port",
"type": "string"
},
"type": {
"enum": [
"named"
],
"example": "named",
"type": "string"
}
},
"required": [
"type",
"name"
],
"type": "object"
}
],
"description": "A command-line `--flag={value}`."
},
"Package": {
"properties": {
"environmentVariables": {
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"fileSha256": {
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"pattern": "^[a-f0-9]{64}$",
"type": "string"
},
"identifier": {
"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"
],
"type": "string"
},
"packageArguments": {
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"registryBaseUrl": {
"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"
],
"format": "uri",
"type": "string"
},
"registryType": {
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
"examples": [
"npm",
"pypi",
"oci",
"nuget",
"mcpb"
],
"type": "string"
},
"runtimeArguments": {
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtimeHint` field should be provided when `runtimeArguments` are present.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"runtimeHint": {
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.",
"examples": [
"npx",
"uvx",
"docker",
"dnx"
],
"type": "string"
},
"transport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for the package"
},
"version": {
"description": "Package version. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"minLength": 1,
"not": {
"const": "latest"
},
"type": "string"
}
},
"required": [
"registryType",
"identifier",
"transport"
],
"type": "object"
},
"PositionalArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"anyOf": [
{
"required": [
"valueHint"
]
},
{
"required": [
"value"
]
}
],
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times in the command line.",
"type": "boolean"
},
"type": {
"enum": [
"positional"
],
"example": "positional",
"type": "string"
},
"valueHint": {
"description": "An identifier for the positional argument. It is not part of the command line. It may be used by client configuration as a label identifying the argument. It is also used to identify the value in transport URL variable substitution.",
"example": "file_path",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A positional input is a value inserted verbatim into the command line."
},
"Repository": {
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"properties": {
"id": {
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/\u003cowner\u003e/\u003crepo\u003e --jq '.id'",
"example": "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9",
"type": "string"
},
"source": {
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github",
"type": "string"
},
"subfolder": {
"description": "Optional relative path from repository root to the server location within a monorepo or nested package structure. Must be a clean relative path.",
"example": "src/everything",
"type": "string"
},
"url": {
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers",
"format": "uri",
"type": "string"
}
},
"required": [
"url",
"source"
],
"type": "object"
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"properties": {
"$schema": {
"description": "JSON Schema URI for this server.json format",
"example": "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json",
"format": "uri",
"type": "string"
},
"_meta": {
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"additionalProperties": true,
"description": "Publisher-provided metadata for downstream registries",
"example": {
"buildInfo": {
"commit": "abc123def456",
"pipelineId": "build-789",
"timestamp": "2023-12-01T10:30:00Z"
},
"tool": "publisher-cli",
"version": "1.2.3"
},
"type": "object"
}
},
"type": "object"
},
"description": {
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"icons": {
"description": "Optional set of sized icons that the client can display in a user interface. Clients that support rendering icons MUST support at least the following MIME types: image/png and image/jpeg (safe, universal compatibility). Clients SHOULD also support: image/svg+xml (scalable but requires security precautions) and image/webp (modern, efficient format).",
"items": {
"$ref": "#/definitions/Icon"
},
"type": "array"
},
"name": {
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"maxLength": 200,
"minLength": 3,
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"type": "string"
},
"packages": {
"items": {
"$ref": "#/definitions/Package"
},
"type": "array"
},
"remotes": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
},
"type": "array"
},
"repository": {
"$ref": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"title": {
"description": "Optional human-readable title or display name for the MCP server. MCP subregistries or clients MAY choose to use this for display purposes.",
"example": "Weather API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"version": {
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"maxLength": 255,
"type": "string"
},
"websiteUrl": {
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples",
"format": "uri",
"type": "string"
}
},
"required": [
"name",
"description",
"version"
],
"type": "object"
},
"SseTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"sse"
],
"example": "sse",
"type": "string"
},
"url": {
"description": "Server-Sent Events endpoint URL",
"example": "https://mcp-fs.example.com/sse",
"format": "uri",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
},
"StdioTransport": {
"properties": {
"type": {
"description": "Transport type",
"enum": [
"stdio"
],
"example": "stdio",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"StreamableHttpTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"streamable-http"
],
"example": "streamable-http",
"type": "string"
},
"url": {
"description": "URL template for the streamable-http transport. Variables in {curly_braces} reference argument valueHints, argument names, or environment variable names. After variable substitution, this should produce a valid URI.",
"example": "https://api.example.com/mcp",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
}
},
"title": "server.json defining a Model Context Protocol (MCP) server"
}

View file

@ -0,0 +1,549 @@
{
"$comment": "This file is auto-generated from docs/reference/api/openapi.yaml. Do not edit manually. Run 'make generate-schema' to update.",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"$ref": "#/definitions/ServerDetail",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Argument": {
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/NamedArgument"
}
],
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution."
},
"Icon": {
"description": "An optionally-sized icon that can be displayed in a user interface.",
"properties": {
"mimeType": {
"description": "Optional MIME type override if the source MIME type is missing or generic. Must be one of: image/png, image/jpeg, image/jpg, image/svg+xml, image/webp.",
"enum": [
"image/png",
"image/jpeg",
"image/jpg",
"image/svg+xml",
"image/webp"
],
"example": "image/png",
"type": "string"
},
"sizes": {
"description": "Optional array of strings that specify sizes at which the icon can be used. Each string should be in WxH format (e.g., '48x48', '96x96') or 'any' for scalable formats like SVG. If not provided, the client should assume that the icon can be used at any size.",
"examples": [
[
"48x48",
"96x96"
],
[
"any"
]
],
"items": {
"pattern": "^(\\d+x\\d+|any)$",
"type": "string"
},
"type": "array"
},
"src": {
"description": "A standard URI pointing to an icon resource. Must be an HTTPS URL. Consumers SHOULD take steps to ensure URLs serving icons are from the same domain as the server or a trusted domain. Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.",
"example": "https://example.com/icon.png",
"format": "uri",
"maxLength": 255,
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for. 'light' indicates the icon is designed to be used with a light background, and 'dark' indicates the icon is designed to be used with a dark background. If not provided, the client should assume the icon can be used with any theme.",
"enum": [
"light",
"dark"
],
"type": "string"
}
},
"required": [
"src"
],
"type": "object"
},
"Input": {
"properties": {
"choices": {
"description": "A list of possible values for the input. If provided, the user must select one of these values.",
"example": [],
"items": {
"type": "string"
},
"type": "array"
},
"default": {
"description": "The default value for the input. This should be a valid value for the input. If you want to provide input examples or guidance, use the `placeholder` field instead.",
"type": "string"
},
"description": {
"description": "A description of the input, which clients can use to provide context to the user.",
"type": "string"
},
"format": {
"default": "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"
],
"type": "string"
},
"isRequired": {
"default": false,
"type": "boolean"
},
"isSecret": {
"default": false,
"description": "Indicates whether the input is a secret value (e.g., password, token). If true, clients should handle the value securely.",
"type": "boolean"
},
"placeholder": {
"description": "A placeholder for the input to be displaying during configuration. This is used to provide examples or guidance about the expected form or content of the input.",
"type": "string"
},
"value": {
"description": "The 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",
"type": "string"
}
},
"type": "object"
},
"InputWithVariables": {
"allOf": [
{
"$ref": "#/definitions/Input"
},
{
"properties": {
"variables": {
"additionalProperties": {
"$ref": "#/definitions/Input"
},
"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.",
"type": "object"
}
},
"type": "object"
}
]
},
"KeyValueInput": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"name": {
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
]
},
"NamedArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times.",
"type": "boolean"
},
"name": {
"description": "The flag name, including any leading dashes.",
"example": "--port",
"type": "string"
},
"type": {
"enum": [
"named"
],
"example": "named",
"type": "string"
}
},
"required": [
"type",
"name"
],
"type": "object"
}
],
"description": "A command-line `--flag={value}`."
},
"Package": {
"properties": {
"environmentVariables": {
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"fileSha256": {
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"pattern": "^[a-f0-9]{64}$",
"type": "string"
},
"identifier": {
"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"
],
"type": "string"
},
"packageArguments": {
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"registryBaseUrl": {
"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"
],
"format": "uri",
"type": "string"
},
"registryType": {
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
"examples": [
"npm",
"pypi",
"oci",
"nuget",
"mcpb"
],
"type": "string"
},
"runtimeArguments": {
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtimeHint` field should be provided when `runtimeArguments` are present.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"runtimeHint": {
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.",
"examples": [
"npx",
"uvx",
"docker",
"dnx"
],
"type": "string"
},
"transport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for the package"
},
"version": {
"description": "Package version. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"minLength": 1,
"not": {
"const": "latest"
},
"type": "string"
}
},
"required": [
"registryType",
"identifier",
"transport"
],
"type": "object"
},
"PositionalArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"anyOf": [
{
"required": [
"valueHint"
]
},
{
"required": [
"value"
]
}
],
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times in the command line.",
"type": "boolean"
},
"type": {
"enum": [
"positional"
],
"example": "positional",
"type": "string"
},
"valueHint": {
"description": "An identifier for the positional argument. It is not part of the command line. It may be used by client configuration as a label identifying the argument. It is also used to identify the value in transport URL variable substitution.",
"example": "file_path",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A positional input is a value inserted verbatim into the command line."
},
"Repository": {
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"properties": {
"id": {
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/\u003cowner\u003e/\u003crepo\u003e --jq '.id'",
"example": "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9",
"type": "string"
},
"source": {
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github",
"type": "string"
},
"subfolder": {
"description": "Optional relative path from repository root to the server location within a monorepo or nested package structure. Must be a clean relative path.",
"example": "src/everything",
"type": "string"
},
"url": {
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers",
"format": "uri",
"type": "string"
}
},
"required": [
"url",
"source"
],
"type": "object"
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"properties": {
"$schema": {
"description": "JSON Schema URI for this server.json format",
"example": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"format": "uri",
"type": "string"
},
"_meta": {
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"additionalProperties": true,
"description": "Publisher-provided metadata for downstream registries",
"example": {
"buildInfo": {
"commit": "abc123def456",
"pipelineId": "build-789",
"timestamp": "2023-12-01T10:30:00Z"
},
"tool": "publisher-cli",
"version": "1.2.3"
},
"type": "object"
}
},
"type": "object"
},
"description": {
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"icons": {
"description": "Optional set of sized icons that the client can display in a user interface. Clients that support rendering icons MUST support at least the following MIME types: image/png and image/jpeg (safe, universal compatibility). Clients SHOULD also support: image/svg+xml (scalable but requires security precautions) and image/webp (modern, efficient format).",
"items": {
"$ref": "#/definitions/Icon"
},
"type": "array"
},
"name": {
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"maxLength": 200,
"minLength": 3,
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"type": "string"
},
"packages": {
"items": {
"$ref": "#/definitions/Package"
},
"type": "array"
},
"remotes": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
},
"type": "array"
},
"repository": {
"$ref": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"title": {
"description": "Optional human-readable title or display name for the MCP server. MCP subregistries or clients MAY choose to use this for display purposes.",
"example": "Weather API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"version": {
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"maxLength": 255,
"type": "string"
},
"websiteUrl": {
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples",
"format": "uri",
"type": "string"
}
},
"required": [
"name",
"description",
"version"
],
"type": "object"
},
"SseTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"sse"
],
"example": "sse",
"type": "string"
},
"url": {
"description": "Server-Sent Events endpoint URL",
"example": "https://mcp-fs.example.com/sse",
"format": "uri",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
},
"StdioTransport": {
"properties": {
"type": {
"description": "Transport type",
"enum": [
"stdio"
],
"example": "stdio",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"StreamableHttpTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"streamable-http"
],
"example": "streamable-http",
"type": "string"
},
"url": {
"description": "URL template for the streamable-http transport. Variables in {curly_braces} reference argument valueHints, argument names, or environment variable names. After variable substitution, this should produce a valid URI.",
"example": "https://api.example.com/mcp",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
}
},
"title": "server.json defining a Model Context Protocol (MCP) server"
}

View file

@ -0,0 +1,574 @@
{
"$comment": "This file is auto-generated from docs/reference/api/openapi.yaml. Do not edit manually. Run 'make generate-schema' to update.",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"$ref": "#/definitions/ServerDetail",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Argument": {
"anyOf": [
{
"$ref": "#/definitions/PositionalArgument"
},
{
"$ref": "#/definitions/NamedArgument"
}
],
"description": "Warning: Arguments construct command-line parameters that may contain user-provided input. This creates potential command injection risks if clients execute commands in a shell environment. For example, a malicious argument value like ';rm -rf ~/Development' could execute dangerous commands. Clients should prefer non-shell execution methods (e.g., posix_spawn) when possible to eliminate injection risks entirely. Where not possible, clients should obtain consent from users or agents to run the resolved command before execution."
},
"Icon": {
"description": "An optionally-sized icon that can be displayed in a user interface.",
"properties": {
"mimeType": {
"description": "Optional MIME type override if the source MIME type is missing or generic. Must be one of: image/png, image/jpeg, image/jpg, image/svg+xml, image/webp.",
"enum": [
"image/png",
"image/jpeg",
"image/jpg",
"image/svg+xml",
"image/webp"
],
"example": "image/png",
"type": "string"
},
"sizes": {
"description": "Optional array of strings that specify sizes at which the icon can be used. Each string should be in WxH format (e.g., '48x48', '96x96') or 'any' for scalable formats like SVG. If not provided, the client should assume that the icon can be used at any size.",
"examples": [
[
"48x48",
"96x96"
],
[
"any"
]
],
"items": {
"pattern": "^(\\d+x\\d+|any)$",
"type": "string"
},
"type": "array"
},
"src": {
"description": "A standard URI pointing to an icon resource. Must be an HTTPS URL. Consumers SHOULD take steps to ensure URLs serving icons are from the same domain as the server or a trusted domain. Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.",
"example": "https://example.com/icon.png",
"format": "uri",
"maxLength": 255,
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for. 'light' indicates the icon is designed to be used with a light background, and 'dark' indicates the icon is designed to be used with a dark background. If not provided, the client should assume the icon can be used with any theme.",
"enum": [
"light",
"dark"
],
"type": "string"
}
},
"required": [
"src"
],
"type": "object"
},
"Input": {
"properties": {
"choices": {
"description": "A list of possible values for the input. If provided, the user must select one of these values.",
"example": [],
"items": {
"type": "string"
},
"type": "array"
},
"default": {
"description": "The default value for the input. This should be a valid value for the input. If you want to provide input examples or guidance, use the `placeholder` field instead.",
"type": "string"
},
"description": {
"description": "A description of the input, which clients can use to provide context to the user.",
"type": "string"
},
"format": {
"default": "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"
],
"type": "string"
},
"isRequired": {
"default": false,
"type": "boolean"
},
"isSecret": {
"default": false,
"description": "Indicates whether the input is a secret value (e.g., password, token). If true, clients should handle the value securely.",
"type": "boolean"
},
"placeholder": {
"description": "A placeholder for the input to be displaying during configuration. This is used to provide examples or guidance about the expected form or content of the input.",
"type": "string"
},
"value": {
"description": "The 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",
"type": "string"
}
},
"type": "object"
},
"InputWithVariables": {
"allOf": [
{
"$ref": "#/definitions/Input"
},
{
"properties": {
"variables": {
"additionalProperties": {
"$ref": "#/definitions/Input"
},
"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.",
"type": "object"
}
},
"type": "object"
}
]
},
"KeyValueInput": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"name": {
"description": "Name of the header or environment variable.",
"example": "SOME_VARIABLE",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
]
},
"LocalTransport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for local/package context"
},
"NamedArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times.",
"type": "boolean"
},
"name": {
"description": "The flag name, including any leading dashes.",
"example": "--port",
"type": "string"
},
"type": {
"enum": [
"named"
],
"example": "named",
"type": "string"
}
},
"required": [
"type",
"name"
],
"type": "object"
}
],
"description": "A command-line `--flag={value}`."
},
"Package": {
"properties": {
"environmentVariables": {
"description": "A mapping of environment variables to be set when running the package.",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"fileSha256": {
"description": "SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity.",
"example": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"pattern": "^[a-f0-9]{64}$",
"type": "string"
},
"identifier": {
"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"
],
"type": "string"
},
"packageArguments": {
"description": "A list of arguments to be passed to the package's binary.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"registryBaseUrl": {
"description": "Base URL of the package registry",
"examples": [
"https://registry.npmjs.org",
"https://pypi.org",
"https://docker.io",
"https://api.nuget.org/v3/index.json",
"https://github.com",
"https://gitlab.com"
],
"format": "uri",
"type": "string"
},
"registryType": {
"description": "Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')",
"examples": [
"npm",
"pypi",
"oci",
"nuget",
"mcpb"
],
"type": "string"
},
"runtimeArguments": {
"description": "A list of arguments to be passed to the package's runtime command (such as docker or npx). The `runtimeHint` field should be provided when `runtimeArguments` are present.",
"items": {
"$ref": "#/definitions/Argument"
},
"type": "array"
},
"runtimeHint": {
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.",
"examples": [
"npx",
"uvx",
"docker",
"dnx"
],
"type": "string"
},
"transport": {
"$ref": "#/definitions/LocalTransport",
"description": "Transport protocol configuration for the package"
},
"version": {
"description": "Package version. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"minLength": 1,
"not": {
"const": "latest"
},
"type": "string"
}
},
"required": [
"registryType",
"identifier",
"transport"
],
"type": "object"
},
"PositionalArgument": {
"allOf": [
{
"$ref": "#/definitions/InputWithVariables"
},
{
"anyOf": [
{
"required": [
"valueHint"
]
},
{
"required": [
"value"
]
}
],
"properties": {
"isRepeated": {
"default": false,
"description": "Whether the argument can be repeated multiple times in the command line.",
"type": "boolean"
},
"type": {
"enum": [
"positional"
],
"example": "positional",
"type": "string"
},
"valueHint": {
"description": "An identifier for the positional argument. It is not part of the command line. It may be used by client configuration as a label identifying the argument. It is also used to identify the value in transport URL variable substitution.",
"example": "file_path",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A positional input is a value inserted verbatim into the command line."
},
"RemoteTransport": {
"allOf": [
{
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
},
{
"properties": {
"variables": {
"additionalProperties": {
"$ref": "#/definitions/Input"
},
"description": "Configuration variables that can be referenced in URL template {curly_braces}. The key is the variable name, and the value defines the variable properties.",
"type": "object"
}
},
"type": "object"
}
],
"description": "Transport protocol configuration for remote context - extends StreamableHttpTransport or SseTransport with variables"
},
"Repository": {
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"properties": {
"id": {
"description": "Repository identifier from the hosting service (e.g., GitHub repo ID). Owned and determined by the source forge. Should remain stable across repository renames and may be used to detect repository resurrection attacks - if a repository is deleted and recreated, the ID should change. For GitHub, use: gh api repos/\u003cowner\u003e/\u003crepo\u003e --jq '.id'",
"example": "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9",
"type": "string"
},
"source": {
"description": "Repository hosting service identifier. Used by registries to determine validation and API access methods.",
"example": "github",
"type": "string"
},
"subfolder": {
"description": "Optional relative path from repository root to the server location within a monorepo or nested package structure. Must be a clean relative path.",
"example": "src/everything",
"type": "string"
},
"url": {
"description": "Repository URL for browsing source code. Should support both web browsing and git clone operations.",
"example": "https://github.com/modelcontextprotocol/servers",
"format": "uri",
"type": "string"
}
},
"required": [
"url",
"source"
],
"type": "object"
},
"ServerDetail": {
"description": "Schema for a static representation of an MCP server. Used in various contexts related to discovery, installation, and configuration.",
"properties": {
"$schema": {
"description": "JSON Schema URI for this server.json format",
"example": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"format": "uri",
"type": "string"
},
"_meta": {
"description": "Extension metadata using reverse DNS namespacing for vendor-specific data",
"properties": {
"io.modelcontextprotocol.registry/publisher-provided": {
"additionalProperties": true,
"description": "Publisher-provided metadata for downstream registries",
"example": {
"buildInfo": {
"commit": "abc123def456",
"pipelineId": "build-789",
"timestamp": "2023-12-01T10:30:00Z"
},
"tool": "publisher-cli",
"version": "1.2.3"
},
"type": "object"
}
},
"type": "object"
},
"description": {
"description": "Clear human-readable explanation of server functionality. Should focus on capabilities, not implementation details.",
"example": "MCP server providing weather data and forecasts via OpenWeatherMap API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"icons": {
"description": "Optional set of sized icons that the client can display in a user interface. Clients that support rendering icons MUST support at least the following MIME types: image/png and image/jpeg (safe, universal compatibility). Clients SHOULD also support: image/svg+xml (scalable but requires security precautions) and image/webp (modern, efficient format).",
"items": {
"$ref": "#/definitions/Icon"
},
"type": "array"
},
"name": {
"description": "Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name.",
"example": "io.github.user/weather",
"maxLength": 200,
"minLength": 3,
"pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$",
"type": "string"
},
"packages": {
"items": {
"$ref": "#/definitions/Package"
},
"type": "array"
},
"remotes": {
"items": {
"$ref": "#/definitions/RemoteTransport"
},
"type": "array"
},
"repository": {
"$ref": "#/definitions/Repository",
"description": "Optional repository metadata for the MCP server source code. Recommended for transparency and security inspection."
},
"title": {
"description": "Optional human-readable title or display name for the MCP server. MCP subregistries or clients MAY choose to use this for display purposes.",
"example": "Weather API",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"version": {
"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. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '\u003e=1.2.3', '1.x', '1.*').",
"example": "1.0.2",
"maxLength": 255,
"type": "string"
},
"websiteUrl": {
"description": "Optional URL to the server's homepage, documentation, or project website. This provides a central link for users to learn more about the server. Particularly useful when the server has custom installation instructions or setup requirements.",
"example": "https://modelcontextprotocol.io/examples",
"format": "uri",
"type": "string"
}
},
"required": [
"name",
"description",
"version"
],
"type": "object"
},
"SseTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"sse"
],
"example": "sse",
"type": "string"
},
"url": {
"description": "Server-Sent Events endpoint URL template. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI.",
"example": "https://mcp-fs.example.com/sse",
"pattern": "^https?://[^\\s]+$",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
},
"StdioTransport": {
"properties": {
"type": {
"description": "Transport type",
"enum": [
"stdio"
],
"example": "stdio",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"StreamableHttpTransport": {
"properties": {
"headers": {
"description": "HTTP headers to include",
"items": {
"$ref": "#/definitions/KeyValueInput"
},
"type": "array"
},
"type": {
"description": "Transport type",
"enum": [
"streamable-http"
],
"example": "streamable-http",
"type": "string"
},
"url": {
"description": "URL template for the streamable-http transport. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI.",
"example": "https://api.example.com/mcp",
"pattern": "^https?://[^\\s]+$",
"type": "string"
}
},
"required": [
"type",
"url"
],
"type": "object"
}
},
"title": "server.json defining a Model Context Protocol (MCP) server"
}

Some files were not shown because too many files have changed in this diff Show more