Skip to content

Commit 60d12e1

Browse files
committed
docs: clarify query leniency and fix exempt_params example
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.
1 parent 9473442 commit 60d12e1

File tree

2 files changed

+24
-14
lines changed

2 files changed

+24
-14
lines changed

docs/migration.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -554,22 +554,22 @@ Four behaviors have changed:
554554
containing `..` as a path component or looking like an absolute path
555555
(`/etc/passwd`, `C:\Windows`) now cause the template to not match.
556556
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:
557+
caught too. Note that `..` is only flagged as a standalone path
558+
component, so values like `v1.0..v2.0` or `HEAD~3..HEAD` are unaffected.
559+
560+
If a parameter legitimately needs to receive absolute paths or
561+
traversal sequences, exempt it:
559562

560563
```python
561564
from mcp.server.mcpserver import ResourceSecurity
562565

563566
@mcp.resource(
564-
"git://diff/{+range}",
565-
security=ResourceSecurity(exempt_params={"range"}),
567+
"inspect://file/{+target}",
568+
security=ResourceSecurity(exempt_params={"target"}),
566569
)
567-
def git_diff(range: str) -> str: ...
570+
def inspect_file(target: str) -> str: ...
568571
```
569572

570-
Note that `..` is only flagged as a standalone path component, so a
571-
value like `v1.0..v2.0` is unaffected.
572-
573573
**Template literals are regex-escaped.** Previously a `.` in your
574574
template matched any character; now it matches only a literal dot.
575575
`data://v1.0/{id}` no longer matches `data://v1X0/42`.

docs/server/resources.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ Reading `logs://api` uses the defaults. Reading
9797
`logs://api?since=15m&level=error` narrows it down. The path identifies
9898
*which* resource; the query tunes *how* you read it.
9999

100+
Query params are matched leniently: order doesn't matter, extras are
101+
ignored, and omitted params fall through to your function defaults.
102+
100103
### Path segments as a list
101104

102105
If you want each path segment as a separate list item rather than one
@@ -141,6 +144,10 @@ Before your handler runs, the SDK rejects any parameter that:
141144
- contains `..` as a path component
142145
- looks like an absolute path (`/etc/passwd`, `C:\Windows`)
143146

147+
The `..` check is component-based, not a substring scan. Values like
148+
`v1.0..v2.0` or `HEAD~3..HEAD` pass because `..` is not a standalone
149+
path segment there.
150+
144151
These checks apply to the decoded value, so they catch traversal
145152
regardless of how it was encoded in the URI (`../etc`, `..%2Fetc`,
146153
`%2E%2E/etc`, `..%5Cetc` all get caught).
@@ -174,19 +181,22 @@ client as a `ResourceError`.
174181

175182
### When the defaults get in the way
176183

177-
Sometimes `..` in a parameter is legitimate. A git commit range like
178-
`HEAD~3..HEAD` contains `..` but it's not a path. Exempt that parameter:
184+
Sometimes the checks block legitimate values. An external-tool wrapper
185+
might intentionally receive an absolute path, or a parameter might be a
186+
relative reference like `../sibling` that your handler interprets
187+
safely without touching the filesystem. Exempt that parameter:
179188

180189
```python
181190
from mcp.server.mcpserver import ResourceSecurity
182191

183192

184193
@mcp.resource(
185-
"git://diff/{+range}",
186-
security=ResourceSecurity(exempt_params={"range"}),
194+
"inspect://file/{+target}",
195+
security=ResourceSecurity(exempt_params={"target"}),
187196
)
188-
def git_diff(range: str) -> str:
189-
return run_git("diff", range)
197+
def inspect_file(target: str) -> str:
198+
# target might be "/usr/bin/python3"; this handler is trusted
199+
return describe_binary(target)
190200
```
191201

192202
Or relax the policy for the whole server:

0 commit comments

Comments
 (0)