@@ -545,6 +545,69 @@ await client.read_resource("test://resource")
545545await 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
550613All parameters after ` name ` are now keyword-only. If you were passing ` version ` or other parameters positionally, use keyword arguments instead:
0 commit comments