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

  1. Remove the duplicate parameter and use one spelling consistently
  2. Rename one of the two parameters so they normalize to distinct identifiers
  3. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/ba597e2b4d5c6da6. Report an issue: GitHub.