Loading video player…

Structuring Your MCP Client

00:00 Open your terminal in the mcp-client directory and start by running the command uv add mcp to add mcp, the dependency, to your project.

00:12 Once that’s done, open your preferred code editor and make sure you’re in the file src, mcp_client, then the __init__, and it may have a couple of lines of code that were added automatically by uv, you can delete those.

00:26 What you want to do, the goal for this lesson, is to write a class MCPClient that is going to be an MCP client that connects to arbitrary MCP servers. The idea is that you tell the client what server to connect to by specifying the shell command that starts the server.

00:54 And then the usage in code will look like this. You’re going to use this client as an asynchronous context manager. So you write it as async with MCPClient and then server_command

01:09 as client, colon, and then you do whatever you want to do with the client. So this is what the usage will look like. What does this mean? First of all, if the server_command is being passed in when you create the instance, that means you need to have another __init__ that accepts this server_command, which is a string.

01:27 It returns None, this method, and then you just save the server_command as an attribute for later.

01:34 This is the start. If you want to be able to use your MCPClient as an asynchronous context manager, it means you need to implement the appropriate dunder methods. For regular context managers, those would be __enter__

01:51 and __exit__.

01:57 But since your MCPClient is going to be an asynchronous context manager, your dunder methods are the coroutines, so async def __aenter__ for asynchronous enter and async def __aexit__ for asynchronous exit.

02:15 In your __aenter__, you want to create a client session. This is where you establish the connection to the server, and your client session will come from awaiting a method called _connect_to_server() that you have not implemented yet. So this is to be implemented.

02:38 And when you’re done, you want to return self so that you can use the context manager like in the usage example. You want to be able to write as client at the end of the statement. And for the dunder method __aexit__, the dunder method accepts a couple of arguments related to the errors that may have occurred inside the context manager and that are forcing it to close.

03:05 But you’re not going to deal with those. For now, you’re just going to do nothing.

03:12 Now your dunder method __aenter__ is depending on a coroutine called _connect_to_server() that you haven’t implemented yet. You’re going to do it in the next lesson because this is going to be a meaty coroutine.

03:27 Assuming the coroutine is in place in this module, you want to have a coroutine main that you can use to test the MCP client. So you just copy the usage example from before, but this time you provide a real command that runs a real server. In this case, mcp-data from the previous lesson.

03:49 And once the connection is established, you just print something to let the user know that the connection succeeded.

03:58 Then for the sake of convenience, you need a synchronous main that uses asyncio to run your asynchronous main. And finally, a guard that checks if this module was ran directly or not.

04:14 And if it was, you run your synchronous main. Now in order for asyncio to be able to run your coroutine, you need to import asyncio at the top of your file.

04:27 There’s something missing. Your dunder method __aenter__ returns Self with a capital S from the module typing. So from typing import Self, because you are returning self.

04:43 This is the basic structure of your MCP client. In the next lesson, you’re going to implement _connect_to_server(). And you’ll also see how that informs the implementation of __aexit__, which is currently incomplete.

Become a Member to join the conversation.