Payload

← All guides

Publishing your MCP server.json without getting rejected

Registries validate server.json mechanically. Most rejections aren't judgment calls — they're schema violations you can catch on your own machine in seconds.

The fields registries actually check

The 5 most common rejections

1. Name isn't namespaced

// BROKEN
{ "name": "payload-sample-mcp-server", "version": "1.0.0" }

// FIXED
{ "name": "io.github.payloadhq/payload-sample-mcp-server", "version": "1.0.0" }

2. Version isn't semver

// BROKEN
{ "version": "v1" }

// FIXED
{ "version": "1.0.0" }

3. Repository source doesn't match the URL

// BROKEN
{ "repository": { "url": "https://github.com/payloadhq/payload-sample-mcp-server",
                  "source": "gitlab" } }

// FIXED
{ "repository": { "url": "https://github.com/payloadhq/payload-sample-mcp-server",
                  "source": "github" } }

4. Package identifier doesn't exist in the registry

// BROKEN — typo'd identifier; nothing published under this name
{ "packages": [{ "registryType": "pypi", "identifier": "payload-sample-mcp-sever",
                 "version": "1.0.0" }] }

// FIXED
{ "packages": [{ "registryType": "pypi", "identifier": "payload-sample-mcp-server",
                 "version": "1.0.0" }] }

Registries check that the package is actually published under the identifier you claim. Publish first, register second.

5. Transport disagrees with reality

// BROKEN — claims stdio, but the package is an HTTP server
{ "packages": [{ "transport": { "type": "stdio" } }] }

// FIXED — match what the entrypoint actually does
{ "packages": [{ "transport": { "type": "stdio" } }] }  // only if it speaks MCP over stdio

Validate locally before you publish

This runnable Python script catches all five problems without any network access:

import json, re, sys

m = json.load(open("server.json"))
errors = []

if not re.fullmatch(r"io\.github\.[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", m.get("name", "")):
    errors.append("name must look like io.github.owner/repo")
if not re.fullmatch(r"\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?", m.get("version", "")):
    errors.append("version must be semver (e.g. 1.0.0)")
repo = m.get("repository", {})
if not (repo.get("url", "").startswith("https://github.com/") and repo.get("source") == "github"):
    errors.append("repository.url/source must agree (github)")
pkgs = m.get("packages") or []
if not pkgs:
    errors.append("packages must list at least one published package")
for p in pkgs:
    if p.get("registryType") not in ("npm", "pypi"):
        errors.append(f"bad registryType: {p.get('registryType')}")
    if not p.get("identifier"):
        errors.append("package identifier is empty")
    t = (p.get("transport") or {}).get("type")
    if t not in ("stdio",):
        errors.append(f"unexpected transport type: {t}")

print("OK - server.json looks publishable" if not errors else "PROBLEMS:")
for e in errors:
    print(" -", e)
sys.exit(1 if errors else 0)

Expected output on a good manifest:

OK - server.json looks publishable

Go further

The free mcp-manifest-validator checks required fields, name format, semver, repository consistency, and package entries before you publish. For the deeper pass — the 48-rule security scan, hardened templates, and CI workflow — see the MCP Launch Readiness Audit ($79, one-time).

Built by Payload

Payload builds practical software that makes AI, automation, and business infrastructure safer, cleaner, more reliable, and easier to ship. Support: kylers.partners@gmail.com