rule_id, the report schema, the CLI shape, and the exit codes.
Those change only with a major version. Do not build on messages, rule names,
or the absolute score. Those move in minor releases, on purpose. This page is
the contract, in force since 1.1.0.
Stable
Rule identifiers
Arule_id such as tools_description_present_in_all is never renamed and
never reused for a different check. Rules are added in any release. A rule can
be retired and stop running, but its identifier keeps its meaning forever.
The rules reference lists retired ones too.
Report schema
The--json report carries schema_version, currently 1. Within a schema
version, existing fields keep their names, types, and meanings. New fields
appear in any release, so consumers must ignore unknown fields. A removal or
type change bumps schema_version.
Package audits
package is null for every server audit. When it is an object, the report
is a package audit and its score comes from the packaging rules alone, a rule
set disjoint from server audits. Anything that compares or aggregates scores
must branch on this field.
For a package audit
transport and spec.negotiated_version are both null.
Smoke results
smoke is absent unless the run used --smoke. It carries
summary (pass/fail/skip counts), coverage (tools_called: distinct tools
the server answered at least one tools/call for; tools_total: distinct
tools the checks looked at), and checks. A check whose call completed sets
details.called: true; a check that received a result also carries a bounded
details.response (content_blocks, content_types, structured_content,
is_error, and first_text capped at 80 code points). Consumers
must not require it. Smoke results are never counted in score or
max_score, and a smoke failure is reported through exit code 4, not
through the report’s score fields.
check_id values are stable in the same way rule_ids are.
Configuration
config is absent unless a mcpscore.toml or [tool.mcpscore] was applied
(configure rules). When present, the score was computed
under that policy: rules turned off did not run and appear in
skipped_rules[] with reason disabled-by-config (a canonical skip reason
is kept when the engine could not have judged the rule anyway), and
re-ranked rules carry
their configured severity in results[] with the default in
details.severity_default. A configured score is comparable only across
runs with the same config.sha256 and engine version. The badge and
mcpscore.dev never apply a configuration.
CLI interface
The invocation shape is stable:mcpscore <target> with --json, --header,
--token, and the MCPSCORE_TOKEN variable. New flags are added; existing
flags keep their meaning. mcpscore --version prints mcpscore <version> to
stdout, exits 0, and needs no target. --json writes exactly one JSON
document to stdout with all logs on stderr. --sarif FILE writes SARIF 2.1.0
with one result per failed rule, keyed by ruleId = rule_id; the
severity-to-level mapping and the primaryLocationLineHash fingerprint (a
hash of the rule and target) keep their meaning, so code scanning alerts
survive an upgrade.
Credentials
Header and token values are never logged and never written to the report. The report records only the booleanauthenticated. This is a permanent
commitment, not a default.
Evolving by design
We would rather grow the checks than freeze the number early. A calibrated
scoring frame will revisit this once the ruleset settles.
Versioning
mcpscore follows SemVer. Breaking anything in the Stable section needs a major version. Rule additions and score movement are minor-version territory. Pre-releases (bN) can change anything.
Failure guidance
A failed rule may includesuggested_fix: a nonblank plain-text repair hint of
at most 255 Unicode code points. The field is omitted on passing results and on
older findings without authored guidance; skipped rules remain separate.
Consumers must tolerate its absence. In JavaScript, count code points with
Array.from(text).length, rather than UTF-16 text.length.
Supported findings include tool, resource, resource-template, prompt,
server-metadata, capability, protocol, transport, auth/security, pagination,
catalog-stability, readiness and package rules. The CLI prints their hint as Fix: on
stderr. JSON stdout remains one complete report. SARIF appends sanitized repair
advice to the finding message, using its stricter URL privacy policy; it does
not invent source edits. Older findings still export without a repair hint. Optional metadata and list-change advice is
labeled as a quality recommendation in the message; it is not a protocol mandate.
Scores and applicability are unchanged.
Schema failures retain existing detail keys and add details.issues, with a tool
catalog index, a JSON Pointer field path, a reason and an expected constraint.
These diagnostics explain the existing validator; they do not broaden it into
complete JSON Schema conformance checking. A tool’s optional output schema is
checked only when present. Arbitrary schema values are not copied into issues.
Each tool contributes its first failing predicate; at most 20 issues are included.
issues_total and issues_omitted describe the evidence limit. Schema and catalog validation paths longer than
255 Unicode code points are omitted with path_omitted: true; stored paths retain their exact JSON Pointer characters, including newlines,
quotes and backslashes. Only CLI rendering escapes those characters. Indexes still identify the affected tool.
Catalog metadata failures use the same bounded details.issues shape. Each issue
identifies an entity_kind and a zero-based entity_index in the collected,
concatenated catalog. Paths are relative to that item, using MCP wire field
names: tool index 2 with /inputSchema/properties/query/description, for example.
Prompt arguments and icons include their own indexes in the path. Server metadata
paths start at /serverInfo, /instructions or /capabilities and have no
catalog index. An empty-tools finding uses /tools relative to the catalog result.
These new issues omit publisher names, URI values and image data. Indexes still
identify entries with missing or duplicated names. Existing detail keys are
retained for compatibility and are not covered by this new evidence bound.
Human-readable messages can show server identity values on passes and up to three
duplicate tool names on failures. Each quoted preview includes at most 60 source
Unicode code points, with an explicit truncation marker; a value that is a single
URL keeps up to 200, and a list renders item by item (at most 10 items, then a
count). Printable Unicode stays readable; quotes, backslashes and nonprinting
characters are escaped, so the rendered preview can be longer than the bound.
The full values remain in
legacy details. This is a display bound, not secret redaction. Website URL values
are not echoed in passing messages.
issues_total counts affected fields or occurrences, which can differ from a
legacy count of distinct names or duplicate identifiers. At most 20 issues are
included; overlong paths use path_omitted: true rather than a truncated pointer.
Messages identify the first affected field; the complete bounded evidence remains
in details. An optional field with an invalid declared value still needs repair.
The guidance describes the current checks, not additional validation: server
icons are checked for an https:// or data: prefix only, and a declared
listChanged flag does not prove that notifications work. Header uniqueness still
groups annotations by tool name; resolve duplicate tool names before treating
cross-entry collisions as a confirmed error within one tool.
Catalog collection failures are recorded in report listing_errors, and in
details.collection_error when a declaration rule fails. Outcomes distinguish
invalid_response, timeout, rpc_error and an unspecified collection_error.
Validation locations and expected types are retained without raw response values
or exception messages. Modern result objects with a missing or non-array tools field also receive
sanitized field-shape diagnostics, while the probe outcome remains unsupported.
These diagnostics do not change verdicts or score weights.
Requirement strength is not inferred from severity or the presence of a hint.
Use the rule’s source basis and applicability; a repair hint is advice for that
finding, not a claim that every scored recommendation is mandatory.
Protocol and probe evidence
Failed protocol/probe checks retain their observations and can adddetails.expected for the particular fields checked. Human messages include
observed error codes/statuses only when present; stdio messages omit HTTP status.
Cursor probes distinguish a returned page from an unexpected response, rather
than inferring success from an absent error code. Audit context is recorded once:
transport at report level and spec.negotiated_version and
spec.session_protocol_version in the specification block. The latter is null
unless a legacy session was established. Existing
target_version, spec and basis fields retain the rule’s revision and source.
Unavailable probes still skip; a hint cannot turn missing evidence into failure.
Readiness result-field issues use entity_kind: "response", the originating
probe_id, and paths relative to the JSON-RPC result. missing_or_null
means the collected observation cannot distinguish omission from explicit null;
invalid_value means a value was observed but failed the existing predicate.
Tool-schema issues use catalog indexes and paths relative to each tool, with the
same 20-issue and 255-code-point path bounds as catalog findings. A network
reference finding identifies the containing schema; it does not prove a fetch.
Rule and probe evidence copies mask response_session_id as [redacted]
(absence stays null), remove URL userinfo, and mask known credential query keys:
access_token, refresh_token, id_token, token, api_key, apikey,
client_secret, password, secret, authorization and code. Key matching
is case-insensitive after URL decoding and treats hyphens like underscores.
All scheme:// URLs (including postgres and HTTP) are matched case-insensitively,
including dictionary keys and URLs embedded in challenge
headers and exception text. Public query parameters, spelling, order, fragments,
WWW-Authenticate challenges and diagnostic exception reasons remain available.
If masked dictionary keys collide, retain every entry using deterministic
[masked name N] suffixes. Literal keys are reserved before allocating aliases;
summary maps with the same source names use the same aliases. These aliases are
report labels, not original tool names; use catalog indexes to identify tools.
Human messages restore bounded quoted previews of resource/issuer identifiers,
version lists, Content-Type values and schema-validator reasons. They use the
same bounded preview and control-character escaping described above, after
credential masking. Indexed schema issues still omit raw values, and affected
tools are counted by catalog index even when their names collide. For readiness
schema failures, offending_tools retains its legacy name-keyed shape with a
union of short, value-free problem labels. offending_tool_indexes maps each
name to all affected catalog indexes, including duplicate names; unlike bounded
issues, this summary index mapping is complete. Validator previews appear only
in the human message, not in these structured summaries.
These protections do not mutate observations used for scoring and are not a
general secret detector. Arbitrary error text and public metadata can contain
sensitive information outside these specific patterns; review a report before
publishing it.
Passing PKCE findings establish advertised S256 support, not an executed
authorization flow. Error-hygiene passes mean no configured patterns were found
in the sampled response, and catalog-stability passes compare two connections
with the same authorization. Modern-protocol hints preserve valid legacy paths;
readiness promotion into the main score is unchanged.
Some existing predicates have narrower discovery coverage than the specifications.
The challenge-origin finding describes mcpscore’s current comparison; it does not
instruct relocating valid metadata. The dated MCP specification permits
challenge or well-known discovery,
and RFC 9728 section 3.3
requires resource identity validation. Likewise, issuer metadata failures describe
the locations attempted, rather than proving no valid document exists. These
limitations require separate correctness changes, not different repair wording.
What’s next
CLI reference
Every flag and exit code, with defaults
Scoring methodology
Why the score is ruleset-dependent, and what that means for your CI gate
Package evidence
Package hints distinguish publisher metadata changes from consumer choices. npm deprecation and PyPI yanking retain their registry-specific names and publisher notices when available (details.withdrawal_reason). Human notice
previews allow 200 Unicode code points so replacement URLs in prose remain
useful; masking and control-character escaping happen before display. Longer
notices are visibly truncated, with the full masked notice retained in details. Existing
yanked detail values and verdicts remain unchanged. Public values use bounded
quoted previews; URL credentials are masked in result messages and details.
A missing license declaration or repository URL describes metadata, not legal
permissions or whether the source exists elsewhere. Package audits never run
the package and cannot confirm its MCP behavior.