Your first MCP client
This is the 'aha' moment. We're building an MCP client that connects to our server and calls tools. Watch how much code disappears compared to the manual approach.
In the client, you write zero lines of: - Tool schema definitions (server provides them) - Name-to-function mapping (protocol handles routing) - execute_tool() dispatcher (client calls session.call_tool()) - Response normalization (protocol handles serialization) The server tells the client everything it needs to know.
import asyncio
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "servers/wikipedia-server-stdio.py"],
env=None,
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print("Connected to MCP server!")
# Auto-discover tools (no schemas needed!)
tools_result = await session.list_tools()
print(f"Available tools ({len(tools_result.tools)}):")
for tool in tools_result.tools:
print(f" - {tool.name}: {tool.description}")
# Call a tool (no dispatcher needed!)
result = await session.call_tool(
"search_articles",
arguments={"topic": "Python programming", "max_results": 3}
)
print(f"Results: {result.content}")
if __name__ == "__main__":
asyncio.run(run())Complete MCP client. Connect, discover, call. No schemas, no dispatchers.
Three key operations: 1. stdio_client(server_params) launches the server as a subprocess 2. session.list_tools() auto-discovers all available tools 3. session.call_tool(name, arguments) calls any tool by name That is the entire client pattern. It works with any MCP server, regardless of what tools it provides.
Fill in the blanks: Complete the MCP client
Loading practice…
Quiz: Quiz
Loading practice…