mcp-migrate 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_migrate-0.1.0/.github/GOOD_FIRST_ISSUES.md +782 -0
- mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/add-server.yml +45 -0
- mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/cookbook-recipe.yml +38 -0
- mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/new-fixer.yml +53 -0
- mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/new-rule.yml +50 -0
- mcp_migrate-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +40 -0
- mcp_migrate-0.1.0/.github/scripts/file_issues.sh +138 -0
- mcp_migrate-0.1.0/.github/workflows/board.yml +43 -0
- mcp_migrate-0.1.0/.github/workflows/ci.yml +29 -0
- mcp_migrate-0.1.0/.github/workflows/release.yml +105 -0
- mcp_migrate-0.1.0/.gitignore +52 -0
- mcp_migrate-0.1.0/CONTRIBUTING.md +388 -0
- mcp_migrate-0.1.0/LICENSE +204 -0
- mcp_migrate-0.1.0/PKG-INFO +345 -0
- mcp_migrate-0.1.0/README.md +332 -0
- mcp_migrate-0.1.0/cookbook/01-sessions-to-explicit-handles.md +123 -0
- mcp_migrate-0.1.0/cookbook/02-initialize-to-server-discover.md +103 -0
- mcp_migrate-0.1.0/cookbook/03-sse-to-streamable-http.md +74 -0
- mcp_migrate-0.1.0/cookbook/04-subscribe-to-subscriptions-listen.md +98 -0
- mcp_migrate-0.1.0/cookbook/05-result-type-and-cache-metadata.md +107 -0
- mcp_migrate-0.1.0/cookbook/06-ping-removed.md +39 -0
- mcp_migrate-0.1.0/cookbook/07-logging-set-level-removed.md +40 -0
- mcp_migrate-0.1.0/cookbook/08-sse-resumability-removed.md +41 -0
- mcp_migrate-0.1.0/cookbook/09-resource-not-found-error-code.md +49 -0
- mcp_migrate-0.1.0/cookbook/10-multi-round-trip-requests.md +49 -0
- mcp_migrate-0.1.0/cookbook/11-tasks-polling.md +41 -0
- mcp_migrate-0.1.0/cookbook/12-dynamic-client-registration-deprecated.md +40 -0
- mcp_migrate-0.1.0/cookbook/13-json-schema-2020-12.md +40 -0
- mcp_migrate-0.1.0/cookbook/14-routing-headers.md +42 -0
- mcp_migrate-0.1.0/cookbook/15-deterministic-tool-ordering.md +43 -0
- mcp_migrate-0.1.0/cookbook/16-extensions-map.md +42 -0
- mcp_migrate-0.1.0/cookbook/17-trace-context-propagation.md +44 -0
- mcp_migrate-0.1.0/cookbook/18-roots-sampling-logging-deprecated.md +42 -0
- mcp_migrate-0.1.0/cookbook/README.md +72 -0
- mcp_migrate-0.1.0/cookbook/_TEMPLATE.md +55 -0
- mcp_migrate-0.1.0/docs/banner.jpg +0 -0
- mcp_migrate-0.1.0/pyproject.toml +29 -0
- mcp_migrate-0.1.0/registry/README.md +11 -0
- mcp_migrate-0.1.0/registry/schema.yaml +11 -0
- mcp_migrate-0.1.0/registry/servers/aws-documentation-mcp-server.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/cloudwatch-mcp-server.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/duckduckgo-mcp-server.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/dynamodb-mcp-server.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-atlassian.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-neo4j-cypher.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-fetch.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-git.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-motherduck.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-qdrant.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-sentry.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-sqlite.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-time.yaml +10 -0
- mcp_migrate-0.1.0/registry/servers/mcp-server-tree-sitter.yaml +10 -0
- mcp_migrate-0.1.0/scripts/render_board.py +54 -0
- mcp_migrate-0.1.0/scripts/validate_registry.py +62 -0
- mcp_migrate-0.1.0/src/mcp_migrate/__init__.py +1 -0
- mcp_migrate-0.1.0/src/mcp_migrate/cli.py +353 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/__init__.py +23 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/_textedit.py +52 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/base.py +56 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/r001_session_id.py +68 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/r004_tool_ordering.py +135 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/r005_extensions.py +87 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/r006_sse_transport.py +108 -0
- mcp_migrate-0.1.0/src/mcp_migrate/fixers/r017_resource_not_found_code_changed.py +46 -0
- mcp_migrate-0.1.0/src/mcp_migrate/grade.py +49 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/__init__.py +20 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/base.py +187 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r001_session_id.py +25 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r002_connection_state.py +63 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r003_routing_headers.py +149 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r004_tool_ordering.py +87 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r005_extensions.py +37 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r006_sse_transport.py +18 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r007_deprecated_features.py +25 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r008_trace_context.py +24 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r009_initialize_handshake_removed.py +48 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r010_server_discover_missing.py +118 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r011_ping_removed.py +49 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r012_logging_set_level_removed.py +36 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r013_subscriptions_replaced.py +36 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r014_sse_resumability_removed.py +29 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r015_result_type_required.py +61 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r016_cacheable_result_required.py +57 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r017_resource_not_found_code_changed.py +38 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py +66 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py +40 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py +38 -0
- mcp_migrate-0.1.0/src/mcp_migrate/rules/r021_json_schema_2020_12_required.py +42 -0
- mcp_migrate-0.1.0/src/mcp_migrate/scan.py +57 -0
- mcp_migrate-0.1.0/tests/conftest.py +16 -0
- mcp_migrate-0.1.0/tests/fixtures/clean_server/server.py +69 -0
- mcp_migrate-0.1.0/tests/fixtures/clean_server/store.py +35 -0
- mcp_migrate-0.1.0/tests/fixtures/comment_only_mentions/server.py +21 -0
- mcp_migrate-0.1.0/tests/fixtures/discover_named_helpers/server.py +47 -0
- mcp_migrate-0.1.0/tests/fixtures/fastmcp_functional_registration/server.py +42 -0
- mcp_migrate-0.1.0/tests/fixtures/fastmcp_wrapper_no_mcp_surface/server.py +27 -0
- mcp_migrate-0.1.0/tests/fixtures/fixer_roundtrip/errors.py +17 -0
- mcp_migrate-0.1.0/tests/fixtures/fixer_roundtrip/server.py +37 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/auth.py +12 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/handshake.py +18 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/legacy_protocol.py +61 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/resumable_stream.py +31 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/server.py +100 -0
- mcp_migrate-0.1.0/tests/fixtures/legacy_server/webhook.py +27 -0
- mcp_migrate-0.1.0/tests/fixtures/rest_wrapper_no_mcp_surface/jira_client.py +26 -0
- mcp_migrate-0.1.0/tests/fixtures/test_skip_project/server.py +7 -0
- mcp_migrate-0.1.0/tests/fixtures/test_skip_project/tests/test_legacy_transport_compat.py +17 -0
- mcp_migrate-0.1.0/tests/test_fixers.py +450 -0
- mcp_migrate-0.1.0/tests/test_registry.py +148 -0
- mcp_migrate-0.1.0/tests/test_regressions.py +352 -0
- mcp_migrate-0.1.0/tests/test_rules.py +120 -0
- mcp_migrate-0.1.0/tests/test_selfcheck.py +52 -0
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
# Good first issues
|
|
2
|
+
|
|
3
|
+
Ready-to-paste issue bodies for two categories of open work:
|
|
4
|
+
|
|
5
|
+
1. Cookbook recipes that exist only as stubs (rule/spec filled in, before/after
|
|
6
|
+
and gotchas still needed) -- see [`cookbook/README.md`](../cookbook/README.md#stubs----open-slots).
|
|
7
|
+
2. Rules that have no fixer yet -- see the "Fixer" column in the
|
|
8
|
+
[README's rule table](../README.md#every-rule).
|
|
9
|
+
|
|
10
|
+
Each block below is delimited by `### ISSUE_START` / `### ISSUE_END` so
|
|
11
|
+
[`scripts/file_issues.sh`](scripts/file_issues.sh) can parse and file them
|
|
12
|
+
in bulk with `gh issue create`. To file them all yourself:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
.github/scripts/file_issues.sh # files into the repo gh is already pointed at
|
|
16
|
+
.github/scripts/file_issues.sh owner/mcp-migrate # or an explicit owner/repo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
To file one by hand instead, copy the `TITLE`/`LABELS`/body between one
|
|
20
|
+
issue's markers into `gh issue create --title "..." --label "..." --body "..."`.
|
|
21
|
+
|
|
22
|
+
Don't edit the markers or the `TITLE:`/`LABELS:`/`DIFFICULTY:`/`BODY_START`/
|
|
23
|
+
`BODY_END` lines -- the script depends on them being exact.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### ISSUE_START
|
|
28
|
+
TITLE: Cookbook recipe: ping removed (R011)
|
|
29
|
+
LABELS: good-first-issue,cookbook
|
|
30
|
+
DIFFICULTY: easy (~15-30 min)
|
|
31
|
+
BODY_START
|
|
32
|
+
`cookbook/06-ping-removed.md` exists as a stub: the rule, severity and spec
|
|
33
|
+
link are already filled in, the before/after code and gotchas aren't.
|
|
34
|
+
|
|
35
|
+
**Context:** R011 ([`src/mcp_migrate/rules/r011_ping_removed.py`](../../src/mcp_migrate/rules/r011_ping_removed.py))
|
|
36
|
+
flags servers still implementing the removed `ping`/`PingRequest`
|
|
37
|
+
request-response. There's no fixer for this rule yet either (see the
|
|
38
|
+
separate "Add a fixer" issue for R011 if you want to take that on too, but
|
|
39
|
+
it's not required for this one).
|
|
40
|
+
|
|
41
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
42
|
+
|
|
43
|
+
**What "done" looks like:** fill in the Before/After/Gotchas sections of
|
|
44
|
+
`cookbook/06-ping-removed.md` following `cookbook/_TEMPLATE.md`'s format
|
|
45
|
+
(see any of the five filled-in recipes, e.g. `cookbook/03-sse-to-streamable-http.md`,
|
|
46
|
+
for the level of detail expected). Move its row from the "Stubs" table to
|
|
47
|
+
the "Filed so far" table in `cookbook/README.md`. No code, no tests --
|
|
48
|
+
markdown only.
|
|
49
|
+
|
|
50
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
51
|
+
BODY_END
|
|
52
|
+
### ISSUE_END
|
|
53
|
+
|
|
54
|
+
### ISSUE_START
|
|
55
|
+
TITLE: Cookbook recipe: logging/setLevel removed (R012)
|
|
56
|
+
LABELS: good-first-issue,cookbook
|
|
57
|
+
DIFFICULTY: easy (~15-30 min)
|
|
58
|
+
BODY_START
|
|
59
|
+
`cookbook/07-logging-set-level-removed.md` exists as a stub.
|
|
60
|
+
|
|
61
|
+
**Context:** R012 ([`src/mcp_migrate/rules/r012_logging_set_level_removed.py`](../../src/mcp_migrate/rules/r012_logging_set_level_removed.py))
|
|
62
|
+
flags servers still implementing the removed `logging/setLevel` request.
|
|
63
|
+
Log level is now per-request, read off `_meta["io.modelcontextprotocol/logLevel"]`.
|
|
64
|
+
|
|
65
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
66
|
+
|
|
67
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
68
|
+
`cookbook/07-logging-set-level-removed.md` per `cookbook/_TEMPLATE.md`.
|
|
69
|
+
Particularly useful: a concrete example of moving from a process-wide log
|
|
70
|
+
level to per-request handling with Python's stdlib `logging` (which is
|
|
71
|
+
process-global by default) -- contextvars-based scoping is probably the
|
|
72
|
+
real answer and worth spelling out. Move its row out of "Stubs" in
|
|
73
|
+
`cookbook/README.md` once filled in.
|
|
74
|
+
|
|
75
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
76
|
+
BODY_END
|
|
77
|
+
### ISSUE_END
|
|
78
|
+
|
|
79
|
+
### ISSUE_START
|
|
80
|
+
TITLE: Cookbook recipe: SSE resumability removed (R014)
|
|
81
|
+
LABELS: good-first-issue,cookbook
|
|
82
|
+
DIFFICULTY: easy (~15-30 min)
|
|
83
|
+
BODY_START
|
|
84
|
+
`cookbook/08-sse-resumability-removed.md` exists as a stub.
|
|
85
|
+
|
|
86
|
+
**Context:** R014 ([`src/mcp_migrate/rules/r014_sse_resumability_removed.py`](../../src/mcp_migrate/rules/r014_sse_resumability_removed.py))
|
|
87
|
+
flags `Last-Event-ID`-based stream resumability, which is removed as of
|
|
88
|
+
2026-07-28 regardless of which HTTP transport you're on.
|
|
89
|
+
|
|
90
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
91
|
+
|
|
92
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
93
|
+
`cookbook/08-sse-resumability-removed.md` per `cookbook/_TEMPLATE.md`. A
|
|
94
|
+
worked example of an event store + `Last-Event-ID` replay handler being
|
|
95
|
+
retired, and a note on what (if anything) needs to change client-side to
|
|
96
|
+
stop sending `Last-Event-ID`.
|
|
97
|
+
|
|
98
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
99
|
+
BODY_END
|
|
100
|
+
### ISSUE_END
|
|
101
|
+
|
|
102
|
+
### ISSUE_START
|
|
103
|
+
TITLE: Cookbook recipe: resource-not-found error code -32002 -> -32602 (R017)
|
|
104
|
+
LABELS: good-first-issue,cookbook
|
|
105
|
+
DIFFICULTY: easy (~15-30 min)
|
|
106
|
+
BODY_START
|
|
107
|
+
`cookbook/09-resource-not-found-error-code.md` exists as a stub. Note this
|
|
108
|
+
rule already ships a `safe`-confidence fixer -- this recipe is for the
|
|
109
|
+
worked example and the fixer's edge cases, not new code.
|
|
110
|
+
|
|
111
|
+
**Context:** R017 ([`src/mcp_migrate/rules/r017_resource_not_found_code_changed.py`](../../src/mcp_migrate/rules/r017_resource_not_found_code_changed.py))
|
|
112
|
+
flags the old `-32002` resource-not-found JSON-RPC error code, replaced by
|
|
113
|
+
`-32602`.
|
|
114
|
+
|
|
115
|
+
**Spec:** https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
116
|
+
|
|
117
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
118
|
+
`cookbook/09-resource-not-found-error-code.md` per `cookbook/_TEMPLATE.md`.
|
|
119
|
+
Specifically call out what happens when the qualifying context (a mention
|
|
120
|
+
of "resource" or "not found") is on a different line than the `-32002`
|
|
121
|
+
literal -- the fixer requires both on one line, and that's a real,
|
|
122
|
+
documentable limitation.
|
|
123
|
+
|
|
124
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
125
|
+
BODY_END
|
|
126
|
+
### ISSUE_END
|
|
127
|
+
|
|
128
|
+
### ISSUE_START
|
|
129
|
+
TITLE: Cookbook recipe: Multi Round-Trip Requests replace server-initiated calls (R018)
|
|
130
|
+
LABELS: good-first-issue,cookbook
|
|
131
|
+
DIFFICULTY: medium (~30-45 min)
|
|
132
|
+
BODY_START
|
|
133
|
+
`cookbook/10-multi-round-trip-requests.md` exists as a stub. This is the
|
|
134
|
+
biggest control-flow change in the spec revision, so this recipe is worth
|
|
135
|
+
more time than most.
|
|
136
|
+
|
|
137
|
+
**Context:** R018 ([`src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py`](../../src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py))
|
|
138
|
+
flags server-initiated `roots/list`, `sampling/createMessage` and
|
|
139
|
+
`elicitation/create` -- all replaced by Multi Round-Trip Requests
|
|
140
|
+
(`InputRequiredResult` + client-driven `inputResponses`).
|
|
141
|
+
|
|
142
|
+
**Spec:** SEP-2322 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
143
|
+
|
|
144
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
145
|
+
`cookbook/10-multi-round-trip-requests.md` per `cookbook/_TEMPLATE.md`. The
|
|
146
|
+
highest-value thing this recipe needs: a concrete answer for where the
|
|
147
|
+
handler's local state goes between "returned InputRequiredResult" and "got
|
|
148
|
+
the retried call with inputResponses" -- it can't just live on a blocked
|
|
149
|
+
coroutine's stack anymore.
|
|
150
|
+
|
|
151
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
152
|
+
BODY_END
|
|
153
|
+
### ISSUE_END
|
|
154
|
+
|
|
155
|
+
### ISSUE_START
|
|
156
|
+
TITLE: Cookbook recipe: Tasks moved to an extension, polling replaces blocking result (R019)
|
|
157
|
+
LABELS: good-first-issue,cookbook
|
|
158
|
+
DIFFICULTY: medium (~30-45 min)
|
|
159
|
+
BODY_START
|
|
160
|
+
`cookbook/11-tasks-polling.md` exists as a stub.
|
|
161
|
+
|
|
162
|
+
**Context:** R019 ([`src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py`](../../src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py))
|
|
163
|
+
flags removed `tasks/list` and the removed blocking `tasks/result`, replaced
|
|
164
|
+
by polling `tasks/get` + `tasks/update`. Tasks itself moves into the
|
|
165
|
+
`io.modelcontextprotocol/tasks` extension.
|
|
166
|
+
|
|
167
|
+
**Spec:** SEP-2663 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
168
|
+
|
|
169
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
170
|
+
`cookbook/11-tasks-polling.md` per `cookbook/_TEMPLATE.md`. Include a
|
|
171
|
+
client-side polling loop sketch (interval/backoff), and a note on the
|
|
172
|
+
relationship (if any) to R018's Multi Round-Trip Requests -- both concern
|
|
173
|
+
long-running work via different mechanisms.
|
|
174
|
+
|
|
175
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
176
|
+
BODY_END
|
|
177
|
+
### ISSUE_END
|
|
178
|
+
|
|
179
|
+
### ISSUE_START
|
|
180
|
+
TITLE: Cookbook recipe: Dynamic Client Registration deprecated (R020)
|
|
181
|
+
LABELS: good-first-issue,cookbook
|
|
182
|
+
DIFFICULTY: medium (~30-45 min, needs external CIMD research)
|
|
183
|
+
BODY_START
|
|
184
|
+
`cookbook/12-dynamic-client-registration-deprecated.md` exists as a stub.
|
|
185
|
+
This is flagged as the least-documented change in the whole revision from a
|
|
186
|
+
"here's exactly what to do instead" angle -- expect to need to research
|
|
187
|
+
Client ID Metadata Documents (CIMD) beyond this repo to write it well.
|
|
188
|
+
|
|
189
|
+
**Context:** R020 ([`src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py`](../../src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py))
|
|
190
|
+
flags RFC 7591 Dynamic Client Registration, deprecated in favor of CIMD.
|
|
191
|
+
|
|
192
|
+
**Spec:** https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
193
|
+
|
|
194
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
195
|
+
`cookbook/12-dynamic-client-registration-deprecated.md` per
|
|
196
|
+
`cookbook/_TEMPLATE.md`, including a link to the actual CIMD spec/RFC and a
|
|
197
|
+
concrete description of the document shape a server publishes instead of
|
|
198
|
+
implementing `register_client`.
|
|
199
|
+
|
|
200
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
201
|
+
BODY_END
|
|
202
|
+
### ISSUE_END
|
|
203
|
+
|
|
204
|
+
### ISSUE_START
|
|
205
|
+
TITLE: Cookbook recipe: JSON Schema 2020-12 required (R021)
|
|
206
|
+
LABELS: good-first-issue,cookbook
|
|
207
|
+
DIFFICULTY: easy (~15-30 min)
|
|
208
|
+
BODY_START
|
|
209
|
+
`cookbook/13-json-schema-2020-12.md` exists as a stub.
|
|
210
|
+
|
|
211
|
+
**Context:** R021 ([`src/mcp_migrate/rules/r021_json_schema_2020_12_required.py`](../../src/mcp_migrate/rules/r021_json_schema_2020_12_required.py))
|
|
212
|
+
flags an explicit older JSON Schema dialect (`draft-07`, `2019-09`, ...)
|
|
213
|
+
pinned on `inputSchema`/`outputSchema`. Advisory, not breaking -- most
|
|
214
|
+
servers never pin a dialect at all.
|
|
215
|
+
|
|
216
|
+
**Spec:** SEP-2106 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
217
|
+
|
|
218
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
219
|
+
`cookbook/13-json-schema-2020-12.md` per `cookbook/_TEMPLATE.md`. Worth
|
|
220
|
+
noting whether dropping an explicit `$schema` pin changes validator
|
|
221
|
+
behavior in practice for common Python JSON Schema libraries.
|
|
222
|
+
|
|
223
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
224
|
+
BODY_END
|
|
225
|
+
### ISSUE_END
|
|
226
|
+
|
|
227
|
+
### ISSUE_START
|
|
228
|
+
TITLE: Cookbook recipe: required Mcp-Method / Mcp-Name routing headers (R003)
|
|
229
|
+
LABELS: good-first-issue,cookbook
|
|
230
|
+
DIFFICULTY: easy (~15-30 min)
|
|
231
|
+
BODY_START
|
|
232
|
+
`cookbook/14-routing-headers.md` exists as a stub.
|
|
233
|
+
|
|
234
|
+
**Context:** R003 ([`src/mcp_migrate/rules/r003_routing_headers.py`](../../src/mcp_migrate/rules/r003_routing_headers.py))
|
|
235
|
+
flags hand-rolled HTTP clients speaking MCP's wire protocol that don't set
|
|
236
|
+
the required `Mcp-Method` (and, on `tools/call`/`resources/read`/
|
|
237
|
+
`prompts/get`, `Mcp-Name`) headers. Read the rule source's comments first --
|
|
238
|
+
this rule was downgraded from `breaking` to `advisory` after a real
|
|
239
|
+
false-positive incident (19 false hits on mcp-atlassian) and the recipe
|
|
240
|
+
should explain why.
|
|
241
|
+
|
|
242
|
+
**Spec:** https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
|
|
243
|
+
|
|
244
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
245
|
+
`cookbook/14-routing-headers.md` per `cookbook/_TEMPLATE.md`, with a
|
|
246
|
+
concrete example each of a file that *does* and *doesn't* trip the rule's
|
|
247
|
+
`_imports_mcp`/`MCP_METHOD_RX` gating, so a reader understands the
|
|
248
|
+
distinction between "hand-rolling MCP transport" and "wrapping an unrelated
|
|
249
|
+
backend REST API."
|
|
250
|
+
|
|
251
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
252
|
+
BODY_END
|
|
253
|
+
### ISSUE_END
|
|
254
|
+
|
|
255
|
+
### ISSUE_START
|
|
256
|
+
TITLE: Cookbook recipe: deterministic tools/list ordering (R004)
|
|
257
|
+
LABELS: good-first-issue,cookbook
|
|
258
|
+
DIFFICULTY: easy (~15-30 min)
|
|
259
|
+
BODY_START
|
|
260
|
+
`cookbook/15-deterministic-tool-ordering.md` exists as a stub. Note this
|
|
261
|
+
rule already ships a `safe`-confidence fixer for the one unambiguous
|
|
262
|
+
shape -- this recipe is for everything the fixer doesn't reach.
|
|
263
|
+
|
|
264
|
+
**Context:** R004 ([`src/mcp_migrate/rules/r004_tool_ordering.py`](../../src/mcp_migrate/rules/r004_tool_ordering.py))
|
|
265
|
+
flags `tools/list` handlers that don't guarantee a stable order.
|
|
266
|
+
|
|
267
|
+
**Spec:** "Deterministic tool ordering" (SHOULD) -- https://modelcontextprotocol.io/specification/draft/changelog
|
|
268
|
+
|
|
269
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
270
|
+
`cookbook/15-deterministic-tool-ordering.md` per `cookbook/_TEMPLATE.md`.
|
|
271
|
+
Specifically: a real example where the fixer's shape detection
|
|
272
|
+
(`src/mcp_migrate/fixers/r004_tool_ordering.py`) doesn't apply -- tools
|
|
273
|
+
built up across an `if`/`append` sequence rather than a single return of a
|
|
274
|
+
list literal -- and a note on when alphabetizing is the wrong call (an
|
|
275
|
+
intentional, product-driven order).
|
|
276
|
+
|
|
277
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
278
|
+
BODY_END
|
|
279
|
+
### ISSUE_END
|
|
280
|
+
|
|
281
|
+
### ISSUE_START
|
|
282
|
+
TITLE: Cookbook recipe: extensions map on ServerCapabilities (R005)
|
|
283
|
+
LABELS: good-first-issue,cookbook
|
|
284
|
+
DIFFICULTY: easy (~15-30 min)
|
|
285
|
+
BODY_START
|
|
286
|
+
`cookbook/16-extensions-map.md` exists as a stub. Note this rule already
|
|
287
|
+
ships a `safe`-confidence fixer that adds `extensions={}` -- this recipe is
|
|
288
|
+
about the full picture, including populated extensions.
|
|
289
|
+
|
|
290
|
+
**Context:** R005 ([`src/mcp_migrate/rules/r005_extensions.py`](../../src/mcp_migrate/rules/r005_extensions.py))
|
|
291
|
+
flags `ServerCapabilities(...)` declared without an `extensions` map.
|
|
292
|
+
|
|
293
|
+
**Spec:** "extensions field on ServerCapabilities" -- https://modelcontextprotocol.io/specification/draft/changelog
|
|
294
|
+
|
|
295
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
296
|
+
`cookbook/16-extensions-map.md` per `cookbook/_TEMPLATE.md`. Include a
|
|
297
|
+
second example beyond the trivial `extensions={}` no-op: a server that
|
|
298
|
+
declares a real extension (`io.modelcontextprotocol/tasks` is the obvious
|
|
299
|
+
one -- see `cookbook/11-tasks-polling.md`) and what a populated map looks
|
|
300
|
+
like.
|
|
301
|
+
|
|
302
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
303
|
+
BODY_END
|
|
304
|
+
### ISSUE_END
|
|
305
|
+
|
|
306
|
+
### ISSUE_START
|
|
307
|
+
TITLE: Cookbook recipe: trace context propagation from _meta (R008)
|
|
308
|
+
LABELS: good-first-issue,cookbook
|
|
309
|
+
DIFFICULTY: easy (~15-30 min)
|
|
310
|
+
BODY_START
|
|
311
|
+
`cookbook/17-trace-context-propagation.md` exists as a stub.
|
|
312
|
+
|
|
313
|
+
**Context:** R008 ([`src/mcp_migrate/rules/r008_trace_context.py`](../../src/mcp_migrate/rules/r008_trace_context.py))
|
|
314
|
+
flags projects that import `opentelemetry` but never read `traceparent` off
|
|
315
|
+
`_meta`.
|
|
316
|
+
|
|
317
|
+
**Spec:** SEP-414 -- https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414
|
|
318
|
+
|
|
319
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
320
|
+
`cookbook/17-trace-context-propagation.md` per `cookbook/_TEMPLATE.md`. The
|
|
321
|
+
single highest-value addition: a real example using OpenTelemetry Python's
|
|
322
|
+
`TraceContextTextMapPropagator`/`propagate.extract` against a `_meta` dict.
|
|
323
|
+
|
|
324
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
325
|
+
BODY_END
|
|
326
|
+
### ISSUE_END
|
|
327
|
+
|
|
328
|
+
### ISSUE_START
|
|
329
|
+
TITLE: Cookbook recipe: Roots / Sampling / Logging deprecated as core capabilities (R007)
|
|
330
|
+
LABELS: good-first-issue,cookbook
|
|
331
|
+
DIFFICULTY: easy (~15-30 min)
|
|
332
|
+
BODY_START
|
|
333
|
+
`cookbook/18-roots-sampling-logging-deprecated.md` exists as a stub.
|
|
334
|
+
|
|
335
|
+
**Context:** R007 ([`src/mcp_migrate/rules/r007_deprecated_features.py`](../../src/mcp_migrate/rules/r007_deprecated_features.py))
|
|
336
|
+
flags Roots, Sampling and Logging as deprecated core capabilities. This
|
|
337
|
+
rule intentionally overlaps with R018 for Sampling/elicitation specifically
|
|
338
|
+
(R007 reports `deprecated`, R018 reports the same code path `breaking`) --
|
|
339
|
+
the recipe should explain that relationship plainly.
|
|
340
|
+
|
|
341
|
+
**Spec:** "Roots, Sampling and Logging deprecated" -- https://modelcontextprotocol.io/specification/draft/changelog
|
|
342
|
+
|
|
343
|
+
**What "done" looks like:** fill in Before/After/Gotchas in
|
|
344
|
+
`cookbook/18-roots-sampling-logging-deprecated.md` per
|
|
345
|
+
`cookbook/_TEMPLATE.md`, with a worked Roots-to-resource-URIs example and a
|
|
346
|
+
short, explicit answer to "R007 and R018 both fired on my file, which do I
|
|
347
|
+
fix first?" (the breaking one, R018/R009/etc., but say so with an example).
|
|
348
|
+
|
|
349
|
+
See CONTRIBUTING.md#add-a-cookbook-recipe-5-minutes.
|
|
350
|
+
BODY_END
|
|
351
|
+
### ISSUE_END
|
|
352
|
+
|
|
353
|
+
### ISSUE_START
|
|
354
|
+
TITLE: Fixer: per-connection state in a module-level dict (R002)
|
|
355
|
+
LABELS: good-first-issue,fixer
|
|
356
|
+
DIFFICULTY: hard (~90+ min, may not be safely automatable)
|
|
357
|
+
BODY_START
|
|
358
|
+
No fixer exists yet for R002 ([`src/mcp_migrate/rules/r002_connection_state.py`](../../src/mcp_migrate/rules/r002_connection_state.py)),
|
|
359
|
+
which flags a module-level dict keyed by connection/session (state that
|
|
360
|
+
breaks the moment a server runs more than one replica).
|
|
361
|
+
|
|
362
|
+
**Spec:** SEP-2567 -- https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567
|
|
363
|
+
|
|
364
|
+
**Before attempting this:** read CONTRIBUTING.md's fixer section, especially
|
|
365
|
+
the standing principle -- **a false positive is worse than a missed
|
|
366
|
+
finding**, and a fixer that guesses wrong here silently corrupts a
|
|
367
|
+
project's persistence layer. Moving state into a real store is an
|
|
368
|
+
architectural decision (what store? what's the key shape?) that a text
|
|
369
|
+
editor cannot make safely. It's plausible the right outcome of this issue
|
|
370
|
+
is "no fixer ships, and the finding stands as `review`-only guidance
|
|
371
|
+
pointing at [`cookbook/01-sessions-to-explicit-handles.md`](../../cookbook/01-sessions-to-explicit-handles.md)"
|
|
372
|
+
-- that's a legitimate resolution, not a failure to close this out. If you
|
|
373
|
+
land on that conclusion, say so in the PR/comment instead of forcing code
|
|
374
|
+
that doesn't belong.
|
|
375
|
+
|
|
376
|
+
**What "done" looks like:** either a `Fixer` subclass in
|
|
377
|
+
`src/mcp_migrate/fixers/` (with the standard fixture + round-trip/idempotency
|
|
378
|
+
tests from `tests/test_fixers.py`'s pattern) for whatever narrow, genuinely
|
|
379
|
+
safe subset you can identify, or a documented decision that this rule stays
|
|
380
|
+
fixer-less with the reasoning recorded.
|
|
381
|
+
|
|
382
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
383
|
+
BODY_END
|
|
384
|
+
### ISSUE_END
|
|
385
|
+
|
|
386
|
+
### ISSUE_START
|
|
387
|
+
TITLE: Fixer: SSE resumability (Last-Event-ID) removed (R014)
|
|
388
|
+
LABELS: good-first-issue,fixer
|
|
389
|
+
DIFFICULTY: medium (~30-45 min)
|
|
390
|
+
BODY_START
|
|
391
|
+
No fixer exists yet for R014 ([`src/mcp_migrate/rules/r014_sse_resumability_removed.py`](../../src/mcp_migrate/rules/r014_sse_resumability_removed.py)),
|
|
392
|
+
which flags `Last-Event-ID`-based stream resumability, removed as of
|
|
393
|
+
2026-07-28.
|
|
394
|
+
|
|
395
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
396
|
+
|
|
397
|
+
**A reasonable scope:** same TODO-annotation pattern as the R011/R012
|
|
398
|
+
fixer issues -- comment out the dead `Last-Event-ID` read/event-store
|
|
399
|
+
replay code with a `# TODO(mcp-migrate): ...` pointing at
|
|
400
|
+
`cookbook/08-sse-resumability-removed.md`. The event store itself (if it
|
|
401
|
+
serves other purposes, e.g. audit logging) should be left alone; only the
|
|
402
|
+
resumability-specific read/replay logic is in scope.
|
|
403
|
+
|
|
404
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`,
|
|
405
|
+
`review` confidence, with fixtures and tests per `tests/test_fixers.py`.
|
|
406
|
+
|
|
407
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
408
|
+
BODY_END
|
|
409
|
+
### ISSUE_END
|
|
410
|
+
|
|
411
|
+
### ISSUE_START
|
|
412
|
+
TITLE: Fixer: missing Mcp-Method/Mcp-Name routing headers (R003)
|
|
413
|
+
LABELS: good-first-issue,fixer
|
|
414
|
+
DIFFICULTY: hard (~60-90 min, advisory severity -- lower priority)
|
|
415
|
+
BODY_START
|
|
416
|
+
No fixer exists yet for R003 ([`src/mcp_migrate/rules/r003_routing_headers.py`](../../src/mcp_migrate/rules/r003_routing_headers.py)),
|
|
417
|
+
which flags hand-rolled HTTP `.post()`/`.request()` calls missing the
|
|
418
|
+
required `Mcp-Method`/`Mcp-Name` headers.
|
|
419
|
+
|
|
420
|
+
**Spec:** https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
|
|
421
|
+
|
|
422
|
+
**Why this is hard:** the rule itself is already `advisory` (downgraded
|
|
423
|
+
from `breaking` after a real false-positive incident -- read the rule
|
|
424
|
+
source's comments on mcp-atlassian's R003 history before starting). Any
|
|
425
|
+
fixer needs to be at least as conservative as the rule's own gating
|
|
426
|
+
(`_imports_mcp`/`MCP_METHOD_RX`), and inserting a header into an arbitrary
|
|
427
|
+
`.post(...)` call site correctly (as a kwarg? into an existing `headers=`
|
|
428
|
+
dict? a new one?) needs real call-site parsing, not just a regex match.
|
|
429
|
+
|
|
430
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
431
|
+
that only fires on the narrowest, most mechanical shape you're confident
|
|
432
|
+
about (e.g. a call site that already has a `headers={...}` dict literal
|
|
433
|
+
inline), leaving everything else alone, confidence tagged `review`. Include
|
|
434
|
+
fixtures and tests per `tests/test_fixers.py`'s pattern.
|
|
435
|
+
|
|
436
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
437
|
+
BODY_END
|
|
438
|
+
### ISSUE_END
|
|
439
|
+
|
|
440
|
+
### ISSUE_START
|
|
441
|
+
TITLE: Fixer: Roots/Sampling/Logging deprecated core features (R007)
|
|
442
|
+
LABELS: good-first-issue,fixer
|
|
443
|
+
DIFFICULTY: medium (~45-60 min)
|
|
444
|
+
BODY_START
|
|
445
|
+
No fixer exists yet for R007 ([`src/mcp_migrate/rules/r007_deprecated_features.py`](../../src/mcp_migrate/rules/r007_deprecated_features.py)),
|
|
446
|
+
which flags dependencies on Roots, Sampling or Logging as core capabilities.
|
|
447
|
+
|
|
448
|
+
**Spec:** "Roots, Sampling and Logging deprecated" -- https://modelcontextprotocol.io/specification/draft/changelog
|
|
449
|
+
|
|
450
|
+
**A reasonable scope:** rather than trying to migrate the functionality
|
|
451
|
+
(impossible without understanding what the handler does), consider the
|
|
452
|
+
`r001_session_id.py` fixer's pattern -- annotate the flagged line with a
|
|
453
|
+
`# TODO(mcp-migrate): ...` comment pointing at the spec and
|
|
454
|
+
`cookbook/18-roots-sampling-logging-deprecated.md`, `review` confidence,
|
|
455
|
+
without touching the code's behavior at all. That's mechanical, safe, and
|
|
456
|
+
genuinely useful (a loud, precise pointer at what needs human attention)
|
|
457
|
+
without pretending to migrate something this fuzzy automatically.
|
|
458
|
+
|
|
459
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
460
|
+
following that TODO-annotation pattern, with fixtures and tests per
|
|
461
|
+
`tests/test_fixers.py`.
|
|
462
|
+
|
|
463
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
464
|
+
BODY_END
|
|
465
|
+
### ISSUE_END
|
|
466
|
+
|
|
467
|
+
### ISSUE_START
|
|
468
|
+
TITLE: Fixer: trace context not propagated from _meta (R008)
|
|
469
|
+
LABELS: good-first-issue,fixer
|
|
470
|
+
DIFFICULTY: medium (~45 min)
|
|
471
|
+
BODY_START
|
|
472
|
+
No fixer exists yet for R008 ([`src/mcp_migrate/rules/r008_trace_context.py`](../../src/mcp_migrate/rules/r008_trace_context.py)),
|
|
473
|
+
which flags OpenTelemetry-using projects that never read `traceparent` off
|
|
474
|
+
`_meta`.
|
|
475
|
+
|
|
476
|
+
**Spec:** SEP-414 -- https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414
|
|
477
|
+
|
|
478
|
+
**Why this is hard to make `safe`:** there's no single call site to patch --
|
|
479
|
+
the finding is project-wide ("OpenTelemetry is used somewhere, `traceparent`
|
|
480
|
+
is read nowhere"), not tied to one line. A useful, honest fixer here
|
|
481
|
+
probably can't insert working extraction code blind; it's more likely to
|
|
482
|
+
land as a `review`-confidence TODO inserted at the request-handling entry
|
|
483
|
+
point(s) the fixer can identify, pointing at
|
|
484
|
+
`cookbook/17-trace-context-propagation.md`.
|
|
485
|
+
|
|
486
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
487
|
+
with a scope you can defend as genuinely mechanical (even if that scope is
|
|
488
|
+
"insert a TODO comment," not "extract the trace context correctly"), with
|
|
489
|
+
fixtures and tests per `tests/test_fixers.py`.
|
|
490
|
+
|
|
491
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
492
|
+
BODY_END
|
|
493
|
+
### ISSUE_END
|
|
494
|
+
|
|
495
|
+
### ISSUE_START
|
|
496
|
+
TITLE: Fixer: initialize/initialized handshake still implemented (R009)
|
|
497
|
+
LABELS: good-first-issue,fixer
|
|
498
|
+
DIFFICULTY: hard (~60-90 min)
|
|
499
|
+
BODY_START
|
|
500
|
+
No fixer exists yet for R009 ([`src/mcp_migrate/rules/r009_initialize_handshake_removed.py`](../../src/mcp_migrate/rules/r009_initialize_handshake_removed.py)),
|
|
501
|
+
which flags the removed `initialize`/`notifications/initialized` handshake.
|
|
502
|
+
|
|
503
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
504
|
+
|
|
505
|
+
**A reasonable scope:** the `r001_session_id.py` fixer's approach (comment
|
|
506
|
+
out the dead handler, leave a `# TODO(mcp-migrate): ...` pointing at
|
|
507
|
+
`cookbook/02-initialize-to-server-discover.md`) is a good template --
|
|
508
|
+
deleting the handler decorator/function outright risks leaving a dangling
|
|
509
|
+
reference elsewhere in the file (an import, a registration call) that a
|
|
510
|
+
text-level fixer can't safely trace. Commenting out is reversible and
|
|
511
|
+
loud; deleting is not.
|
|
512
|
+
|
|
513
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
514
|
+
using that pattern, `review` confidence, with fixtures and tests per
|
|
515
|
+
`tests/test_fixers.py`.
|
|
516
|
+
|
|
517
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
518
|
+
BODY_END
|
|
519
|
+
### ISSUE_END
|
|
520
|
+
|
|
521
|
+
### ISSUE_START
|
|
522
|
+
TITLE: Fixer: server/discover missing (R010)
|
|
523
|
+
LABELS: good-first-issue,fixer
|
|
524
|
+
DIFFICULTY: hard (~60+ min, may not be safely automatable)
|
|
525
|
+
BODY_START
|
|
526
|
+
No fixer exists yet for R010 ([`src/mcp_migrate/rules/r010_server_discover_missing.py`](../../src/mcp_migrate/rules/r010_server_discover_missing.py)),
|
|
527
|
+
which flags projects that register MCP handlers but never implement
|
|
528
|
+
`server/discover`.
|
|
529
|
+
|
|
530
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
531
|
+
|
|
532
|
+
**Why this is hard:** unlike most fixers in this project, this finding
|
|
533
|
+
requires *adding* new, correct behavior (a `server/discover` response
|
|
534
|
+
describing this specific server's protocol versions, capabilities and
|
|
535
|
+
identity), not editing existing code. A fixer can't invent a server's name
|
|
536
|
+
or which capabilities it actually supports. A plausible outcome here is a
|
|
537
|
+
scaffold-only fixer that inserts a stub handler with obvious placeholder
|
|
538
|
+
values and a loud `# TODO(mcp-migrate): fill in your real capabilities` --
|
|
539
|
+
confidence `review`, never `safe`. It's also plausible this rule should
|
|
540
|
+
stay fixer-less; say so in the PR if you land there.
|
|
541
|
+
|
|
542
|
+
**What "done" looks like:** either a scaffold-inserting `Fixer` subclass
|
|
543
|
+
with fixtures/tests per `tests/test_fixers.py`, or a documented decision
|
|
544
|
+
that this stays fixer-less.
|
|
545
|
+
|
|
546
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
547
|
+
BODY_END
|
|
548
|
+
### ISSUE_END
|
|
549
|
+
|
|
550
|
+
### ISSUE_START
|
|
551
|
+
TITLE: Fixer: ping removed (R011)
|
|
552
|
+
LABELS: good-first-issue,fixer
|
|
553
|
+
DIFFICULTY: medium (~30-45 min)
|
|
554
|
+
BODY_START
|
|
555
|
+
No fixer exists yet for R011 ([`src/mcp_migrate/rules/r011_ping_removed.py`](../../src/mcp_migrate/rules/r011_ping_removed.py)),
|
|
556
|
+
which flags the removed `ping`/`PingRequest` request-response.
|
|
557
|
+
|
|
558
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
559
|
+
|
|
560
|
+
**A reasonable scope:** this is close in shape to the shipped
|
|
561
|
+
`r001_session_id.py` fixer -- comment out the dead `PingRequest` handler
|
|
562
|
+
(and/or the `method == "ping"` dispatch branch) with a
|
|
563
|
+
`# TODO(mcp-migrate): ...` pointing at `cookbook/06-ping-removed.md`, being
|
|
564
|
+
careful (like R001's fixer) never to comment out a block-opener line that
|
|
565
|
+
would leave a dangling suite.
|
|
566
|
+
|
|
567
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`,
|
|
568
|
+
`review` confidence, with fixtures and tests per `tests/test_fixers.py`.
|
|
569
|
+
|
|
570
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
571
|
+
BODY_END
|
|
572
|
+
### ISSUE_END
|
|
573
|
+
|
|
574
|
+
### ISSUE_START
|
|
575
|
+
TITLE: Fixer: logging/setLevel removed (R012)
|
|
576
|
+
LABELS: good-first-issue,fixer
|
|
577
|
+
DIFFICULTY: medium (~30-45 min)
|
|
578
|
+
BODY_START
|
|
579
|
+
No fixer exists yet for R012 ([`src/mcp_migrate/rules/r012_logging_set_level_removed.py`](../../src/mcp_migrate/rules/r012_logging_set_level_removed.py)),
|
|
580
|
+
which flags the removed `logging/setLevel` request.
|
|
581
|
+
|
|
582
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
583
|
+
|
|
584
|
+
**A reasonable scope:** same pattern as the R011 fixer issue above -- comment
|
|
585
|
+
out the dead `SetLevelRequest` handler, leave a `# TODO(mcp-migrate): ...`
|
|
586
|
+
pointing at `cookbook/07-logging-set-level-removed.md`.
|
|
587
|
+
|
|
588
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`,
|
|
589
|
+
`review` confidence, with fixtures and tests per `tests/test_fixers.py`.
|
|
590
|
+
|
|
591
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
592
|
+
BODY_END
|
|
593
|
+
### ISSUE_END
|
|
594
|
+
|
|
595
|
+
### ISSUE_START
|
|
596
|
+
TITLE: Fixer: resources/subscribe and resources/unsubscribe replaced (R013)
|
|
597
|
+
LABELS: good-first-issue,fixer
|
|
598
|
+
DIFFICULTY: hard (~60-90 min)
|
|
599
|
+
BODY_START
|
|
600
|
+
No fixer exists yet for R013 ([`src/mcp_migrate/rules/r013_subscriptions_replaced.py`](../../src/mcp_migrate/rules/r013_subscriptions_replaced.py)),
|
|
601
|
+
which flags the removed `resources/subscribe`/`resources/unsubscribe`
|
|
602
|
+
methods, replaced by `subscriptions/listen`.
|
|
603
|
+
|
|
604
|
+
**Spec:** SEP-2575 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
605
|
+
|
|
606
|
+
**Why this is hard:** unlike a rename, the new shape genuinely collapses two
|
|
607
|
+
handlers (subscribe, unsubscribe) plus a notification stream into one
|
|
608
|
+
long-lived listen call -- see `cookbook/04-subscribe-to-subscriptions-listen.md`
|
|
609
|
+
for the shape. A safe fixer probably can't rewrite this correctly in
|
|
610
|
+
general; a `review`-confidence TODO-annotation on the old handlers
|
|
611
|
+
(matching the R001/R009/R011/R012 fixer pattern) is the most defensible
|
|
612
|
+
starting scope.
|
|
613
|
+
|
|
614
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
615
|
+
with whatever scope you can defend as genuinely mechanical, with fixtures
|
|
616
|
+
and tests per `tests/test_fixers.py`.
|
|
617
|
+
|
|
618
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
619
|
+
BODY_END
|
|
620
|
+
### ISSUE_END
|
|
621
|
+
|
|
622
|
+
### ISSUE_START
|
|
623
|
+
TITLE: Fixer: required resultType missing (R015)
|
|
624
|
+
LABELS: good-first-issue,fixer
|
|
625
|
+
DIFFICULTY: hard (~45-60 min, may not be safely automatable)
|
|
626
|
+
BODY_START
|
|
627
|
+
No fixer exists yet for R015 ([`src/mcp_migrate/rules/r015_result_type_required.py`](../../src/mcp_migrate/rules/r015_result_type_required.py)),
|
|
628
|
+
which flags result-returning handlers missing the required `resultType`
|
|
629
|
+
field.
|
|
630
|
+
|
|
631
|
+
**Spec:** SEP-2322 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
632
|
+
|
|
633
|
+
**Why this is hard:** the correct value (`"complete"` vs `"input_required"`)
|
|
634
|
+
depends on what the specific handler actually does, and blindly inserting
|
|
635
|
+
`"resultType": "complete"` into every `return`/dict literal in a file risks
|
|
636
|
+
being *wrong* for a handler that genuinely needs another round trip --
|
|
637
|
+
which is arguably worse than the missing field, since it looks fixed. See
|
|
638
|
+
`cookbook/05-result-type-and-cache-metadata.md` for the distinction.
|
|
639
|
+
|
|
640
|
+
**What "done" looks like:** either a narrowly-scoped `safe` fixer for the
|
|
641
|
+
one shape you're confident is always `"complete"` (if you can identify
|
|
642
|
+
one), or a documented decision that this rule stays fixer-less because
|
|
643
|
+
guessing the value is unsafe. Either way, fixtures/tests per
|
|
644
|
+
`tests/test_fixers.py` if code ships.
|
|
645
|
+
|
|
646
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
647
|
+
BODY_END
|
|
648
|
+
### ISSUE_END
|
|
649
|
+
|
|
650
|
+
### ISSUE_START
|
|
651
|
+
TITLE: Fixer: ttlMs/cacheScope missing on list/read results (R016)
|
|
652
|
+
LABELS: good-first-issue,fixer
|
|
653
|
+
DIFFICULTY: hard (~45-60 min, may not be safely automatable)
|
|
654
|
+
BODY_START
|
|
655
|
+
No fixer exists yet for R016 ([`src/mcp_migrate/rules/r016_cacheable_result_required.py`](../../src/mcp_migrate/rules/r016_cacheable_result_required.py)),
|
|
656
|
+
which flags list/read handlers missing `ttlMs`/`cacheScope`.
|
|
657
|
+
|
|
658
|
+
**Spec:** SEP-2549 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
659
|
+
|
|
660
|
+
**Why this is hard:** both values are judgment calls specific to the data
|
|
661
|
+
being cached (how often does this list change? does it vary per-client?).
|
|
662
|
+
A fixer that inserts a wrong `cacheScope` could cause one client's data to
|
|
663
|
+
be served from another client's cache -- silently corrupting behavior,
|
|
664
|
+
which is exactly the failure mode this project's fixers are built to avoid.
|
|
665
|
+
See `cookbook/05-result-type-and-cache-metadata.md`.
|
|
666
|
+
|
|
667
|
+
**What "done" looks like:** either a fixer that inserts conservative
|
|
668
|
+
placeholder values with a loud `# TODO(mcp-migrate): confirm this TTL/scope
|
|
669
|
+
is correct` (review confidence, never safe), or a documented decision that
|
|
670
|
+
this rule stays fixer-less. Fixtures/tests per `tests/test_fixers.py` if
|
|
671
|
+
code ships.
|
|
672
|
+
|
|
673
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
674
|
+
BODY_END
|
|
675
|
+
### ISSUE_END
|
|
676
|
+
|
|
677
|
+
### ISSUE_START
|
|
678
|
+
TITLE: Fixer: server-initiated calls replaced by Multi Round-Trip Requests (R018)
|
|
679
|
+
LABELS: good-first-issue,fixer
|
|
680
|
+
DIFFICULTY: hard (~90+ min, may not be safely automatable)
|
|
681
|
+
BODY_START
|
|
682
|
+
No fixer exists yet for R018 ([`src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py`](../../src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py)),
|
|
683
|
+
which flags server-initiated `roots/list`, `sampling/createMessage` and
|
|
684
|
+
`elicitation/create` -- all replaced by Multi Round-Trip Requests.
|
|
685
|
+
|
|
686
|
+
**Spec:** SEP-2322 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
687
|
+
|
|
688
|
+
**Why this is hard:** this is a genuine control-flow rewrite (a blocking
|
|
689
|
+
call becomes two separate request/response pairs correlated by a client
|
|
690
|
+
retry -- see `cookbook/10-multi-round-trip-requests.md`), not a rename or
|
|
691
|
+
an added field. It is very unlikely a text-level fixer can do this safely
|
|
692
|
+
in general.
|
|
693
|
+
|
|
694
|
+
**What "done" looks like:** most likely, a documented decision that this
|
|
695
|
+
rule stays fixer-less, backed by the cookbook recipe instead (make sure
|
|
696
|
+
`cookbook/10-multi-round-trip-requests.md` is filled in first -- see the
|
|
697
|
+
matching cookbook issue). If you find a genuinely narrow, safe subset
|
|
698
|
+
(e.g. annotating the call site with a TODO), that's welcome too, with
|
|
699
|
+
fixtures/tests per `tests/test_fixers.py`.
|
|
700
|
+
|
|
701
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
702
|
+
BODY_END
|
|
703
|
+
### ISSUE_END
|
|
704
|
+
|
|
705
|
+
### ISSUE_START
|
|
706
|
+
TITLE: Fixer: tasks/list and blocking tasks/result removed (R019)
|
|
707
|
+
LABELS: good-first-issue,fixer
|
|
708
|
+
DIFFICULTY: hard (~60-90 min)
|
|
709
|
+
BODY_START
|
|
710
|
+
No fixer exists yet for R019 ([`src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py`](../../src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py)),
|
|
711
|
+
which flags removed `tasks/list` and the removed blocking `tasks/result`.
|
|
712
|
+
|
|
713
|
+
**Spec:** SEP-2663 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
714
|
+
|
|
715
|
+
**Why this is hard:** converting a blocking wait into a `tasks/get` +
|
|
716
|
+
`tasks/update` polling loop is a real control-flow rewrite (see
|
|
717
|
+
`cookbook/11-tasks-polling.md`), not a mechanical edit. A `review`-confidence
|
|
718
|
+
TODO-annotation on the old handler (same pattern as the R009/R011/R012/R013
|
|
719
|
+
fixer issues) is the most defensible starting scope.
|
|
720
|
+
|
|
721
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
722
|
+
with whatever scope you can defend as genuinely mechanical, with fixtures
|
|
723
|
+
and tests per `tests/test_fixers.py`.
|
|
724
|
+
|
|
725
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
726
|
+
BODY_END
|
|
727
|
+
### ISSUE_END
|
|
728
|
+
|
|
729
|
+
### ISSUE_START
|
|
730
|
+
TITLE: Fixer: Dynamic Client Registration deprecated (R020)
|
|
731
|
+
LABELS: good-first-issue,fixer
|
|
732
|
+
DIFFICULTY: medium (~45 min)
|
|
733
|
+
BODY_START
|
|
734
|
+
No fixer exists yet for R020 ([`src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py`](../../src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py)),
|
|
735
|
+
which flags RFC 7591 Dynamic Client Registration usage (`register_client`,
|
|
736
|
+
`RegisterClientRequest`, `DynamicClientRegistration`).
|
|
737
|
+
|
|
738
|
+
**Spec:** https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
739
|
+
|
|
740
|
+
**A reasonable scope:** since `deprecated` (not `breaking`) and migrating
|
|
741
|
+
off DCR requires standing up a real Client ID Metadata Document, a
|
|
742
|
+
mechanical fixer probably can't do the migration itself -- but it could
|
|
743
|
+
annotate the flagged code with a `# TODO(mcp-migrate): ...` pointing at
|
|
744
|
+
`cookbook/12-dynamic-client-registration-deprecated.md`, matching the
|
|
745
|
+
TODO-annotation pattern used elsewhere in this project.
|
|
746
|
+
|
|
747
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`
|
|
748
|
+
using that pattern, `review` confidence, with fixtures and tests per
|
|
749
|
+
`tests/test_fixers.py`.
|
|
750
|
+
|
|
751
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
752
|
+
BODY_END
|
|
753
|
+
### ISSUE_END
|
|
754
|
+
|
|
755
|
+
### ISSUE_START
|
|
756
|
+
TITLE: Fixer: older JSON Schema dialect pinned (R021)
|
|
757
|
+
LABELS: good-first-issue,fixer
|
|
758
|
+
DIFFICULTY: easy (~30-45 min)
|
|
759
|
+
BODY_START
|
|
760
|
+
No fixer exists yet for R021 ([`src/mcp_migrate/rules/r021_json_schema_2020_12_required.py`](../../src/mcp_migrate/rules/r021_json_schema_2020_12_required.py)),
|
|
761
|
+
which flags an explicit older JSON Schema dialect (`draft-07`, `2019-09`,
|
|
762
|
+
...) pinned via a `$schema` URL string.
|
|
763
|
+
|
|
764
|
+
**Spec:** SEP-2106 -- https://modelcontextprotocol.io/specification/2026-07-28/changelog
|
|
765
|
+
|
|
766
|
+
**A reasonable scope:** this is one of the more mechanical ones in this
|
|
767
|
+
list, similar in spirit to the shipped R017 fixer -- rewriting an old
|
|
768
|
+
`$schema` dialect URL string to the 2020-12 equivalent
|
|
769
|
+
(`http://json-schema.org/draft/2020-12/schema`) is a close-to-exact string
|
|
770
|
+
substitution once you're confident the match is a real `$schema` value and
|
|
771
|
+
not, say, a comment mentioning an old draft. Confidence `safe` is plausible
|
|
772
|
+
here if you scope it tightly (e.g. only rewrite when the match is inside a
|
|
773
|
+
`"$schema":` key's value).
|
|
774
|
+
|
|
775
|
+
**What "done" looks like:** a `Fixer` subclass in `src/mcp_migrate/fixers/`,
|
|
776
|
+
with fixtures and tests per `tests/test_fixers.py`, including a fixture that
|
|
777
|
+
proves the fixer backs off on an ambiguous shape (the dialect string
|
|
778
|
+
appearing somewhere that isn't clearly a `$schema` value).
|
|
779
|
+
|
|
780
|
+
See CONTRIBUTING.md#add-a-fixer-45-minutes.
|
|
781
|
+
BODY_END
|
|
782
|
+
### ISSUE_END
|