Skip to content

Commit 928698b

Browse files
committed
docs: add migration guide entry for resource template changes
Documents the RFC 6570 support, security hardening defaults, and opt-out configuration for the resource template rewrite. Grouped with the existing resource URI section.
1 parent 5cbbc70 commit 928698b

File tree

1 file changed

+63
-0
lines changed

1 file changed

+63
-0
lines changed

docs/migration.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,69 @@ 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
549+
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.
553+
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+
- Decode to contain structural delimiters that their operator forbids
570+
(e.g., `%2F` smuggled into a simple `{name}`)
571+
572+
If your template parameters legitimately contain `..` (e.g., git commit
573+
ranges like `HEAD~3..HEAD`) or absolute paths, exempt them:
574+
575+
```python
576+
from mcp.server.mcpserver import MCPServer, ResourceSecurity
577+
578+
mcp = MCPServer()
579+
580+
@mcp.resource(
581+
"git://diff/{+range}",
582+
security=ResourceSecurity(exempt_params=frozenset({"range"})),
583+
)
584+
def git_diff(range: str) -> str:
585+
...
586+
```
587+
588+
Or relax the policy server-wide:
589+
590+
```python
591+
mcp = MCPServer(
592+
resource_security=ResourceSecurity(reject_path_traversal=False),
593+
)
594+
```
595+
596+
**Filesystem handlers:** even with `{+path}` allowing slashes, you must
597+
still guard against traversal in your handler. Use `safe_join`:
598+
599+
```python
600+
from mcp.shared.path_security import safe_join
601+
602+
@mcp.resource("file://docs/{+path}")
603+
def read_doc(path: str) -> str:
604+
return safe_join("/data/docs", path).read_text()
605+
```
606+
607+
**Malformed templates now fail at decoration time** with
608+
`InvalidUriTemplate` (a `ValueError` subclass carrying the error
609+
position), rather than silently misbehaving at match time.
610+
548611
### Lowlevel `Server`: constructor parameters are now keyword-only
549612

550613
All parameters after `name` are now keyword-only. If you were passing `version` or other parameters positionally, use keyword arguments instead:

0 commit comments

Comments
 (0)