Model Context ProtocolMCP Python SDK 2.2 · LangChain 1.4 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →

MCP client

Client is the part of an application that speaks MCP to one server, and it can connect to a server object in memory as this course does.

Last updated: 29 Sep, 2026 · MCP 2.2

The last lesson listed a tool. Now call it. The same Client that lists tools also runs them, over the real protocol, whether the server is in memory, a subprocess or across a network.

The Client API

python
from mcp import Client

async with Client(mcp) as client:      # a server object means in memory
    await client.list_tools()          # what the server offers
    await client.call_tool(name, args) # run one tool

Connecting in memory

Client(mcp) is given the server object, so it connects in memory: no process to start, no port. async with connects when the block starts and disconnects when it ends. Every client method is async, so it is awaited inside main, which asyncio.run starts.

python
async with Client(mcp) as client:
    result = await client.call_tool("lookup_order", {"order_id": "A17"})
Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports this file. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep it in the same folder.
View the code here
shop.py
from mcp.server import MCPServer

mcp = MCPServer("Shop support")

ORDERS = {
    "A17": {"item": "blue mug", "status": "shipped", "total": 12.50},
    "B42": {"item": "desk lamp", "status": "processing", "total": 48.00},
}


@mcp.tool()
def lookup_order(order_id: str) -> str:
    """Look up an order by its id and say where it is."""
    order = ORDERS[order_id]
    return f"Order {order_id}: {order['item']}, {order['status']}."

Calling a tool end to end

The whole call in one file. The result comes back in three parts.

Example
import asyncio

from mcp import Client
from shop import mcp


async def main():
    async with Client(mcp) as client:
        result = await client.call_tool("lookup_order", {"order_id": "A17"})
        print(result.content[0].text)
        print(result.structured_content)
        print(result.is_error)


asyncio.run(main())

The three parts of a result

  • content is a list of blocks for the model to read; here, one text block.
  • structured_content is the same result as data for the application's code; a string return is wrapped as {"result": ...}.
  • is_error says whether the call failed, and Tool errors covers when it is True.

Reading what the connection knows

When a client connects, the two sides agree on a protocol version, and the server declares its capabilities: the kinds of request it will answer.

Example
import asyncio

from mcp import Client
from shop import mcp


async def main():
    async with Client(mcp) as client:
        print(client.server_info.name)
        print(client.protocol_version)
        print(client.server_capabilities.tools)
        print(client.server_capabilities.completions)


asyncio.run(main())

MCPServer always declares tools; Resources and Prompts arrive later in the course. Completions, argument autocomplete, needs a handler this server does not have, so it is None, and a client will not ask for it.

In memory vs a subprocess vs HTTP

Connect withWhat happensWhere it is covered
Client(mcp)In memory, no process, no portthis course throughout
StdioServerParametersThe server runs as a subprocessthe stdio transport lesson
a URLAn HTTP connection to a remote serverthe Streamable HTTP lesson

When to use the in-memory client

  • Trying a server while you build it, with no process to launch.
  • Testing a server, where the in-memory client becomes a pytest fixture in part 6.
Watch out. Read is_error before you read structured_content. A failed call still returns a result object, and reaching into its data without checking the flag hides the failure from your code.
Try it yourself
  • Call lookup_order for B42.
  • Print result itself, not its parts, to see every field.
  • Call a tool that does not exist, "cancel_order", and print is_error and the text.

This is what real progress feels like.