PrefectHQ/fastmcp · error · ValueError
URI template parameters '{seen[normalized]}' and '{raw_name}
Error message
URI template parameters '{seen[normalized]}' and '{raw_name}' both normalize to '{normalized}'. Use one or the other, not both. What it means
URI template parameters are normalized (hyphens become underscores) so they match Python identifiers. If two raw parameters like {user-id} and {user_id} normalize to the same Python name, the binding would be ambiguous, so from_function raises at registration. RFC 6570 allows hyphens in names but Python functions cannot use them, hence the normalization collision check.
Source
Thrown at fastmcp_slim/fastmcp/resources/template.py:473
if param.kind == inspect.Parameter.VAR_POSITIONAL:
raise ValueError(
"Functions with *args are not supported as resource templates"
)
# Extract path and query parameters from URI template.
# Allow hyphens in names and normalize to underscores so they
# match Python function parameter names.
raw_path_params = set(re.findall(r"{([\w-]+)(?:\*)?}", uri_template))
raw_query_params = extract_query_params(uri_template)
# Detect collisions: two raw param names that normalize to the
# same Python identifier (e.g. {user-id} and {user_id}).
all_raw = raw_path_params | raw_query_params
seen: dict[str, str] = {}
for raw_name in sorted(all_raw):
normalized = raw_name.replace("-", "_")
if normalized in seen:
raise ValueError(
f"URI template parameters '{seen[normalized]}' and "
f"'{raw_name}' both normalize to '{normalized}'. "
f"Use one or the other, not both."
)
seen[normalized] = raw_name
path_params = {p.replace("-", "_") for p in raw_path_params}
query_params = {p.replace("-", "_") for p in raw_query_params}
all_uri_params = path_params | query_params
if not all_uri_params:
raise ValueError("URI template must contain at least one parameter")
# Use wrapper to get user-facing parameters (excludes injected params)
from fastmcp.server.dependencies import (
transform_context_annotations,
without_injected_parameters,
)View on GitHub (pinned to 1f02114297)
Solutions
- Remove the duplicate parameter and use one spelling consistently
- Rename one of the two parameters so they normalize to distinct identifiers
- Use hyphenated spelling in the URI and underscored name in the function (that mapping is supported)
Example fix
// before
uri_template="report://{user-id}?sort={user_id}"
// after
uri_template="report://{user_id}?sort={sort_key}" Defensive patterns
Strategy: validation
Validate before calling
import re
def check_no_collisions(uri_template):
raw = set(re.findall(r"\{([^}]+)\}", uri_template))
seen = {}
for r in raw:
n = r.replace("-", "_")
if n in seen:
raise ValueError(f"{seen[n]} and {r} both normalize to {n}")
seen[n] = r Type guard
def uri_params_unique(uri_template) -> bool:
import re
raw = re.findall(r"\{([^}]+)\}", uri_template)
norms = [r.replace("-", "_") for r in raw]
return len(norms) == len(set(norms)) Try / catch
try:
tpl = FunctionResourceTemplate.from_function(fn, uri_template=uri)
except ValueError as e:
if "both normalize to" in str(e):
raise ValueError(f"Fix duplicate URI params in {uri!r}") from e
raise Prevention
- Pick one spelling convention (snake_case) per template
- Lint URI templates for duplicate placeholders before registering
- Keep URI templates as named constants, not ad-hoc strings
When it happens
Trigger: Registering a template whose URI contains both {x-y} and {x_y} (in path and/or query parts of the template), e.g. uri_template="api://{user-id}?ref={user_id}".
Common situations: Hand-written URI templates mixing kebab-case and snake_case for the same concept; concatenating template strings from different sources; copying a URL path style into a template alongside Python-style params.
Related errors
- URI template must contain at least one parameter
- Query parameters {invalid_query_params} must be optional fun
- Required function arguments {required_params} must be a subs
- URI parameters {all_uri_params} must be a subset of the func
- Expected integer, got {raw!r}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/ba597e2b4d5c6da6.
Report an issue: GitHub.