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
name— namespaced, e.g.io.github.payloadhq/payload-sample-mcp-server. The namespace must match the publisher's GitHub owner.version— strict semantic versioning, e.g.1.0.0. Novprefix, no bare1.repository—{ "url": "...", "source": "github" }; the source must match the URL's host.packages— at least one entry withregistryType(npmorpypi),identifier, andversion. The identifier must be a real, published package under that exact name.transport— e.g.{ "type": "stdio" }; it must agree with how the package actually runs.
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