Skip to content

Commit 9cd02f4

Browse files
committed
Add tooling to auto-generate server.schema.json from OpenAPI spec
This PR adds a Go tool that extracts the ServerDetail schema from openapi.yaml and generates server.schema.json, ensuring the two specs stay in sync. Key changes: - New Go tool at tools/extract-server-schema/ that auto-discovers all schemas referenced by ServerDetail and extracts them from OpenAPI - New make targets: generate-schema and check-schema - check-schema is now included in make validate and make check - Fixed missing Package.transport field in OpenAPI (was in Go model) - Split transport types into StdioTransport, StreamableHttpTransport, and SseTransport with proper discriminated unions - Package.transport supports all three types (anyOf) - ServerDetail.remotes supports only remote types (no stdio) - Added GOTOOLCHAIN=auto to Makefile for Go 1.25 compatibility - Added DRIFT_ANALYSIS.md documenting all schema drift issues found The generated server.schema.json now perfectly matches the OpenAPI spec, with all 20 validation examples passing.
1 parent 66993e9 commit 9cd02f4

File tree

5 files changed

+785
-399
lines changed

5 files changed

+785
-399
lines changed

DRIFT_ANALYSIS.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Schema Drift Analysis
2+
3+
This document details the drift that was found between `server.schema.json` and `openapi.yaml`, and what was fixed by the automated extraction tooling.
4+
5+
## Summary
6+
7+
The `server.schema.json` and `openapi.yaml` specs had significant drift. A Go tool at `tools/extract-server-schema/` now auto-generates `server.schema.json` from `openapi.yaml` to keep them in sync.
8+
9+
The tool automatically discovers all schemas referenced by `ServerDetail` (recursively) and extracts them from OpenAPI, converting them to JSON Schema format.
10+
11+
## Key Drift Issues Found
12+
13+
### 1. **Package.transport field** ✅ FIXED
14+
- **JSON Schema (old)**: `transport` was **required** in Package
15+
- **OpenAPI (old)**: `transport` did **NOT exist** in Package ❌
16+
- **Go implementation**: Has `Transport` field in Package struct ✅
17+
- **Resolution**: Added `transport` field to OpenAPI Package schema and created new `Transport` schema that supports stdio/streamable-http/sse (all three transport types). Package.transport references this new Transport schema.
18+
19+
### 2. **Package.additionalProperties**
20+
- **JSON Schema (old)**: Had `"additionalProperties": false` (line 40)
21+
- **OpenAPI**: No additionalProperties restriction
22+
- **Resolution**: Removed the restriction to match OpenAPI
23+
24+
### 3. **Package.version validation**
25+
- **JSON Schema (old)**: Had complex validation with `not: {const: "latest"}` and `minLength: 1`
26+
- **OpenAPI**: Simple string with description
27+
- **Resolution**: Simplified to match OpenAPI
28+
29+
### 4. **Repository field descriptions**
30+
- **JSON Schema (old)**: Had extensive descriptions on `url`, `source`, `id` fields
31+
- **OpenAPI**: Minimal or no descriptions on some fields
32+
- **Resolution**: Removed descriptions to match OpenAPI (descriptions should be in OpenAPI as source of truth)
33+
34+
### 5. **Input.value field description**
35+
- **JSON Schema (old)**: "value for the input. If this is not set..."
36+
- **OpenAPI**: "**default** value for the input. If this is not set..."
37+
- **Resolution**: Updated to match OpenAPI clarification
38+
39+
### 6. **Input.default field description**
40+
- **JSON Schema (old)**: Long description about valid values vs placeholder
41+
- **OpenAPI**: Short "The default value for the input"
42+
- **Resolution**: Simplified to match OpenAPI
43+
44+
### 7. **Input.placeholder field** ❌ MISSING IN OPENAPI
45+
- **JSON Schema (old)**: Had `placeholder` field with description
46+
- **OpenAPI**: **NO placeholder field**
47+
- **Go implementation**: Has `Placeholder` in Input struct? (needs verification)
48+
- **Resolution**: Removed from schema. **This may need to be added to OpenAPI if it's in the Go model.**
49+
50+
### 8. **PositionalArgument.valueHint description**
51+
- **JSON Schema (old)**: Detailed description mentioning transport URL variable substitution
52+
- **OpenAPI**: Simpler description
53+
- **Resolution**: Updated to match OpenAPI
54+
55+
### 9. **Argument warning comment** ❌ MISSING
56+
- **JSON Schema (old)**: Had extensive warning about command injection risks (line 294)
57+
- **OpenAPI**: No such warning
58+
- **Resolution**: Removed. **This security warning should probably be added to OpenAPI.**
59+
60+
### 10. **Icon.sizes examples format**
61+
- **JSON Schema (old)**: Single example array
62+
- **OpenAPI**: Multiple example arrays showing different patterns
63+
- **Resolution**: Updated to match OpenAPI's richer examples
64+
65+
### 11. **ServerDetail.name pattern and length**
66+
- **JSON Schema (old)**: Had `pattern`, `minLength: 3`, `maxLength: 200`
67+
- **OpenAPI**: No validation constraints
68+
- **Resolution**: Removed constraints. **These should probably be in OpenAPI.**
69+
70+
### 12. **ServerDetail.description length**
71+
- **JSON Schema (old)**: `minLength: 1`, `maxLength: 100`
72+
- **OpenAPI**: No length constraints
73+
- **Resolution**: Removed constraints. **These should probably be in OpenAPI.**
74+
75+
### 13. **ServerDetail.title length**
76+
- **JSON Schema (old)**: `minLength: 1`, `maxLength: 100`
77+
- **OpenAPI**: No length constraints
78+
- **Resolution**: Removed constraints. **These should probably be in OpenAPI.**
79+
80+
### 14. **ServerDetail.version description**
81+
- **JSON Schema (old)**: Long description about semantic versioning and rejecting version ranges
82+
- **OpenAPI**: Shorter description
83+
- **Resolution**: Updated to match OpenAPI. **The detailed validation rules should be in OpenAPI.**
84+
85+
### 15. **ServerDetail._meta structure**
86+
- **JSON Schema (old)**: Had `additionalProperties: true` at top level with nested publisher-provided
87+
- **OpenAPI**: Similar but with example nested in properties
88+
- **Resolution**: Matched OpenAPI structure
89+
90+
### 16. **Format enum value** ❌ BUG IN GO CODE
91+
- **Go model (types.go:51)**: Uses `FormatFilePath Format = "file_path"` (with underscore)
92+
- **OpenAPI**: Uses `"filepath"` (no underscore)
93+
- **JSON Schema (old)**: Uses `"filepath"` (no underscore)
94+
- **Resolution**: Schema now matches OpenAPI. **The Go code has a bug and should use "filepath".**
95+
96+
## Changes Made by Automated Tool
97+
98+
The tool makes these changes:
99+
100+
1. ✅ Adds `$comment` header explaining file is auto-generated
101+
2. ✅ Auto-discovers all schemas referenced by ServerDetail (recursively via `$ref`)
102+
3. ✅ Extracts discovered schemas from OpenAPI `components/schemas`
103+
4. ✅ Converts `#/components/schemas/` refs to `#/definitions/`
104+
5. ✅ Outputs as JSON Schema format with proper structure
105+
106+
**Note:** The tool does NOT add any custom schemas or transformations - it simply extracts what's in OpenAPI and converts the reference paths.
107+
108+
## Recommendations
109+
110+
### High Priority
111+
1. **Fix Go code**: Change `FormatFilePath` from `"file_path"` to `"filepath"` in `pkg/model/types.go:51`
112+
2. **Add to OpenAPI**: Validation constraints for name, description, title fields (pattern, minLength, maxLength)
113+
3. **Add to OpenAPI**: Version validation details (semantic versioning, no ranges)
114+
4. **Clarify Package.transport**: Confirm whether transport belongs in Package or not
115+
116+
### Medium Priority
117+
1. **Add to OpenAPI**: Command injection warning for Arguments
118+
2. **Add to OpenAPI**: Package.additionalProperties: false if that was intentional
119+
3. **Clarify Input.placeholder**: Determine if placeholder field should exist
120+
121+
### Low Priority
122+
1. Consider whether Repository field descriptions should be in OpenAPI
123+
2. Review which descriptions are most useful in OpenAPI vs JSON Schema
124+
125+
## Testing the Tool
126+
127+
```bash
128+
# Generate schema from OpenAPI
129+
make generate-schema
130+
131+
# Check if schema is in sync with OpenAPI
132+
make check-schema
133+
134+
# Run all validation (includes check-schema)
135+
make validate
136+
```
137+
138+
## Notes
139+
140+
- The Go implementation at `pkg/api/v0/types.go` uses `ServerJSON` not `ServerDetail`, but they represent the same concept
141+
- The actual database model uses `ServerJSON` as the JSON blob stored
142+
- OpenAPI should be the single source of truth for the schema
143+
- Any differences between JSON Schema and OpenAPI indicate either:
144+
- Missing validation in OpenAPI (should be added)
145+
- Over-specification in JSON Schema (should be removed)
146+
- Bugs in implementation code (should be fixed)

