Let's Build an MCP Server (Step by Step Tutorial) β
Model Context Protocol (MCP)Recentπ
2025-03-12
Build Process β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BUILD CHATBOT WORKFLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β PLANNING β
β ββββββββ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Define βββββΊβ Choose βββββΊβ Setup β β
β β Scope β β Stack β β Project β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β
β βΌ β
β DEVELOPMENT β
β βββββββββββ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Build βββββΊβ Add AI βββββΊβ Test & β β
β β Features β β Logic β β Debug β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β
β βΌ β
β DEPLOYMENT β
β ββββββββββ β
β βββββββββββββββ βββββββββββββββ β
β β Deploy βββββΊβ Chatbot β βββ LIVE! β
β β to Cloud β β Running β β
β βββββββββββββββ βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββKey Components β
- Project architecture
- Core features implementation
- Best practices
Transcript β
[00:00] In this video you'll learn how to add custom tools to your agents using MCP. There are plenty of videos out there that goes through exactly what MCP is, but in short it's a standard protocol that you can use to build custom tools using Python or JavaScript and then give AI agents access to those tools. As per the MCP documentation, you can think of MCP like a USB-C port for all AI applications, just as USB-C provides a standardized way to connect your devices
[00:30] to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools. For example, if we have a look at Claude Desktop, we can see all the available tools to this agent by clicking on this button. Here we have about 16 different tools for interacting with the file system like creating directories, editing files, etc. Then we also have custom tools which we will build in this tutorial that will add items to a to-do list and
[01:02] it can also retrieve our to-dos, remove to-dos, etc. This means that with in software like Claude, we can ask our agent something like "What are my current to-dos?" and our agent will ask our permission to access our custom to-do tool and if I approve this, it's saying I've got one item in my to-do list by bread. Let's add an item by saying "Add a new to-do called call Mom". Again, the agent will ask our permission to call this tool which I'll approve and now it's saying this
[01:32] item was successfully added. So if we ask again "What are my to-dos?", our agent will now note that we've got two items, call Mom and by bread and of course we can also ask it to please remove item 2 from my to-dos which is by bread and because it's the first time we're running this specific tool, it will ask us to approve this and it's saying it was indeed removed. So finally, let's say "Please list my to-dos" and it's saying we only have one to-do left and that's called call Mom. These tools are not limited to
[02:02] Claude either. We can access these exact same tools using other software that support mcp servers like Cursor and Windsurf. So if I start a new chat in Cursor, I can ask this agent "What are my to-dos?" and it's saying I'm checking our to-dos for you, it's identified that we've got this mcp tool to get to-dos so I'm going to approve this run and it's saying we've got one item called call Mom. So let's say we were busy developing some software and we just want to make a note of something so we could say
[02:33] "Please add a to-do to update the readme file." Again, it will ask us to approve this call which I will and it's saying I've added this to-do to your to-do list with an id of 4. Of course, if we ask it "What are my to-dos?" it's checking our to-dos for us and it's saying we've got "Update the readme file" and call Mom. And because these tools are sharing the exact same database, we can switch back over to something like Claude and ask "What are my to-dos?"
[03:03] So it's retrieving our to-dos and it's saying "Update the readme file." So in this video, you will learn how mcp works by building your very own mcp server. This server will support three actions, add to-do, get to-dos, and remove to-dos. These items will be stored in a SQLite database which will be shared between all of these different clients. I do want to mention that you can download the complete project for free by using the link in the description of this video. Lastly, I will also leave a link in the
[03:34] description to the mcp document so if you do get stuck, I highly recommend going to Quick Start and simply following the Quick Start Guide for Server Developers. In fact, to get started, we will actually just copy some of the code from this page across just to save us a little bit of time. And a lot of this is simply initial setup and not really related to mcp servers itself. So if you go to core mcp concepts, simply switch over to Node. Of course, if you're a Python developer,
[04:04] you can follow along with Python but we'll be using Node and more specifically TypeScript in this video. If you're not too familiar with TypeScript, JavaScript experience will help as well. We won't be doing any fancy TypeScript in this video. Right, so if we click on Node and we scroll down, we are interested in this "set up your environment" section. This is saying that we need at least Node.js version 16 or higher and in order to set up the project, these are the different instructions. So open up your IDE, you can use Visual
[04:35] Studio Code, I'll be using cursor, then open up a new folder. First, let's instantiate a new Node environment by running npm init y. Then using the documentation, let's copy these dependencies. So let's copy this, npm install and the model context protocol slash stk as well as ZOD and press enter. Then let's also copy this command to install TypeScript and the TypeScript
[05:07] types package. I do want to mention that if you don't want to use TypeScript, you can follow along by simply creating a new file in the root of this directory called index.js and you will then simply do all of the coding within this file. But of course, we will get TypeScript to generate that file for us. So what we need to do is create a new folder called source and within this folder, let's create a new file called index.ts. So that's effectively these steps over here. Then
[05:38] we need to update the package.json file by copying everything from type all the way down to files like so. Then in the package file, I will add this code below the main property. So we now have type bin scripts and files. Then we can simply delete this additional scripts property and we should be good to go. Also rename weather to the root folder name which I called mcp. Then finally, let's
[06:09] create this TypeScript.json file. So in the root, let's create a new file called TypeScript config.json and let's copy all of this code and paste it into this file as well. So far, we've only set up the project and we haven't really touched mcp so far. But it is indeed time to start building our very first mcp server. So I'm going to move the documentation aside and what we want to build is an mcp server that will allow us to interact with a to-do database. So how do we set up an
[06:41] mcp server? Well thankfully that's really easy. At the top of the code, let's import something from model context protocol SDK and I'm actually just going to disable the cursor autocomplete as it is a little bit distracting and within this model context protocol package slash SDK, we can scroll into server and within server let's select mcp.js and from this package we want to import
[07:12] the mcp server. Now we can instantiate the server so I'll just say create server and we can create a variable called server which is equal to new mcp server and this class takes in an object as input and here we need to specify the name of the server which I'll call to do and then we also have to specify the version which I'll simply set to 0.0.0
[07:43] then we just have to write some logic to start the server so I'll just write a comment called start server and for this I'm going to create an async function called main and in this function we have to specify how the data will be passed between the mcp client like Claude or cursor to the mcp server which is our to-do list application. So in this architecture page they kind of demonstrate it like this. You would have
[08:13] your mcp host which would be something like Claude and you would have your back end server like our to-do server. These two layers need some way to pass the data back and forth and that is simply called the transport layer. It's sort of like a telephone line. There are two types of transport layers. First is the standard IO so standard input output transport and this is a very common transport where our client would send a message to the server and the server will return some response. With SSE
[08:46] transport the server is able to send events to the client so this could be some back end process that will send an event to the client once it completes. What we're interested in is the standard IO transport. So what does that actually look like in the code? First to instantiate this transport we need to import it. So at the top of the file let's import something from model context protocol slash SDK slash server slash
[09:16] ST studio. You can also see SSE over here but let's select ST studio and from this package we want to import the standard IO server transport. Now in our main function let's create a new variable called transport which is equal to a new instance of the standard server transport and now that we have a mechanism for passing messages back and forth all we have to do is call our server so we'll await server dot connect and for the connection
[09:49] we'll simply pass in our transport and finally all we have to do is call this main function. So let's call main like so and we can add some exception handling by adding dot catch and this will receive an error message and with this error we can simply console log this error and finally it's called process dot and with the value of one. This will simply terminate the server right so we've now defined our server and we've added logic to start
[10:20] the server. Now how do we actually add tools to the server? Well thankfully that's super easy so I'll just add a comment called add tools and to add a tool to the server let's call server dot and on the server we can do a few things we can define a tool a resource and a prong each of these are explained in detail in the documentation but for this tutorial we're simply interested in adding tools. For each tool we need to add a few parameters the first parameter is the name of the tool so
[10:52] the first one I want to add is an add to do tool to add new to dos to our database. You can call this whatever you want by the way then for the second property we need to provide an object and within this object we can specify the input parameters for this tool so for a to do tool it would make sense to pass in some text and we can also make this type safe by using zod this means that at the top of the code I am going to import z from zod
[11:24] and this means next to the sticks property we can now call z dot and this will be a string field like so then after this object we now need to specify a callback function and it's within this function that will actually write our code so let's define a function as an async function and this function takes in a text parameter and this is an anonymous function and in curly braces we can now start to write our code
[11:55] so this is add a comment to say add logic here and you can build any code here the most important thing is that you return an object in the correct format so these functions will return an object with a property called content and content can be an array of values and for each value we will pass an object and this object will always have a type property and will simply return text and a second property called text and in here
[12:26] I'm simply going to create a piece of text so I'll just say that whatever value we received was added to our to do with id 99 just as an example of course in a few minutes we will add the actual logic to read the database but in order to get started let's simply create these tools and simply return some dummy text so let's go ahead and create a remove to do and list to dos as well all we have to do to add additional tools is to call server
[12:58] dot tool and we'll do exactly the same thing that we did up here in fact I'm going to copy this add to do tool and then paste it down here then let's rename this to get to dos this does not take any input and therefore we are not expecting anything to be passed in so it's just an empty object in fact we don't even need these curly braces and then for the response let's simply pass back something like call mom with an id of 99
[13:28] cool let's copy this one more time for the remove to do so let's rename this to remove to do this tool will accept an input so we're expecting to receive the id of the to do to remove and to add some type safety we'll use zod so we'll call z dot number like so and now for this callback function we are expecting to receive the id and for the response let's simply say to do and this pass in the
[13:59] variable of id was removed okay great so we've now instantiated the server we've added three tools so we've got add to do get to do and remove to do with some dummy text being returned we also have logic for starting the server now if you use simple javascript this is pretty much all you have to do but if you are using typescript we have to compile this typescript code into javascript and we can do that by opening up the terminal and in the script file
[14:31] we've got this bold script that we can call so let's run npm run bold and this now created this bold folder with this index.js file this index file is extremely important this is actually what we'll point to when loading our custom mcp servers another question is how do we load the server into something like cloth desktop or cursor the easiest solution will be to add this tool to cursor for testing but i know a lot of you are not using cursor so what you could do is
[15:03] install cloth desktop and this also allows you to add these tools to your agents so now i'll show you how to set up both approaches and then you can decide which client you want to use to continue testing your mcp server let's start with the cloth desktop go to cloth.ai download and then download and install cloth desktop for your operating system after starting cloth desktop you will be able to interact with cloth as per usual but you won't see any tools at this stage it's just the
[15:34] standard cloth features to add your own mcp server simply go to these three buttons go to file and settings then go to developer and here you can add mcp servers so click on edit config this should open up the folder containing your cloth desktop config then open this file in a code editor initially this file will be blank or it might contain this mcp service object which
[16:04] doesn't contain any values at the moment what we can do now is add our own servers within this object by first giving the server a name like todo colon and then we have to specify a few more properties for the server the first being the command and for this we'll call node and then the second property are the arguments for this command and in here we need to specify an array of values in other words an array of arguments
[16:34] but in our example we only have one argument and that is the location of this index.js file so i'm simply going to copy the path to this file and paste it into this argument we just have to escape these backslashes by adding backslash backslash backslash and backslash cool this might look slightly different if you are on linux or mac but for windows this is what you need to use if someone knows what the string should look like on mac or linux then leave a comment below it will
[17:06] be much appreciated so this is identical to opening the terminal and simply running node followed by bold and index.js this has now started up the server so it's actively now listening for instructions let's simply stop this by pressing ctrl c but hopefully you get it the mcp client will simply run this script sitting in this location now that we've saved this file we can go back to clod and simply restart clod what i like to do is to
[17:36] press ctrl r to do a hard refresh in clod but sometimes this results in this black screen when that happens you can try and close clod and open it up again but if that fails as well you might have to just close out clod in the task manager like so either way just try to restart clod so this means the next time you start clod things will look slightly different now we can see this tools icon with three available tools when we click on this we can see we've got add to do get to do and remove to do awesome let's
[18:09] test it by saying add a new to do called by braid of course our action won't do anything at the moment so we can approve this but in our code we simply hard coded this response to say i've added this to do and we've assigned id 99 let's also test please remove to do 99 let's approve this and it's actually passed id 99 to the tool and it's saying that this item was removed by the way
[18:40] if you want to see what was passed to the tool and the tool's response you can simply click on this link and this is showing that id 99 was passed in and this was the response from our action so this is just a little tip for troubleshooting these actions finally let's also ask it to please list my to dos so it's asking us to call this tool and it's saying that we've got this to do in our list called call mom awesome so if you want to you can simply follow along with this tutorial using the clod desktop app but
[19:10] for the rest of you i'll show you how to add this to cursor this is super simple as well again we need to copy the path to this index or js file then under file go to preferences and cursor settings then click on mcp i've already got the to do list loaded so i'm just going to remove the server then let's click on add new server under server name let's call it to dos and this is of type command this is not sse then we need to enter
[19:42] node and let's paste in the path to that index file and click save if everything was set up correctly the server should be green and we should be able to interact with the server already so let's open up the chat by pressing ctrl i and let's say please list my to dos it's asking us to call this tool so let's approve it and it's saying we've got one to do called call mom there's no need to test the rest as we know this will work right now let's have a look at adding an
[20:12] actual database that will store our to dos first let's install sqlite let's run npm install better dash sqlite 3 and let's press enter better sqlite is simply a wrapper around the sqlite SDK it just makes it simply easier to work with sqlite databases after installing this package let's create a new file in the source directory and let's call it database.ts let's start by importing database from better sqlite 3 then what i
[20:44] also want to do is create a new variable called db location and this will be the path to the database on our file system if you don't specify a path the script will actually create a database relative to the client's directory in other words when you run these actions from cursor it will create a sqlite database within the cursor directory and any cursor will have a view of the database and if you then run the same script in something like claud desktop a new instance of the database
[21:15] will be created with in claud desktop's folder and basically these two applications will have their own instances of the database but what i want is one single instance that is shared between all my different clients so i've simply created a new folder in my c drive called todos and this needs to be a front slash on windows and i know this path will be different based on your operating system so to try and assist you based on your operating system i'll simply leave you with this comment on
[21:47] windows you'll simply pass in the path to the folder where you want to store the database on mac os you will use the join function with the home there function then the name of the folder and the same with linux so depending on your operating system you might want to import a few more things like join and home there cool now that we've set our db location let's first make sure that this folder actually does exist so let's say const data
[22:18] directory is equal to resolve and db location we also have the import resolve which we can import from path over here okay then we can check if this directory exists by calling if not exists sync which we can import from fs over here and what we want to pass in is our data there so if this directory does not exist we simply want to create it so we can call a function called mkdir sync which
[22:49] we can also import from fs and here we'll pass in the data directory name and for the second parameter we'll pass in an object with recursive equals to true so at this point we can assume that the folder now exists so let's initialize the database by calling const db path is equal to join and what we want to join is the data directory and the name of the database which will simply call todos.db then we
[23:20] can connect to the database and this will actually create a database if it doesn't exist so we'll call db is equal to new database along with this database path what we want to do next is create a todos table if it doesn't yet exist so to create a table we can call db.exec and this pass in this command create table if not exists so it will only create a table if it doesn't yet exist then in parentheses let's specify all the
[23:53] fields first is the id field which is simply an integer that will auto increment as we add new todos then we'll add the text for the todo and this is of type text and it may not be null and for completeness sake we'll also create a created add field and that should be it for our database and on that list we're still getting this type script error on better sqlite3 and that is because we're missing a few type script type definitions for this package so to fix that we can simply open a terminal and run npmi dash dash save dev with a
[24:27] package types slash better dash sqlite3 so that's how I resolve this error right so now that we've created our database and set up the initial table for the todos we need to specify the different operations we need operations to add todos remove todos and list todo so i'm actually going to just export a const called db operations which is equal to an object and in this object we'll create a function to add todos so this function
[25:00] will simply make an arrow function like this let's also create one to get todos we'll also just create an arrow function for now and let's also do remove todos and this is also an arrow function let's start with add todos we know that add todo will receive text and this is of type string for the logic let's prepare the sql statement so i'll just call stmt is equal to db dot
[25:30] prepare and prepare simply takes in a string and for prepare we'll pass in this string that says insert into the todos table over here into column text and for the value we'll simply use this question mark which is just a placeholder then to actually execute the statement we need to run statement dot run and what we want to pass in is this text value that we received in the function call and we also want to get the response back of this run so i'll
[26:01] just create a new variable called info which is equal to statement dot run so info will give us information about the record that was inserted for example the inserted id this now means that we can return an object and all we're really interested in is the id which we can get from info dot last inserted row and we need this to be returned as a number and i guess we can also pass back the original text let's do the same for the rest of these methods forget
[26:32] todo we're not expecting to receive anything we simply want to return all the todos in the database so again let's prepare our statement which is equal to db dot prepare and let's pass in a string like select all from todos and we'll order the results by the id in descending order now we simply want to return the list of todos so let's return statement dot all so this will execute the
[27:03] statement and we're asking it to return all the results and if we are using type script we might want to give a type to this array so i'll simply call this type todo but it's also defined the type somewhere in our code so maybe at the top of the code let's export a new interface called todo and we've got two properties the id and text and that's all we have to do for get todos now for remove todos we also want to receive the id this is of type number and in this
[27:36] logic let's again create our statement so const statement equals db dot prepare and for the text we'll simply pass in delete from todos where the id equals this placeholder now we can run the statement so const info equals statement dot run and we'll pass in the id this will simply be injected into this placeholder over here now we simply want to return either a true or false so true if the item was
[28:08] successfully deleted or false if nothing was deleted how do we do that well all we have to do is say return info dot changes greater than zero so if the amount of changes were greater than zero then it means this deletion was successful otherwise if it's zero nothing changed right and because this is type script we'll simply tell this function that the response will be of type boolean you can simply omit this if you are not using type script great now that we have all our
[28:38] operations defined we are actually done with all the database related stuff so back in our index file let's simply add all of these operations to our mcp actions under add todos i'm going to replace add logic here with db operations which we need to import dot and now we can see our different functions here so we want to call add todo and we need to pass the text to this add todo function and we can store the result in a new variable
[29:10] called todo and this means we can now adjust this response to say that text was added to our todo with id and let's now grab the correct id from todo dot and on todo we've got id of course you can add some exception handling if you want so maybe if the todo failed then you would return some response saying that todo couldn't be added but i'm okay with this then under get todos let's delete this and let's call db operations
[29:43] dot get todos we don't have to pass anything to this function we simply want to store our todos in a variable and this will return a list of todos so what we can say is if we don't find any todos we could say if todos dot length is equal to zero then we want to return i'm actually just going to copy this return structure over here just to save a bit of time and what we want
[30:14] to return is something like you have no todo items yet now it's important to note that we can't simply return this object or this array back to the model we have to provide text so what we can simply do then is map through those items so let's actually create a new variable called todo list and what we can do is simply say todos dot map so we will map through each todo and now we can create a piece of string for each of these todos
[30:44] for example i want these todos to be despite in a very specific format i want to show the todo text along with the id so it should look something like 99 colon and the name of the todo like by bread as an example so what we can do is replace this with todo dot id and we'll replace this with todo dot text just like that now this will still give us an array of values and we have to convert this array into a string in
[31:16] javascript we can simply call the join function and i'll also join these items with a new line this will simply convert this array into a piece of string in fact if we hover over this we can see that todo list is now simply a string value and that means we can now copy the string and that is what will return back to the mcp client we can also take this a step further by adding additional context so i'm actually going to i'm going to return
[31:46] this as a variable and we can pass some additional info back to the model as well like you have and let's say todos dot length todo items and i'll add a new line and then followed by the actual todo list so just imagine that when we interact with the chat model or the agent this will tell the agent how many items there are so how many items are in the todo list along with the actual array of the items hopefully this is all making sense
[32:18] so far and if you are enjoying this video please hit the like button and subscribe to my channel for more technical tutorials like this lastly let's also implement this remove todo action let's remove this comment then let's say under db operations let's call remove todos this takes in the id of todo and if you can recall this will return a boolean value which will tell us whether this deletion was successful or not so i'm actually going to store the result in a variable
[32:49] called todo and first if we couldn't remove this todo it might mean that the todo simply didn't exist so i'm simply going to assume that's the case so i'll say that if todo is false then i'm going to just copy this return place it in here and for the text i'm just going to say error no todo item found with id and this id value otherwise we'll just return a message saying this todo was removed and believe
[33:20] it or not that should be everything before we test this out we have to rebuild this project if we are using type script so let's run npm run build again so now in the build path we have index and we've got database so i'm first going to test this out in cursor by saying add a todo called update readme file then it's approved this and this seems to be repeating the same information that we provided earlier which is not correct so if that happens
[33:52] what we can do is simply re-add the server so i'm going to copy this path then it's going to file let's go to preferences cursor settings mcp let's delete this one let's add a server i'll give it a name like todos in the command let's enter node let's paste in that path to the index file right this is all green so let's close this let's open up the chat again i'm going to start a new chat and it's say add a todo called update
[34:24] readme.md okay let's approve this and that looks better now saying great i've added the new todo item with id1 that sounds correct and and what i can also see is in this database folder this todos database was indeed created so if you do run into issues simply remove the server and add it back in let's also try to add another item so let's say add another item called subscribe to leon's
[34:55] channel let's approve this and it's saying that the new todo was added with id2 so far that sounds good let's now say list my todos let's approve this and look at that we get our two items back let's delete one let's say delete todo number one uh let's let's approve this and it's saying this item was deleted let's go ahead and test this in claud so i'm just going to refresh claud we can still see our three tools here i'm just going to start a new chat to make sure everything is
[35:26] clean let's say list my todos let's approve this and we get the same list of todos back that we saw in cursor let me know down in the comments if there are any other tools that you would like me to create using mcp server also if you found this video useful hit the like button subscribe to my channel and otherwise i'll see you in the next one bye bye