How to Use MCPClient for Reusable MCP Server Connections in aisuite
MCPClient provides a high-level Python interface for maintaining persistent connections to MCP servers, caching tool definitions automatically so you can reuse the same connection across multiple LLM chat completions without repeated handshake overhead.
MCPClient is the primary entry point in the aisuite library for communicating with Model Context Protocol (MCP) servers. It abstracts both local process (stdio) and remote (HTTP) transports while handling connection lifecycle management automatically. According to the andrewyng/aisuite source code, this client keeps underlying sessions open until explicitly closed, making it ideal for notebooks, long-running scripts, or multi-turn conversations where connection reuse significantly improves performance.
Understanding MCPClient Architecture
The MCPClient class in aisuite/mcp/client.py serves as a unified abstraction over MCP transport protocols. It validates configuration, manages asynchronous event loops, and caches discovered tools to eliminate redundant server round-trips.
Transport Options: Stdio vs HTTP
MCPClient supports exactly one transport method per instance, enforced during initialization. The constructor validates transport flags (has_stdio and has_http) and raises a ValueError if both or neither are provided (see lines 97-105 in aisuite/mcp/client.py).
- Stdio transport: Spawns a local subprocess (e.g., via
npx) and communicates over stdin/stdout - HTTP transport: Connects to a remote MCP server via
httpx.AsyncClientusing a providedserver_url
Connection Lifecycle and Caching
When instantiated, MCPClient automatically creates or reuses an existing asyncio event loop, then connects via the appropriate async routine (_async_connect for stdio or _async_connect_http for HTTP) as implemented in lines 33-44. After the initial handshake, the server’s tool list is fetched once and stored in _tools_cache. Subsequent calls to list_tools() or get_callable_tools() return cached results immediately, avoiding repeated discovery overhead (lines 87-103).
Creating Reusable Stdio Connections
For local MCP servers distributed as command-line tools, use the stdio transport to spawn and maintain a persistent subprocess.
from aisuite.mcp import MCPClient
import aisuite as ai
# Initialize once - starts the server process
mcp = MCPClient(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "/my/docs"],
name="filesystem",
)
# Reuse the same connection for multiple interactions
for prompt in ["List files", "Read README.md"]:
response = ai.Client().chat.completions.create(
model="openai:gpt-4o",
messages=[{"role": "user", "content": prompt}],
tools=mcp.get_callable_tools(), # Returns cached tools, no new handshake
max_turns=2,
)
print(response.choices[0].message.content)
# Clean up when done
mcp.close()
The get_callable_tools() method (lines 45-75) returns thin wrappers around the cached tool definitions. These wrappers follow aisuite’s tool-calling contract and can be passed directly to chat.completions.create().
Reusable HTTP Connections with Configuration
For remote MCP servers exposed via HTTP, MCPClient maintains a persistent httpx.AsyncClient session. You can instantiate manually or use the configuration-driven factory methods.
from aisuite.mcp import MCPClient
import aisuite as ai
config = {
"type": "mcp",
"name": "my-api",
"server_url": "http://localhost:8000",
"headers": {"Authorization": "Bearer <TOKEN>"},
"allowed_tools": ["list_files", "read_file"],
"use_tool_prefix": True,
}
# Validation and instantiation handled automatically
tools = MCPClient.get_tools_from_config(config)
# HTTP connection stays alive across calls
client = ai.Client()
response = client.chat.completions.create(
model="openai:gpt-4o-mini",
messages=[{"role": "user", "content": "List the root folder"}],
tools=tools,
max_turns=1,
)
The get_tools_from_config() static method delegates to validate_mcp_config() in aisuite/mcp/config.py (lines 49-66) before construction, ensuring required fields like server_url or command are present and mutually exclusive.
Context Manager Pattern for Automatic Cleanup
MCPClient implements __enter__ and __exit__ methods (lines 71-78) to guarantee connection cleanup even when exceptions occur. This pattern is recommended for Jupyter notebooks or scripts where deterministic resource management matters.
from aisuite.mcp import MCPClient
import aisuite as ai
with MCPClient(
server_url="http://localhost:8000",
name="remote",
) as mcp:
tools = mcp.get_callable_tools(use_tool_prefix=True)
response = ai.Client().chat.completions.create(
model="openai:gpt-4o",
messages=[{"role": "user", "content": "What files are in /tmp?"}],
tools=tools,
max_turns=2,
)
print(response.choices[0].message.content)
# Connection closes automatically here
The context manager calls close() automatically, which performs async cleanup for both stdio ( terminating the subprocess) and HTTP (closing the AsyncClient) transports.
Tool Filtering and Prefixing
When calling get_callable_tools(), you can constrain which tools are exposed to the LLM using the allowed_tools parameter, or namespace tool names with use_tool_prefix=True to prevent collisions when connecting to multiple MCP servers.
# Only expose specific tools with prefixed names
tools = mcp.get_callable_tools(
allowed_tools=["read_file", "write_file"],
use_tool_prefix=True
)
# Results in tools named: "filesystem_read_file", "filesystem_write_file"
This filtering occurs in aisuite/mcp/client.py before wrapper generation, ensuring only permitted tools receive callable wrappers via create_mcp_tool_wrapper in aisuite/mcp/tool_wrapper.py.
Summary
- MCPClient in
aisuite/mcp/client.pyprovides the primary interface for reusable MCP server connections - Transport options are mutually exclusive: choose either
command/argsfor stdio orserver_urlfor HTTP - Tool discovery happens once during initialization and is cached in
_tools_cachefor subsequent reuse - Use
get_callable_tools()to obtain Python callables that wrap MCP tools for aisuite compatibility - Context manager support (
withstatements) ensures proper cleanup of async resources and subprocesses - Configuration-driven setup via
from_config()orget_tools_from_config()validates inputs againstaisuite/mcp/config.pyschemas
Frequently Asked Questions
How do I maintain a persistent connection to an MCP server across multiple LLM calls?
Instantiate MCPClient once with your transport parameters, then reuse the same instance across multiple chat.completions.create() calls. The underlying ClientSession (stdio) or httpx.AsyncClient (HTTP) remains open until you call close() or exit a with block. Tool definitions are cached after the initial discovery, eliminating repeated handshake overhead.
Can I filter which tools are available to the LLM when using MCPClient?
Yes. Pass a list of tool names to the allowed_tools parameter in get_callable_tools(). The client will only create callable wrappers for tools listed in this parameter, effectively restricting the LLM's access to specific server capabilities without modifying the MCP server itself.
What is the difference between stdio and HTTP transport in MCPClient?
Stdio transport spawns a local subprocess (such as an npx package) and communicates via stdin/stdout, making it ideal for local tool servers. HTTP transport connects to a remote URL using httpx.AsyncClient, suitable for hosted MCP servers. The constructor enforces that exactly one transport is configured via validation logic in lines 97-105 of aisuite/mcp/client.py.
How does MCPClient handle resource cleanup?
MCPClient provides explicit close() methods that shut down the underlying async session and terminate stdio subprocesses. Additionally, the class implements Python's context manager protocol (__enter__ and __exit__), allowing you to use with MCPClient(...) as client: syntax to guarantee cleanup even if exceptions occur during tool execution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →