fix(server): stop stacking a tool profile's visibility on every session
Release / test (full, windows-latest, 3.11) (push) Canceled after 0s
Release / test (pdf, ubuntu-latest, 3.11) (push) Canceled after 0s
Release / test (pdf, ubuntu-latest, 3.12) (push) Canceled after 0s
Release / build (push) Canceled after 0s
Release / publish-pypi (push) Canceled after 0s
Release / github-release (push) Canceled after 0s
Release / test (full, windows-latest, 3.11) (push) Canceled after 0s
Release / test (pdf, ubuntu-latest, 3.11) (push) Canceled after 0s
Release / test (pdf, ubuntu-latest, 3.12) (push) Canceled after 0s
Release / build (push) Canceled after 0s
Release / publish-pypi (push) Canceled after 0s
Release / github-release (push) Canceled after 0s
What the first Linux CI run of the 1.6 tree found. - fastmcp 3's enable()/disable() append a Visibility transform per call, and a tool lookup walks the transform stack as nested calls. The lifespan re-applies the tool profile on every client connection, so an in-process server grew two layers per session until every tool call, system_status included, raised RecursionError (CPython 3.11 counts C frames against the limit). The profile's transforms now replace the previous ones, as _apply_discovery_mode already did; a test switches the profile 600 times and then calls a tool. The local suite runs in half the time it took with the stack growing. - 21 fake-ActiveX tests build a real pywin32 VARIANT; they now skip without pywin32, like the suite's other VARIANT-dependent COM tests, and the Windows lane keeps running them. - A test spelled a path through a directory it never created, which only Windows normalises away; it creates the directory. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
17bf3a39ce
commit
cdb1063896
@@ -1089,6 +1089,9 @@ PACK_TOOL_NAMES: dict[str, frozenset[str]] = {
|
||||
}
|
||||
|
||||
_active_tool_profile: dict | None = None
|
||||
#: The Visibility transforms the last _apply_tool_profile added, detached before
|
||||
#: the next one is applied.
|
||||
_profile_transforms: list = []
|
||||
|
||||
|
||||
def _enabled_packs() -> tuple[set[str], list[str]]:
|
||||
@@ -1160,10 +1163,23 @@ async def _apply_tool_profile(profile: str | None = None) -> dict:
|
||||
registered = {tool.name for tool in await _registered_tools() if getattr(tool, "name", None)}
|
||||
enabled = _profile_enabled_names(selected, registered)
|
||||
disabled = sorted(registered - enabled)
|
||||
# Replace the previous profile's visibility, never stack on it. fastmcp 3's
|
||||
# enable()/disable() append a Visibility transform per call and a tool lookup
|
||||
# walks the whole stack as nested calls; the lifespan runs this on every
|
||||
# client connection, so an in-process server grew two layers per session
|
||||
# until every tool call raised RecursionError (Linux CI, CPython 3.11). The
|
||||
# same detach-first idiom as _apply_discovery_mode.
|
||||
for transform in _profile_transforms:
|
||||
try:
|
||||
mcp._transforms.remove(transform)
|
||||
except (AttributeError, ValueError) as exc: # pragma: no cover - layout change
|
||||
log.debug("Could not detach a tool-profile transform: %s", exc)
|
||||
before = list(mcp.transforms)
|
||||
if enabled:
|
||||
mcp.enable(names=set(enabled))
|
||||
if disabled:
|
||||
mcp.disable(names=set(disabled))
|
||||
_profile_transforms[:] = [t for t in mcp.transforms if not any(t is b for b in before)]
|
||||
_active_tool_profile = {
|
||||
"profile": selected,
|
||||
"registered_count": len(registered),
|
||||
|
||||
Reference in New Issue
Block a user