Makefile

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help build test test-unit test-integration test-endpoints test-publish test-all lint lint-fix validate validate-schemas validate-examples check dev-compose clean publisher
1+
.PHONY: help build test test-unit test-integration test-endpoints test-publish test-all lint lint-fix validate validate-schemas validate-examples check dev-compose clean publisher generate-schema check-schema
22

33
# Default target
44
help: ## Show this help message
@@ -14,6 +14,17 @@ publisher: ## Build the publisher tool with version info
1414
@mkdir -p bin
1515
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
1616

17+
# Schema generation targets
18+
generate-schema: ## Generate server.schema.json from openapi.yaml
19+
@mkdir -p bin
20+
GOTOOLCHAIN=auto go build -o bin/extract-server-schema ./tools/extract-server-schema
21+
@./bin/extract-server-schema
22+
23+
check-schema: ## Check if server.schema.json is in sync with openapi.yaml
24+
@mkdir -p bin
25+
GOTOOLCHAIN=auto go build -o bin/extract-server-schema ./tools/extract-server-schema
26+
@./bin/extract-server-schema -check
27+
1728
# Test targets
1829
test-unit: ## Run unit tests with coverage (requires PostgreSQL)
1930
@echo "Starting PostgreSQL for unit tests..."
@@ -49,14 +60,14 @@ validate-schemas: ## Validate JSON schemas
4960
validate-examples: ## Validate examples against schemas
5061
./tools/validate-examples.sh
5162

