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.
Files changed (113) hide show
  1. mcp_migrate-0.1.0/.github/GOOD_FIRST_ISSUES.md +782 -0
  2. mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/add-server.yml +45 -0
  3. mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/cookbook-recipe.yml +38 -0
  4. mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/new-fixer.yml +53 -0
  5. mcp_migrate-0.1.0/.github/ISSUE_TEMPLATE/new-rule.yml +50 -0
  6. mcp_migrate-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +40 -0
  7. mcp_migrate-0.1.0/.github/scripts/file_issues.sh +138 -0
  8. mcp_migrate-0.1.0/.github/workflows/board.yml +43 -0
  9. mcp_migrate-0.1.0/.github/workflows/ci.yml +29 -0
  10. mcp_migrate-0.1.0/.github/workflows/release.yml +105 -0
  11. mcp_migrate-0.1.0/.gitignore +52 -0
  12. mcp_migrate-0.1.0/CONTRIBUTING.md +388 -0
  13. mcp_migrate-0.1.0/LICENSE +204 -0
  14. mcp_migrate-0.1.0/PKG-INFO +345 -0
  15. mcp_migrate-0.1.0/README.md +332 -0
  16. mcp_migrate-0.1.0/cookbook/01-sessions-to-explicit-handles.md +123 -0
  17. mcp_migrate-0.1.0/cookbook/02-initialize-to-server-discover.md +103 -0
  18. mcp_migrate-0.1.0/cookbook/03-sse-to-streamable-http.md +74 -0
  19. mcp_migrate-0.1.0/cookbook/04-subscribe-to-subscriptions-listen.md +98 -0
  20. mcp_migrate-0.1.0/cookbook/05-result-type-and-cache-metadata.md +107 -0
  21. mcp_migrate-0.1.0/cookbook/06-ping-removed.md +39 -0
  22. mcp_migrate-0.1.0/cookbook/07-logging-set-level-removed.md +40 -0
  23. mcp_migrate-0.1.0/cookbook/08-sse-resumability-removed.md +41 -0
  24. mcp_migrate-0.1.0/cookbook/09-resource-not-found-error-code.md +49 -0
  25. mcp_migrate-0.1.0/cookbook/10-multi-round-trip-requests.md +49 -0
  26. mcp_migrate-0.1.0/cookbook/11-tasks-polling.md +41 -0
  27. mcp_migrate-0.1.0/cookbook/12-dynamic-client-registration-deprecated.md +40 -0
  28. mcp_migrate-0.1.0/cookbook/13-json-schema-2020-12.md +40 -0
  29. mcp_migrate-0.1.0/cookbook/14-routing-headers.md +42 -0
  30. mcp_migrate-0.1.0/cookbook/15-deterministic-tool-ordering.md +43 -0
  31. mcp_migrate-0.1.0/cookbook/16-extensions-map.md +42 -0
  32. mcp_migrate-0.1.0/cookbook/17-trace-context-propagation.md +44 -0
  33. mcp_migrate-0.1.0/cookbook/18-roots-sampling-logging-deprecated.md +42 -0
  34. mcp_migrate-0.1.0/cookbook/README.md +72 -0
  35. mcp_migrate-0.1.0/cookbook/_TEMPLATE.md +55 -0
  36. mcp_migrate-0.1.0/docs/banner.jpg +0 -0
  37. mcp_migrate-0.1.0/pyproject.toml +29 -0
  38. mcp_migrate-0.1.0/registry/README.md +11 -0
  39. mcp_migrate-0.1.0/registry/schema.yaml +11 -0
  40. mcp_migrate-0.1.0/registry/servers/aws-documentation-mcp-server.yaml +10 -0
  41. mcp_migrate-0.1.0/registry/servers/cloudwatch-mcp-server.yaml +10 -0
  42. mcp_migrate-0.1.0/registry/servers/duckduckgo-mcp-server.yaml +10 -0
  43. mcp_migrate-0.1.0/registry/servers/dynamodb-mcp-server.yaml +10 -0
  44. mcp_migrate-0.1.0/registry/servers/mcp-atlassian.yaml +10 -0
  45. mcp_migrate-0.1.0/registry/servers/mcp-neo4j-cypher.yaml +10 -0
  46. mcp_migrate-0.1.0/registry/servers/mcp-server-fetch.yaml +10 -0
  47. mcp_migrate-0.1.0/registry/servers/mcp-server-git.yaml +10 -0
  48. mcp_migrate-0.1.0/registry/servers/mcp-server-motherduck.yaml +10 -0
  49. mcp_migrate-0.1.0/registry/servers/mcp-server-qdrant.yaml +10 -0
  50. mcp_migrate-0.1.0/registry/servers/mcp-server-sentry.yaml +10 -0
  51. mcp_migrate-0.1.0/registry/servers/mcp-server-sqlite.yaml +10 -0
  52. mcp_migrate-0.1.0/registry/servers/mcp-server-time.yaml +10 -0
  53. mcp_migrate-0.1.0/registry/servers/mcp-server-tree-sitter.yaml +10 -0
  54. mcp_migrate-0.1.0/scripts/render_board.py +54 -0
  55. mcp_migrate-0.1.0/scripts/validate_registry.py +62 -0
  56. mcp_migrate-0.1.0/src/mcp_migrate/__init__.py +1 -0
  57. mcp_migrate-0.1.0/src/mcp_migrate/cli.py +353 -0
  58. mcp_migrate-0.1.0/src/mcp_migrate/fixers/__init__.py +23 -0
  59. mcp_migrate-0.1.0/src/mcp_migrate/fixers/_textedit.py +52 -0
  60. mcp_migrate-0.1.0/src/mcp_migrate/fixers/base.py +56 -0
  61. mcp_migrate-0.1.0/src/mcp_migrate/fixers/r001_session_id.py +68 -0
  62. mcp_migrate-0.1.0/src/mcp_migrate/fixers/r004_tool_ordering.py +135 -0
  63. mcp_migrate-0.1.0/src/mcp_migrate/fixers/r005_extensions.py +87 -0
  64. mcp_migrate-0.1.0/src/mcp_migrate/fixers/r006_sse_transport.py +108 -0
  65. mcp_migrate-0.1.0/src/mcp_migrate/fixers/r017_resource_not_found_code_changed.py +46 -0
  66. mcp_migrate-0.1.0/src/mcp_migrate/grade.py +49 -0
  67. mcp_migrate-0.1.0/src/mcp_migrate/rules/__init__.py +20 -0
  68. mcp_migrate-0.1.0/src/mcp_migrate/rules/base.py +187 -0
  69. mcp_migrate-0.1.0/src/mcp_migrate/rules/r001_session_id.py +25 -0
  70. mcp_migrate-0.1.0/src/mcp_migrate/rules/r002_connection_state.py +63 -0
  71. mcp_migrate-0.1.0/src/mcp_migrate/rules/r003_routing_headers.py +149 -0
  72. mcp_migrate-0.1.0/src/mcp_migrate/rules/r004_tool_ordering.py +87 -0
  73. mcp_migrate-0.1.0/src/mcp_migrate/rules/r005_extensions.py +37 -0
  74. mcp_migrate-0.1.0/src/mcp_migrate/rules/r006_sse_transport.py +18 -0
  75. mcp_migrate-0.1.0/src/mcp_migrate/rules/r007_deprecated_features.py +25 -0
  76. mcp_migrate-0.1.0/src/mcp_migrate/rules/r008_trace_context.py +24 -0
  77. mcp_migrate-0.1.0/src/mcp_migrate/rules/r009_initialize_handshake_removed.py +48 -0
  78. mcp_migrate-0.1.0/src/mcp_migrate/rules/r010_server_discover_missing.py +118 -0
  79. mcp_migrate-0.1.0/src/mcp_migrate/rules/r011_ping_removed.py +49 -0
  80. mcp_migrate-0.1.0/src/mcp_migrate/rules/r012_logging_set_level_removed.py +36 -0
  81. mcp_migrate-0.1.0/src/mcp_migrate/rules/r013_subscriptions_replaced.py +36 -0
  82. mcp_migrate-0.1.0/src/mcp_migrate/rules/r014_sse_resumability_removed.py +29 -0
  83. mcp_migrate-0.1.0/src/mcp_migrate/rules/r015_result_type_required.py +61 -0
  84. mcp_migrate-0.1.0/src/mcp_migrate/rules/r016_cacheable_result_required.py +57 -0
  85. mcp_migrate-0.1.0/src/mcp_migrate/rules/r017_resource_not_found_code_changed.py +38 -0
  86. mcp_migrate-0.1.0/src/mcp_migrate/rules/r018_multi_round_trip_replaces_server_initiated.py +66 -0
  87. mcp_migrate-0.1.0/src/mcp_migrate/rules/r019_tasks_polling_replaces_blocking_result.py +40 -0
  88. mcp_migrate-0.1.0/src/mcp_migrate/rules/r020_dynamic_client_registration_deprecated.py +38 -0
  89. mcp_migrate-0.1.0/src/mcp_migrate/rules/r021_json_schema_2020_12_required.py +42 -0
  90. mcp_migrate-0.1.0/src/mcp_migrate/scan.py +57 -0
  91. mcp_migrate-0.1.0/tests/conftest.py +16 -0
  92. mcp_migrate-0.1.0/tests/fixtures/clean_server/server.py +69 -0
  93. mcp_migrate-0.1.0/tests/fixtures/clean_server/store.py +35 -0
  94. mcp_migrate-0.1.0/tests/fixtures/comment_only_mentions/server.py +21 -0
  95. mcp_migrate-0.1.0/tests/fixtures/discover_named_helpers/server.py +47 -0
  96. mcp_migrate-0.1.0/tests/fixtures/fastmcp_functional_registration/server.py +42 -0
  97. mcp_migrate-0.1.0/tests/fixtures/fastmcp_wrapper_no_mcp_surface/server.py +27 -0
  98. mcp_migrate-0.1.0/tests/fixtures/fixer_roundtrip/errors.py +17 -0
  99. mcp_migrate-0.1.0/tests/fixtures/fixer_roundtrip/server.py +37 -0
  100. mcp_migrate-0.1.0/tests/fixtures/legacy_server/auth.py +12 -0
  101. mcp_migrate-0.1.0/tests/fixtures/legacy_server/handshake.py +18 -0
  102. mcp_migrate-0.1.0/tests/fixtures/legacy_server/legacy_protocol.py +61 -0
  103. mcp_migrate-0.1.0/tests/fixtures/legacy_server/resumable_stream.py +31 -0
  104. mcp_migrate-0.1.0/tests/fixtures/legacy_server/server.py +100 -0
  105. mcp_migrate-0.1.0/tests/fixtures/legacy_server/webhook.py +27 -0
  106. mcp_migrate-0.1.0/tests/fixtures/rest_wrapper_no_mcp_surface/jira_client.py +26 -0
  107. mcp_migrate-0.1.0/tests/fixtures/test_skip_project/server.py +7 -0
  108. mcp_migrate-0.1.0/tests/fixtures/test_skip_project/tests/test_legacy_transport_compat.py +17 -0
  109. mcp_migrate-0.1.0/tests/test_fixers.py +450 -0
  110. mcp_migrate-0.1.0/tests/test_registry.py +148 -0
  111. mcp_migrate-0.1.0/tests/test_regressions.py +352 -0
  112. mcp_migrate-0.1.0/tests/test_rules.py +120 -0
  113. 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