shutdown() so subprocesses, streams, and sockets close reliably.
MCP(...) accepts three equivalent construction forms — see Three equivalent forms.
Quick Start
1
Simple Usage
2
With Configuration
How It Works
Agent-Managed Cleanup
When you pass an MCP client through the constructor (tools=[MCP(...)]), agent.close() and agent.aclose() now walk the agent’s tools and shut down anything exposing .shutdown() / .aclose() — so the MCP subprocess and its background thread are cleaned up with the agent.
Constructor-pattern MCP clients (
tools=[MCP(...)]) are now auto-shut-down by agent.close() / agent.aclose() — you don’t need remove_mcp_server() any more just for cleanup. aclose() prefers a tool’s aclose() and falls back to shutdown().Manual Cleanup
For cases where a context manager is not suitable:Request Cancellation
An MCP client can abort an in-flight request by sending anotifications/cancelled notification naming the requestId it wants to stop.
1
Send the request
The client calls a tool with a JSON-RPC id it can reference later.
2
Cancel it
The client sends a cancellation notification with the same id as
requestId.3
Receive the cancelled response
The server cancels the running task and replies with a JSON-RPC error.
Only id-bearing requests can be cancelled. Fire-and-forget notifications have no id to reference and return nothing.
Lifecycle Methods
Authenticating the HTTP transport
Whenapi_key is configured on the MCP HTTP-stream server, all of GET, POST, and DELETE require:
hmac.compare_digest (timing-attack resistant). Missing or wrong tokens return 401 Unauthorized with {"error": "Unauthorized"}. DELETE returning 401 instead of 405 prevents information disclosure about whether sessions exist.
__enter__ / __exit__
Context manager protocol for automatic resource management:
shutdown()
Explicitly close all connections and cleanup resources:
shutdown() closes an MCP connection. For stdio transports it enqueues a sentinel on the runner queue, joins the background thread, and lets the transport terminate its child process. For HTTP-stream / WebSocket transports it dispatches to close() (or aclose() if awaitable). It is idempotent — a second call is a no-op — and it never raises: cleanup errors are swallowed as best-effort.
shutdown() now joins the runner thread and terminates the stdio subprocess in the same call. In long-lived processes (gateways, API servers, notebooks, CI parallel tests) this prevents one leaked npx / node child per agent lifecycle — a regression fixed in PR #5078.shutdown() also releases the server-name registry entries this instance registered via with_tool_prefix() — one decrement per registered name, deduping the raw/sanitised pair. The release is guarded by an internal _server_names_released flag so a second shutdown() or __del__ never underflows another live holder’s count. The consequence: MCP.list_active_server_names() reflects live instances, which is exactly what the STRICT skill capability gate reads. Added in PR #5136.Idempotency & retry
shutdown() is safe to call multiple times:
shutdown() can retry the join. The thread is a daemon, so a surviving runner never blocks interpreter exit.
MCPToolRunner.stop() is the internal helper that queues the sentinel and joins the daemon thread. Since PR #5078 it runs on both normal MCP.shutdown() (via the MCP.shutdown() → runner.stop() call) and on construction failures (handshake timeout / init error). It is not a public API — public shutdown stays mcp.shutdown() or the context-manager form. See PR #5078 and issue #5074.__del__
Destructor ensures cleanup even if shutdown() was not called:
Connection Types
MCP supports multiple connection types, all with proper cleanup:shutdown(). HTTP-stream and WebSocket close via the close() fallback inside MCP.shutdown() — previously these two leaked subprocesses/threads on shutdown, while stdio and SSE cleaned up correctly.
Long-running processes
Gateways, HTTP invoke APIs, Jupyter kernels, and CI test runners create manyAgent(mcp=...) instances over their lifetime. Every stdio MCP spawns a child process (npx / node) and a daemon runner thread. If those aren’t torn down when the agent is done, the host process leaks one child per MCP per agent.
The recommended pattern for long-lived hosts is a try/finally around every agent that carries MCP tools:
Best Practices
Always use a context manager
Always use a context manager
Prefer
with MCP(...) as mcp: — cleanup runs even when an exception is raised.Handle exceptions inside the block
Handle exceptions inside the block
Wrap tool calls in
try/except inside the with block; __exit__ still closes the connection.Nest multiple MCP instances carefully
Nest multiple MCP instances carefully
Open each MCP in its own
with block or nest them — both instances shut down in reverse order.Pass secrets via env, not inline
Pass secrets via env, not inline
Use the
env= parameter with os.getenv(...) rather than hard-coding API keys in recipe files.Close every agent in long-running hosts
Close every agent in long-running hosts
Gateways, API servers, and notebooks accumulate leaked
node / npx children if MCP.shutdown() (or agent.close()) is never called. Wrap each request or session in try/finally and call agent.close() — or use with MCP(...) as mcp: for per-MCP scope. Since PR #5078, shutdown() reliably terminates the stdio subprocess and joins the runner thread.Related
MCP CLI
Run and inspect MCP servers from the terminal
MCP Transports
Stdio, SSE, HTTP stream, and WebSocket options
Agent Lifecycle Cleanup
How agent.close() shuts down MCP tools and breakers