52-
validate: validate-schemas validate-examples ## Run all validation checks
63+
validate: validate-schemas validate-examples check-schema ## Run all validation checks (includes schema sync check)
5364

5465
# Lint targets
5566
lint: ## Run linter (includes formatting)
56-
golangci-lint run --timeout=5m
67+
GOTOOLCHAIN=auto golangci-lint run --timeout=5m
5768

5869
lint-fix: ## Run linter with auto-fix (includes formatting)
59-
golangci-lint run --fix --timeout=5m
70+
GOTOOLCHAIN=auto golangci-lint run --fix --timeout=5m
6071

6172
# Combined targets
6273
check: dev-down lint validate test-all ## Run all checks (lint, validate, unit tests) and ensure dev environment is down

docs/reference/api/openapi.yaml

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,12 @@ components:
306306
type: string
307307
description: A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.
308308
examples: [npx, uvx, dnx]
309+
transport:
310+
anyOf:
311+
- $ref: '#/components/schemas/StdioTransport'
312+
- $ref: '#/components/schemas/StreamableHttpTransport'
313+
- $ref: '#/components/schemas/SseTransport'
314+
description: Transport protocol configuration for the package
309315
runtimeArguments:
310316
type: array
311317
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.
@@ -435,21 +441,53 @@ components:
435441
- $ref: '#/components/schemas/PositionalArgument'
436442
- $ref: '#/components/schemas/NamedArgument'
437443

438-
Remote:
444+
StdioTransport:
445+
type: object
446+
required:
447+
- type
448+
properties:
449+
type:
450+
type: string
451+
enum: [stdio]
452+
description: Transport type
453+
example: "stdio"
454+
455+
StreamableHttpTransport:
456+
type: object
457+
required:
458+
- type
459+
- url
460+
properties:
461+
type:
462+
type: string
463+
enum: [streamable-http]
464+
description: Transport type
465+
example: "streamable-http"
466+
url:
467+
type: string
468+
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.
469+
example: "https://api.example.com/mcp"
470+
headers:
471+
type: array
472+
description: HTTP headers to include
473+
items:
474+
$ref: '#/components/schemas/KeyValueInput'
475+
476+
SseTransport:
439477
type: object
440478
required:
441479
- type
442480
- url
443481
properties:
444482
type:
445483
type: string
446-
enum: [streamable-http, sse]
447-
description: Transport protocol type
484+
enum: [sse]
485+
description: Transport type
448486
example: "sse"
449487
url:
450488
type: string
451489
format: uri
452-
description: Remote server URL
490+
description: Server-Sent Events endpoint URL
453491
example: "https://mcp-fs.example.com/sse"
454492
headers:
455493
type: array
@@ -536,7 +574,9 @@ components:
536574
remotes:
537575
type: array
538576
items:
539-
$ref: '#/components/schemas/Remote'
577+
anyOf:
578+
- $ref: '#/components/schemas/StreamableHttpTransport'
579+
- $ref: '#/components/schemas/SseTransport'
540580
_meta:
541581
type: object
542582
description: Extension metadata using reverse DNS namespacing

0 commit comments

Comments
 (0)