Skip to content

[BUG] MCPClient cleanup raises PythonFinalizationError at interpreter shutdown on Python 3.14 #2143

@minorun365

Description

@minorun365

Checks

  • I have searched the existing issues and documentation
  • I have provided a clear, reproducible test case
  • I have included the relevant versions and environment details

Strands Version

1.35.0

Python Version

3.14.3 (CPython, macOS arm64, installed via uv)

Operating System

macOS 14.4.0 (Darwin 25.4.0, arm64 / Apple Silicon)

Installation Method

uv (PyPI)

Steps to Reproduce

When an Agent that has an MCPClient tool is garbage-collected at interpreter shutdown on Python 3.14, the cleanup path in Agent.__del__ raises PythonFinalizationError: cannot join thread at interpreter shutdown. The error is swallowed (Exception ignored while calling deallocator) but Strands also emits its own log lines and a traceback to stderr, which is visible to users.

Minimal pattern (excerpted and simplified from a real codebase using the public AWS Knowledge MCP endpoint):

from mcp.client.streamable_http import streamable_http_client
from rich import print as rprint
from rich.text import Text
from strands import Agent
from strands.hooks import HookProvider, HookRegistry
from strands.hooks.events import BeforeToolCallEvent
from strands.models import BedrockModel, CacheConfig
from strands.tools.executors import SequentialToolExecutor
from strands.tools.mcp import MCPClient


class ToolLoggingHook(HookProvider):
    def register_hooks(self, registry: HookRegistry) -> None:
        registry.add_callback(BeforeToolCallEvent, self.on_tool_start)

    def on_tool_start(self, event: BeforeToolCallEvent) -> None:
        rprint(Text(f"{event.agent.name} : {event.tool_use['name']}"))


mcp_client = MCPClient(
    lambda: streamable_http_client("https://knowledge-mcp.global.api.aws")
)

agent = Agent(
    name="RetrievalAgent",
    model=BedrockModel(
        model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0",
        cache_config=CacheConfig(strategy="auto"),
    ),
    system_prompt="Search AWS docs and summarize.",
    tools=[mcp_client],
    tool_executor=SequentialToolExecutor(),
    callback_handler=None,
    hooks=[ToolLoggingHook()],
)

# Trigger multiple MCP tool calls (search + read) then exit.
result = agent("Explain AWS Bedrock AgentCore briefly.")
print(str(result)[:200])

Environment:

uv init --python 3.14
uv add "strands-agents==1.35.0" "strands-agents-tools[rss]==0.4.1" "mcp==1.27.0" "rich==14.3.3" "boto3[crt]==1.42.90"
AWS_PROFILE=... AWS_DEFAULT_REGION=us-east-1 uv run python repro.py

Notes on narrowing the repro:

  • A single agent with a single MCP tool call that returns quickly does not always trigger the warning.
  • The issue reliably appears when the agent performs multiple MCP tool calls in one turn (e.g. aws___search_documentation then aws___read_documentation), then the interpreter exits. It also reproduces inside a Swarm that routes through an MCP-owning agent.
  • Python 3.13 does not raise this error; the underlying cause is cpython#113964, Python 3.14's stricter PythonFinalizationError around joining non-daemon threads at shutdown.

Expected Behavior

On Python 3.14, Agent.__del__ / MCPClient.remove_consumer should cleanly shut down the MCP transport's background thread without raising PythonFinalizationError, so that no traceback appears on stderr during interpreter finalization.

Actual Behavior

Stderr at program exit (even when the agent's response has already been produced successfully):

error=<cannot join thread at interpreter shutdown> | failed to cleanup MCP client
provider=<MCPClient>, error=<Failed to cleanup MCP client: cannot join thread at interpreter shutdown> | failed to remove provider consumer
Exception ignored while calling deallocator <function Agent.__del__ at 0x...>:
Traceback (most recent call last):
  File ".../strands/agent/agent.py", line 742, in __del__
  File ".../strands/tools/registry.py", line 731, in cleanup
  File ".../strands/tools/registry.py", line 722, in cleanup
  File ".../strands/tools/mcp/mcp_client.py", line 316, in remove_consumer
strands.types.exceptions.ToolProviderException: Failed to cleanup MCP client: cannot join thread at interpreter shutdown

The agent's response is complete and correct; the traceback is purely noise from shutdown. However, the Traceback / Exception ignored / Failed to cleanup wording makes end users (especially beginners working through tutorials) think the run has failed.

Additional Context

This surfaced while validating a technical book's sample code under Python 3.14 (AWS AgentCore CLI v0.8.2 bumped its default runtime to PYTHON_3_14 on 2026-04-16). The book's readers run MCP-enabled Strands agents in handson chapters, and the shutdown traceback is likely to cause unnecessary confusion.

Related upstream context:

Possible Solution

Two options worth exploring:

  1. Mark the MCP background thread as a daemon thread (in strands/tools/mcp/mcp_client.py) so the interpreter does not attempt to join it at shutdown. Combine with best-effort stop() in Agent.__del__.
  2. Guard remove_consumer / __del__ against PythonFinalizationError: if sys.is_finalizing() is True, skip the thread-join path and just let the runtime reclaim resources, avoiding the traceback entirely.

Option 2 is less invasive and should fully silence the shutdown noise without changing thread lifetime semantics during normal operation.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions