Quality score
Trust answers “can I believe this listing”. Quality answers “is this a good MCP server to connect an agent to”. Both are transparent: the descriptor lists every check, its status and the evidence that decided it.
Checks run on what our credential-free, read-only probes observe. Tools are never called and no code audit is performed.
Categories and weights
Section titled “Categories and weights”| category | weight | what it looks at |
|---|---|---|
| protocol | 25 | 2026-07-28 support, stateless answers, legacy-only transports, cacheable tool lists (ttlMs) |
| auth | 15 | for OAuth servers: Protected Resource Metadata, authorization server metadata, CIMD versus the deprecated DCR; for every server: no secret in the URL |
| hygiene | 30 | tool descriptions present and concise, object input schemas, readOnlyHint or destructiveHint annotations, all four directory hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint; the Claude and OpenAI directories each require some of them) on every tool, the token cost of loading every tool, “an API mapped one endpoint per tool”, and hidden text in tool definitions |
| stability | 15 | tool changes in the last 30 days, any tool that kept its name but changed most of its description, and changes that named a new site |
| dependencies | 15 | for servers with an npm or PyPI package: known advisories in the resolved dependency graph (failing on a high or critical one that has a fix), the official MCP SDK’s own advisories, and whether the graph resolved completely |
Each check is pass (1 point), warn (half), fail (0) or na.
A category’s value is the average of its applicable checks; the score is the weighted average of the categories that apply.
Checks that do not apply leave the denominator, and a server with fewer than three applicable checks outside dependencies gets no score (null) rather than a flattering one: dependencies never score a server on their own, so a local-only package shows its dependency report without a score.
Stability only counts after a week of recorded tool history: not having seen a change yet is not evidence of stability.
Servers behind OAuth or an API key usually have no public tool list, so their hygiene and stability checks are na and the report says so.
Labels
Section titled “Labels”Next to the number, every server gets a plain-language label. Labels describe the signals above; none of them says a server is safe, and no audit is implied.
| label | when |
|---|---|
strong |
quality 90 or more and trust 75 or more |
good |
quality 75 or more |
needs work |
quality 40 to 74 |
poor |
quality under 40, or a tool hides text (even on a new server) |
new |
the server appeared in the registry less than 7 days ago |
not scored |
fewer than three checks apply (usually a local package or a remote nobody could probe) |
blocked |
the listing is on the blocklist |
The label is in the descriptor (_meta["dev.protogrid/quality"].label), in /quality, and in every search result as quality_label next to quality_score.
Colors on the portal and the badge use the same thresholds: green from 75, amber from 40, red below.
Where to find it
Section titled “Where to find it”- The descriptor’s
_meta["dev.protogrid/quality"]:score,components,checks(id, category, status, detail),drivers(-idfor failures,~idfor warnings),flags(see quality flags),tool_count,token_estimate,tools_hash,tools_changed_at. GET /v1/servers/{name}/qualityadds the daily score for the last 90 days;GET /v1/servers/{name}/changeslists the tool-definition history (see the REST API).- Every server page on the portal has a Quality section with each check and its evidence, the token cost of the tool list and the latest tool-definition changes, with the description before and after.
tools_hashchanges whenever any tool’s name, description, schemas or annotations change: pin it to notice a change before your agent trusts the new definitions.
Every server has a public SVG badge with its current quality score, at https://protogrid.dev/badge/{name}.svg.
It follows the score within about five minutes and says not scored when too few checks apply.
When the server’s owner has claimed it, the badge adds an owner verified segment; add ?owner=0 to the image URL for the compact version.
Paste it into a README, linking to the server page:
[](https://protogrid.dev/servers/dev.protogrid/registry)The server page has the snippet ready to copy under “Badge for your README”.
Hidden text
Section titled “Hidden text”A model reads every character of a tool’s definition; a person reviewing it on screen does not.
tools.hidden_text looks for text that only the model sees, in each tool’s name, description, input schema and annotations.
It fails when a tool holds something that can carry a whole instruction:
- Unicode tag characters, which spell ASCII text invisibly;
- runs of variation selectors, which can hide bytes;
- bidirectional controls, which reorder what is displayed;
- HTML hidden from view (
display:none,visibility:hidden, zero size or opacity, thehiddenattribute); - an HTML or Markdown comment addressed to the model, a tool or an instruction.
It warns on stray invisible characters, such as zero-width spaces, and on other long comments. Text quoted as code with backticks, a single emoji variation selector, and the joiners used inside Persian, Arabic, Hebrew or emoji sequences never count.
The evidence names the tools and the kind of hidden text, never the text itself, so the descriptor cannot pass the instruction on to the next model that reads it.
Hidden text is not benign: a failure caps the score at 39 and labels the server poor, and raises the hidden-text quality flag.
New sites
Section titled “New sites”When a tool is added or its definition changes, protogrid lists the sites it names, through a URL, a domain or an email address, and keeps those that no earlier definition of the server named.
The server’s own sites never count: its namespace read as a domain, its website, its repository and its remote endpoints.
Placeholders such as example.com, local names and addresses are ignored.
A new site is the shape of a change that starts sending data somewhere else while the wording stays almost the same, which the change-of-meaning check cannot see.
It is also how documentation grows, so stability.new_links warns rather than fails, for 30 days.
The change history on the server page, the Markdown twin and /changes mark each such change with its sites, and the alerts and webhooks of those who monitor the server name them.
Dependencies
Section titled “Dependencies”For servers published as an npm or PyPI package, protogrid reads the dependency graph of the published version as a clean install would resolve it, and matches every package in it against known advisories. Nothing is downloaded, installed or run. Advisories are matched against every graph daily, so a newly published advisory shows up without the server changing. A new release is read right away; otherwise graphs are refreshed monthly, and daily for servers their owners claimed and monitor.
deps.known_vulnsfails when a package in the graph has a high or critical advisory (or is a known malicious package) and a fixed version exists, and warns on any other advisory; the evidence names the advisory, the package and whether it is a direct or indirect dependency.deps.mcp_sdk_versionlooks at the official MCP SDK (@modelcontextprotocol/sdkand its v2 packages on npm,mcpon PyPI) the same way and says which version to update to; its advisories are not counted again indeps.known_vulns.deps.resolvablewarns when the graph resolved with errors, because advisories may then be missing, and isnawhen the package version is not known to the resolver.
Remote-only servers have no package, so their dependency checks are na.
Quality flags
Section titled “Quality flags”Three failures also become flags you can filter search on:
| flag | raised when |
|---|---|
known-vulns |
deps.known_vulns fails: a high or critical advisory with a fixed version, or a known malicious package |
outdated-mcp-sdk |
deps.mcp_sdk_version fails: the official MCP SDK version in use has such an advisory |
hidden-text |
tools.hidden_text fails: a tool definition holds text a model reads but a person does not see |
A warning never raises a flag.
Every search result carries quality_flags[]; exclude_quality_flags=known-vulns leaves flagged servers out, quality_flags= keeps only them, and min_quality= sets a minimum score (servers that are not scored are left out).
The same filters exist on the MCP search tool.
A server without the flag is not a server without vulnerabilities: remote-only servers publish no package, so nothing can be said about their dependencies.
Data sources: dependency graphs from deps.dev and advisories from OSV.dev, including the GitHub Advisory Database and the PyPI Advisory Database, all under CC BY 4.0.