mcp-migrate

mcp-migrate finds and fixes what the MCP 2026-07-28 spec revision breaks in
your server: protocol sessions and Mcp-Session-Id removed, initialize /
notifications/initialized replaced by server/discover, ping and
logging/setLevel gone, resources/subscribe replaced by
subscriptions/listen, required resultType and cache metadata on results,
server-initiated Sampling/Roots/Elicitation replaced by Multi Round-Trip
Requests, and more. The official Python SDK ships no codemod for any of
this -- only a manual migration guide. The TypeScript codemod only handles
the v1→v2 package rename, not the protocol changes. mcp-migrate fix is the
only thing that edits your server's code for you.
uvx mcp-migrate check .
uvx mcp-migrate fix . --write
mcp-migrate check
$ uvx mcp-migrate check tests/fixtures/fixer_roundtrip
mcp-migrate v0.2.0 -> fixer_roundtrip
2 Python files, 21 rules, spec 2026-07-28
rule where what
breaking R001 server.py:28 Mcp-Session-Id was removed from the Streamable HTTP transport.
breaking R001 server.py:29 Mcp-Session-Id was removed from the Streamable HTTP transport.
breaking R017 errors.py:8 -32002 for resource-not-found is the old code; 2026-07-28 uses -32602.
deprecated R006 server.py:15 HTTP+SSE transport is deprecated.
deprecated R006 server.py:24 HTTP+SSE transport is deprecated.
advisory R004 server.py:32 Tools are returned without an explicit sort.
advisory R005 server.py:16 Capabilities are declared but `extensions` is absent.
advisory R010 (project) This project registers MCP request handlers (tools/resources/prompts)
but has no server/discover implementation anywhere in the project.
advisory R016 server.py:32 This file implements a list/read handler but neither `ttlMs` nor
`cacheScope` appears in it.
R001 Uses Mcp-Session-Id, which no longer exists
SEP-2567 https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567
Sessions are gone from the transport. Mint an explicit handle server-side and take it as an
ordinary tool argument instead.
[... one block like this per rule that fired ...]
Grade F (26/100) 3 breaking, 2 deprecated, 4 advisory
Add your server to the board: mcp-migrate entry --repo owner/name
Zero findings prints Grade A and a ready-to-paste badge instead. Add
--json for machine-readable, uncapped output (the terminal table caps each
rule at 5 rows with a "+N more" line so it stays readable; JSON always has
every finding).
--rule R001 (repeatable, e.g. --rule R001 --rule R017) restricts which
rules run at all, not just what's printed -- the natural way to adopt this
tool on an existing codebase is one rule at a time. An unknown id is a usage
error (exit 2), same as --write --dry-run together on fix. A grade
computed from part of the rule set isn't a grade, so --rule suppresses it
entirely rather than printing one that only ever looked at a subset.
--severity breaking (repeatable, composes with --rule) only changes what
gets displayed -- the grade and the exit code are always computed from every
finding, so two runs against the same code never disagree just because
someone chose to look at a narrower slice of the output.
--fail-on {breaking,deprecated,advisory,never} (default breaking) is the
minimum severity that fails the run. A team mid-migration that has decided
deprecated blocks a merge sets --fail-on deprecated; a team still
adopting the tool that wants findings reported without failing the build yet
sets --fail-on never -- which is not the same as check || true: exit 2
("could not check it") is a refusal, not a severity, and no --fail-on
setting suppresses it.
check --json contract
--json emits one JSON object with these always-present top-level keys:
tool, version, spec, path, scannable, languages, grade,
score, rule_filtered, filters, fail_on, files_scanned, counts,
findings, suppressed, and unused_suppressions.
filtersis{"rule": [...], "severity": [...]}, the ids/severities passed to--rule/--severity, empty arrays when neither was given.rule_filteredistruewhenever--rulewas given.countsandfindingsreflect the requested filters;gradeandscoreare alwaysnullwhenrule_filteredistrue.fail_onechoes the--fail-onthreshold that decided the process exit code, so a consumer doesn't have to re-derive it.
Conditional keys:
is_sdkandsdk_reasononly when the tree is a protocol SDK. In that casegradeandscorearenull.reasononly whenscannableisfalse. In that casegradeandscorearenull.gradeandscoreare alsonullwhen a tree was read but is not gradable, for example TypeScript-only trees.
suppressed holds findings silenced by an inline mcp-migrate: ignore[R0NN]
comment; they are excluded from findings, from counts, and from the grade,
and each carries the reason from its comment.
unused_suppressions holds directives that matched no finding — either it was
fixed or the code moved. Each entry has rule, path, line, and reason,
one per rule id. It is always present, empty array included: stale suppressions
accumulate in CI, and CI is the consumer that reads --json and never sees a
console line.
Each finding always has rule, severity, path, line, and message.
path and line may be null for project-level findings. fix is present
only when the rule has remediation text; it is omitted, never null.
The executable contract is
schemas/check-json.schema.json. Pre-1.0,
this shape may change in a breaking way; such changes are documented under
Changed in CHANGELOG.md, so consumers should pin a version
and watch that section.
check --format sarif
SARIF 2.1.0, for GitHub code scanning and anything else that speaks it:
- run: mcp-migrate check . --format sarif > mcp-migrate.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: mcp-migrate.sarif
Findings land in the Security tab and annotate the diff, instead of scrolling
past in a log. Severities map to SARIF levels as breaking -> error,
deprecated -> warning, advisory -> note. deprecated is deliberately
not error: code scanning's default gate fails on error alone, and the spec
gives deprecated features 12+ months, so blocking a merge today over a change
that doesn't break until next year is how an integration gets switched off.
--format {text,json,sarif} is the general flag; --json remains as an alias
for --format json. Output is validated against the vendored
SARIF 2.1.0 schema in CI.
Suppressing a finding
This tool has open false-positive classes and says so above. When a finding is wrong, or the code is deliberate and not changing, silence that one line rather than the whole rule:
mcp_session_id = req.headers["X-Sid"] # mcp-migrate: ignore[R001] -- proxy shim, not MCP session state
const mcpSessionId = req.headers["x-sid"]; // mcp-migrate: ignore[R001] -- proxy shim
The rule id is required — a blanket ignore would also silence rules that
don't exist yet, and nobody ever revisits it. A reason after -- is expected;
check reports directives that lack one.
A suppressed finding doesn't count against the grade — a suppression that still costs you the grade isn't one, and the only move left would be to stop running the tool. That does make the grade partly self-reported, so three things keep it auditable:
- the count is always printed, never behind a flag
--show-suppressionslists every one with its file, rule and reasoncheckreports suppressions that matched nothing, so stale ones don't quietly accumulate
--json carries them under suppressed, each with its reason, and the stale
ones under unused_suppressions.
One caveat, because "line-scoped" promises slightly less than it delivers for
three rules. R005, R015 and R016 ask a question about a file — does
this file declare capabilities without extensions, does it build a JSON-RPC
result without resultType, does it implement a list/read handler without cache
metadata — so each reports at most one finding per file, on the first line that
matches. Suppressing that line therefore silences the rule for the whole
file, not just that line. Every other rule reports each occurrence
separately, and there suppression really is per line. If you suppress one of
these three, you are accepting the rule's verdict on that file.
Exit codes, so it drops straight into CI:
| code | meaning |
|---|---|
0 |
checked it, nothing breaking |
1 |
checked it, found something breaking |
2 |
could not check it — no readable source in a supported language |
2 means we did not read your code, not "your code is fine" — an empty
finding set otherwise conflates "we read it and it's clean" with "we read
nothing", and a grade that can't tell those apart is worthless. A tree with
nothing readable in it gets silence instead of an A.
Python is graded. TypeScript is scanned but still not graded — and as of
R002 landing, that is no longer justified by coverage. All 21 rules read
TypeScript. The grade is withheld by a PARTIAL flag, and now that
coverage is complete the reason it gives is a decision, not a fraction:
$ mcp-migrate check ./my-ts-server
mcp-migrate v0.2.0 -> my-ts-server
No grade for this one. Found 1 TypeScript. Every rule reads it now, but
whether it gets graded is still an open decision, not a coverage gap --
see https://github.com/dheerajjha/mcp-migrate/issues/172.
breaking R001 server.ts:4 Mcp-Session-Id was removed from the Streamable HTTP transport.
deprecated R006 server.ts:1 HTTP+SSE transport is deprecated.
deprecated R006 server.ts:5 HTTP+SSE transport is deprecated.
3 finding(s) from the rules that do cover this language. Real, and worth
fixing -- but a letter grade would be a claim about a decision that
hasn't been made.
Whether a language every rule reads should ever get a letter is tracked as #172. Until that is resolved, a TypeScript tree gets findings but no grade, no badge, and no registry entry. Findings are real and complete; only the letter is withheld.
The exit code works on TypeScript, so this drops into CI today regardless:
a breaking finding exits 1 whether or not a grade was issued. (Until
v0.1.3 it always exited 2, so CI could not fail on a TypeScript
regression — that was #98.)
No rule is Python-only any more. Issue #30 is closed on coverage; what remains is the grading decision in #172.
By default, check skips test code: anything under a tests/, test/,
testing/, fixtures/, examples/, or docs/ directory, plus test_*.py,
*_test.py, and conftest.py files. A backward-compat test that
deliberately exercises a deprecated transport, or an integration test that
posts a literal {"method": "tools/list"} payload, is evidence your project
is well tested -- not evidence the server itself is broken. Pass
--include-tests to scan those paths too.
Project config
Everything above is a flag, which means it has to be retyped on every
invocation and can't be shared with a team or with CI. Put it in
[tool.mcp-migrate] in pyproject.toml instead:
[tool.mcp-migrate]
skip = ["vendor/", "generated/"]
include-tests = false
[tool.mcp-migrate.rules]
R008 = "off" # no reason recorded
R016 = "off -- ttl is enforced by the gateway in front of this"
No pyproject.toml? Which, given this tool targets MCP servers, is most
JavaScript and TypeScript projects -- put the same keys in a standalone
.mcp-migrate.toml at the project root instead:
skip = ["vendor/"]
[rules]
R008 = "off -- we propagate trace context out of band"
If a pyproject.toml exists at all, it wins, whether or not it actually
sets [tool.mcp-migrate] -- the standalone file is only read for projects
that have no pyproject.toml in the first place, so the two never end up
silently disagreeing about which one applies.
Precedence: a flag beats config, config beats the built-in default.
--include-tests on the command line always wins; config can turn it on
project-wide when the flag doesn't.
A disabled rule never runs, rather than running and being discarded.
It costs nothing against the grade, and it is never mistaken for a pass --
check reports how many rules config switched off (N rule(s) disabled by config, with each rule id and its reason) in every run, not behind a flag,
same as suppressions. --json carries the same list under disabled_rules.
fix skips the fixer for a disabled rule too.
Malformed config (bad TOML, an unrecognised rule id, rules.R008 = "sometimes")
doesn't fail the run -- it falls back to defaults and reports what it
couldn't understand, under config_warnings in --json output and as a
config warning line in text output.
mcp-migrate fix
$ uvx mcp-migrate fix tests/fixtures/fixer_roundtrip
errors.py
--- a/errors.py
+++ b/errors.py
@@ -5,7 +5,7 @@
def read_resource(handle: str) -> dict:
if not _exists(handle):
- return {"code": -32002, "message": "resource not found"}
+ return {"code": -32602, "message": "resource not found"}
return {"contents": _load(handle)}
[R017/safe] line 8: resource-not-found error code -32002 -> -32602
server.py
--- a/server.py
+++ b/server.py
@@ -12,26 +12,29 @@
from __future__ import annotations
from mcp.server import Server
-from mcp.server.sse import SseServerTransport
+from mcp.server.streamable_http import StreamableHTTPServerTransport
from mcp.types import ServerCapabilities, Tool, ToolsCapability
server = Server("fixture-server")
capabilities = ServerCapabilities(
tools=ToolsCapability(list_changed=True),
+ extensions={},
)
-transport = SseServerTransport("/messages")
+# TODO(mcp-migrate): verify no constructor args were lost moving off SSE, see https://modelcontextprotocol.io/specification/draft/changelog
+transport = StreamableHTTPServerTransport()
def _session_for(request):
- mcp_session_id = request.headers.get("Mcp-Session-Id")
+ # TODO(mcp-migrate): replaced by an explicit handle argument, see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567
+ # mcp_session_id = request.headers.get("Mcp-Session-Id")
return mcp_session_id
@server.list_tools()
async def list_tools() -> list[Tool]:
- return [
+ return sorted([
Tool(name="zeta", description="Last alphabetically."),
Tool(name="alpha", description="First alphabetically."),
- ]
+ ], key=lambda t: t.name)
[R001/review] line 28: commented out Mcp-Session-Id header access, added TODO
[R004/safe] line 35: wrapped returned tool list in sorted(key=lambda t: t.name)
[R005/safe] line 20: added extensions={} to ServerCapabilities(...)
[R006/review] line 15: import Streamable HTTP transport instead of SSE
[R006/review] line 25: SseServerTransport(...) -> StreamableHTTPServerTransport(), flagged for review
2 file(s), 6 change(s): 3 safe, 3 flagged for human review
4 finding(s) still need a human after this fix -- run `mcp-migrate check tests/fixtures/fixer_roundtrip` for details.
Dry run -- nothing was written. Re-run with --write to apply.
fix runs in dry-run mode by default (that's what --dry-run names
explicitly; it's implied when you pass neither flag) -- it prints the exact
unified diff for every file that would change and writes nothing. Pass
--write to apply it. --dry-run and --write are mutually exclusive.
Every change is tagged safe or review in the diff output:
safe-- the fixer is certain the transformation can't change behavior (e.g. wrapping an already-unordered list literal insorted(), or addingextensions={}where absent already meant "no extensions"). Apply these with confidence.review-- the fixer did the mechanical part it can be sure of (renaming an import, commenting out a now-nonexistent header read) and left a# TODO(mcp-migrate): ...exactly where a human has to finish the job, because the rest genuinely requires a judgment call this tool can't make for you (what do you name the new handle argument? what constructor args did the old transport need that the new one doesn't take?).
Pass --safe-only to apply only safe fixers, --rule R006 to restrict to
one or more rules (repeatable, same flag check uses), --include-tests to
also fix test/fixture paths (skipped by default, same rule as check).
Only 19 of the 21 rules ship a fixer at all -- see the table below and
mcp-migrate fixers. Fixers are
deliberately conservative: when a fixer can't be sure a transformation is
correct, it leaves the source untouched rather than guess. A wrong fix that
silently corrupts your server is worse than reporting the finding and doing
nothing.
Catch it before it is committed
# .pre-commit-config.yaml
repos:
- repo: https://github.com/dheerajjha/mcp-migrate
rev: v0.2.0
hooks:
- id: mcp-migrate
A breaking finding fails the hook, which is the point. A repository with no
readable source does not — the hook runs mcp-migrate-precommit, a thin
wrapper that maps check's exit 2 ("could not check it") to 0. pre-commit
treats every non-zero exit as a failure, so without that the hook would block
every commit in a repo the tool cannot read, which is a reason the user can do
nothing about. The message still prints; it just isn't fatal.
The hook scans the project, not the staged files (pass_filenames: false),
and that isn't an oversight to optimise away: several rules are whole-project
questions — R010 asks whether server/discover exists anywhere — and handed a
partial view they fire wrongly. Cost of that choice, measured on 600 files
(300 Python + 300 TypeScript): ~0.32 s.
Other commands
mcp-migrate rules # list every rule this version ships, with severity and spec ref
mcp-migrate fixers # list every fixer, with confidence (safe/review)
mcp-migrate entry --repo owner/name # print a registry/servers/*.yaml entry for the board
Every rule
| Rule | Severity | What breaks | Fixer |
|---|---|---|---|
| R001 | breaking | Mcp-Session-Id is gone from the Streamable HTTP transport (SEP-2567). |
yes (review) |
| R002 | breaking | Servers are required to be stateless (SEP-2567); a module-level dict keyed by connection breaks behind a load balancer or a restart. | yes (review) |
| R003 | advisory | Hand-rolled HTTP clients that skip the new required Mcp-Method/Mcp-Name routing headers get rejected by anything enforcing the new transport. |
yes (review) |
| R004 | advisory | tools/list order is not guaranteed; non-deterministic ordering defeats caching. |
yes (safe) |
| R005 | advisory | Optional capabilities negotiate through an extensions map that isn't declared. |
yes (safe) |
| R006 | deprecated | HTTP+SSE is deprecated in favor of Streamable HTTP; stays in the spec 12+ months, then leaves. | yes (review) |
| R007 | deprecated | Roots, Sampling and Logging are deprecated as core capabilities. | yes (review) |
| R008 | advisory | Trace context (traceparent, tracestate, baggage) now travels in _meta (SEP-414); OpenTelemetry breaks at your server if it's never read. |
yes (review) |
| R009 | breaking | The initialize/notifications/initialized handshake (SEP-2575) is gone; a server still implementing it never becomes usable to a 2026-07-28 client. |
yes (review) |
| R010 | advisory | Servers must implement server/discover (SEP-2575); registering handlers without it leaves clients with no way to learn what you support. Downgraded from breaking: this checks for something the new spec introduced, so it fires on ~100% of pre-migration servers and has no discriminating power. |
no |
| R011 | breaking | ping (SEP-2575) is removed from the protocol; liveness rides on the transport now. |
yes (review) |
| R012 | breaking | logging/setLevel (SEP-2575) is removed; log level is per-request via _meta now. |
yes (review) |
| R013 | breaking | resources/subscribe/resources/unsubscribe (SEP-2575) are replaced by subscriptions/listen. |
yes (review) |
| R014 | breaking | SSE resumability via Last-Event-ID (SEP-2575) is removed; a dropped connection is just dropped now. |
yes (review) |
| R015 | advisory | Every result now requires resultType (SEP-2322). Fires only on servers that build their own JSON-RPC envelopes: the official SDK stamps the field on every result it serializes, so an SDK-based server has nothing to add. |
no |
| R016 | advisory | List/read results require ttlMs/cacheScope (SEP-2549). The SDK fills these only when the server is built with cache_hints=, so configuring hints anywhere satisfies it. Advisory: nothing has adopted the new API yet. |
yes (review) |
| R017 | breaking | The resource-not-found error code changed from -32002 to -32602. |
yes (safe) |
| R018 | breaking | Server-initiated Roots/Sampling/Elicitation (SEP-2322) are replaced by Multi Round-Trip Requests (InputRequiredResult + inputResponses). |
yes (review) |
| R019 | breaking | tasks/list is removed and blocking tasks/result (SEP-2663) is replaced by polling; Tasks moves to an extension. |
yes (review) |
| R020 | deprecated | RFC 7591 Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents. | yes (review) |
| R021 | advisory | Implementations must support at least JSON Schema 2020-12 (SEP-2106) for inputSchema/outputSchema. |
yes (safe) |
Run mcp-migrate rules to see this list for the exact version you have
installed, and mcp-migrate fixers for the fixer confidence table. Full
spec changelog: https://modelcontextprotocol.io/specification/draft/changelog.
Missed something and want to fill in a fixer, or just document the manual
fix? See Contribute below and
cookbook/ for every change that doesn't have a
recipe yet.
The grade
Every finding costs points, but no single rule can sink your grade by
itself: each rule's total contribution is capped, no matter how many times
it fires. Real evidence for why this matters: mcp-atlassian's R003 once
hit 19 times on the same false-positive pattern, for 475 raw penalty
points -- over 4x what one rule is now allowed to cost.
| Severity | Cost per finding | Cap per rule | Meaning |
|---|---|---|---|
breaking |
-25 | -25 | Your server stops working under 2026-07-28. |
deprecated |
-8 | -12 | Still works today, on a 12+ month clock. |
advisory |
-3 | -6 | Best practice, not a compatibility risk. |
The letter counts kinds of problem, not amount of work. Because every
rule is capped, one Mcp-Session-Id read and fourteen of them across four
files both score 75 and both grade C. The score moves when a different
rule fires, not when the same one fires again.
That is deliberate -- repetition of one systemic issue is one issue -- but it
means the grade cannot be used to size a migration. For that, read
counts and findings in --json: they carry every occurrence, uncapped.
Someone reasonably read the letter the other way and estimated two very
different migrations as the same day of work, which is the failure this
paragraph exists to prevent.
Score starts at 100 and floors at 0. The letter grade comes from the score:
| Score | Grade | Badge color |
|---|---|---|
| 95-100 | A | brightgreen |
| 80-94 | B | green |
| 60-79 | C | yellow |
| 40-59 | D | orange |
| 0-39 | F | red |
Get your badge
Run the check, fix what's breaking, then generate your entry and badge in
one shot:
uvx mcp-migrate entry --repo owner/name > registry/servers/name.yaml
entry refuses — writing nothing to stdout, so the redirect above leaves no
file behind — if it can't read your server, or if your Python is a small
minority of a repo that's mostly something else. A board entry is a claim
about a whole repo, and this project would rather publish nothing than
publish a grade it can't stand behind.
That prints something like:
# registry/servers/name.yaml
name: name
repo: owner/name
language: python
grade: A
score: 100
checked_with: mcp-migrate 0.1.0
spec: "2026-07-28"
status: ready
notes: >-
Replace this line with one sentence about what your server does.
Edit the notes: line to one sentence about what your server does, then PR
it into this repo (steps in
CONTRIBUTING.md).
CI validates the YAML against registry/schema.yaml and regenerates the
board below on merge. Listing is automated and unopinionated: schema
passes and the repo exists, it merges -- no maintainer reviews the server
itself or argues about your grade.
Drop this in your own README once you know your grade (swap the letter and color using the table above):
[](https://github.com/owner/name)
The board
16 servers checked (7x A, 7x B, 1x C, 1x D)
All of these were checked by this project, not submitted by the servers' maintainers -- so read it as a survey, not as adoption. If you maintain one of these, submit your own entry and it becomes yours.
| server | grade | status | language | what it does |
|---|---|---|---|---|
| aws-documentation-mcp-server | A | ready | python | AWS Labs MCP server that fetches, searches, and recommends AWS documentation pages, converted to markdown. |
| cloudwatch-mcp-server | A | ready | python | AWS Labs MCP server for CloudWatch that gives troubleshooting agents alarm, metric, and log data for root cause analysis. |
| duckduckgo-mcp-server | A | ready | python | MCP server that provides web search through DuckDuckGo, with additional content fetching and parsing features. |
| dynamodb-mcp-server | A | ready | python | Official AWS DynamoDB MCP server providing expert data modeling guidance, validation, and cost analysis tools. |
| mcp-server-motherduck | A | ready | python | Local MCP server connecting AI assistants to DuckDB and MotherDuck for SQL analytics and data engineering. |
| mcp-server-qdrant | A | ready | python | Official MCP server for Qdrant that acts as a semantic memory layer for keeping and retrieving memories in the vector search engine. |
| mcp-server-tree-sitter | A | ready | python | MCP server providing tree-sitter code analysis so AI assistants get structure-aware access to codebases in many languages. |
| arxiv-mcp-server | B | ready | python | Search, download, and read arXiv papers, with semantic search and citation tools, over MCP. |
| mcp-server-fetch | B | ready | python | Reference MCP server that fetches web pages and converts HTML to markdown so LLMs can read them in chunks. |
| mcp-server-sentry | B | ready | python | Archived reference MCP server for retrieving and analyzing issues, stacktraces, and debugging info from Sentry.io. |
| mcp-server-sqlite | B | ready | python | Archived reference MCP server for SQLite that runs SQL queries and auto-generates business insight memos. |
| mcp-server-time | B | ready | python | Reference MCP server giving LLMs current time and timezone conversion using IANA timezone names. |
| excel-mcp-server | B | ready | python | Read, write, and format Excel workbooks (formulas, charts, pivot tables) over MCP, via SSE or Streamable HTTP. |
| mcp-neo4j-cypher | B | ready | python | MCP server for Neo4j that runs Cypher graph queries and supports Text2Cypher workflows over graph data. |
| mcp-atlassian | C | migrating | python | MCP server for Atlassian products (Confluence and Jira), supporting both Cloud and Server/Data Center deployments. |
| mcp-server-git | D | ready | python | Reference MCP server for Git repository interaction, giving LLMs tools to read, search, and manipulate repos. |
Contribute
Three ways in, cheapest first:
- A cookbook recipe (~5 minutes). Pick an open slot in
cookbook/-- markdown, no Python, no tests. See CONTRIBUTING.md. - A rule (~15 minutes). A
Rulesubclass -- acheck(project)method, a regex or an AST walk, aFindingfor every hit. Most rules are under 25 lines. See CONTRIBUTING.md. - A fixer (~45 minutes). A
Fixersubclass that turns one rule's finding into a text-level edit, taggedsafeorreview. See CONTRIBUTING.md.
The standing principle behind all three: a false positive is worse than a
missed finding. When a rule can't be made precise, it ships advisory, or
it doesn't ship. Reviews happen within 48 hours; one passing test is enough
to merge.
Listing your own server on the board is separate from all three and takes about 60 seconds -- see CONTRIBUTING.md.
Contributors
TypeScript support exists because these people built it. The language backend shipped with two reference ports (R001 and R006). The other twelve were contributed -- in parallel, by seven people who mostly hadn't spoken to each other, each working from a separate issue.
| Contribution | |
|---|---|
| @syf2211 | R015, R016, R020 ports; R007 and R014 fixers; the R003 cookbook recipe. Also found and reported the R016 false negative that became #66 rather than leaving it buried. |
| @PuvaanRaaj | R012, R018, R019 ports |
| @atiqur-rahman-pro | R003 and R004 ports, then replaced R004's fixed 40-line look-ahead with a brace-depth scan (#65) |
| @MasRama | R005 and R011 ports. Reported the hardcoded coverage assertion that would have made every parallel port conflict with every other one -- the single most useful bug anyone has filed here. |
| @s35153 | R007 and R009 ports |
| @IronLad123 | Routed R016's metadata check through content spans, so a comment can no longer silence a real finding (#66) |
| @li2631026381-alt | Hoisted R015's TypeScript scans out of the per-file loop -- 63.0ms to 3.1ms at 200 files |
git shortlog -sne is the authoritative list; .mailmap folds
the duplicate identities so nobody is counted twice or split in half.
Working principles
A false positive is worse than a missed finding. This tool publishes
public grades about other people's projects. A wrong breaking verdict costs
a maintainer their badge and their trust in the tool; a missed advisory
costs a little visibility and nothing else. When a detection can't be made
precise it ships advisory, or it doesn't ship. Fixers follow the same rule:
one that can't be certain a transformation is correct leaves the source
untouched.
An unearned grade is as damaging as an undeserved finding, and harder to
notice. A server that scores A because a rule couldn't see its handlers is a
bug, not a pass -- see
r010_server_discover_missing.py,
where three registration idioms used by real servers went undetected.
No grade is published that hasn't been reproduced. Every entry in
registry/servers/ comes from a scan someone ran and read, at that server's
own directory. Nothing is estimated, and nothing is carried forward from a
previous version of the rules.
Contributing stays cheap. One small file is the unit of contribution, and a submission that passes schema validation and points at a real repository is merged -- there's no usage bar, no curation step, and no reviewer taste test. Reviews happen within 48 hours and one passing test is enough to merge a rule.
The longer form of all of this, with the API you'd actually use, is in CONTRIBUTING.md.
License
Apache-2.0. See LICENSE.
Listing on the board is automated and unopinionated: if
registry/servers/*.yaml passes schema validation and the repo it points at
exists, it gets merged. No maintainer reviews the server itself.