Building a client in Python

Model Context Protocol (MCP) · Practical · 12 min

Your own apps, scripts and agents need a client to use MCP servers.

The same mcp package includes a client. In SDK v2 it is one class, Client, used with async with so the connection is opened and closed cleanly.

What you pass decides the transport:

  • a URL string such as "http://localhost:8000/mcp": Streamable HTTP,
  • StdioServerParameters(command=..., args=[...]): launch a local server as a subprocess over stdio,
  • the server object itself (Client(mcp)): connect in memory, no process or port. Ideal for tests.

Entering the async with block connects and agrees on a protocol version automatically. Afterwards you can read client.protocol_version, client.server_capabilities, client.server_info and client.instructions (optional usage notes from the server).

The main calls mirror the protocol methods:

  • await client.list_tools() then result.tools (each has name, description, input_schema),
  • await client.call_tool(name, {...}) returns content, structured_content and is_error,
  • list_resources(), list_resource_templates(), read_resource(uri),
  • list_prompts(), get_prompt(name, {...}).

Client code is asynchronous; run it with anyio.run(main) or asyncio.run(main()).

When talking to servers you don't control, remember SDK v2's client validates responses strictly: a server that sends invalid data raises a validation error instead of being silently accepted. If a v1 tutorial shows ClientSession plus await session.initialize(), that lower layer still exists, but Client replaces it for normal use.

Example

import sys
import anyio
from mcp import Client, StdioServerParameters


async def main() -> None:
    server = StdioServerParameters(command=sys.executable, args=["notes_server.py"])
    async with Client(server) as client:            # or Client("http://localhost:8000/mcp")
        print(client.protocol_version)               # 2026-07-28

        tools = await client.list_tools()
        print([t.name for t in tools.tools])         # ['list_notes']

        result = await client.call_tool("list_notes", {})
        print(result.is_error, result.structured_content)

        note = await client.read_resource("notes://groceries")
        print(note.contents[0].text)                 # milk, eggs


anyio.run(main)

Remember

  • Client(url) = HTTP, Client(StdioServerParameters) = subprocess, Client(server) = in memory
  • async with handles connection and version negotiation
  • list_tools / call_tool / read_resource / get_prompt mirror the protocol
  • Client replaces v1's ClientSession + initialize() for normal use

Check yourself

  1. How do you connect a Client to a server defined in the same Python process, with no subprocess?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: py.sdk.modelcontextprotocol.io/client/