Getting Started: Creating Your First MCP Manifest

Getting Started: Creating Your First MCP Manifest

A hands-on tutorial for MCP server authors to enable autodiscovery and one-click installation with a single JSON file.

David H. Friedel Jr.· 2026-04-01 ·mcp-manifest tutorial

Introduction: What You'll Build

If you've published an MCP server, you know the drill: users clone your repo, run an install command, copy-paste a JSON blob into their client config, and hope they got the syntax right. Every server is a snowflake. Every README has different instructions.

This tutorial changes that.

By the end, you'll have created an mcp-manifest.json file that lets MCP clients:

  • Discover your server automatically from your domain name
  • Install it with the right package manager (npm, pip, dotnet, cargo, Docker)
  • Configure it with typed parameters (API keys, paths, URLs)
  • Connect without manual JSON editing

We'll start with a minimal 10-line manifest and progressively add features. You'll learn:

  1. The bare minimum to enable autodiscovery
  2. How to declare multiple installation methods
  3. How to define configuration parameters with types and prompts
  4. How to validate your manifest with JSON Schema
  5. How to publish it for client discovery

Let's build.

The Minimal Manifest: 10 Lines to Autodiscovery

Here's the smallest valid mcp-manifest.json:

{
  "$schema": "https://mcp-manifest.dev/schema/v0.1.json",
  "version": "0.1",
  "server": {
    "name": "my-server",
    "displayName": "My MCP Server",
    "description": "A minimal example manifest",
    "version": "1.0.0"
  },
  "install": [{
    "method": "npm",
    "package": "my-mcp-server",
    "command": "my-mcp-server",
    "priority": 0
  }],
  "transport": "stdio"
}

That's it. Ten lines. Let's break it down:

Required Fields

  • $schema — Points to the JSON Schema for validation and editor autocomplete
  • version — Spec version ("0.1" for now)
  • server.name — Unique identifier (lowercase, hyphens allowed). This becomes the key in mcpServers config.
  • server.displayName — Human-readable name shown in client UIs
  • server.description — One-line summary of what your server does
  • server.version — Your server's version (semver format)
  • install — Array of installation methods (at least one required)
  • transport — How clients connect: "stdio", "sse", or "streamable-http"

The Install Block

Each install method needs:

  • method — One of: npm, pip, dotnet-tool, cargo, binary, docker
  • package — Package name or image
  • command — The CLI command available after installation
  • priority — Lower numbers = preferred (default: 0)

Clients will check if command exists on PATH. If not, they'll offer to install via the highest-priority method that matches the user's environment.

What This Enables

With just this minimal manifest:

  1. A client can discover your server from your domain
  2. Check if my-mcp-server is installed
  3. If not, run npm install -g my-mcp-server
  4. Add it to the user's MCP config automatically
  5. Connect via stdio transport

No README copy-paste. No manual JSON editing.

Adding Installation Methods: npm, pip, and dotnet

Most MCP servers can be installed multiple ways. The SQLite server, for example, is available via both npm and pip:

{
  "$schema": "https://mcp-manifest.dev/schema/v0.1.json",
  "version": "0.1",
  "server": {
    "name": "sqlite",
    "displayName": "SQLite Explorer",
    "description": "Query and explore SQLite databases",
    "version": "0.5.0",
    "author": "Anthropic",
    "repository": "https://github.com/anthropics/mcp-servers",
    "license": "MIT",
    "keywords": ["database", "sqlite", "sql", "query"]
  },
  "install": [
    {
      "method": "npm",
      "package": "@anthropic/mcp-server-sqlite",
      "command": "mcp-server-sqlite",
      "priority": 0
    },
    {
      "method": "pip",
      "package": "mcp-server-sqlite",
      "command": "mcp-server-sqlite",
      "priority": 1
    }
  ],
  "transport": "stdio"
}

A minimal manifest is just 10 lines — everything else is optional, progressive complexity.

Priority Matters

