# Stateful vs Stateless MCP Clients in AgentScope: Architecture and Usage Guide

> Understand stateful vs stateless MCP clients in AgentScope. Learn how each handles sessions for tool discovery and execution through explicit connect/close or automatic cleanup.

- Repository: [AgentScope-AI/agentscope](https://github.com/agentscope-ai/agentscope)
- Tags: architecture
- Published: 2026-03-09

---

**Stateful MCP clients maintain a persistent `ClientSession` requiring explicit `connect()` and `close()` calls, while stateless MCP clients create temporary sessions per request with automatic cleanup, both exposing identical APIs through `list_tools()` and `get_callable_function()`.**

AgentScope, the open-source multi-agent framework from `agentscope-ai/agentscope`, implements the Model Context Protocol (MCP) through two distinct client architectures. Understanding the difference between **stateful and stateless MCP clients in AgentScope** is essential for optimizing connection overhead, managing authentication state, and selecting the appropriate transport mechanism for your agent tools.

## What Are Stateful and Stateless MCP Clients?

**Stateful MCP clients** in AgentScope are designed for long-running interactions that require session continuity. These clients establish a persistent connection to the MCP server, maintaining state such as authentication cookies, browsing history, or conversation context across multiple tool invocations. According to the source code in [`src/agentscope/mcp/_stateful_client_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_stateful_client_base.py), these clients store a `ClientSession` object in `self.session` and manage its lifecycle through an `AsyncExitStack`.

**Stateless MCP clients** provide a lightweight alternative for ephemeral interactions. These clients create a fresh `ClientSession` for every individual tool call, automatically disposing of the transport and session immediately after the RPC completes. As implemented in [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py), the stateless variant uses a disposable context manager pattern via `get_client()` rather than maintaining persistent socket connections.

## Key Differences Between Stateful and Stateless MCP Clients

### Session Lifetime and Resource Management

The fundamental distinction lies in how each client manages the underlying **MCP session** and transport resources.

**Stateful clients** maintain open sockets or subprocesses for the entire lifetime of the client object. In [`src/agentscope/mcp/_stateful_client_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_stateful_client_base.py) (lines 46-70), the `connect()` method initializes an `AsyncExitStack`, enters the transport context (either `sse_client` or `streamablehttp_client`), creates a `ClientSession`, and calls `session.initialize()`. This session remains active until `close()` is explicitly invoked, holding file descriptors and memory throughout.

**Stateless clients** minimize resource usage by releasing connections immediately after each call. The `HttpStatelessClient` class in [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py) (lines 79-89) implements `get_client()` to return a temporary async context manager. Each invocation of `list_tools()` or `get_callable_function()` enters this context, creates a transient `ClientSession`, executes the RPC, and exits to automatically close the transport.

### Connection Lifecycle Requirements

**Stateful clients require explicit lifecycle management.** You must call `await client.connect()` before any tool operations and `await client.close()` when finished. The `StatefulClientBase` class tracks connection state via `self.is_connected` and raises errors if methods are called while disconnected. This pattern is mandatory for both `HttpStatefulClient` and `StdIOStatefulClient` variants.

**Stateless clients require no explicit connection management.** The client acts as a lightweight wrapper that internally handles context creation and cleanup. As shown in [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py) (lines 46-53), the `list_tools` method simply enters the disposable client context, executes the MCP `list_tools` call, and returns results without exposing session management to the user.

### Performance and Error Handling Characteristics

**Stateful clients** incur lower per-call overhead after the initial connection but maintain persistent resource consumption. They are susceptible to session-level errors; if the underlying transport fails, all subsequent tool calls fail until `connect()` is called again to establish a fresh session.

**Stateless clients** trade connection overhead for isolation. Each call bears the cost of transport initialization and `ClientSession` creation, making them less efficient for high-frequency sequential calls. However, errors are isolated to individual calls, and memory/connection leaks are impossible since resources are immediately released.

## Implementation Details in the AgentScope Source Code

The architectural distinction is enforced through inheritance patterns in the `src/agentscope/mcp/` directory.

**Stateful Implementation:** The `StatefulClientBase` class in [`_stateful_client_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/_stateful_client_base.py) inherits from `MCPClientBase` and adds session persistence. It stores `self.session`, `self.stack` (an `AsyncExitStack`), and `self.is_connected`. The concrete implementations `HttpStatefulClient` ([`_http_stateful_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/_http_stateful_client.py)) and `StdIOStatefulClient` ([`_stdio_stateful_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/_stdio_stateful_client.py)) build their respective transports (HTTP with SSE or Streamable-HTTP, or subprocess StdIO) within this persistent framework.

**Stateless Implementation:** The `HttpStatelessClient` in [`_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/_http_stateless_client.py) inherits directly from `MCPClientBase` and implements the `get_client()` abstract method to return disposable context managers. It uses `sse_client` or `streamablehttp_client` transports but never stores them as instance variables, ensuring each RPC operates in isolation.

## Code Examples: Using Stateful and Stateless MCP Clients

### Stateless HTTP Client Example

The **stateless HTTP client** requires no connection management. Each method call creates and destroys a temporary session automatically.

```python
from agentscope.mcp import HttpStatelessClient

stateless = HttpStatelessClient(
    name="maps_stateless",
    transport="streamable_http",
    url="https://mcp.example.com/mcp",
)

# Each call creates a fresh session under the hood

tools = await stateless.list_tools()
geo_fn = await stateless.get_callable_function(
    func_name="maps_geo",
    wrap_tool_result=True,
)

result = await geo_fn(address="Tiananmen Square", city="Beijing")
print(result)

```

### Stateful HTTP Client Example

The **stateful HTTP client** requires explicit lifecycle management via `connect()` and `close()` to maintain a persistent session.

```python
from agentscope.mcp import HttpStatefulClient

stateful = HttpStatefulClient(
    name="maps_stateful",
    transport="streamable_http",
    url="https://mcp.example.com/mcp",
)

await stateful.connect()  # establish persistent session

tools = await stateful.list_tools()
geo_fn = await stateful.get_callable_function(
    func_name="maps_geo",
    wrap_tool_result=True,
)

result = await geo_fn(address="Tiananmen Square", city="Beijing")
print(result)

await stateful.close()  # clean up sockets

```

### StdIO Stateful Client Example

AgentScope only provides a **stateful variant for StdIO transport**, as subprocess-based servers typically require persistent process management.

```python
from agentscope.mcp import StdIOStatefulClient

stdio = StdIOStatefulClient(
    name="my_stdio",
    command="python -m my_mcp_server",
)

await stdio.connect()
tools = await stdio.list_tools()

# ... use tools ...

await stdio.close()

```

## When to Choose Stateful vs Stateless MCP Clients

Select **stateful MCP clients** when your application requires:
- **Session continuity**: Authentication cookies, browsing history, or conversation state must persist across multiple tool calls
- **High-frequency sequential calls**: Amortizing connection overhead across many operations
- **Subprocess-based servers**: StdIO transport inherently requires process lifecycle management

Select **stateless MCP clients** when your application requires:
- **Minimal resource footprint**: Short-lived connections that immediately release file descriptors and memory
- **Fault isolation**: Ensuring transport errors affect only single calls without crashing persistent sessions
- **Simple integration**: Eliminating boilerplate `connect()` and `close()` calls in ephemeral scripts or serverless functions

## Summary

- **Stateful MCP clients** maintain a persistent `ClientSession` throughout the client object's lifetime, requiring explicit `connect()` and `close()` calls to manage transport resources.
- **Stateless MCP clients** create disposable sessions for each RPC call, automatically handling cleanup without manual lifecycle management.
- Both client types expose identical high-level APIs including `list_tools()` and `get_callable_function()`, allowing seamless interchangeability based on resource and state requirements.
- The architectural distinction is implemented in [`src/agentscope/mcp/_stateful_client_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_stateful_client_base.py) versus [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py).

## Frequently Asked Questions

### Can I convert a stateful MCP client to stateless behavior by calling close() after each tool use?

While technically possible, this approach defeats the purpose of stateful clients and incurs unnecessary overhead. The `close()` method in `StatefulClientBase` cleans up the `AsyncExitStack` and underlying transport, requiring a full reconnection via `connect()` for subsequent calls. For per-call session isolation, use `HttpStatelessClient` instead, which is optimized for this pattern in [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py).

### Why does AgentScope only provide stateful clients for StdIO transport?

StdIO-based MCP servers run as subprocesses that require explicit process lifecycle management. A stateless model would spawn and terminate subprocesses for every tool call, creating unacceptable overhead and potential resource leaks. The `StdIOStatefulClient` in [`src/agentscope/mcp/_stdio_stateful_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_stdio_stateful_client.py) maintains the subprocess throughout the client lifetime, ensuring efficient communication via standard input/output streams.

### Do stateful and stateless MCP clients support the same transport protocols?

HTTP-based clients support both **Server-Sent Events (SSE)** and **Streamable HTTP** transports regardless of state management model. The `HttpStatefulClient` and `HttpStatelessClient` classes both accept `transport="sse"` or `transport="streamable_http"` parameters. However, StdIO transport is only available for stateful clients via `StdIOStatefulClient`, as stateless subprocess management is not implemented in the AgentScope codebase.

### How does error handling differ between stateful and stateless MCP clients?

In **stateful clients**, transport or session errors persist until the client is explicitly reconnected. If the underlying `ClientSession` fails in [`src/agentscope/mcp/_stateful_client_base.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_stateful_client_base.py), all subsequent tool calls will raise connection errors until `close()` followed by `connect()` resets the state. In **stateless clients**, errors are isolated to individual calls because each RPC uses a fresh transport context created in [`src/agentscope/mcp/_http_stateless_client.py`](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/mcp/_http_stateless_client.py), preventing cascading failures across tool invocations.