createmcps.com

Reference

What goes in a server.json file for MCP

Last verified: 10 August 2026 · spec 2026-07-28 · ruleset 1.4.2

server.json is the metadata file that describes an MCP server to the registry. Four fields are required — $schema, name, description and version — plus at least one of packages or remotes. Everything else is optional.

A minimal, complete file

server.json — npm-packaged server
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "io.github.my-username/weather",
  "description": "An MCP server for weather information.",
  "version": "1.0.1",
  "repository": {
    "url": "https://github.com/my-username/mcp-weather-server",
    "source": "github"
  },
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@my-username/mcp-weather-server",
      "version": "1.0.1",
      "transport": { "type": "stdio" }
    }
  ]
}
Generate the skeleton rather than typing it
mcp-publisher init

Every top-level field

FieldRequiredTypeWhat it is
$schemaYesstringURL of the schema version this file targets
nameYesstringReverse-DNS namespace identifier, e.g. io.github.you/weather
descriptionYesstringWhat the server does
versionYesstringVersion number; semver recommended
titleNostringHuman-readable display name
repositoryNoobjectSource repository details
websiteUrlNostringDocumentation URL
packagesNoarrayInstallable package definitions
remotesNoarrayHosted endpoint definitions
_metaNoobjectPublisher metadata, reverse-DNS namespaced
At least one of packages or remotes: A file with neither is invalid. They answer different questions: packages says “install this and run it locally”, remotes says “connect to this endpoint I host”. A server offering both may declare both.

Naming rules — the field most likely to reject your publish

name is reverse-DNS, namespace first, then a slash, then the server name:

Valid and invalid
io.github.my-username/weather      ✓ GitHub namespace
io.github.my-org/weather           ✓ needs Owner role in the org
com.example/weather                ✓ verified domain example.com
com.example.tools/weather          ✓ subdomain form

weather                            ✗ no namespace
my-username/weather                ✗ not reverse-DNS
com.example/Weather Server         ✗ spaces

Two separate things must line up, and they fail with different errors:

  • The namespace must be one you authenticated for. mcp-publisher login github grants io.github.you/*; login dns --domain example.com grants com.example.*/*. See verifying a DNS namespace.
  • If you declare an npm package, name must exactly match its mcpName. That pairing is the package-ownership proof.
The pairing
// package.json                    // server.json
{                                  {
  "name": "@you/mcp-weather",        "name": "io.github.you/weather",
  "mcpName": "io.github.you/weather" …
}                                  }
          └──────── must be identical ────────┘
Publishers must verify ownership of their namespace through GitHub, DNS, or HTTP challenges, preventing arbitrary spam submissions.
MCP-REG-002 MAY · spec 2026-07-28

packages[] — for servers users install

Full package object
{
  "registryType": "npm",          // npm | pypi | cargo | nuget | oci | mcpb
  "registryBaseUrl": "https://registry.npmjs.org",
  "identifier": "@you/mcp-weather",
  "version": "1.0.1",
  "runtimeHint": "npx",
  "fileSha256": "…",              // mcpb only
  "transport": { "type": "stdio" },
  "packageArguments": [],
  "runtimeArguments": [],
  "environmentVariables": []
}

registryBaseUrl must be an official registry — registry.npmjs.org, pypi.org, api.nuget.org/v3/index.json or crates.io. Private registries are disallowed, because ownership cannot be verified on a host the registry cannot read.

Arguments and environment variables
"environmentVariables": [
  {
    "name": "WEATHER_API_KEY",
    "description": "API key for the upstream weather provider",
    "isRequired": true,
    "isSecret": true
  }
],
"packageArguments": [
  {
    "type": "named",
    "name": "--units",
    "description": "Measurement system",
    "choices": ["metric", "imperial"],
    "default": "metric",
    "isRequired": false
  }
]
Our recommendation, not a spec quote: mark every credential isSecret: true. Clients use that flag to decide whether to mask a value in a UI and keep it out of logs — a missing flag is how an API key ends up in a screenshot.

