Skip to content

Conversation

@felixweinberger
Copy link
Contributor

@felixweinberger felixweinberger commented Jan 15, 2026

Summary

Adds a high-level Client class that provides an ergonomic API for testing MCP servers with in-memory transport.

Motivation and Context

Testing MCP servers currently requires using the verbose create_connected_server_and_client_session function. This PR introduces a simpler API:

from mcp import Client
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("test")

@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b

async with Client(mcp) as client:
    result = await client.call_tool("add", {"a": 1, "b": 2})

Related issues:

How Has This Been Tested?

  • 24 new tests for Client and InMemoryTransport
  • Migrated existing tests from create_connected_server_and_client_session to new API
  • Full test suite passes

Breaking Changes

  • Removed create_connected_server_and_client_session from mcp.shared.memory (v2 breaking change)

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

New API

High-level (recommended):

from mcp import Client

async with Client(server) as client:
    result = await client.call_tool("my_tool", {"arg": "value"})

Low-level (for advanced use cases):

from mcp.client.transports import InMemoryTransport
from mcp.client.session import ClientSession

transport = InMemoryTransport(server)
async with transport.connect() as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        # Full ClientSession access...

@felixweinberger felixweinberger force-pushed the fweinberger/client-v2 branch 2 times, most recently from d0f5279 to 3fce73d Compare January 16, 2026 08:40
This commit adds:
- Client class stub with from_server() class method
- InMemoryTransport class stub
- Comprehensive test suite for Client (TDD approach)

All methods currently raise NotImplementedError.

Github-Issue:#1728
- Client class wraps ClientSession with transport management
- Client.from_server() provides ergonomic in-memory testing
- create_in_memory_transport() reuses existing memory stream logic
- All 23 Client tests pass

Github-Issue:#1728
- Export Client from mcp and mcp.client modules
- Add deprecation warning to create_connected_server_and_client_session
- Document migration path in docstring

Github-Issue:#1728
- Migrate tests/server/fastmcp/test_title.py to use Client.from_server()
- Fix circular import by not re-exporting ClientSessionGroup from client module
- Demonstrates cleaner API: no need to access ._mcp_server

Github-Issue:#1728
Migrate the following files to use the new Client API:
- tests/server/fastmcp/test_server.py: Use Client.from_server(mcp) instead of
  client_session(mcp._mcp_server)
- tests/shared/test_session.py: Use InMemoryTransport + ClientSession for
  low-level session tests
- tests/shared/test_progress_notifications.py: Use InMemoryTransport for
  progress callback exception test
- examples/fastmcp/weather_structured.py: Use Client.from_server(mcp) for
  the example

The high-level Client API is used for tests that just need to call tools,
resources, and prompts. The low-level InMemoryTransport + ClientSession
pattern is used for tests that need direct session access (e.g., _in_flight,
send_notification, send_request).
…r_and_client_session

Breaking change for v2:
- Replace create_connected_server_and_client_session() function with InMemoryTransport class
- Add Client.from_server() for ergonomic testing
- Migrate all tests to use new API patterns
- Update stream spy fixture to patch both module locations

High-level tests now use:
  async with Client.from_server(server) as client:
      result = await client.call_tool(...)

Low-level tests use:
  transport = InMemoryTransport(server)
  async with transport.connect() as (read, write):
      async with ClientSession(read, write) as session:
          ...

Github-Issue:#1728
- Refactor Client constructor to accept Server/FastMCP directly
- Remove from_server() classmethod (simpler, matches FastMCP pattern)
- Update all tests and examples to use new pattern

Github-Issue:#1728
- Add module-level pytestmark = pytest.mark.anyio
- Convert 16 test classes to standalone async functions
- Remove self parameters and individual decorators
- Maintain 100% test coverage
The session methods in this branch don't have the deprecated cursor
overloads, so convert cursor to PaginatedRequestParams instead of
trying to call a non-existent deprecated signature.
The ClientSession in this branch only supports the params parameter,
not the deprecated cursor parameter. Remove tests that were testing
the deprecated cursor behavior and consolidate to params-only tests.
- Replace AnyUrl usage with plain strings for Resource URIs
- Update test_1574 to use Client instead of removed client_session helper
- Add list_resources() call to test_with_simple_server to cover the handler
- Remove incomplete test_logging_callback that never triggered logging
- Mark fixture handlers as no-cover where they exist only for metadata
  (list tests don't invoke handlers, they only list available items)
- Convert TestInMemoryTransport class to plain test functions
- Remove section header comments from test_client.py
- Make pragma: no cover comments more explicit about why handlers aren't covered
- Add TODO in Client docstring outlining planned transport expansion
@felixweinberger
Copy link
Contributor Author

Key files to review:

  • src/mcp/client/client.py (new) - unified Client class
  • src/mcp/client/transports/memory.py (new) - InMemoryTransport
  • src/mcp/client/__init__.py - public exports
  • src/mcp/client/transports/__init__.py - transport exports
  • src/mcp/shared/memory.py (deleted) - replaced by InMemoryTransport

Remainder is tests being updated to use the new interface.

NOTE: This only implements test support. Refactoring Client to support different transports for the end-user API (streamable HTTP, stdio, SSE, etc.) is coming, marked as TODO in the Client docstring.

@felixweinberger felixweinberger marked this pull request as ready for review January 16, 2026 12:52

try:
# Create transport and connect
transport = InMemoryTransport(self._server, raise_exceptions=self._raise_exceptions)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now this is hard-coded but we'd add a infer_transport_from_args or similar method during init that would set up SHTTP / STDIO / SSE as required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ergonomic test client for testing MCP servers

2 participants