Skip to main content
MCP connections in PraisonAI Agents support context managers and explicit shutdown() so subprocesses, streams, and sockets close reliably.
MCP(...) accepts three equivalent construction forms — see Three equivalent forms.
The user runs an agent inside an MCP context manager; connections shut down cleanly when the session ends.

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 a notifications/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

When api_key is configured on the MCP HTTP-stream server, all of GET, POST, and DELETE require:
Comparison uses constant-time 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:
If an in-flight tool call keeps the runner thread alive past the join timeout, the runner reference is kept so a later 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:
All four transports now close uniformly on 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 many Agent(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:
Async equivalent:

Best Practices

Prefer with MCP(...) as mcp: — cleanup runs even when an exception is raised.
Wrap tool calls in try/except inside the with block; __exit__ still closes the connection.
Open each MCP in its own with block or nest them — both instances shut down in reverse order.
Use the env= parameter with os.getenv(...) rather than hard-coding API keys in recipe files.
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.

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