remotes[] — for servers you host

Remote object
"remotes": [
  {
    "type": "streamable-http",     // streamable-http | sse
    "url": "https://mcp.example.com/mcp",
    "headers": [
      {
        "name": "Authorization",
        "description": "Bearer token issued by example.com",
        "isRequired": true,
        "isSecret": true
      }
    ]
  }
]

The URL supports {variable} templating against declared arguments, environment variables or remote-specific variables — useful for a per-tenant subdomain.

A remote server MUST be publicly accessible at its specified URL.
MCP-REG-004 MAY · spec 2026-07-28
Our recommendation, not a spec quote: prefer streamable-http over sse for anything new. The legacy HTTP+SSE transport is formally deprecated under MCP's lifecycle policy — declaring it in a fresh listing publishes a server on a transport with a countdown on it.

repository

Repository object
"repository": {
  "url": "https://github.com/you/mcp-weather-server",
  "source": "github",
  "id": "…",                  // optional UUID
  "subfolder": "servers/weather"   // for a monorepo
}

Optional, but worth filling in: it is the link anyone evaluating your server will follow first, and subfolder is the only way to point at the right directory in a monorepo.

_meta — and what survives publishing

Custom metadata is allowed only under _meta, in a reverse-DNS namespaced key. For the official registry, exactly one key survives:

The only key that persists
"_meta": {
  "io.modelcontextprotocol.registry/publisher-provided": {
    "tool": "my-release-script",
    "version": "2.1.0",
    "build_info": { "commit": "a1b2c3d" }
  }
}
Other keys are silently dropped: Not rejected — dropped. Your publish succeeds and the data is gone, which is far harder to notice than an error. Anything you depend on must live under io.modelcontextprotocol.registry/publisher-provided, and that block is capped at 4KB; exceeding it fails the publish with the actual size in the message.

Validating it

Before publishing
mcp-publisher validate

# or against a specific file
mcp-publisher validate ./server.json
Validates `server.json` against schema
MCP-REG-001 MAY · spec 2026-07-28

We check the same thing from the outside as MCP-REG-001, alongside MCP-REG-003 for version format and MCP-REG-004 for whether a declared remote endpoint is actually reachable at its stated URL.

Validate the server, not just the file

A valid server.json means your listing is well-formed. Whether the server it describes conforms to the 2026-07-28 protocol is a separate question.

Validate a server →

Frequently asked

What goes in a server.json file for MCP?

Four required fields — $schema, name, description and version — plus at least one of packages or remotes. Optional fields are title, repository, websiteUrl and _meta. The name must be a reverse-DNS identifier such as io.github.username/server-name, and it must sit inside a namespace you have authenticated for.

Is packages or remotes required in server.json?

At least one of them must be present. Use packages for a server users install and run locally, listing the registry type, identifier, version and transport. Use remotes for a server you host, listing the endpoint type and URL. A server can declare both if it offers each option.

What is the correct $schema URL for server.json?

https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json is the current schema version. Getting this wrong is a quiet failure — validation runs against whatever you point at, so a stale or misspelled URL can pass locally and be rejected at publish time.

How should I name an MCP server?

In reverse-DNS form, namespace first: io.github.username/server-name for a GitHub namespace, or com.example/server-name for a verified domain. The namespace half must be one you have authenticated for, and if your server.json declares an npm package, the name must exactly match the mcpName field in that package's package.json.

Can I put custom fields in server.json?

Only under _meta, and only in a reverse-DNS namespaced key. For the official registry, just io.modelcontextprotocol.registry/publisher-provided survives publishing — other keys are silently dropped, so anything you rely on must live under that key. That block is also capped at 4KB.

Which package registries can server.json reference?

Only official ones at fixed URLs: npm at registry.npmjs.org, PyPI at pypi.org, NuGet at api.nuget.org/v3/index.json and Cargo at crates.io, plus specific container registries. Private registries are disallowed because the registry cannot verify package ownership on a host it cannot read.

Sources

Field tables verified on 10 August 2026 against: