Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
go-version-file: 'go.mod'
cache: true

- name: Prepare schema for embedding
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as below on "embedding"

run: make prep-schema

- name: Run lint
uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47
with:
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/sync-schema.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ validate-schemas
coverage.out
coverage.html
deploy/infra/infra
./registry
registry

# Generated schema directory for embedding
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"embedding" as a term is a little misdirecting at first glance (given the prominence of vector embeddings in AI); wherever you use this can we please make clear it's about "embedding the static file into the Go binary"

internal/validators/schema/
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ help: ## Show this help message
@echo "Available targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'

# Preparation targets
prep-schema: ## Copy schema file for embedding
@mkdir -p internal/validators/schema
@cp docs/reference/server-json/server.schema.json internal/validators/schema/server.schema.json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we lock this into being a static, live version of the schema (instead of this linked one, which is a draft unreleased version)?

I think in general it would be smart to not make changes in the registry codebase that don't correspond to an already-published server.json schema, so I don't think that constraint would hold us back would it?


# Build targets
build: ## Build the registry application with version info
build: prep-schema ## Build the registry application with version info
@mkdir -p bin
go build -ldflags="-X main.Version=dev-$(shell git rev-parse --short HEAD) -X main.GitCommit=$(shell git rev-parse HEAD) -X main.BuildTime=$(shell date -u +%Y-%m-%dT%H:%M:%SZ)" -o bin/registry ./cmd/registry

publisher: ## Build the publisher tool with version info
publisher: prep-schema ## Build the publisher tool with version info
@mkdir -p bin
go build -ldflags="-X main.Version=dev-$(shell git rev-parse --short HEAD) -X main.GitCommit=$(shell git rev-parse HEAD) -X main.BuildTime=$(shell date -u +%Y-%m-%dT%H:%M:%SZ)" -o bin/mcp-publisher ./cmd/publisher

Expand All @@ -26,7 +31,7 @@ check-schema: ## Check if server.schema.json is in sync with openapi.yaml
@./bin/extract-server-schema -check

# Test targets
test-unit: ## Run unit tests with coverage (requires PostgreSQL)
test-unit: prep-schema ## Run unit tests with coverage (requires PostgreSQL)
@echo "Starting PostgreSQL for unit tests..."
@docker compose up -d postgres 2>&1 | grep -v "Pulling\|Pulled\|Creating\|Created\|Starting\|Started" || true
@echo "Waiting for PostgreSQL to be ready..."
Expand Down
10 changes: 9 additions & 1 deletion cmd/publisher/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/modelcontextprotocol/registry/internal/validators"
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
"github.com/modelcontextprotocol/registry/pkg/model"
)
Expand Down Expand Up @@ -405,8 +406,15 @@ func createServerJSON(
}

// Create server structure
// Get current schema version from embedded schema
currentSchema, err := validators.GetCurrentSchemaVersion()
if err != nil {
// Should never happen (schema is embedded)
panic(fmt.Sprintf("failed to get embedded schema version: %v", err))
}

return apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Schema: currentSchema,
Name: name,
Description: description,
Repository: repo,
Expand Down
19 changes: 15 additions & 4 deletions cmd/publisher/commands/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -40,13 +40,24 @@ func PublishCommand(args []string) error {

// Check for deprecated schema and recommend migration
// Allow empty schema (will use default) but reject old schemas
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing empty schema seems weird here - do you agree? Realize it's out of scope but if you agree I'll file an Issue

if serverJSON.Schema != "" && !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) {
return fmt.Errorf(`deprecated schema detected: %s.
if serverJSON.Schema != "" {
// Get current schema version from embedded schema
currentSchema, err := validators.GetCurrentSchemaVersion()
if err != nil {
// Schema is embedded, so this should never happen
return fmt.Errorf("failed to get current schema version: %w", err)
}

if serverJSON.Schema != currentSchema {
return fmt.Errorf(`deprecated schema detected: %s.

Expected current schema: %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)
📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema, currentSchema)
}
}

// Load saved token
Expand Down
81 changes: 81 additions & 0 deletions cmd/publisher/commands/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package commands

import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/modelcontextprotocol/registry/internal/validators"
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
)

func ValidateCommand(args []string) error {
// Parse arguments
serverFile := "server.json"

for _, arg := range args {
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)
}

// Check for deprecated schema and recommend migration
// Allow empty schema (will use default) but reject old schemas
if serverJSON.Schema != "" {
// Get current schema version from embedded schema
currentSchema, err := validators.GetCurrentSchemaVersion()
if err != nil {
// Should never happen (schema is embedded)
return fmt.Errorf("failed to get current schema version: %w", err)
}

if serverJSON.Schema != currentSchema {
return fmt.Errorf(`deprecated schema detected: %s.

Expected current schema: %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, currentSchema)
}
}

// Run detailed validation (this is the whole point of the validate command)
// Include schema validation for comprehensive validation
result := validators.ValidateServerJSONExhaustive(&serverJSON, true)

if result.Valid {
_, _ = fmt.Fprintln(os.Stdout, "✅ server.json is valid")
return nil
}

// Print all issues
_, _ = fmt.Fprintf(os.Stdout, "❌ Validation failed with %d issue(s):\n\n", len(result.Issues))
for i, issue := range result.Issues {
_, _ = fmt.Fprintf(os.Stdout, "%d. [%s] %s (%s)\n", i+1, 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)
}

return fmt.Errorf("validation failed")
}
3 changes: 3 additions & 0 deletions cmd/publisher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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
Expand Down Expand Up @@ -65,6 +67,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.")
}
Loading
Loading