# How to Use MCPClient for Reusable MCP Server Connections in aisuite

> Master reusable MCP server connections with MCPClient in aisuite. This Python interface maintains persistent connections and caches tool definitions for efficient LLM chat completions. Reduce handshake overhead.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-07-30

---

**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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.AsyncClient` using a provided `server_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.

```python
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.

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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.

```python
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.

```python

# 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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/tool_wrapper.py).

## Summary

- **MCPClient** in [`aisuite/mcp/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/client.py) provides the primary interface for reusable MCP server connections
- Transport options are mutually exclusive: choose either `command`/`args` for **stdio** or `server_url` for **HTTP**
- Tool discovery happens once during initialization and is cached in `_tools_cache` for subsequent reuse
- Use `get_callable_tools()` to obtain Python callables that wrap MCP tools for aisuite compatibility
- **Context manager support** (`with` statements) ensures proper cleanup of async resources and subprocesses
- Configuration-driven setup via `from_config()` or `get_tools_from_config()` validates inputs against [`aisuite/mcp/config.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/config.py) schemas

## 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`](https://github.com/andrewyng/aisuite/blob/main/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.