What your repo needs to be accepted
When you submit a repository, we clone it and look for evidence of what it
ships. If we cannot find that evidence we reject the submission with
no_manifest — and that is a statement about file layout, not about your URL,
your credentials, or your runtime. This page is what we look for.
The fastest way to check is to look at your repository's top level. Most rejections are decided there.
The short version
A repository is accepted if either of these is true:
- It has a recognised package manifest in the root directory, or
- It carries one of the per-type markers described below.
nerlo submit https://github.com/you/your-repo --token $NERLO_API_TOKEN
A root manifest — the main path
Any one of these files, in the repository root, is enough on its own:
package.jsonpyproject.tomlCargo.tomlgemini-extension.jsongo.mod
We read the first one we find, in that order, and take the name, version, and description from it. A polyglot repository is therefore described by its richest manifest rather than by whichever we happened to see first.
If you have one of these at the root, nothing else on this page applies to you.
The trap: manifests in subdirectories
We read the root directory only. A monorepo that ships its manifests one or more levels down has no root manifest as far as ingestion is concerned, and is rejected even though it obviously contains real servers.
This is the single most common surprise. One well-known MCP monorepo carries 61
pyproject.toml files and not one of them is at the root.
If that describes your repository, you have two options: add a root manifest, or use one of the MCP self-declarations below, which are allowed to sit below the root.
MCP servers
A root manifest is the simplest path. Failing that, any one of these three self-declarations admits the repository. We check them in this order and stop at the first hit.
An official server.json
A server.json one or two directories below the root — not at the root
itself, where a manifest would already have been found.
Its $schema must point at the official MCP registry server schema: the value
has to contain both modelcontextprotocol.io and server.schema.json. Any
dated version of that schema is fine. A server.json without that $schema,
or with some other one, does not count — the schema is what makes the file a
declaration rather than a coincidence of naming.
A Smithery config
A smithery.yaml or smithery.yml at the root, containing a startCommand
key. The filename is the claim; the key inside it is the evidence, so a
Smithery config without startCommand does not admit the repo. Unlike
server.json, this one is read at the root only.
An ./mcp package entry point
A package.json one or two directories below the root whose exports map
has a "./mcp" key — what a consumer writes as
import "@your-scope/your-package/mcp".
It must be a key of the exports object. A value of "./mcp" pointing at a
directory is not a declaration and does not count.
Where we will not look
Both depth-bounded checks skip vendored and installed trees entirely — .git,
node_modules, vendor, third_party, testdata, fixtures, .venv, and
venv. A dependency's server.json is that dependency declaring something, not
you. Symlinked files are skipped too.
Claude Skills
A SKILL.md bundle in any one of three layouts. The filename is matched
case-insensitively, so skill.md works as well as SKILL.md:
skills/<name>/SKILL.md— the publish layout, what a skills library ships. A singularskill/<name>/SKILL.mdis accepted too.plugins/<plugin>/skills/<name>/SKILL.md— the plugin layout..claude/skills/<name>/SKILL.md— the consumption layout.
One caveat worth knowing. If your repository is primarily software — it has
a root package.json, pyproject.toml, Cargo.toml, go.mod, setup.py,
Gemfile, pom.xml, or build.gradle — then a stray skill bundle kept for
your own workflow does not retype the repository as a Claude Skill. A Go MCP
server that keeps a release-automation skill is still listed as an MCP server.
A repository whose product genuinely is a library of skills is still listed as
skills.
Cursor rules
Either of these, at the root:
- a
.cursorrulesfile, or - a
.cursor/rules/directory — the modern.mdcrule layout.
Gemini extensions
A gemini-extension.json at the root.
How we classify it depends on what is inside, and this catches people out:
- With an
mcpServerskey, we list it as an MCP server. An MCP server packaged for Gemini is still, in substance, an MCP server. - Without one, we list it as a Gemini extension.
A gemini-extension.json under .gemini/extensions/<name>/ is the layout
produced on a machine that has installed an extension, not the layout of a
repository that publishes one, so it is not a marker.
Size limit
A clone larger than 500 MiB is rejected as size_exceeded rather than
scanned. This is a different outcome from no_manifest: we found your
repository, it is simply too large for the ingest budget. A shallow,
history-light repository or a smaller extracted package is the way through.
After you submit
Submission queues the repository for ingestion and scanning and returns immediately. There is no flag that waits for the result, so a pipeline cannot yet block on the verdict for a repo it just submitted.
You can state the artifact kind explicitly instead of letting us infer it:
nerlo submit https://github.com/you/your-repo --type mcp_server --token $NERLO_API_TOKEN
--type takes mcp_server, claude_skill, gemini_extension, or
cursor_rule.
For the full command reference see the CLI reference, and for installing or upgrading the tool itself see install and upgrade.