One logic fix and a sweep of stale references left over from the
regex-to-scan rewrite.
ifemp round-trip (_scan_prefix): the name-continuation guard rejected
the empty-value case when the template's next literal started with a
non-stop-char. api{;key}X{+rest} with key='' expands to api;keyX/tail
but matched None because 'X' after ;key was treated as a name
continuation. Now checks whether the next literal starts at the
current position before rejecting.
Doc/style cleanups:
- match() docstring: 'regex derived from the template' -> 'linear scan'
- _split_query_tail: 'strict regex' -> 'strict scan'
- test comments: 5x 'regex' -> 'scan'
- DEFAULT_RESOURCE_SECURITY: docstring now mentions null-byte rejection
- migration.md: describe client-visible 'Unknown resource' error rather
than the internal ResourceSecurityError type
- _Atom type alias: remove unnecessary string quoting
- UriTemplate fields: list[...] not tuple[..., ...] — arbitrary-sized
tuples are not a defence worth having
The previous matcher was a naive replace('{', '(?P<').replace('}',
'>[^/]+)') that threw re.error on any operator character. Removed
items describing constraints on features that did not exist in v1.x:
- 'At most one multi-segment variable': {+var}/{#var}/explode all
threw re.error in v1.x, so nobody had a working template with one
let alone two. Covered in resources.md.
- 'Query parameters match leniently': {?q} also threw re.error. The
lenient-query feature is new, not a behavior change.
Also folded the structural-delimiter change into the literals item
and softened 'malformed templates' to note it's an error-timing
change (re.error at match time -> InvalidUriTemplate at decoration).
- migration.md: path-safety checks now raise ResourceSecurityError
rather than silently falling through; null bytes are rejected by
default; templates may have at most one multi-segment variable
- resources.md: add reject_null_bytes to the settings table; note
that ResourceSecurity is a heuristic and safe_join remains the
containment boundary
The adjacency check rejected {+a}{b} but not the symmetric {a}{+b}.
Both produce overlapping greedy quantifiers; a 64KB crafted input
against prefix{a}{+b}.json takes ~23s to reject.
Added prev_path_expr tracking so {+var} immediately after any path
expression is rejected. {expr}{#var} remains allowed since the #
operator prepends a literal '#' that the preceding group's character
class excludes, giving a natural boundary.
Also adds the missing 'from typing import Any' to the three low-level
server examples in docs/server/resources.md.
Added a link to the MCP resources specification after the intro.
Rewrote the multi-segment paths section to lead with the problem:
show a URI that fails with {name} before introducing {+name} as the
fix. Code comments align inputs with outputs for at-a-glance parsing.
Rewrote the query parameters section to lead with the two concrete
URIs a user would want to support (base and with-query), then show
how one template covers both.
Bare dict return types are now parameterized (dict[str, str] or
dict[str, Any] as appropriate). Low-level handler examples now
include ServerRequestContext[Any] and PaginatedRequestParams types
for the ctx and params parameters, with the corresponding imports
added to each code block.
migration.md: added note that static URIs with Context-only handlers
now error at decoration time. The pattern was previously silently
unreachable (the resource registered but could never be read); now
it surfaces early. Duplicate-variable-names rejection was already
covered in the malformed-templates paragraph.
resources.md: clarified that the .. check is depth-based (rejects
values that would escape the starting directory, so a/../b passes).
Changed template reference table intro from 'what the SDK supports'
to 'the most common patterns' since the table intentionally omits
the rarely-used fragment and path-param operators.
test_uri_template.py: corrected the stray-} test comment. RFC 6570
section 2.1 strictly excludes } from literals; we accept it for
TypeScript SDK parity, not because the RFC is lenient.
Adds a sentence on lenient query matching (order-agnostic, extras
ignored, defaults apply) after the logs example.
Adds the component-based clarification for the .. check so users know
values like HEAD~3..HEAD and v1.0..v2.0 are unaffected.
Fixes the exempt_params motivating example in both resources.md and
migration.md. The previous git://diff/{+range} example used
HEAD~3..HEAD, which the component-based check already passes without
exemption. Replaced with inspect://file/{+target} receiving absolute
paths, which genuinely requires the opt-out.
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.
UriTemplate.match() no longer rejects decoded values containing
characters like /, ?, #, &. It now faithfully returns whatever
expand() would have encoded, so match(expand(x)) == x holds for all
inputs.
The previous check broke round-trip for legitimate values (a&b
expanded to a%26b but match rejected it) and was inconsistent with
every other MCP SDK. The spec's own canonical example file:///{path}
requires multi-segment values; Kotlin and C# already decode without
rejection and document handler-side validation as the security
contract.
Path-safety validation remains in ResourceSecurity (configurable) and
safe_join (the gold-standard check). The %2F path-traversal attack
vector is still blocked: ..%2Fetc%2Fpasswd decodes to ../etc/passwd,
which contains_path_traversal rejects. Tests confirm this end-to-end.
This aligns us with Kotlin's documented model: decode once, pass to
handler, handler validates.
Adds docs/server/resources.md as the first page under the planned
docs/server/ directory. Covers static resources, RFC 6570 template
patterns, the built-in security checks and how to relax them, the
safe_join pattern for filesystem handlers, and equivalent patterns for
low-level Server implementations.
Creates the docs/server/ nav section in mkdocs.yml.
Changes the type from frozenset[str] to collections.abc.Set[str] so
users can write exempt_params={"range"} instead of
exempt_params=frozenset({"range"}). The default factory stays
frozenset for immutability.
Documents the RFC 6570 support, security hardening defaults, and
opt-out configuration for the resource template rewrite. Grouped with
the existing resource URI section.