Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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 @@ -40,6 +40,9 @@ jobs:
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0

- 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
run: make lint

Expand Down
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
@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 @@ -300,8 +301,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: model.Repository{
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