diff --git a/docs/reference/api/openapi.yaml b/docs/reference/api/openapi.yaml index 61cae2da7..993539de7 100644 --- a/docs/reference/api/openapi.yaml +++ b/docs/reference/api/openapi.yaml @@ -1015,6 +1015,116 @@ components: commit: "abc123def456" timestamp: "2023-12-01T10:30:00Z" pipelineId: "build-789" + io.modelcontextprotocol.registry/security-scan: + type: array + description: "Optional scanner-neutral security scan receipts. Each receipt is scoped to the scanned artifact, scanner version, rule set, policy profile, scan scope, and evidence reference; a clean verdict is not a global safety claim." + items: + type: object + required: + - scannedArtifactDigest + - scanScope + - verdict + - attestation + properties: + scanner: + type: string + description: "Name or identifier of the scanner that produced the receipt." + example: "example-scanner" + maxLength: 200 + scannerVersion: + type: string + description: "Version of the scanner that produced the receipt." + example: "1.2.3" + maxLength: 100 + ruleSetRef: + type: string + description: "Scanner-specific rule set reference, preferably pinned by digest." + example: "agent-threat-rules@sha256:abc123..." + maxLength: 500 + policyProfile: + type: string + description: "Policy profile or scan configuration used to evaluate the artifact." + example: "default-mcp-registry-v1" + maxLength: 200 + scannedArtifactRef: + type: string + description: "Package URL, image reference, source archive reference, or other stable identifier for the artifact that was scanned." + example: "pkg:npm/@modelcontextprotocol/server-filesystem@1.0.2" + maxLength: 500 + scannedArtifactDigest: + type: string + description: "Digest of the artifact that was scanned. Registry clients should only display artifact-bound claims when this digest matches the current package or artifact." + example: "sha256:abc123..." + pattern: "^[a-z0-9][a-z0-9+.-]*:.+$" + maxLength: 200 + scanScope: + type: array + description: "Machine-readable list of surfaces evaluated by this scan. This describes what was checked, not every capability the server exposes." + minItems: 1 + uniqueItems: true + items: + type: string + enum: + - dependency + - package + - source + - handler-validation + - runtime-behavior + - configuration + - unknown + example: ["dependency", "package"] + verdict: + type: string + description: "Scanner verdict for the stated artifact, rule set, policy profile, and scan scope." + enum: + - clean + - warnings + - findings + - inconclusive + example: "warnings" + inconclusiveReason: + type: string + description: "Machine-readable reason when the verdict is inconclusive. Omit or set to other only when no more specific value applies." + enum: + - artifact_digest_mismatch + - unsupported_package_type + - scope_excludes_handler_validation + - evidence_unavailable + - stale_scan + - scanner_error + - policy_not_supported + - other + example: "scope_excludes_handler_validation" + scannedAt: + type: string + format: date-time + description: "Timestamp when the scan was performed." + example: "2026-06-28T00:00:00Z" + freshnessExpiresAt: + type: string + format: date-time + description: "Timestamp after which clients should treat the receipt as stale unless refreshed." + example: "2026-07-28T00:00:00Z" + evidenceRef: + type: string + format: uri + description: "URL for the full machine-readable report or attestation evidence." + example: "https://example.org/report.json" + evidenceDigest: + type: string + description: "Digest of the evidence referenced by evidenceRef." + example: "sha256:def456..." + pattern: "^[a-z0-9][a-z0-9+.-]*:.+$" + maxLength: 200 + attestation: + type: string + description: "Who asserts or attests the receipt. This does not define signature or key-distribution semantics." + enum: + - publisher-asserted + - registry-attested + - third-party-attested + example: "third-party-attested" + additionalProperties: true ServerResponse: description: API response format with separated server data and registry metadata diff --git a/docs/reference/server-json/CHANGELOG.md b/docs/reference/server-json/CHANGELOG.md index c55ef4179..ef3986c6c 100644 --- a/docs/reference/server-json/CHANGELOG.md +++ b/docs/reference/server-json/CHANGELOG.md @@ -6,6 +6,14 @@ Changes to the server.json schema and format. This section tracks changes that are in development and not yet released. The draft schema is available at [`server.schema.json`](./draft/server.schema.json) in this repository. +### Added + +#### Security Scan Receipt Metadata + +The `_meta` field can now document optional scanner-neutral security scan receipts under `io.modelcontextprotocol.registry/security-scan`. + +Each receipt is scoped to a scanned artifact digest, scan scope, verdict, attestation, and optional evidence reference/digest. The shape keeps `clean` scoped to the exact artifact, scanner, rule set, policy profile, and scan scope instead of making a global server-safety claim. `inconclusiveReason` provides machine-readable failure modes such as artifact digest mismatch, unsupported package type, excluded handler-validation scope, unavailable evidence, and stale scans. + ### Changed #### Transport URL Pattern Now Accepts Template Variables @@ -313,4 +321,4 @@ All JSON field names standardized to camelCase. **All existing `server.json` fil ## 2025-07-09 -Initial release of the server.json schema. \ No newline at end of file +Initial release of the server.json schema. diff --git a/docs/reference/server-json/draft/server.schema.json b/docs/reference/server-json/draft/server.schema.json index e3aeab6a6..085c24347 100644 --- a/docs/reference/server-json/draft/server.schema.json +++ b/docs/reference/server-json/draft/server.schema.json @@ -427,6 +427,142 @@ "version": "1.2.3" }, "type": "object" + }, + "io.modelcontextprotocol.registry/security-scan": { + "description": "Optional scanner-neutral security scan receipts. Each receipt is scoped to the scanned artifact, scanner version, rule set, policy profile, scan scope, and evidence reference; a clean verdict is not a global safety claim.", + "items": { + "additionalProperties": true, + "properties": { + "attestation": { + "description": "Who asserts or attests the receipt. This does not define signature or key-distribution semantics.", + "enum": [ + "publisher-asserted", + "registry-attested", + "third-party-attested" + ], + "example": "third-party-attested", + "type": "string" + }, + "evidenceDigest": { + "description": "Digest of the evidence referenced by evidenceRef.", + "example": "sha256:def456...", + "maxLength": 200, + "pattern": "^[a-z0-9][a-z0-9+.-]*:.+$", + "type": "string" + }, + "evidenceRef": { + "description": "URL for the full machine-readable report or attestation evidence.", + "example": "https://example.org/report.json", + "format": "uri", + "type": "string" + }, + "freshnessExpiresAt": { + "description": "Timestamp after which clients should treat the receipt as stale unless refreshed.", + "example": "2026-07-28T00:00:00Z", + "format": "date-time", + "type": "string" + }, + "inconclusiveReason": { + "description": "Machine-readable reason when the verdict is inconclusive. Omit or set to other only when no more specific value applies.", + "enum": [ + "artifact_digest_mismatch", + "unsupported_package_type", + "scope_excludes_handler_validation", + "evidence_unavailable", + "stale_scan", + "scanner_error", + "policy_not_supported", + "other" + ], + "example": "scope_excludes_handler_validation", + "type": "string" + }, + "policyProfile": { + "description": "Policy profile or scan configuration used to evaluate the artifact.", + "example": "default-mcp-registry-v1", + "maxLength": 200, + "type": "string" + }, + "ruleSetRef": { + "description": "Scanner-specific rule set reference, preferably pinned by digest.", + "example": "agent-threat-rules@sha256:abc123...", + "maxLength": 500, + "type": "string" + }, + "scanScope": { + "description": "Machine-readable list of surfaces evaluated by this scan. This describes what was checked, not every capability the server exposes.", + "example": [ + "dependency", + "package" + ], + "items": { + "enum": [ + "dependency", + "package", + "source", + "handler-validation", + "runtime-behavior", + "configuration", + "unknown" + ], + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + }, + "scannedArtifactDigest": { + "description": "Digest of the artifact that was scanned. Registry clients should only display artifact-bound claims when this digest matches the current package or artifact.", + "example": "sha256:abc123...", + "maxLength": 200, + "pattern": "^[a-z0-9][a-z0-9+.-]*:.+$", + "type": "string" + }, + "scannedArtifactRef": { + "description": "Package URL, image reference, source archive reference, or other stable identifier for the artifact that was scanned.", + "example": "pkg:npm/@modelcontextprotocol/server-filesystem@1.0.2", + "maxLength": 500, + "type": "string" + }, + "scannedAt": { + "description": "Timestamp when the scan was performed.", + "example": "2026-06-28T00:00:00Z", + "format": "date-time", + "type": "string" + }, + "scanner": { + "description": "Name or identifier of the scanner that produced the receipt.", + "example": "example-scanner", + "maxLength": 200, + "type": "string" + }, + "scannerVersion": { + "description": "Version of the scanner that produced the receipt.", + "example": "1.2.3", + "maxLength": 100, + "type": "string" + }, + "verdict": { + "description": "Scanner verdict for the stated artifact, rule set, policy profile, and scan scope.", + "enum": [ + "clean", + "warnings", + "findings", + "inconclusive" + ], + "example": "warnings", + "type": "string" + } + }, + "required": [ + "scannedArtifactDigest", + "scanScope", + "verdict", + "attestation" + ], + "type": "object" + }, + "type": "array" } }, "type": "object" diff --git a/docs/reference/server-json/generic-server-json.md b/docs/reference/server-json/generic-server-json.md index 7415988ae..5c9e45457 100644 --- a/docs/reference/server-json/generic-server-json.md +++ b/docs/reference/server-json/generic-server-json.md @@ -30,6 +30,43 @@ The optional `_meta` field allows publishers to include custom metadata alongsid When publishing to the official registry, custom metadata must be placed under the key `io.modelcontextprotocol.registry/publisher-provided`. See the [official registry requirements](./official-registry-requirements.md) for detailed restrictions and examples. +### Optional Security Scan Receipts + +Generic registries and subregistries may expose scanner-neutral security scan receipts under `_meta["io.modelcontextprotocol.registry/security-scan"]`. + +Each receipt is scoped to the evidence that produced it. A `clean` verdict means the stated artifact was clean for the listed scanner, scanner version, rule set, policy profile, and scan scope. It is not a global claim that the server is safe. + +```jsonc +{ + "_meta": { + "io.modelcontextprotocol.registry/security-scan": [ + { + "scanner": "example-scanner", + "scannerVersion": "1.2.3", + "ruleSetRef": "agent-threat-rules@sha256:abc123...", + "policyProfile": "default-mcp-registry-v1", + "scannedArtifactRef": "pkg:npm/@modelcontextprotocol/server-filesystem@1.0.2", + "scannedArtifactDigest": "sha256:abc123...", + "scanScope": ["dependency", "package"], + "verdict": "warnings", + "inconclusiveReason": "scope_excludes_handler_validation", + "scannedAt": "2026-06-28T00:00:00Z", + "freshnessExpiresAt": "2026-07-28T00:00:00Z", + "evidenceRef": "https://example.org/report.json", + "evidenceDigest": "sha256:def456...", + "attestation": "third-party-attested" + } + ] + } +} +``` + +Registry clients should only display artifact-bound security claims when `scannedArtifactDigest` matches the current package or artifact. Clients should also include the displayed `scanScope`, because a dependency or package scan can be clean while handler-side validation remains unassessed. + +Use `verdict: "inconclusive"` with an `inconclusiveReason` when the receipt cannot support a stronger claim, such as `artifact_digest_mismatch`, `unsupported_package_type`, `scope_excludes_handler_validation`, `evidence_unavailable`, or `stale_scan`. + +The `attestation` field distinguishes self-asserted publisher metadata from registry- or third-party-attested receipts. It does not define signature or key-distribution semantics. + ## Examples