Skip to content

Commit 9473442

Browse files
committed
docs: trim migration guide to breaking changes only
The resource template migration section was documenting new features alongside behavior changes. Trimmed to the four actual breakages: path-safety checks now applied by default, template literals regex- escaped, lenient query matching, and parse-time validation. New capabilities and best-practice guidance moved to the Resources doc via a link at the end.
1 parent 278e5e7 commit 9473442

File tree

1 file changed

+27
-48
lines changed

1 file changed

+27
-48
lines changed

docs/migration.md

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -545,69 +545,48 @@ await client.read_resource("test://resource")
545545
await client.read_resource(str(my_any_url))
546546
```
547547

548-
### Resource templates: RFC 6570 support and security hardening
548+
### Resource templates: matching behavior changes
549549

550-
Resource template matching has been rewritten to support RFC 6570 URI
551-
templates (Levels 1-3 plus path-style explode) and to apply path-safety
552-
checks to extracted parameters by default.
550+
Resource template matching has been rewritten with RFC 6570 support.
551+
Four behaviors have changed:
553552

554-
**New capabilities:**
555-
556-
- `{+path}` (reserved expansion) now works — it matches multi-segment
557-
paths like `src/main.py`. Previously only simple `{var}` was supported.
558-
- All Level 3 operators: `{.ext}`, `{/seg}`, `{;param}`, `{?query}`, `{&cont}`
559-
- Path-style explode: `{/path*}` extracts a `list[str]` of segments
560-
- Template literals are now regex-escaped (a `.` in your template no
561-
longer matches any character — this was a bug)
562-
563-
**Security hardening (may require opt-out):**
564-
565-
By default, extracted parameter values are now rejected if they:
566-
567-
- Contain `..` as a path component (e.g., `..`, `../etc`, `a/../../b`)
568-
- Look like an absolute filesystem path (e.g., `/etc/passwd`, `C:\Windows`)
569-
570-
These checks apply to the decoded value, so they catch traversal
571-
regardless of encoding (`../etc`, `..%2Fetc`, `%2E%2E/etc` all caught).
572-
573-
If your template parameters legitimately contain `..` (e.g., git commit
574-
ranges like `HEAD~3..HEAD`) or absolute paths, exempt them:
553+
**Path-safety checks applied by default.** Extracted parameter values
554+
containing `..` as a path component or looking like an absolute path
555+
(`/etc/passwd`, `C:\Windows`) now cause the template to not match.
556+
This is checked on the decoded value, so `..%2Fetc` and `%2E%2E` are
557+
caught too. If a parameter legitimately contains these (a git commit
558+
range, a fully-qualified identifier), exempt it:
575559

576560
```python
577-
from mcp.server.mcpserver import MCPServer, ResourceSecurity
578-
579-
mcp = MCPServer()
561+
from mcp.server.mcpserver import ResourceSecurity
580562

581563
@mcp.resource(
582564
"git://diff/{+range}",
583565
security=ResourceSecurity(exempt_params={"range"}),
584566
)
585-
def git_diff(range: str) -> str:
586-
...
567+
def git_diff(range: str) -> str: ...
587568
```
588569

589-
Or relax the policy server-wide:
570+
Note that `..` is only flagged as a standalone path component, so a
571+
value like `v1.0..v2.0` is unaffected.
590572

591-
```python
592-
mcp = MCPServer(
593-
resource_security=ResourceSecurity(reject_path_traversal=False),
594-
)
595-
```
573+
**Template literals are regex-escaped.** Previously a `.` in your
574+
template matched any character; now it matches only a literal dot.
575+
`data://v1.0/{id}` no longer matches `data://v1X0/42`.
596576

597-
**Filesystem handlers:** even with `{+path}` allowing slashes, you must
598-
still guard against traversal in your handler. Use `safe_join`:
577+
**Query parameters match leniently.** A template like
578+
`search://{q}{?limit}` now matches `search://foo` (with `limit` absent
579+
from the extracted params so your function default applies). Previously
580+
this returned no match. If you relied on all query parameters being
581+
required, add explicit checks in your handler.
599582

600-
```python
601-
from mcp.shared.path_security import safe_join
602-
603-
@mcp.resource("file://docs/{+path}")
604-
def read_doc(path: str) -> str:
605-
return safe_join("/data/docs", path).read_text()
606-
```
583+
**Malformed templates fail at decoration time.** Unclosed braces,
584+
duplicate variable names, and unsupported syntax now raise
585+
`InvalidUriTemplate` when the decorator runs, rather than silently
586+
misbehaving at match time.
607587

608-
**Malformed templates now fail at decoration time** with
609-
`InvalidUriTemplate` (a `ValueError` subclass carrying the error
610-
position), rather than silently misbehaving at match time.
588+
See [Resources](server/resources.md) for the full template syntax,
589+
security configuration, and filesystem safety utilities.
611590

612591
### Lowlevel `Server`: constructor parameters are now keyword-only
613592

0 commit comments

Comments
 (0)