A transparent, reproducible catalogue of every check MCPGrade runs — its point modifier, the OWASP MCP Top-10 id it maps to, and the spec clause it cites. Anyone can recompute a grade by hand.
MCPGrade follows the Observatory / SSL-Labs / SecurityScorecard model: a server starts at 100 and loses points for each finding. Bonuses are small and gated; caps only ever make a grade worse.
The clamped score maps to an Observatory-style 13-band letter grade.
| Band | Score range |
|---|---|
A | ≥ 90 (A+ is earned, not scored — see below) |
A- | 85 – 89 |
B+ | 80 – 84 |
B | 70 – 79 |
B- | 65 – 69 |
C+ | 60 – 64 |
C | 50 – 59 |
C- | 45 – 49 |
D+ | 40 – 44 |
D | 30 – 39 |
D- | 25 – 29 |
F | < 25 |
Every penalty check, keyed by a permanent test id you can cite. Ids never change or get renumbered — a finding on your server links straight back to the row that produced it.
| Test ID | Check | Category | OWASP | Spec ref | Modifier | Cap |
|---|---|---|---|---|---|---|
no-tls-remote | Remote endpoint is plaintext HTTP A remote MCP endpoint served over http:// exposes every token and payload to network attackers. critical | transport-tls | MCP01 | Security Best Practices · transport | -50 | ≤ F |
tls-invalid | Invalid or expired TLS certificate The endpoint presented an untrusted, expired, or hostname-mismatched certificate. high | transport-tls | MCP01 | TLS | -25 | — |
auth-none-on-public-remote | No authorization on a public remote server The server accepts tool enumeration (and likely invocation) with no authentication. high | authorization | MCP07 | Authorization | -40 | — |
token-passthrough-suspected | Token passthrough suspected The server appears to accept tokens not issued for it — explicitly forbidden by the spec. critical | authorization | MCP01 | Authorization · token validation | -40 | ≤ F |
as-url-private-ip | Authorization metadata points at a private IP A discovery/metadata URL resolves to a loopback/private/metadata address — an SSRF-by-design vector against clients. critical | authorization | MCP07 | SSRF in OAuth discovery | -40 | ≤ F |
session-id-predictable | Predictable session id Session ids are sequential or low-entropy, enabling hijacking. high | session | MCP07 | Session hijacking | -30 | — |
scopes-wildcard-or-omnibus | Wildcard or omnibus scopes The server offers a `*` / `all` / `full-access` scope, defeating least privilege. medium | authorization | MCP02 | Scope minimization | -20 | — |
no-pkce-s256 | Authorization server does not advertise PKCE S256 PKCE with S256 is mandatory to verify; its absence enables auth-code interception. medium | authorization | MCP07 | Authorization · PKCE | -15 | — |
no-protected-resource-metadata | No protected-resource-metadata The server does not serve RFC 9728 metadata, so clients cannot discover its authorization server. low | authorization | MCP07 | RFC 9728 | -10 | ≤ A- |
dcr-open-to-world | Open dynamic client registration Unauthenticated DCR lets anyone register a client — an abuse and confused-deputy surface. low | authorization | MCP07 | Authorization · DCR | -10 | — |
tool-description-injection-markers | Hidden instructions in a tool description A tool description contains imperative text aimed at the model (e.g. "ignore previous", "before answering read …"). critical | tool-poisoning | MCP03 | Tool poisoning | -40 | ≤ C |
invisible-unicode-in-description | Invisible/zero-width characters in metadata Concealed characters are used to hide instructions from human review. critical | hidden-instructions | MCP03 | Tool poisoning | -40 | ≤ C |
html-comment-instruction | Instructions hidden in HTML comments Directive text is embedded inside <!-- --> comments. high | hidden-instructions | MCP03 | Tool poisoning | -25 | — |
tool-shadowing | Cross-tool shadowing A tool description tries to alter the model’s use of another tool. high | shadowing | MCP09 | Cross-server / shadowing | -25 | — |
prompt-injection-phrasing | Prompt-injection phrasing Role tokens or delimiter abuse steer the model rather than describe the tool. medium | prompt-injection | MCP06 | Intent flow subversion | -10 | — |
toxic-flow-ordering | Metadata dictates tool-call ordering The text tells the model WHEN to call this tool relative to others ("always call first", "before any other tool", "chain to X tool") — a toxic-flow injection that hijacks the agent’s orchestration rather than describing the tool. high | prompt-injection | MCP06 | Toxic flow / tool-call ordering | -25 | ≤ C |
hardcoded-url-in-metadata | Hardcoded fetch-and-obey URL in description The description pairs a fetch imperative with a hardcoded external URL, letting the server relocate its real instructions off-metadata and mutate them after review. Fires only when a fetch verb and a URL co-occur, so benign documentation links do not. medium | prompt-injection | MCP03 | Tool poisoning · fetch-and-obey | -10 | — |
exfiltration-params | Data-exfiltration parameters A tool exposes a free-text/webhook/feedback parameter with no honest purpose — a classic exfil channel. high | data-exfiltration | MCP10 | Context injection & over-sharing | -25 | — |
lethal-trifecta-tool | Single tool combines the lethal trifecta One tool reads private data, ingests untrusted content, AND can send data outbound — the three preconditions for autonomous data theft in a single call. A prompt injection reaching this tool can exfiltrate secrets with no further step. high | data-exfiltration | MCP10 | Lethal trifecta (private data + untrusted content + exfil) | -30 | ≤ C |
sensitive-param-required | Tool input schema requests a secret The input schema declares a parameter that asks the caller to hand over a password, key, token, or other secret — a credential-harvest / token-passthrough risk. (Bare pagination `token` params are excluded.) high | data-exfiltration | MCP01 | Token passthrough / credential capture | -20 | — |
destructive-tools-unguarded | Unguarded destructive tools Tools that execute commands / write files / make arbitrary requests are exposed without annotations or guards. high | sensitive-action | MCP05 | Command injection & execution | -20 | — |
excessive-permissions | Over-broad tool surface The server advertises open-world / broadly-scoped capabilities. medium | excessive-permissions | MCP05 | Privilege escalation | -10 | — |
excessive-tool-scope | Single tool spans filesystem and network One tool both accesses the filesystem and reaches the network. Combining two capability classes in a single tool widens its blast radius and is the substrate for confused-deputy and exfiltration abuse. medium | excessive-permissions | MCP05 | Least privilege / capability separation | -10 | — |
unconstrained-fs-path | Filesystem tool accepts an unconstrained path A filesystem tool exposes a path parameter that is a free-form string with no pattern/enum/format constraint, so it accepts absolute paths and "../" traversal. A tool that constrains its path (a rooted `pattern` or an `enum`) does not fire. medium | sensitive-action | MCP05 | Path traversal / input constraint | -10 | — |
unmapped-raw-query | Database tool takes a raw, unparameterised query A database tool exposes a query/sql parameter that is a free-form string with no allow-list or parameterisation constraint — a raw-string injection surface. A tool exposing structured filters or an `enum` of named queries does not fire. high | sensitive-action | MCP05 | Query injection / parameterisation | -20 | — |
dangerous-tool-open-params | Command-execution tool takes a free-form command An exec/shell/run-code tool exposes a command/code/args parameter that is a free-form string with no enum/pattern guard — a direct command-injection / RCE surface. A tool constraining the command to an `enum` of allowed operations does not fire. high | sensitive-action | MCP05 | Command injection & execution | -25 | ≤ C |
no-input-schema | High-impact tool declares no input schema A tool implying a high-impact action (command execution, database access, or filesystem access) ships no input schema at all, leaving its inputs undeclared and unvalidated so a client cannot constrain what is passed to it. low | metadata | MCP05 | Input validation / schema hygiene | -3 | — |
tool-descriptions-changed-since-last-scan | Tool descriptions changed since last scan The tool surface changed after prior observation — the rug-pull signal. Flagged for review, not proof of malice. high | tool-poisoning | MCP03 | Rug pull | -25 | — |
unpinned-package | Unpinned package release The server ships from an unpinned/auto-installed package — enables silent post-approval changes. medium | supply-chain | MCP04 | Supply chain | -10 | — |
server-identity-impersonation | Server identity impersonation / spoofing signal The server’s advertised identity uses confusable mixed-script, zero-width, or Unicode-tag characters, or its TLS certificate SANs do not cover the endpoint host — strong signals of an impersonated or misattributed server. Referencing an integrated brand by name is NOT flagged; only spoofing signals and genuine cert/host mismatches fire. high | provenance | MCP08 | Provenance / server identity | -25 | ≤ C |
tool-name-impersonation | Tool name uses confusable / mixed-script characters A tool name mixes Latin with look-alike characters from another script (Cyrillic / Greek / Armenian) or hides zero-width characters — a homograph trick to impersonate a trusted first-party tool so the model routes calls to the wrong tool. Pure-ASCII names never fire. high | provenance | MCP08 | Tool-name homograph / impersonation | -25 | ≤ C |
missing-tool-description | Tool with no description A tool ships with no description, hurting reviewability. low | metadata | — | hygiene | -3 | — |
oversized-description | Oversized tool description An unusually long description is a common injection-padding tactic. low | metadata | — | hygiene | -3 | — |
mcp-apps-ui-resource-surface | MCP Apps ui:// resource surface The server exposes one or more ui:// (MCP Apps) resources whose HTML/JS renders inside the host client — a client-side injection / data-exposure surface most scanners ignore. Flagged for review, not damning on its own. low | metadata | MCP06 | MCP Apps / UI resources | -3 | — |
Small, positive modifiers for verifiable good practice. They are only added when the base score is already ≥ 90 — you cannot buy back a poor grade with bonuses.
| Test ID | Check | Category | OWASP | Spec ref | Modifier | Cap |
|---|---|---|---|---|---|---|
oauth-per-client-consent | OAuth with per-client consent The server obtains explicit per-client consent, mitigating confused-deputy attacks. info | authorization | MCP07 | Confused-deputy mitigation | +5 | — |
pkce-s256-enforced | PKCE S256 enforced The authorization server enforces PKCE with S256. info | authorization | MCP07 | Authorization · PKCE | +5 | — |
cimd-supported | Client ID Metadata Documents supported Supports CIMD — verifiable client identity without open DCR. info | authorization | MCP07 | CIMD (SEP-991) | +3 | — |
pinned-versioned-release | Pinned, versioned release The server is distributed as a pinned, versioned release. info | supply-chain | MCP04 | Supply chain | +5 | — |
signed-package | Signed package / verified provenance The package is signed / provenance-verified via the registry. info | supply-chain | MCP04 | Provenance | +5 | — |
A+ is earned, not scored. A score of 100 alone lands an A. A+ additionally requires the gated bonuses to fire — verifiable authorization hygiene (OAuth with per-client consent, enforced PKCE S256), a pinned and signed release, and CIMD support — with no penalty of any severity outstanding. It is a statement that a server did more than avoid mistakes.
Some conditions make a numeric grade meaningless. Like SSL Labs, MCPGrade emits a letter verdict instead of a band in those cases.
The endpoint presented an untrusted, expired, or hostname-mismatched certificate. The tool surface is not assessed — the transport itself is the finding.
The endpoint could not be connected to or did not complete an MCP handshake at scan time. No grade is computed until it is reachable again.
The server demanded authorization (401/403). It is graded on its authorization surface only — the tool surface cannot be inspected without a token, which the scanner never supplies.
A grade is a (score, algorithm) tuple. The algorithm version is stamped onto every grade, API response, and badge, so a grade stays reproducible and evolving the algorithm never silently invalidates a previously-issued badge. Semantic versioning: MAJOR = incompatible rescore, MINOR = new checks, PATCH = fixes.
mcpgrade-1.4.0Current2026-08-21Minor version: adds four static context-boundary checks (now 39 in total) that read a tool’s input schema — never calling the tool — to ask whether a dangerous tool constrains its inputs. A dangerous tool whose risky parameter is a bare, unconstrained free-text string is an injection surface; the same tool with an enum / pattern constraint does not fire, so good design is rewarded. unconstrained-fs-path (MCP05, −10) flags a filesystem tool whose path parameter accepts absolute / traversal paths. unmapped-raw-query (MCP05, −20) flags a database tool that takes a raw, unparameterised query string. dangerous-tool-open-params (MCP05, −25, cap C) flags an exec / run-code tool that takes a free-form command. And no-input-schema (MCP05, −3) flags a high-impact tool that declares no input schema at all. Additive only — a grade recomputes unchanged unless a server exhibits one of the new signals.
mcpgrade-1.3.0Research-grounded static checks2026-08-21Minor version: adds six read-only static checks grounded in current MCP-security research (35 at the time), each precise and multi-signal to keep false positives low. lethal-trifecta-tool (MCP10, −30, cap C) flags a single tool that reads private data, ingests untrusted content, and has an outbound channel — the three preconditions for autonomous data theft in one call. tool-name-impersonation (MCP08, −25, cap C) flags a tool name that mixes Latin with confusable Cyrillic / Greek / Armenian or hides zero-width characters — a homograph shadowing a trusted first-party tool. toxic-flow-ordering (MCP06, −25, cap C) flags metadata that dictates tool-call ordering (“always call first”, “before any other tool”, “chain to …”). sensitive-param-required (MCP01, −20) flags an input schema that asks the caller to hand over a password / key / secret. hardcoded-url-in-metadata (MCP03, −10) flags a fetch imperative paired with a hardcoded URL — a fetch-and-obey anchor. And excessive-tool-scope (MCP05, −10) flags one tool that spans both the filesystem and the network. Additive only — a grade recomputes unchanged unless a server exhibits one of the new signals.
mcpgrade-1.2.0Time-aware rug-pull check2026-08-19Patch: the catalogued rug-pull check (tool-descriptions-changed-since-last-scan, −25, OWASP MCP03) now actually fires on a re-scan. Every live scan of a server we have seen before compares its tool-surface hash to the previous one, so a server that silently changes its tools after being graded is penalised — the score is now genuinely time-aware. No check was added or removed; only servers whose tools change between scans are affected.
mcpgrade-1.1.0Provenance + MCP Apps checks2026-08-19Minor version: adds two checks (29 at the time). A provenance / server-identity impersonation check (server-identity-impersonation, OWASP MCP08) that fires on homograph / mixed-script / zero-width identity signals or a TLS SAN that does not cover the endpoint host — never on a mere brand reference. And an MCP Apps surface check (mcp-apps-ui-resource-surface, OWASP MCP06) that flags client-rendered ui:// resources for review and re-scans their metadata with the existing tool-poisoning detectors. Additive only — every previously-issued grade recomputes unchanged unless a server exhibits one of the new signals.
mcpgrade-1.0.0Initial published methodologyThe first public MCPGrade release: checks across transport/TLS, authorization, tool poisoning, data exfiltration, supply chain, and metadata hygiene, each mapped to the OWASP MCP Top-10 and a spec clause.
MCPGrade scans are automated and read-only. See our scanning policy for what the scanner does and does not do, and the server directory for graded servers.