Thank you for contributing! Every definition in servers/ becomes a one-click install for every McpMux user once it's merged. This guide walks you through the full lifecycle of a contribution: license, setup, definition anatomy, validation, PR workflow, review criteria, and the trademark rules that keep the registry honest.
If you're in a hurry, jump to:
- Quick Start — five commands to a green PR
- Anatomy of a Definition — every field, whether it's required, and what it does
- PR Checklist — what CI and reviewers verify
- Troubleshooting — fixes for the validation errors you'll actually hit
Looking to request a server rather than add one yourself? Open a Request a Server issue. Looking to report a broken definition? Use the Bug in a Server Definition template.
- License & DCO
- Prerequisites
- Quick Start
- Repository Layout
- Anatomy of a Definition
- ID & Filename Rules
- Transports
- User Inputs
- Authentication
- Categories
- Capabilities
- Links, Contributor, Platforms, Media
- Platform-Managed Fields (Do Not Set)
- Validation Commands
- PR Workflow
- PR Checklist
- Review Criteria
- Updating an Existing Server
- Removing a Server
- Trademark & Branding Policy
- Security Expectations
- Troubleshooting
- Questions & Help
This repository is licensed under the Elastic License 2.0 (ELv2). By contributing, you agree that your contributions will be licensed under the same license.
All contributions require a Developer Certificate of Origin sign-off on every commit. Add it automatically with -s:
git commit -s -m "Add community.my-server"That produces a trailer like Signed-off-by: Your Name <you@example.com> on the commit. By signing off, you certify the statements in the Developer Certificate of Origin v1.1:
- You created the contribution, or
- It's based on work you have the right to submit under this license, or
- It was provided to you by someone who certified the above.
CI rejects unsigned commits. If you forget, amend with git commit --amend -s --no-edit and force-push your branch.
- Node.js 20+ and pnpm 9+ — no Rust, no native dependencies.
- A GitHub account with your fork of this repo.
- A text editor with JSON support. VS Code picks up
"$schema": "../schemas/server-definition.schema.json"and gives you field-level autocomplete and inline errors while you type.
# 1. Fork on github.com, then clone your fork
git clone https://github.com/<you>/mcp-servers.git
cd mcp-servers && pnpm install
# 2. Copy the closest starter template
cp examples/complete-example.json servers/community.my-server.json
# 3. Edit the file (at minimum: id, name, description, transport, categories, links.repository)
# 4. Validate locally
pnpm validate servers/community.my-server.json
pnpm check-conflicts
pnpm test
# 5. Commit (signed off) and open a PR
git checkout -b add-my-server
git add servers/community.my-server.json
git commit -s -m "Add community.my-server"
git push origin add-my-server
# Open the PR in your browser from the link git printsYour PR will be validated automatically and reviewed by a maintainer. Once merged, the next registry bundle build publishes the definition to every McpMux install.
mcp-servers/
├── servers/ # One JSON file per server — the registry
├── schemas/
│ └── server-definition.schema.json # JSON Schema 2020-12 — the contract
├── categories.json # Allowed category IDs
├── examples/
│ ├── complete-example.json # stdio server with API key + obtain
│ ├── remote-hosted-example.json # HTTP server with OAuth
│ ├── read-only-example.json # HTTP docs/search server, no auth
│ └── sponsored-example.json # stdio with integration token
├── scripts/
│ ├── validate.js # AJV validation + conflict detection
│ └── build-bundle.js # Aggregates definitions into bundle.json
├── tests/ # Vitest — schema, consistency, categories
├── bundle/ # Generated — do not edit
└── CONTRIBUTING.md # This file
Pick the example that most resembles the server you're contributing and start from there — it's faster than building from scratch and ensures you don't miss fields.
Every server is a single JSON file at servers/<id>.json. The schema at schemas/server-definition.schema.json is the authoritative spec. This section summarises what you'll actually use.
Three fields are non-negotiable — validation fails without them:
| Field | Type | Notes |
|---|---|---|
id |
string | Pattern ^[a-z0-9]+\.[a-z0-9][a-z0-9-]*$. See ID & Filename Rules. |
name |
string | Human-readable display name. Minimum length 1. |
transport |
object | See Transports. Must be stdio or http. |
Strongly encouraged — the registry UI shows them prominently and users filter by them:
| Field | Type | Notes |
|---|---|---|
$schema |
string | "../schemas/server-definition.schema.json" — gives editors autocomplete. |
description |
string | One sentence, plain English. Shown in listings. |
alias |
string | Short CLI alias, pattern ^[a-z0-9-]+$. Must not collide with any id or other alias. |
logo |
string | Stable HTTP(S) URL to a logo image (avatar or project PNG/SVG). Emoji are not accepted — use a GitHub avatar URL like https://avatars.githubusercontent.com/u/{id}?v=4. McpMux does not host logo files. (The legacy field name icon is still accepted for backward compatibility, but logo is the preferred name.) |
schema_version |
string | Currently "2.1". Bump when the project publishes a new contributor schema version. |
categories |
string[] | IDs from categories.json. At least one. |
tags |
string[] | Lowercase keywords for search ("git", "search", "wiki"). 3–8 is a good target. |
auth |
object | See Authentication. Tells users what credential they need. |
contributor |
object | Who's adding this (name, github, url). |
links |
object | repository, homepage, documentation. At least repository if one exists. |
platforms |
string[] | ["all"] or any subset of ["windows", "macos", "linux"]. |
capabilities |
object | tools, resources, prompts, read_only_mode — see Capabilities. |
Use when they add value:
| Field | Type | Notes |
|---|---|---|
media.screenshots |
string[] | Up to 5 URLs. Recommended 1200×800. |
media.demo_video |
string | YouTube / Vimeo / similar. |
media.banner |
string | For featured display (1200×400). |
changelog_url |
string | Releases or CHANGELOG URL. |
IDs follow the format {tld}.{publisher}-{name}:
| Namespace | Who | Examples |
|---|---|---|
com.* |
Official publisher or a well-known org with legitimate claim to the namespace | com.github-mcp, com.notion-mcp, com.cloudflare-docs |
community.* |
Third-party contributors — this is where most submissions live | community.sqlite, community.brave-search |
Additional rules the validator enforces:
- Lowercase only. No uppercase, no spaces, no underscores in the publisher or name segment.
- Regex:
^[a-z0-9]+\.[a-z0-9][a-z0-9-]*$. One dot, first segment is TLD-ish, second segment starts with alphanumeric and may contain hyphens. - Filename must match ID:
servers/com.github-mcp.json. The bundler derives the filename from the ID. - Uniqueness:
pnpm check-conflictsfails if an ID is reused, or if analiascollides with anyidor otheraliasin the registry. - Multiple servers per publisher are fine:
com.cloudflare-docsandcom.cloudflare-bindingscoexist happily.
Pick the alias deliberately — users type it, so short and obvious wins (gh, brave, cf-docs).
Exactly one of two transport types is allowed. The oneOf in the schema means mixing fields from both will fail validation.
Spawns a process on the user's machine; McpMux speaks MCP over stdin/stdout. Use for CLI tools, local SDKs, and anything that already ships as npx, uvx, docker, python, or node.
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@brave/brave-search-mcp-server"],
"env": {
"BRAVE_API_KEY": "${input:BRAVE_API_KEY}"
},
"metadata": {
"inputs": [
{
"id": "BRAVE_API_KEY",
"label": "Brave Search API Key",
"type": "password",
"required": true,
"secret": true,
"placeholder": "BSAxxxx",
"obtain": {
"url": "https://brave.com/search/api/",
"instructions": "1. Sign up\n2. Subscribe (free tier)\n3. Copy the key",
"button_label": "Get API Key"
}
}
]
}
}Allowed fields: type, command (required), args, env, cwd, metadata. Nothing else — additionalProperties: false.
Command choice matters:
npx -y <pkg>anduvx <pkg>are preferred — they install on first run with no separate setup.docker run --rm -i ...works too; use-ifor stdin and--rmto clean up.python,node, or a raw binary path only if users realistically have them available.- Avoid
bash -c,sh -c, or anything that invokes a shell to run a script. See Security Expectations.
Points at a hosted Streamable-HTTP MCP server. No local install required.
"transport": {
"type": "http",
"url": "https://mcp.atlassian.com/v1/mcp",
"headers": {
"X-My-Header": "${input:HEADER_VAL}"
},
"metadata": { "inputs": [] }
}Allowed fields: type, url (required), headers, metadata. headers values support ${input:ID} interpolation just like env.
Prefer HTTP transport when the upstream is a SaaS product — users don't have to install anything locally, and OAuth flows stay clean.
Anything a user has to provide — API keys, file paths, workspace slugs, enable/disable toggles — goes through transport.metadata.inputs. McpMux renders a setup form from this array, substitutes values at runtime, and encrypts secrets.
Reference inputs as ${input:ID} anywhere inside env, args, or headers:
"env": { "GITHUB_TOKEN": "${input:GITHUB_TOKEN}" },
"args": ["--db-path", "${input:DB_PATH}"],
"headers": { "Authorization": "Bearer ${input:TOKEN}" }Every placeholder must have a matching inputs[].id. The build-bundle.js step doesn't catch this today, but the UX breaks silently if a user submits the form and McpMux tries to substitute a placeholder that has no corresponding input.
| Property | Required | Notes |
|---|---|---|
id |
Yes | Uppercase regex ^[A-Z0-9_]+$ (e.g. API_KEY, DB_PATH). Matches the ${input:ID} placeholder. |
label |
Yes | Human-readable label shown in the UI. |
type |
No | See Input Types. Default "text". |
required |
No | Default false. Required inputs block form submission until provided. |
secret |
No | Default false. true → stored in the OS keychain, never written to plain SQLite or logged. |
description |
No | Help text shown beneath the field. |
default |
No | Pre-filled value used when the user provides none. |
placeholder |
No | Greyed hint inside the input. |
obtain |
No | See The obtain Block. |
options |
No | For type: "select" only — array of { value, label, description? }. |
Pick the type that matches how the value is used:
| Type | When to use |
|---|---|
text |
Free-form string (default). |
number |
Numeric values — the UI renders a number input. |
boolean |
On/off toggles — becomes "true" / "false" as a string when substituted. |
url |
URLs — the UI validates shape. |
select |
Closed set of options — pair with options: [{ value, label, description? }]. |
file_path |
File picker. |
directory_path |
Directory picker. |
There is no dedicated password input type in the schema enum — use type: "text" plus secret: true for API keys. (The UI masks secret fields regardless of type.) Existing definitions use type: "password" informally; the schema's strict input-type enum is the source of truth, so prefer text + secret: true for new submissions.
Always pair API keys with secret: true and required: true. That's how McpMux knows to stash the value in the OS keychain instead of SQLite.
Whenever a value requires the user to visit a dashboard, sign up, or generate a token, include an obtain block. It becomes a one-click button next to the field in the setup UI.
"obtain": {
"url": "https://github.com/settings/tokens/new",
"instructions": "1. Click 'Generate new token (classic)'\n2. Give it a name like 'McpMux'\n3. Select scopes: repo, read:org\n4. Click 'Generate token'\n5. Copy the token (starts with ghp_)",
"button_label": "Create Token"
}Guidance:
- Number your steps and use
\nbetween them — the UI renders each as a separate line. - Be specific about scopes / permissions users should check. A GitHub token with no scopes is useless; they'll open an issue asking why the server "doesn't work".
- Link deep when you can (
.../settings/tokens/new?scopes=repo,read:org&description=McpMux) — GitHub and a few others pre-fill the form. - Keep
button_labelshort (<= 20 characters). "Create Token", "Get API Key", "Open Console".
The top-level auth field advertises what kind of credential the server expects, independent of how it's wired up in transport.
type |
When to use |
|---|---|
none |
No credentials (local filesystem, public docs, open search). |
api_key |
A token is required for any useful call. |
optional_api_key |
Works without auth (rate-limited or read-only) but unlocks more with a key. |
oauth |
Full OAuth 2.1 + PKCE flow. McpMux drives the redirect. |
basic |
HTTP Basic auth (username + password). Rare; prefer api_key or oauth. |
"auth": {
"type": "api_key",
"instructions": "Get an API key at https://brave.com/search/api/"
}instructions is free-form. Keep it to one or two sentences — the detailed flow belongs in the input's obtain block.
Note:
auth.typeis a hint to the UI; McpMux doesn't use it to decide whether to inject credentials into the transport — that's driven byinputsand${input:...}placeholders. Both need to be consistent.
Every server should declare at least one category from categories.json:
| ID | Name |
|---|---|
developer-tools |
Developer Tools |
version-control |
Version Control |
cloud |
Cloud Services |
productivity |
Productivity |
database |
Database |
search |
Search & Web |
communication |
Communication |
file-system |
File System |
documentation |
Documentation |
ai-ml |
AI & Machine Learning |
monitoring |
Monitoring & Observability |
security |
Security |
design |
Design |
Multiple categories are fine when they genuinely apply (e.g. ["productivity", "documentation"] for a wiki tool). Don't stuff every plausible category — it dilutes filtering.
Need a new category? Open an issue before opening your PR — category changes touch categories.json, the schema tests, and the discovery UI, so they're easier to land separately.
Declare what the server actually implements — the registry UI uses these as filters.
"capabilities": {
"tools": true, // Exposes callable MCP tools
"resources": true, // Provides readable MCP resources
"prompts": false, // Provides prompt templates
"read_only_mode": false // true = server never writes or performs destructive actions
}tools,resources,promptsmirror the MCP capabilities the server advertises oninitialize.read_only_mode: trueis a strong claim. Set it only for docs lookup, search, observability dashboards, and similar — any write endpoint disqualifies. This drives a filter users rely on to sandbox agents.
If you're unsure, install the server locally, run it, and check the initialize response. Declaring a capability the server doesn't implement is a reviewer reject.
"links": {
"repository": "https://github.com/brave/brave-search-mcp-server",
"homepage": "https://brave.com/search/api/",
"documentation": "https://api.search.brave.com/app/documentation"
}At least repository when one exists. Format: valid URI. Broken links are a reviewer reject.
"contributor": {
"name": "Brave",
"github": "brave",
"url": "https://brave.com"
}This is you, or the org you represent. github is a username without the @.
"platforms": ["all"]
// or
"platforms": ["macos", "linux"]Use ["all"] unless the server genuinely doesn't run on one of Windows / macOS / Linux (e.g. it spawns .dll hooks). Containerised servers are typically all.
Optional but discoverable — up to 5 screenshots, one demo video, one banner:
"media": {
"screenshots": [
"https://example.com/screens/1.png",
"https://example.com/screens/2.png"
],
"demo_video": "https://youtube.com/watch?v=...",
"banner": "https://example.com/banner-1200x400.png"
}Only reference images you have the right to display. McpMux hot-links these; a broken or rotating URL will look bad in the registry.
The validator strips and warns on these during pnpm validate. Setting them in a PR is a review reject:
badges— computed from publisher verification statusstats— installs, stars, usage metrics (computed)sponsored— commercial sponsorship slot (McpMux only)featured— homepage feature selection (McpMux only)publisher.official,publisher.verified,publisher.domain_verified— require maintainer verification- Any top-level key prefixed with
_platform— reserved for the bundler
If you believe your server qualifies for verified or official status, say so in the PR description and a maintainer will follow up separately.
Run locally before pushing — CI runs the same commands and will fail the PR otherwise:
pnpm validate servers/community.my-server.json # Validate a specific file
pnpm validate:all # Validate every server
pnpm check-conflicts # ID / alias collision detection
pnpm test # Full vitest suite (schema, consistency, categories, bundle)
pnpm build # Generate bundle/bundle.json (optional locally)validate accepts multiple file arguments: pnpm validate servers/a.json servers/b.json. It prints:
PASS servers/your-file.json— structurally valid.FAIL servers/your-file.json— followed by- /field/path: reasonlines from AJV.WARNING:lines when platform-managed fields are present (they'll be stripped at build time).
check-conflicts reports:
CONFLICT Duplicate ID "..."— sameidin two files.CONFLICT Duplicate alias "..."— samealiasin two files.CONFLICT ID "..." collides with alias in ...— your ID matches an existing alias (or vice versa).
-
Fork & branch. Keep unrelated changes on separate branches; one server per PR keeps review fast.
-
Add or edit the JSON in
servers/<id>.json. -
Validate locally — all three commands should pass before you push:
pnpm validate servers/<id>.json pnpm check-conflicts pnpm test
-
Commit with sign-off:
git commit -s -m "Add community.my-server" -
Push and open a PR. Use the PR template. A good title is
Add com.example-toolorFix env var name for community.sqlite. -
CI runs. The
validate-prworkflow runs the same commands you ran locally, plus bundle regeneration. Red tick → read the log, fix, push again (no need to close/reopen). -
Review. A maintainer will sanity-check the definition and, for stdio servers, may test the install locally. Expect feedback within a few days; poke the thread if you don't hear back in a week.
-
Merge. On merge, the registry bundler picks up the new definition and uploads a fresh
bundle.jsonto Cloudflare R2. Desktop apps see it on their next refresh (usually under a minute).
CI automatically verifies:
- JSON file placed in
servers/ - Filename matches the
idfield (servers/com.example.json↔"id": "com.example") - Schema validates (
pnpm validate) - No ID / alias conflicts (
pnpm check-conflicts) - All required fields present (
id,name,transport) - DCO sign-off on every commit
Reviewers additionally check:
-
descriptionis accurate, one sentence, and doesn't oversell -
transport.command/transport.urlactually works (for stdio, preferably installable vianpx/uvx/docker) - Every
${input:ID}placeholder has a matchinginputs[].id - Secrets are marked
secret: trueso they land in the OS keychain -
obtainblocks exist and have usable instructions for every credential - At least one valid category from
categories.json -
links.repository(when one exists) is public and resolves -
capabilitiesreflect what the server actually implements - No platform-managed fields (
badges,stats,sponsored,featured,publisher.official, etc.) - No trademark / branding violations (see below)
Beyond the checklist, reviewers apply judgement on:
- Is this useful to more than one person? The registry isn't a dump for hobby scripts. A "works on my machine" tool with no upstream repo is usually a reject.
- Is the upstream project alive? Commit recency, open issue triage, signs of maintenance. Abandoned servers get nudged toward archival.
- Is the install command correct on every declared platform? If you claim
platforms: ["all"]and the command only works on Linux, that's a blocker. - Does the description avoid marketing fluff? "The best MCP server for X" → no. "Search Brave with privacy-focused results" → yes.
- Does the definition avoid claiming official/verified status it hasn't earned? That's an auto-reject — see the policy below.
Same process as adding one: edit the JSON in servers/, validate, sign-off commit, PR. Title the PR Update <id>: <what changed> so it's obvious at a glance.
If you're the original contributor, you can make changes freely. If someone else originally submitted the server, prefer opening an issue first to coordinate — especially for breaking changes like renaming inputs, changing the transport, or adjusting scopes.
Never change an id on an existing server. Users with the server installed will lose their configuration. If you truly need to rename, add a new file under the new ID and flag the old one for archival in the PR description.
Open an issue or PR explaining why (upstream archived, security issue, duplicate, trademark takedown). Maintainers handle the actual removal so the bundler knows to signal existing installs.
The registry is community-built but consumed via the McpMux brand, so we're careful here.
- Descriptive use is fine: "MCP Server for GitHub", "Connects to Notion", "Works with Linear".
- Implied endorsement is not: "Official GitHub MCP", "Certified by Notion", "Endorsed by Linear" — unless you actually represent the trademark owner and have been verified.
- Phrases to prefer: works with, for, connects to, integrates with.
- McpMux does not host logo files. The
logofield must be an HTTP(S) URL to an image. Emoji are not accepted. - Only reference assets you have the right to use. Corporate logos are usually fine for descriptive, non-commercial use in a directory listing; but don't hot-link assets with restrictive terms.
- Prefer GitHub avatars (
https://avatars.githubusercontent.com/u/<id>?v=4) or official brand URLs over random CDN copies that may rotate. - The legacy field name
iconis still accepted for backward compatibility, but new submissions should uselogo.
- All community submissions default to unverified.
publisher.official,publisher.verified,publisher.domain_verified, and"official"/"verified"badges are maintainer-controlled. PRs that set them are rejected unless submitted by a verified publisher.- To request verification, open an issue from your org's GitHub account and we'll follow up.
See TRADEMARK-TAKEDOWN.md for the full IP policy and the maintainer contact path.
By opening a PR you agree that:
- You have the right to reference any URLs you include.
- You're not claiming official status or endorsement you don't have.
- You accept responsibility for trademark compliance of your submission.
- You grant McpMux a license to display your definition — including rendering any referenced logo URLs — on the discover site and desktop app.
- McpMux may modify or remove your submission in response to trademark concerns.
The registry installs code on end-user machines (for stdio servers) and proxies credentials on their behalf. We hold definitions to a higher bar than typical community packages.
- No arbitrary shell scripts.
bash -c "curl ... | sh"and similar get rejected on sight. Stick tonpx,uvx,docker,python,node, or HTTP endpoints. - Credential sanity. Mark every token/key with
secret: true. Never bake credentials intoargsthat persist in process listings; route them throughenvinstead. - No network side effects from the definition itself. The JSON file must be static — no fetched values, no templated install scripts.
- OAuth prefers PKCE. If
auth.type: "oauth", the upstream should support PKCE; otherwise add a note inauth.instructions. - Declare
read_only_mode: truehonestly. A server that deletes calendar events is not read-only, no matter how "safe" the tool author thinks the call is. - Report vulnerabilities privately. If you spot a security issue in an existing definition or upstream server, don't open a public issue — email the maintainers (see repo for current contact) or use GitHub private vulnerability reporting.
You've violated a regex. The two most common culprits:
id: must match^[a-z0-9]+\.[a-z0-9][a-z0-9-]*$. No uppercase, no underscores in the publisher segment, exactly one dot.- Input
id: must match^[A-Z0-9_]+$. Uppercase + underscores only.
You've mixed fields from both transport types. An http transport can't have command/args/env/cwd; a stdio transport can't have url/headers. Start fresh from the matching example in examples/.
A transport with "type": "stdio" requires command. A transport with "type": "http" requires url. These are enforced by the transport's oneOf branches.
Someone else already registered that ID. Pick another — or if you're updating, make sure you're editing the existing file rather than creating a new one.
Aliases share a namespace with IDs. Pick a different alias.
You've set a field the platform controls. Remove it from your definition — leaving it in works (the validator strips it) but a reviewer will ask you to clean it up anyway.
A URL field isn't a valid URI. Common fix: missing scheme. example.com fails; https://example.com passes.
"$schema": "../schemas/server-definition.schema.json" works when the file is in servers/. If you moved it, adjust the relative path. Restart the JSON language server if it's stuck.
- Ask a question or request a new category: open an issue.
- Discuss designs or roadmap: GitHub Discussions on the main repo.
- Browse before contributing: the live registry is at mcpmux.com — search first to avoid duplicates.
- Starter templates:
examples/— copy the closest match.
Thanks for making the registry better.