The priority field controls which method clients prefer:

  • Lower = preferred (0 is highest priority)
  • Clients filter by available runtimes (e.g., skip pip if Python isn't installed)
  • Then pick the lowest-priority match

Here, npm is preferred (priority 0), but if the user doesn't have Node.js, the client falls back to pip (priority 1).

Custom Registries

If your package lives in a private or custom registry, use the source field:

{
  "method": "dotnet-tool",
  "package": "IronLicensing.Mcp",
  "source": "https://git.marketally.com/api/packages/ironservices/nuget/index.json",
  "command": "ironlicensing-mcp",
  "priority": 0
}

Clients will pass this to the install command:

dotnet tool install -g IronLicensing.Mcp --add-source https://git.marketally.com/api/packages/ironservices/nuget/index.json

Metadata Fields

Notice we also added optional metadata:

  • author — Who built this
  • repository — Where the source lives
  • license — SPDX identifier (MIT, Apache-2.0, etc.)
  • keywords — Tags for discovery and search

These aren't required, but they make your server more discoverable and trustworthy.

Defining Configuration Parameters

Most servers need configuration: API keys, database paths, URLs. The config array declares these parameters with types, defaults, and prompts.

Here's the GitHub server manifest:

{
  "config": [
    {
      "key": "github-token",
      "description": "GitHub personal access token",
      "type": "secret",
      "required": true,
      "env_var": "GITHUB_TOKEN",
      "prompt": "GitHub personal access token (ghp_...)"
    }
  ],
  "settings_template": {
    "command": "mcp-server-github",
    "args": []
  }
}

Config Field Reference

Field Required Description
key Parameter name (used in variable substitution)
description What this parameter does
type string, boolean, number, path, url, secret
required Whether the parameter is mandatory (default: false)
default Default value if not provided
env_var Environment variable that supplies this value
arg CLI argument name (e.g., "--api-key")
prompt Human-readable prompt for interactive setup

Types Matter

The type field controls how clients handle the parameter:

  • secret — Masked in UI, never logged, stored securely
  • path — Clients may show a file picker
  • url — Clients may validate format
  • boolean — Rendered as checkbox/toggle
  • number — Numeric input with validation
  • string — Free-form text

Resolution Order

Clients resolve config values in this order:

  1. User-provided value (via UI prompt or manual entry)
  2. Environment variable (env_var field)
  3. CLI argument (arg field)
  4. Default value (default field)

This lets users configure via environment (CI/CD-friendly) or interactively (local dev).

Advanced Example: Multiple Parameters

The IronLicensing server has three config parameters with different types:

{
  "config": [
    {
      "key": "profile",
      "description": "Named account profile from ~/.ironlicensing/config.json",
      "type": "string",
      "required": false,
      "arg": "--profile",
      "prompt": "Account profile (leave empty for default)"
    },
    {
      "key": "api-key",
      "description": "IronLicensing API key (sk_live_xxx)",
      "type": "secret",
      "required": false,
      "env_var": "IRONLICENSING_API_KEY",
      "arg": "--api-key",
      "prompt": "API key (or configure via add_account tool)"
    },
    {
      "key": "base-url",
      "description": "IronLicensing API base URL",
      "type": "url",
      "required": false,
      "default": "http://localhost:5000",
      "env_var": "IRONLICENSING_BASE_URL",
      "arg": "--base-url",
      "prompt": "API base URL"
    }
  ],
  "settings_template": {
    "command": "ironlicensing-mcp",
    "args": ["--profile", "${profile}"]
  }
}

Notice:

  • profile is a string, optional, passed via --profile arg
  • api-key is a secret, can come from IRONLICENSING_API_KEY env var
  • base-url is a URL with a default, can be overridden

Settings Template with Variable Substitution

The settings_template shows the exact JSON structure clients write to their config file. Variables use ${key} syntax:

{
  "settings_template": {
    "command": "ironlicensing-mcp",
    "args": ["--profile", "${profile}"]
  }
}

If the user enters profile = "production", the client writes:

{
  "mcpServers": {
    "ironlicensing": {
      "command": "ironlicensing-mcp",
      "args": ["--profile", "production"]
    }
  }
}

No manual editing. No syntax errors.

Testing Your Manifest with JSON Schema

The $schema field at the top of your manifest enables automatic validation in most editors (VS Code, IntelliJ, etc.):

{
  "$schema": "https://mcp-manifest.dev/schema/v0.1.json",
  "version": "0.1",
  ...
}

This gives you:

  • Autocomplete for field names
  • Inline errors for invalid values
  • Hover documentation for every field

Validate from the Command Line

You can also validate using any JSON Schema validator. With Node.js:

npm install -g ajv-cli
ajv validate -s https://mcp-manifest.dev/schema/v0.1.json -d mcp-manifest.json

With Python:

pip install check-jsonschema
check-jsonschema --schemafile https://mcp-manifest.dev/schema/v0.1.json mcp-manifest.json

Common Validation Errors

Missing required fields:

Error: must have required property 'server'

➜ Add the server block with name, displayName, description, version.

Invalid server name:

Error: server.name must match pattern "^[a-z][a-z0-9-]*$"

➜ Use lowercase letters, numbers, and hyphens only. Must start with a letter.

Invalid install method:

Error: install[0].method must be equal to one of the allowed values

➜ Use one of: npm, pip, dotnet-tool, cargo, binary, docker.

Invalid config type:

Error: config[0].type must be equal to one of the allowed values

➜ Use one of: string, boolean, number, path, url, secret.

Test Before Publishing

Before you publish your manifest, validate it:

  1. JSON syntax is valid
  2. Schema validation passes
  3. server.name is unique and lowercase
  4. install[].command matches your actual CLI command
  5. config[].key values are used in settings_template variables
  6. Required config fields are actually required by your server

A valid manifest is the difference between "one-click install" and "file a GitHub issue."

Publishing and Enabling Autodiscovery

You've built a manifest. Now make it discoverable.

Step 1: Add to Your Repository

Place mcp-manifest.json at the root of your repo:

my-mcp-server/
├── mcp-manifest.json  ← here
├── package.json
├── src/
└── README.md

Commit and push.

Step 2: Serve at a Well-Known URL

Clients look for manifests at:

https://your-domain.com/.well-known/mcp-manifest.json

This is the preferred discovery method for headless services and APIs.

How to set it up:

  • Static site: Add mcp-manifest.json to your .well-known/ directory
  • Express.js: Serve it as a static file or route
  • Next.js: Place it in public/.well-known/mcp-manifest.json
  • Nginx: Alias the path to your manifest file

Important: Set the correct Content-Type header:

Content-Type: application/json

And enable CORS for cross-origin client requests:

Access-Control-Allow-Origin: *

If you have a website, add this to your <head>:

<link rel="mcp-manifest" type="application/json" href="/.well-known/mcp-manifest.json" />

This works like RSS feed discovery — clients can find your manifest by fetching your homepage and parsing the HTML.

Multiple servers? Add multiple link tags:

<link rel="mcp-manifest" type="application/json" href="/manifests/analytics.json" title="Analytics Server" />
<link rel="mcp-manifest" type="application/json" href="/manifests/licensing.json" title="Licensing Server" />

Step 4: Test Discovery

Verify that clients can find your manifest:

Test the well-known URL:

curl -I https://your-domain.com/.well-known/mcp-manifest.json

Expected response:

HTTP/2 200
Content-Type: application/json
Access-Control-Allow-Origin: *

Test the HTML link tag:

curl https://your-domain.com/ | grep mcp-manifest

Expected output:

<link rel="mcp-manifest" type="application/json" href="/.well-known/mcp-manifest.json" />

Step 5: Update Your README

Tell users about autodiscovery:

## Installation

### Via MCP Client (Recommended)

Add this server from your MCP client using:

your-domain.com


The client will discover, install, and configure automatically.

### Manual Installation

```bash
npm install -g my-mcp-server

Add to your MCP config:

{
  "mcpServers": {
    "my-server": {
      "command": "my-mcp-server"
    }
  }
}

Now users have a choice: type your domain name (easy), or follow manual instructions (legacy).

### Bonus: Add a Badge

Show that your server supports manifest autodiscovery:

```markdown
[![MCP Manifest](https://mcp-manifest.dev/media/mcp-manifest-badge-light.svg)](https://mcp-manifest.dev)

This signals to users that your server is "manifest-ready."

Next Steps

You've created a manifest, validated it, and published it for autodiscovery. Here's what to explore next:

1. Add Scopes

Declare whether your server is typically configured globally or per-project:

{
  "scopes": ["global"]
}

Options:

  • global — User-wide (e.g., personal tools, account management)
  • project — Per-project (e.g., database access, project-specific APIs)
  • both — Reasonable in either scope

Clients use this to decide where to write the config (global settings file vs. project .mcp/ directory).

2. Support Multiple Transports

If your server supports SSE or streamable HTTP in addition to stdio, declare it:

{
  "transport": "sse",
  "endpoint": "https://api.your-domain.com/mcp/sse"
}

Clients will connect to the endpoint instead of spawning a process.

3. Add More Metadata

Enhance discoverability with keywords:

{
  "server": {
    "keywords": ["database", "sql", "analytics", "reporting"]
  }
}

Future MCP registries may index manifests by keywords for search.

4. Version Your Manifest

As your server evolves, update the server.version field:

{
  "server": {
    "version": "1.2.0"
  }
}

Clients can check if a newer version is available and prompt users to upgrade.

5. Explore the Full Spec

This tutorial covered the essentials. The full specification includes:

  • Binary and Docker installation methods
  • Environment variable precedence
  • Client resolution algorithm details
  • Future considerations (registries, dependencies, health checks)

6. Build Client Support

If you're building an MCP client, implement manifest discovery:

  1. Accept a domain/URL from the user
  2. Try /.well-known/mcp-manifest.json
  3. Fall back to HTML link tag discovery
  4. Parse the manifest and walk the user through install + config
  5. Write the final config using settings_template with substituted variables

Reference implementation coming soon.

7. Join the Conversation

The spec is in draft (v0.1). Feedback, questions, and contributions welcome:


You've just made your MCP server discoverable, installable, and configurable with zero manual steps.

Welcome to the post-snowflake era.

Back to Blog