The Ultimate MCP Crash Course - Build From Scratch
By Web Dev Simplified
Summary
## Key takeaways - **Understanding MCP: Model Context Protocol**: MCP, or Model Context Protocol, is a communication protocol similar to REST or GraphQL APIs, enabling seamless interaction between MCP servers and clients. It defines how these components should communicate, allowing one client to connect to multiple servers. [01:47], [01:58] - **Core MCP Server Components**: An MCP server is primarily composed of four elements: tools, resources, prompts, and samplings. Tools are the most frequently used, enabling clients to execute code on the server, while resources represent data accessible by the server. [02:25], [03:48] - **MCP Tools: Enabling Server-Side Actions**: Tools within an MCP server allow clients to invoke code or actions on the server, such as creating an Excel document or performing complex data computations. They act as a bridge, enabling AI clients to interact with various applications and programs through the MCP protocol. [02:51], [03:40] - **MCP Resources: Data Access and Representation**: Resources in an MCP server are essentially sets of data, which can include databases, files, images, or even specific data points within an application like rows in an Excel table. They provide the server with access to information that clients can query. [03:48], [04:22] - **Building Your Own MCP Server and Client**: The video demonstrates how to build both an MCP server and a client from scratch, utilizing SDKs provided by the Model Context Protocol project. This allows for custom integrations with AI applications like GitHub Copilot. [00:13], [05:45] - **MCP Client Interaction with Tools and Resources**: An MCP client can interact with server-side tools and resources. For instance, a 'create user' tool can be called from an AI chatbot, and resources like user data can be accessed and displayed within the chat interface. [21:19], [31:33]
Topics Covered
- MCP: A Protocol Similar to REST/GraphQL
- MCP Server Components: Tools, Resources, Prompts, and Samplings
- Understanding MCP Prompts vs. Samplings
- Interactive CLI Menu for AI Tool Execution
- Handling AI Tool Execution and Response Formatting
Full Transcript
MCP is incredibly popular right now.
Everyone's talking about it, but no
one's going in-depth enough on how it
works and what it means for you. So, in
this video, I'm not only going to
explain everything you need to know
about MCP and how it works, but I'm also
going to be showing you how to create
your very own MCP server that you can
hook up to any MCP client that you want.
And then on top of that, I'm even going
to show you how to create your very own
MCP client that can hook up to any
server out there, whether you created it
or someone else created it. This is
going to be the complete crash course on
everything you need to know about MCP.
[Music]
If you're struggling to learn web
development, it may be because the
content you're using doesn't resonate
with your learning style. Some people
really learn best from workshop style
events, and that's where our sponsor,
Frontend Masters, specializes. They have
hundreds of different courses and
learning paths that you can check out
that follow through with workshop based
content. So you get that more
collaborative kind of experience you get
in a workshop, but for way less cost
than an actual workshop. They also have
a brand new feature which are these
tutorials. And these are kind of like a
curated version of YouTube that has
exclusive videos. I actually have some
exclusive videos out there right now and
I'm continually making more exclusive
videos for them. And I even have a
workshop that I'm going to be doing
which is going to go onto one of those
courses. So, if you're interested in
checking out some of this additional
content that I have on front-end masters
or really diving more into this workshop
based way of doing things, I'll have a
link down in the description for you to
use. That'll give you a discount when
you check out. Welcome back to WebDev
Simplified. My name is Kyle and my job
is to simplify the web for you so you
can start building your dream project
sooner. And I know you're dying to get
into the code and see how we create
something like this. But we first need
to understand what MCP is. There's a
really nice site for MCP. I'll link it
down in the description below. It goes
over a lot of really good information on
what MCP is, how it works, and
everything like that. So, I highly
recommend checking this out for more
in-depth knowledge. But really, MCP is
just a protocol. It stands for model
context protocol. And all it is is a
protocol similar to like a REST API or a
GraphQL API. And if we look here, it
just allows you to communicate between
MCP server and an MCP client.
Essentially, it tells you how the client
and server should communicate with each
other, so they both know how to send
messages back and forth. And one client
can hook up to as many MCP servers as it
wants to. And again, like I said, it's
very similar to REST APIs or GraphQL.
Essentially, it's just a language that
you can use on your client to
communicate with a server and that the
server can use to send information back
to the client so they know how to work
with each other properly to do all these
really cool different things. Now,
inside of an MCP server, there are
essentially four main things that it's
made up of, but two of them are the most
important. That is going to be tools,
resources, prompts, and samplings. Now,
of those four, tools and resources are
by far the most used with tools probably
being the most used hands down out of
all four of them. And really, the
entirety of your server is made up of
these four things. And the MCP, the
protocol itself, is what allows you to
communicate between all these different
things the server has. Now, the first of
these that I want to talk about is going
to be tools. And tools are essentially a
way for a client to call code on the
server. So, essentially, let's say that
I have an MCP server for Excel. I can
have a tool that says create an Excel
document. And then me as a user using
some type of AI chat program, I could
tell my AI chat program, hey, create me
an Excel sheet that has this information
inside of it. That AI chat program knows
there's an MCP server for Excel that has
a tool for creating an Excel sheet. So
essentially, the AI will call that tool
to create a new Excel sheet. And these
tools can be as simple or as complex as
you want. It could be as simple as
creating an Excel sheet or it could be
as complicated as doing a bunch of
different data computation in an Excel
sheet to create charts and so on.
Essentially, it's a way for these AI
clients to interact with different
applications and user interfaces and
programs through a specific model
context protocol. So, tools are just a
way for the AI to essentially call
functions and do things inside of
whatever program this MCP wraps around.
A resource is quite a bit simpler and
really all it is is a set of data. Now,
this could be like a database. It could
be files on your file system. It can
literally be anything that contains
data. Images, files, database, doesn't
matter what it is. This is just
something that you have access to on a
particular MCP server. So, in the case
of Excel, you may have an MCP server and
the resources could be the rows inside
of your Excel tables. It could be the
Excel files themselves or it could even
be like charts inside of your Excel
files. There's lots of different things
that could be resources depending on
what your application is. A lot of times
if you're doing like a application
that's a web application, your MCP
resources are probably going to be
things related to database records or
maybe files that users have uploaded.
Now prompts and samplings are quite a
bit less used, but they're still
important. A prompt is essentially a
pre-created prompt that you can ask your
MCP server for and it'll send down a
really well- formatted prompt for you to
do specific tasks. So this is, like I
said, not as useful because you can
already have tools that do some of this
for you or resources that give you the
information. But if you specifically
want to create a prompt that helps a
user with something, you can create a
prompt on the server, they can ask for
that prompt and it'll send them back
that prompt so that they can use that
inside of their AI client. It's just a
really great way to create robust
prompts on your server that then the
client or user can interact with.
Finally, samplings is kind of the
opposite of all the stuff we've talked
about where normally the AI client is
talking to the server and saying, "Hey,
do this thing for me. Give me this
information. Give me this prompt."
Whatever it is. But samplings are the
opposite. That is when the server wants
to get information from an AI and it
says, "Hey, I need some additional
information for you. Can you run this
prompt on your AI and then send the
result back to me?" So, a sampling is
just a way for a server to ask the
client for some type of information
based on a prompt that they send down to
an AI. So, it's just the reverse of
everything else that we've talked about.
And that's pretty much all there is to
this model context protocol when it
comes to the server client
communication. The next thing that we
want to do is actually dive into the
code and start implementing this
ourselves. And the really nice thing
about this is we don't have to start
entirely from scratch because this same
site, this model context protocol site
that has all the detailed information on
this. They also have SDKs for pretty
much every single language that you
could want. So in our case, they have a
TypeScript SDK that we can open up. And
if we scroll all the way down here quite
a ways, you'll eventually find the
installation step and we can just copy
that into our very own project. So now
that we have that copied over, we can go
to our actual project. And you'll notice
on the side that I have Copilot
currently open. We're not going to be
using that right away, but when we get
our MCP server set up, I want to be able
to show you how you can integrate it
with Copilot and how you can actually
call commands directly from here. And
this is where you can integrate it with
any application you want. So, if you're
using Cloud, you can integrate with
Cloud. All the integrations work almost
exactly the same. So, let's go ahead and
we're going to install that library. And
all it is is a very thin wrapper over
the protocol that is created from this
model context protocol. But we still
have to write a lot of the main code
ourselves. Now, you'll also notice I
have a very small amount of starting
code here. I just have a very basic
boilerplate tsconfig file. I pretty much
copied this directly from the model
context protocol site, but really all it
is is just a simple file for a Node.js
TypeScript project. Then in my package
JSON, I have a few dev dependencies. I
can see I have the types for Node. I
have tsx, which allows us to run this
dev command to restart our server
automatically when files change. And
then I have TypeScript, obviously,
because we're in a TypeScript project.
If you were using JavaScript, you
wouldn't need any of these dev
dependencies or this TS config. It's
only because I'm using TypeScript that
we have these. And I have three
commands. The build command converts my
TypeScript to JavaScript. The build
watch command just creates those files
every single time something changes. So
every time I update my files, it
rebuilds it. And then the dev command
allows me to just really quickly work
through my process and automatically
refresh every time I make changes.
Overall, relatively straightforward.
Now, in order to set up our application
inside of our server, we want to create
a new MCP server. So we'll say const
server
is equal to new MCP server. Just like
that. And let me make sure I get those
parenthesis or those brackets in the
correct place. Make sure I spell this
correctly. And then we need to make sure
that we import this. So we're going to
say that we're going to import MCP
server. That's coming from that ATM
model context protocol/ SDK. Let me make
sure everything is spelled properly
here. And we specifically want to make
sure we get this from the server file
and we want to get it from the MCP file.
Just like that. So now we have that MCP
server and we can set up all the files
and names and stuff inside here. So
really all we need to do is give it a
name. I'm just going to call this test.
You can call it whatever you want. So,
if you're creating an Excelbased MCP
server, you could call it Excel. It
doesn't really matter what the name is.
And then we need to give it a version.
In our case, we'll just call this
version 1.0. Now, we also have a section
down below here that is called
capabilities. And this just essentially
says what is our server capable? What
are the different things that we can do?
Well, in our case, we have resources, we
have tools, and we have prompts. Those
are going to be the main things that our
server is capable of, which is
essentially all three of the things that
a server can do because samplings are
things you send from the server to the
client. So we don't have to specify that
as a capability because we need the
client to be able to support that not
ourselves. So we support resources,
tools, and prompts. So to be able to set
up that capability, all we do is we just
pass it an empty object. And that says,
hey, we support this particular thing.
And we want to do this for tools as well
as for prompts. And we're just saying we
support all three of these because in
this project, we're going to create a
server that does all three of these
things. So you can see the differences
between them and how they all interact.
Now, the next thing we need to do is to
run our server. And we'll just create a
function called main. Just like that.
And then we'll call this main function
down here. And the only reason I'm doing
that is because this is going to be an
asynchronous function. So I can put
async a weight inside of here. Now to be
able to use an MCP server and client
relationship, you need to specify what
the actual transport protocol is going
to be. And there are technically three,
but really only two main protocols. The
first one is going to be standard input
output. Essentially, that's just going
to be using the standard IO, like when
you do console log. Essentially, it's
that type of communication through a
terminal. This is great when you have an
application running locally on the same
place that your actual client is
running. So, in our case, we have
Copilot running on my computer and we
have our program also running on my
computer. So, we can use a standard IO
process for this. The other option is
going to be HTTP streaming. This is
great if you have like a web application
where you're streaming this down to
another web application that's not
connected on the same network. That
would be a use case for a streaming
version. And then technically there's
another one called server sent events
but that's deprecated so we don't really
need to worry about it. It was replaced
by HTTP streaming. So in our case we
need to set up what our transport is
going to be. For the most part all the
code stays exactly the same no matter
which transport you use. It just depends
on how your application runs. Is it
local or is it going to be remote? In
our case we have a local application. So
we're going to be using the standard IO.
So we can say standard IO server
transport just like this. And then I
just need to make sure that I import
this. So if I just scroll to the top,
I'll paste in the code for importing
that. You can see it comes from the SDK
server standard IO library. And then we
can just say that we want to connect our
server. So we can say await
server.connect and we can connect on
that transport just like that. And now
we've essentially created an MCP server.
We hooked up to a transport layer. It
just doesn't do anything yet. But we do
have an MCP server. Now before we go
about implementing any of the complex
stuff inside of our MCP server, I want
to make sure what we've written actually
works. So what we're going to do is
we're going to download a tool coming
from that same model context protocol
team that allows us to essentially
inspect all the stuff inside of our
server. It's like Postman but for MCP.
So what we want to do is we want to
npmi. This is going to be a dev
dependency and it's at that model
context protocol. This is called
inspector. So we're just going to
install that library real quick. And
then what we want to do is we want to
create a really simple package JSON
script to be able to run that inspector
for us. So we can go into our package
JSON. And what I want to do is I want to
have a server inspect.
And what this command is going to do is
going to run that model context protocol
library that we just downloaded. So we
can say at model context
protocol/insspector.
That's going to run the library. And
what we need to do is pass it a command
that we can run to actually run our
server. So our server command is called
serverdev. So we'll pass along the
command npm run serverdev. And what that
essentially does is it tells our
inspector that our server is using this
command to run. So when we start up our
inspector, it'll also start up our
server at the exact same time. Now, one
other thing I'm going to do that will
make this a little bit easier for
testing purposes is I'm going to set an
environment variable called dangerously
omit off. I'm going to set that to true.
Just like that. So essentially, I'm
setting this environment variable before
I run my command. This is just going to
make it easier for us to test because
normally whenever we would make changes,
we would need to restart our inspector,
which gives us a brand new OF token. So,
we would need to manually reopen our
website at that brand new off token
every single time. But by making sure we
set this to be true, we at least get rid
of that off token. So, when we're doing
our testing, we don't have to worry
about restarting it every single time.
So, this is just a little bit easier for
us to test with. So, now let's see if
that works. We'll say npm run server
inspect. Just like that. You should see
that it's going to start up. It's going
to give us a little warning because
we're essentially omitting that off
token, but it will give us a URL that
when we open up is actually going to
give us our thing. So, if we open this
up right here and I drastically zoom
this back out to a more reasonable size,
you can see that essentially we have
here our transport type, which in our
case is standard IO. We have the command
npm run server dev. That's what we typed
in. And now we have the ability to
connect if we want to. So, when we click
on connect, you'll see it looks like
it's not quite working. And there we go.
It just took a little second. So, now if
we connect, you can see we are
connected. And I can click this ping
server button and it ran the ping
command, sent it to our server and it
gave us back an empty response which is
exactly what you're supposed to get when
you do a ping command. So essentially we
have a server. It is running. We're able
to connect. We just don't have any
tools, prompts or resources yet. We just
have the ability to ping it and get back
a response for that. So let's get
started by creating our very first tool.
So we can come into here into our
server. And to create a tool, it's super
simple. We can just say server.tool. And
then what we need to do is we need to
pass along information for what our tool
is going to look like. Now, there's a
lot of different ways you can create a
tool, but every tool needs to have a
name. And we're going to call this one
create user. This is essentially the
name that the AI is going to see. And
then we can also use a human readable
name as well as a description for the AI
as well. So, the next thing we're going
to pass along is a description. And this
is what the AI can use to determine what
this function does. So, we'll just say
create a new user in the database. There
we go. And then finally, we can pass
along what all the different parameters
we're passing in. So in our case to
create a user we need to pass along
things such as the name, email and so
on. So we'll come in here. We'll say
that the name is going to be a ZOD
string and we're going to be using ZOD
for this. Now in the particular case of
this library you can use JSON schema or
ZOD but I find ZOD much easier and
better to work with. So we're going to
use ZOD. So we just need to make sure
that we install ZOD. So let's come over
here, open a brand new terminal, npm
ZOD. That'll install that ZOD library
for us. Put it directly inside of here
so we can use that. There we go. Looks
like that just finished. So now we can
come into here and we can import Z from
Zod. And now we have a name field. We
can set an email field. We can set a
address field.
And then we can finally come in here
with a phone number. And that is going
to be a string as well. So now we have a
name. We have a description. We have all
the different arguments this thing
accepts. It could be an empty object if
we don't have any arguments at all. And
then the next step is going to be to
define all the different annotations for
this. So if we open this up, there's
quite a few different annotations we can
pass along. And then on top of that, we
also need to pass along what the
function is going to do. So we're going
to have a function here. And for now,
we'll just return an empty object inside
this function. So before we get to that
function, let's go back to the
annotation section cuz this is able to
provide hints to the AI to help it know
what it can do and what it can't do. So
as you can see here, we have a title.
This is like a string you can give to
the user. This is just a more human
readable version. So we'll call this
create user. We then have this
destructive hint, item potent hint, open
world hint, and read only hint. And
these essentially determine how this
interacts with different things. So
create user is obviously not read only.
So we're going to set read only to
false. This helps the AI know that this
is something that does not read data. It
also manipulates or creates or inserts
or updates data. We also can determine
does this destroy things. In our case,
this doesn't delete any data. So we can
say that this is not destructive. Again,
this gives the AI a little bit of a hint
because if it is a destructive action,
the AI may want to warn you and say,
"Hey, this is going to delete data. Are
you sure you want to do this?" It gives
you extra steps of warning potentially.
And then we can come in here and we can
say is this item potent. What that
essentially means is if you run this
multiple times, is it going to have any
side effects or is it going to change
things differently? In our case, if you
run this function multiple times with
the same input, it's going to create
multiple users. So obviously this is not
item potent. So we're going to come in
here with false. You can kind of think
about this. Is this a pure function or
not? And if you're kind of unsure what a
pure function is, I'll link a video in
the cards and description where I talk
all about pure functions. Now finally,
we can come in here with an open world
hint. And this determines is this
something that accesses data outside of
the particular environment it's inside
of. Does it go and access the web for
data and things like that. Now in our
particular case, I'm going to set this
to true just because we're interacting
with a fake database and a database
would be external to our particular
application. So it is going to interact
with something external to itself. So
these sections right here for all these
annotations, they're all optional. We
don't have to pass any of these along. I
can actually remove this entire
parameter and everything is still going
to work fine. The only reason I'm
passing along all of these different
parameters is because it allows me to
provide extra information to the AI to
give it the best possible chance of
using these tools the best that it can.
Now, finally, we come into our function
down here. This is going to get all of
our different parameters. And if we
hover over this params type, it's just
giving a bunch of errors right now. But
essentially, this param type right here
is a type that is this name, email,
address, and phone. Now, in order to
create and interact with a user in a
database, we need some type of database.
In our case, I'm just going to be using
a file as our database. So, we'll come
in here with a file at the data
location. We'll say users.json. And I'll
just paste in some random placeholder
JSON data for three different users. It
doesn't matter what this data is. This
could be a real database. It could be an
API request. It doesn't matter how you
interact with your data. This is just a
placeholder for whatever that data
interaction looks like. Now, here we can
actually go ahead and we can create a
user. So, I'm going to wrap this in a
try catch just in case we have any
errors. I want to make sure we return an
error down to the actual AI to let it
know there was an error. And when you're
working with these AIs, they expect you
to return down a very specific response.
Inside this response, it should be an
object. We need to pass along what the
content is going to be inside of here.
And this content is an array of data.
And inside of here, you can see we have
a bunch of different stuff we can pass
along. In our particular case, we're
going to be passing along text. So,
we're going to set the type of this to
text. And this is just going to be a
little bit of a warning. So, we'll pass
along the text portion. And this is just
going to say failed to save user. So,
we're just essentially passing along an
error to the AI. That way they can
notify the client or the person using
the AI, hey, this didn't actually work
like we expected it to. Then inside of
our try, we can just really simply come
in here and we can call a function
called create user, which we're going to
create in a second. We'll pass along all
the different parameters that we want
and this will return us an ID that we
get for that particular user that was
created. Then we can return some
information inside of here. So I'll put
a little return. We can get rid of this
one down here and this will be a
successful one. Essentially, we'll just
come in here and say user, pass in the
ID created successfully.
There we go. So, now we know what user
was created and that they were created
successfully. Now, we can just go ahead
and create that really simple function
for creating a user. So, we'll say
create user. That's going to take in all
of our params. So, we're going to have a
user object with a name, which is a
string, an email, which is a string, an
address, which is a string, and finally
a phone, which is a string.
There we go. That gets rid of all the
errors that we had up here. So, this
entire function is essentially done. We
don't need to look at this anymore. All
we need to do is implement the actual
creation of a user inside of our
database. Now, to do this, we're going
to use a little bit of an experimental
feature inside of Node.js that allows us
to dynamically import JSON files. So, we
don't have to worry about the file
system or anything like that. We can
just directly import by saying that
we're going to get our users. We're
going to call the import function. We're
going to pass it in the path to that
data folder. Just like that. There's
users JSON. And then this second
argument here, we can say width and we
can pass along the type which is going
to be JSON. So this allows us to do an
experimental JSON import where we're
importing essentially all that
information and it's coming directly
into this user's object. And as you can
see we get all that information in this
default object. So what I want to do
want to come in here with a simple then
I want to take my module and I want to
get the default export. So now you can
see we get a array of information being
returned to us. Now what I can do is I
can come in here. I can say my ID is
just going to be my user's length plus
one. And then we can push in a new user
by saying users.push. We can push along
that ID as well as all of our user
information just like that. So now we
have a brand new user in that array. And
then we can use the file system to
actually write it at. So we'll come in
here. We'll import fs from and we want
to get that from node slash or sorry
fs/promises. There we go. That gives us
the promise version of this particular
library. We need to make sure we export
it like that or import it like that.
Sorry. So now we can come in here with
write file and essentially we can just
write whatever our user array is back to
that particular file. So let's copy over
that path to our file. This we need to
make sure comes from the source folder.
So we're going to say source like that
because we're writing a file is
essentially from our root directory
while this import is relative to the
file we're inside of. Then what we can
do is we can pass along the data we
want. So we can just stringify up our
users and we're going to make sure that
this prints itself pretty. So we can
come in here with null and two and
that's going to make sure it prints it
with proper spacing and so on like we
have here already instead of just
collapsing it down to the smallest size
possible. So now we can wait for that to
finish and we can return the ID of the
user. So this function all it does is
just add a user to this particular file
when we call the function. And up here
this tool allows an AI user to know hey
we have a name, email, address, they're
all strings that need to be passed along
and it's saying all this additional
information about it and it's going to
just call that function for us. So,
we've essentially created a way for our
AI to know that we have the ability to
create users. It knows what all the
parameters are. It knows additional
information about this function. And
now, when I try to interact with my AI,
I can tell it, hey, create me a user
with this name, this email, this
address, and this phone. And it'll go
ahead and push that information to this
tool for me. So, now let's go ahead and
test that. We can save this file, and
we're just going to go to where we're
doing our inspection. And I'm just going
to restart that server completely. This
is why I removed that off section, so
that's a lot easier to work with. And if
we bring over our MCP server, we can
give this a quick refresh. We can click
the connect button. We should see now we
have tools. And the really nice thing
about these MCP servers is they have the
ability to list tools. So we can click
list tools. And it gives us a list of
all the tools that we have available.
Then with that list tools, we can say,
hey, we have our create user tool. It
has our description. We can click on
this tool. You can see we get fields for
all the different information. So I can
come in here with Kyle test.test.com.
Let's say the address is just bogus and
it's just some random numbers for the
phone. We can click run tool. And that's
actually going to call out to our API.
You can see it says it was a success and
it says user 4 was created successfully.
And if we go back to our application in
that user JSON file, we see we get user
4 with all that information that I typed
in. So at least we know that we can
inspect this and that it's working. But
how do we interact with an actual
chatbot to make this work? Now depending
on what chatbot you're using, it might
be slightly different, but overall it's
going to be relatively similar. Inside
of VS Code, if I hit control shiftp, I
can search for MCP. And you can see here
that I have a list servers and ad
server. List server just shows me the
servers I have configured. And add
server allows me to add a brand new
server. And that's obviously what we
want to do. So we're going to click on
that. And you can see here we have the
option from the standard IO way, the
HTTP way, or we can hook up npm packages
directly that export this configurations
as well as Python and Docker. In our
case, we have standard IO. So that's
what we're going to hook into. And all
we need to do is write whatever the
command we want to run is. So in our
case, this is that npm run. And that is
going to be server
dev. Just like that. That's the command
to start up our environment. That is our
MCP server. Depending on how you have
your server configured for standard IO,
you may run different commands depending
on what you install, but that is our
command for our application. Then we can
give our server a name. We're just going
to call this test MCP
video server. There we go. It doesn't
matter what the name is. And now what we
can see is where do we want to save
this? I'm going to save this inside of
my workspace settings so that it's only
available in this particular workspace.
So we can see what that looks like. And
now if I give that a quick save, you can
see I have a VS Code folder with an
MCP.json. And inside here, it gives me
all the information for my server. So
you can see it runs npm run server dev
just like that. And you'll also see it
tells me right here how many tools I
have running in this particular server.
Now there are a few different things
that I want to show you about this
specifically in VS Code that make it
really nice to work inside of because we
have really good debugging tools as
well. We have an option that we can pass
in called dev. And what we can pass
inside of here is the ability to debug.
So we can say debug just like that. And
I can say I want to debug the type of
node. And what that's going to do is
allow me to debug a nodebased command.
But our case, we're using npm as our
command. So we want to use node instead
as our command. And node should
essentially run whatever script is in my
build file. So server.js.
Just like that. The only reason I'm
swapping this is so that I can use the
debugging features if I wanted to. You
have to have the command up here match
the type. And this type can only be node
or python right now. And we're obviously
using node. So we'll type in node. Also,
we can pass in what the current working
directory is going to be. In our case,
we're going to use the workspace folder
for our current work direct working
directory for where it runs different
things from. And then we can also set up
a watch command inside of here. So,
we're going to say watch and we're going
to say that this is specifically going
to be that build folder looking for any
JavaScript files. So, anytime a
JavaScript file changes inside of here,
it'll refresh for when we're doing our
debugging purposes. Now, to make sure
that we have files inside of our build
section, we need to make sure that we
actually run that command. So we can say
npm run server
build watch. That's a command directly
from our package. JSON. And all this
does is it rebuilds our files every
single time that they change and puts
them inside of that build folder for us.
So if we look at our files, you see we
have build and inside of here we have
our server.js file. And that's going to
be what's hooked up to our server right
here. You can see we have our server
running with one tool running. Now you
may need to click this restart button on
your server to get it actually running.
And the nice thing about that is it
shows you all the output for your server
down here. So you can actually see the
communication between our server and our
client. It's giving back information
such as, hey, here I exist. Here give me
all your different tools. Here's all the
different tools you have. Give me all
your resources. And so on. So you can
see it's like, hey, what do you have?
What do you have here? What do you have?
So it's telling me all the stuff this
thing has, then you can actually work
with GitHub Copilot or whatever AI tool
you want. Now, you may need to restart
Copilot or whatever AI tool you have as
soon as your server is added the first
time just so that it can hook it up. But
if you want to run a command or a tool,
should I say directly from your AI
chatbot, just hit the hashtag symbol
here inside of GitHub copilot and you
can type in create user. And you can see
I have that command right here. And if I
hit enter, it's going to work on that
command and it'll most likely say, hey,
I see that you have this. I need
specific parameters from you. So now
it's essentially giving me a bunch of
different parameters. And I just went
ahead reduced my font size a little bit,
but as you can see, it's giving me a
name, an email, an address, and a phone.
Just a generic version of all these
things. And I can essentially say, hey,
do I want to particularly do this?
Because you can see it's saying all
these fields are create required. Let me
create that for you. And if I click
continue, it'll run this command with
this particular input. So if I hit
continue here, you can see it's taking a
second. It's running. And you can see it
says it successfully created a user with
the ID5. You can even see the response
that I got back as well as the input
that was passed in. And if we look at
our user data, you can see user 5 with
all that data passed in. So now I was
able to successfully call a function on
my server from an AI chatbot just by
asking it to do it directly. Now I can
also ask it to do it indirectly as well.
I could come in here and I could say,
"Can you please create a new user for me
with the name Kyle, the email test.com,
the address
1 2 3 4 Main Street, and the phone,
and just some random numbers. And it
should go ahead and actually use that
function for us as well." As you can
see, it detected that we have a create
user function. It's using all that
information I passed in. And if I click
continue, you can see right away it
created a brand new user with all that
information that I asked it to do. So
now we have tools hooked up and we can
call them inside of our client and we
can use them on our server to do some
really cool stuff. So now I want to go
ahead and start looking at resources
before we dive into some of the more
complex things such as sampling. So
we'll come in here. We collapse down
that tool. And to do a resource, it's
super simple. Just type in
server.resource. And we're going to pass
in a lot of the similar stuff. We need
to have a name. We'll call this one
users. Just like that. We also need to
pass in a URI. Now, this is essentially
a unique identifier that we can create
however we want, but it must match a
certain protocol. Protocol is relatively
straightforward. Essentially, it's
similar to like a URL, but it can be
referencing anything. It doesn't have to
just be HTTP, for example. We could have
a file in here or we could even have our
own custom type. For example, we could
call this like a users type and we could
have it linked to all, for example. It
doesn't really matter what you do. It
just must match this similar format that
you get with HTTP inside of like web
browser URLs. But it can match anything
whether it's a file, users, whatever. So
depending on your application, you may
have your own standards you use. You may
create your own schemas. We're just
going to use this users protocol at the
start here because that's just a custom
one that we can create for our
application. Then we can specify again
some additional properties inside here.
So we can specify for example a
description that helps the AI know what
this does. So we can say get all users
data from the database. There we go.
Then we can come in here with a title.
This is again human readable title.
We'll call this users. And we can also
specify a mine type. For example, what
type of data is being returned. In our
case, we have application JSON. So we'll
say that it's JSON data. Lastly, we have
a function which is going to take in
whatever that URI is that I'm trying to
pass in. And we can come in here with
that information. Now, this URI, if we
hover over it, is essentially just a
URL. So it's relatively straightforward
what it is. And inside here, we return
essentially the exact same thing we
returned from our tool down here. It's
that same exact like content and so on.
So, I'll come in here. I'll copy that
over. And our content is going to be a
type of whatever. And then we're going
to pass along some information for it.
So, what we want to do is we want to get
all of our users. Well, we already know
how to get our users because we did that
inside the create user function. So, I'm
just going to copy this and I'm going to
paste it up here because getting our
users is relatively straightforward.
Then, what we can do is we can pass down
our content information. And this is
going to take not exactly this
information because it doesn't really
need a type. Instead, we're going to
have a URI that we pass along, which is
just our URI.href. Essentially, what is
the URL of the resource that we are
trying to access. Then, what I want to
do after that is I want to get our text.
So, our text here is just going to be a
JSON stringified version of our users.
And then finally, we can pass along a
mime type just like this. And the mime
type in our case is application/json.
And that just tells our application how
to actually use this particular piece of
information. We know that it is JSON
data being returned to us. And now let's
go ahead and test this before we
actually start working with it. But you
do see we have an error. Actually, the
reason for that error is that this
should say contents instead of content.
That should hopefully fix our error. And
also, I misspelled text down here. There
we go. That actually cleaned up all of
our different errors. So now we have the
correct information being passed down to
use these resources. Now, in order to
test this, all we want to do is just
restart our inspection server. We're
going to pull over our inspection
server, and we're just going to give
this a quick restart. And as you can
see, now we have the ability to list out
our resources. You can see we have this
users resource. I'm going to open that
up. And if we look over on our screen
over here, you can see we get our URI,
which is our users all. And the text
inside of here is just an array of all
of our information inside that JSON
format. So you can see it's returning to
us all of our different user data. So
now let's see how we can actually use
this inside of our AI chatbot. This is
relatively simple to do inside of GitHub
Copilot, for example. You can add this
as a context. So I can click add
context. And you'll actually notice it
does not list the information for our
resources. You should see a section
inside here for resources, but we don't
see it. The reason for that is because
when an MCP server starts up for the
first time, it asks the server, "What
are all your resources and what are all
your tools?" Well, our server has
already started and it told our client,
"We have one tool and we don't have any
resources." So, we just need to restart
our server because now we've added
resources and tools into this section.
So, now we should hopefully see that it
has those tools inside of it. And if I
go to add context, looks like it's still
not showing up. My guess why this is
happening is because I need to restart
Copilot because now I have new
information for it to access. I can try
creating a new actual chat and see if
that pops it up, but it does not look
like it does. One nice thing is I can
come in here and I can click on browse
resources and we can see that resource
right there. And when I click on it, it
is going to open up a file with all that
information inside of it, which is
great. But I want to get it inside the
chat window. So let me just restart that
real quick. Now all I did was just
restart Visual Studio Code. That
restarted everything. And now when I
click add context, you can see I have
MCP resources and I can open up any of
those resources. If I make this a little
bit larger, it should be easier to see.
You can see I can click on that users.
And now if I click on that, it adds that
into the context and I can say what is
the name of the user with
the ID4. And now it should tell me
whatever that user is. You can see it
says their name is Kyle. And if we
double check that, you can see that that
name is Kyle. So at least it's giving us
information. We can use that resource
for different things inside of the
context chat for our application. Now
getting a user based on the ID may be
something that's really common that we
want to be able to do a lot. So we can
actually expose a resource directly for
that. So what I can do inside my server
is I can create a resource template. So
we'll come in here. We'll say
server.resource.
I want to add a resource for user
details. And that's going to give me all
the information for a user based on
their ID. And instead of passing along
just a single URI like this, I want this
to be a template. So we can say we want
a new resource template. And this
resource template is going to look like
users/ just like that. We're going to
have a dynamic user ID. We're going to
put that inside of these curly brackets
to denote that this is a dynamic
parameter. And then we'll say that this
is like the profile route or something
like that. It doesn't really matter too
much as long as you have consistent
URIs, but that's just going to be the
URI for us. Now, also, we're going to
come in here and we're going to say list
and we're going to set that to
undefined. Essentially, this list is for
matching all of the resources that match
this template. We don't really care
about this, so we're just going to set
it to undefined to essentially
completely ignore it. Then, we can go
along and we can pass essentially all
the same stuff we passed to here, our
description, so on, as well as our
function. So, let's go ahead. I'm going
to paste all that in. close off our
function just like that. And now we can
specify that this is going to be getting
a users
details from the database.
And we can come in here. We can say it's
going to be user details. And it's going
to return to us JSON data. Now, we want
to do pretty similar stuff to what we
did up here. So, I'm actually just going
to copy this. I'm going to paste it into
here. And then we'll modify it to get a
particular user instead of all of our
users. And that's because this function
not only takes in a URI, but it's also
going to take in all of our parameters.
In our case, our user ID parameter is
the only one that we really care about.
You can see this is going to be a string
or a string array because it works just
like a URL essentially. So now we have
our user ID information. We can come in
here. We can get our user which is equal
to users.find. And we want to get the
user here where the user ID is equal to
parsing an int for our user ID. And we
know that this is going to be a string.
So we'll just cast it to a string just
like that. This is going to get us a
user. Obviously if we don't have a user
we want to let them know. So if user
equals null, well essentially just
returns no information. So we can return
contents.
Contents is going to be an array. We're
going to pass in our URI href just like
we did before. We're going to pass in
some text. And this is going to be JSON
to match that up here where we had our
mime type. We want to make sure that
matches. So we're going to pass along
JSON that just says error user not
found. And then finally, we can come in
here with that mime type, which is going
to be application slashjson.
Then what I want to do down here is
instead of returning all of my users, I
want to just return a single user if I'm
able to find them. So if I don't find
them, give them an error. Otherwise,
return what that user information is. So
now I have a brand new resource that I
can use. Let's try to restart our server
to hopefully see if it picks up on that
new resource. If we come in here with
MCP resources and we expand this, you do
see we have our user detail right here.
When I click on this, you can see it's
asking me for a value for user ID. Let's
type in four, like here. And we're going
to click enter. And now it added in the
information for that user right here.
And I can say, what is this user's
information? There we go. And it should
just hopefully print out whatever that
user's information is. And you can see
the user with the ID4 has this
particular information. So this is a
really great way for me to be able to
access different resources and attach
them into queries. For example, if I
wanted to do something with user 4, I
could just come in here. I could say I
want to get the user details for four.
And now my ABI knows that this is the
user with all this information. And I
can do something like make a sale for
this user. And it could do different
stuff on the back end for all of that.
Now, the next thing that we need to work
on is going to be creating prompts. And
prompts, like I said, are much less used
than tools and resources, but they're
still really handy for particular use
cases. In our particular use case here,
it's going to be a little bit contrived,
but it's going to work out just fine.
So, let's come in here and minimize a
lot of this code that we have because we
don't need all of this to be open all
the time. And down here below all of
this, we're going to come in here,
server.prompt.
Again, just like everything else, we
need to give it a name, description, all
that different stuff. So, we'll call it
generate fake user. Just like that. And
this will say generate a fake user based
on a given name. So, this allows me to
create a prompt that's going to give me
some fake user information. I also need
to pass along any params that I have. In
our case, we just have one pram, which
is a name. So, we'll pass along the name
just like that. And then finally we have
our function which takes in whatever
that parameters is. So we have our name
object is the only thing being passed
in. Now inside of here we can return an
array that contains messages and these
are essentially the messages that we
want our AI to run. In our case we just
have one single message. We're going to
say that the role for this one is user.
And then we're going to specify the
content for that message. So we'll say
type here is going to be text. And then
we can specify what the text of that
message is going to be. I'm going to
copy this over because it's kind of a
little bit of a long text. So, we'll
just paste that in. Essentially, it just
says generate a fake user with the name,
whatever our name is. The user should
have a realistic email address and phone
number. So, essentially what this prompt
does right here is I give it a name and
it's going to return to me all of this
prompt right here that I can run inside
of my AI tool. So, this is a really
powerful way to create really
complicated prompts from very small
amounts of information. In this case,
it's not a very complicated prompt, but
you can hopefully see the power of this
as we start to implement it. So, before
we use this inside of our co-pilot tool,
let's first of all get it working inside
of our inspector. So, we can just close
out of our inspector, rerun it so that
it starts up with the latest code. We
can bring this over real quick. Click on
the restart button. And now you can see
we have a prompt section. We can list
our prompt. Here is our prompt right
here. We can give it a name, Kyle, for
example.
There we go. And now I can say get
prompt. And as you can see, it returns
to me that exact prompt with that name
being passed in right here. Obviously,
you can make yours more complicated than
this, but that's going to work for our
particular use case. Now, to run a
prompt inside of Copilot, we just need
to come in here with a slash command.
And you can see this MCP test MCP video
server and it's generate fake whatever.
If I get it expanded all the way, you
can see generate fake user. Gives me my
description. I can just say that I want
to run that particular prompt. I can
insert whatever that name is. So we'll
just say that the name for this one is
going to be Sally. There we go. Now I
can hit enter and it's given me back a
prompt. Generate a fake user with the
name Sally. The user should have a
realistic email address and phone
number. So now when I hit enter on this,
it should generate that user for us. As
you can see, it gave me information for
a particular user. And now I can add
that user to the database. It even says,
"Would you like that to be added to your
data? Let me know if you want it
inserted." Yes. Insert. And now
hopefully it's going to call that
function with that brand new data.
Actually, it's directly just inserting
it into my JSON file, which is not quite
what I want. But obviously, if you were
working on this in a more real world
application, you wouldn't have access to
the database file inside of your
project. So, mine just read that
database file and inserted it there.
But, it could use that create user
function, and normally it would if
obviously that database file wasn't
there. Now, the next thing on the server
side before we start diving into the
client side is going to be sampling. And
this is kind of where we bridge between
the client and the server. So let's just
kind of minimize that down. We'll get a
brand new setup started. So when we
start working with this new tool, we can
actually see how the sampling works. And
to do this sampling, I'm actually going
to do it inside of a tool. So we can say
server.tool. And for this tool, I want
this to be called create random user.
And it'll just say create a random user
with fake data. There we go. And then
inside of here, we're going to pass
along all of the different additional
things. Same thing that we did inside of
our tool here. This one doesn't take
parameters. So we're going to skip that
step and we're just going to copy this
code over directly. So we can come into
here, paste down all that code and this
one is going to be called create random
user and in our particular case
obviously it is not a readon. It is not
destructive. It is not item potent and
it does access things that are external
because it's going to create a user in
our database. So we have essentially the
exact same prompts here. Now we have an
asynchronous function
just like that. And for this function I
need to get random fake user data. Now,
I could write some type of program to do
that for me, or I could say, you know
what, I just want the AI to give me fake
data for me. So, I'm going to use
sampling to tell the AI, I have a prompt
I want you to run, and then I want you
to send me that information. So, to do
that, we can call the
server.server.request.
And request allows me to send a request
directly to whatever the client is. So,
we can come in here with that request
and we can specify the method in this
case is going to be sampling/create
message. And this allows me to run a
prompt on the AI. Elicitation allows me
to ask the user for additional
information while sampling allows me to
essentially say, "Hey, run this prompt
on your AI." So, we want to run this
particular prompt. I can come in here
with params and we're going to say that
we have our messages and the messages
are just going to be an array of the
things we want to run. So, in our case,
we're going to have a role of user and
we can have some content just like that.
It's going to be a text prompt that we
want to run and it's going to have a
bunch of text inside of it. I'm just
going to copy this over, but as you can
see, it says generate fake user data.
The user should have a realistic name,
email address, and phone number. Return
this data as a JSON object with no other
text or formatter so it can be used with
JSON.parse. Lastly, we need to pass
along a max token. So, we'll just say
1024. We don't need a ton of tokens.
We'll just pass along a value here. And
then finally, at the very very end of
this request right here, we need to
essentially specify what the schema is.
In our case, this is a create message
results schema. Just like that. So, now
if we give that a save, you can see all
this is working. And we're going to get
some results back from this by calling
await. And if you look at this result
object, you can see here it's got a
bunch of information for all the
messages and stuff being passed through.
So now right below this, what I can do
is I can use this message. First of all,
I can say if my res.content
type is not equal to text, and it looks
like I'm getting an error, and that's
because I imported the wrong thing. This
is a message result schema. Essentially,
this is supposed to be specifying what
the result of this request should be. So
this is the result of my call out to
this. And now you can see that I have
that content. And if the type is not
equal to text, well, I'm just going to
return some type of error. So, I'm just
going to say, hey, I expected some text
because I expect some JSON formatted
text. So, I'm just going to say there's
failed to create the user. Then what we
can do below that is we can just wrap a
try catch to actually try to use this
fake user information. So, I can get my
fake user. I want to do a simple
JSON.parse and I want to take my
res.content.ext.
I'm going to make sure I trim that text
to get rid of any whites space. And I'm
also going to do a little bit of array
replacement because a lot of times when
I'm using these different AI tools, they
like to wrap things inside of markdown.
So I want to remove the markdown from
this if it has any. So I can come in
here and I can say I want to replace
anything that starts with that d-json or
anything that ends with those triple
tickbacks. Those would be the JSON way
of formatting code. We obviously don't
want that. So we're going to remove
those from us. So now that's going to
give us all the information for our fake
user. And to finish off our try catch
just to get rid of all our errors, we
can do that just like that. Now, if we
do have a failure, I want to just make
sure we come in here and we can return
that information. So, inside of our
catch, if we have some type of failure,
we'll return that. Now that we have our
fake user, you can see this is our user
information. What we want to do is we
want to get our ID by calling await
create user, passing it in our fake user
information. And then we can turn return
all of our information down here. And
this is just going to say type text. And
the text for this is going to be user.
Pass in that ID created successfully. So
the whole purpose of this function is
not really to be super useful because
you're probably not going to write code
like this very often, but it's to show
you how this sampling style interaction
works. So now we have a brand new tool.
We'll need to most likely restart our
server so it knows about that. Actually,
it already restarted for us. As you can
see, we have that watch set up. So it
looks like it was smart enough to do
that. So now we can come down here and I
can type in create. And you can see now
we have create random user. And if I run
this, it should hopefully respond to us
by saying, hey, do you want to run this
prompt? So we'll just click enter on
this. It's going to work. And now it
says, do you want to run this function?
That's fine. This is to run the tool.
And now you can see this message
essentially saying has issued a request
to make a language model call. Do you
want to allow it to make request?
Essentially, this is them confirming,
hey, your tool is asking you to run a
command. Is that okay? I'm going to say
that that's okay. I'm going to allow
that. And you can see here, sorry is
unable to generate random user data this
time. Please try again later or let me
know if you want to proceed differently.
So, it looks like the AI failed to
generate random data for some reason.
I'm going to maybe try swapping over to
a different model to see if maybe that's
going to work slightly better. So, we're
going to try running that exact same
command. See if we get a slightly
different response. For some reason, the
AI just didn't want to run randomly
generate data. You can see I'm
generating some data. Click on that
right here. Give it a quick second to
run and hopefully it should actually
work. Looks like we're still getting
errors. I believe the reason for that is
actually our server is throwing this
failed to generate user data
information. It's not from the actual
client. So something is going wrong in
this particular section with our
response. So let's just do a quick
console log of our response to see what
it looks like. And we'll just
json.stringify this so it's a little bit
easier for us to actually see everything
that's going on inside of here. Give
that a quick save. And now we'll just
try rerunning that exact same thing to
hopefully see what the actual response
information is getting back from us.
Looks like now it's not even trying to
call our server. Maybe it thinks our
server is down or something. So what I'm
going to do is I'm going to go over to
our inspect tool. Let's go ahead and
make sure that we close out of where
we're inspecting. restart it so we can
test to see if the tool is at least
working in the inspect tool cuz that's
generally the first place to start. So
let's just do a quick connection here.
We can go over to our tools. Now we have
the create random user tool. And if we
run this tool, you'll notice we now get
a sampling popup and it is specifically
asking, hey, I want to run this
particular message. Do we want to
approve this? Let's click on approve.
And now if we go back over to our tool,
we are going to get an error because it
doesn't actually return any data when
you do these fake samplings like this.
But at least we see that it should be
sampling particular data for us. So now
we just need to figure out why it's not
actually working inside of our
particular application. One thing that
we can maybe do is try restarting our
server. So we're just going to give it a
quick restart. I'm going to start a
brand new fresh chat. And I just want to
call that create random user to see if
hopefully with a new chat it'll work. So
we'll click continue on this. You can
see it still failed. But hopefully we
get an output over here. So after lots
of looking through the code and
debugging and trying to figure out what
the particular problem was, I realized
that the problem all along was that I
had this trim in the wrong place. I had
the trim after my JSON parse which
essentially meant I was trying to trim
an object which clearly wasn't working
and throwing an error. So now if I put
my trim right here that should fix my
particular problem because I just want
to trim after I do all my replacements.
So now let's try that out. Let's just
call that create random user function.
You can see here it's trying to run
create random user. It's going to send a
request to the AI to say hey give me
some fake data. It's going to then send
that back to our application up here. We
don't even need this console log
anymore. And inside of our application,
it's going to use that information to
try to create a user right here. And as
we can see, it created user 8
successfully. And if I look at my actual
user JSON file and I look at user 8, you
can see it created all this information
based on a prompt that was sent down to
the AI. You can see none of this
information was something that I typed
in myself. The AI randomly generated all
that information for me. So this is
essentially how we set up to use these
different AI prompts directly inside of
here by using a sampling to tell the AI,
hey, give me information from this
prompt and then you can use that
information in whatever tool, resource
or prompt that you want. So now with
that done, we can actually go ahead and
we can work on creating our client
because we've done everything that you
could do with a server. Obviously, you
could get more complex with more tools,
resources, and so on, but that's the
basics of how you set up all of these
different systems, and you just need to
integrate it in your own project. Now,
we can go ahead and we can create a
client.ts. ts. Obviously, you normally
wouldn't create these in the same
location, but this is just going to be
for testing purposes to show you how to
create a client. Doesn't matter where
you put them. We're just going to put
them in the same project so we can have
them side by side. So, we can come in
here and we can say we want to create a
new client that's coming from that model
context protocol library again. And we
pass in pretty much the same things we
did into the other thing. So, we have a
name. We're just going to call this our
test client video. Then, we come in here
with a version. We'll call this 1.0.0.
And then what we come in here is we
could specify all the different
capabilities that this thing has as
well. So we'll say capabilities. In our
case, we have sampling as our
capability. So we'll pass that in as an
empty object. And again, because that
sampling is something that happens on
the client, we tell the client, hey, I
want you to give me information. So it's
a client capability. So we'll create our
MCP client and we'll just call it MCP.
Then we need to specify our transport
protocol. And again, this is just like
on the server. We need to specify how
we're doing our transport. So, we're
going to use a standard IO client
transport this time. And in here, it's
going to look very similar to what we
have in this MCP file with command,
args, and so on. Because if you come in
here, you can see we have a command. So,
we can come in here and say that that's
going to be node. We have our args. In
our case, that's going to be
build/server.js.
Just like that. So, there we go. That is
our server now running. And we can also
come in here and we're going to say
standard error. And we're going to set
that to ignore. So if something throws
an error and prints to the error console
inside of our server, it won't show up
in our client. Now the only reason that
I'm doing that is because we're using
experimental features inside of node
specifically features for example here
where we do our import with a JSON file
and this actually prints a warning to
the standard error saying that there is
a experimental feature. So I'm just
making sure I ignore that so it doesn't
show up in our output because we're
going to be creating a client interface
application a CLI to interact with this.
And you can do this however you want.
You can create a website, CLI, it
doesn't matter. This is just the easiest
way for me to show you how to get
working with this system. So now let's
go ahead and we're going to create an
async function called main.
Then below that we'll just call main
just like that. So now we at least have
this async function. And what I want to
do is I want to first connect. So I can
say mcp.connect
and I want to connect to that particular
transport. So we can say transport just
like that. Then I can try to get my
tools resources and so on. So I can say
await mcp.list list tools and that's
going to give me all of my tool
information. So I can come in here, I
can say tools just like that and
probably you should wrap this inside of
like a promise.all. So we're going to do
that. We'll just put all of these inside
of here. So we'll do our list tools.
Then we'll come in here. Whoops. There
we go. MCP.list prompts.
We're going to do another one. There we
go. MCP.list
resources.
Then we're going to get MCP.list list
and we want to get our resource
templates this time and that's all the
stuff we want to get from the server
essentially give me all the things that
you can do and this is going to return
to us a bunch of data and we're just
going to extract that data out by
calling await right here so the first
thing we're going to get is our tools
then the next thing that we're going to
get is our prompts then we're going to
get our resources and finally our
resource templates so essentially this
is just all the things that our server
is capable of listed out for us so we
can use each of those individual things
now for creating our AI application it's
going to via CLI. And to make working
with the CLI a little bit easier, I'm
actually going to create a or I'm going
to use a library called
inquirer/prompts.
That's going to allow us to really
easily work with these prompts. So, we
can say npmi. I want to install that
inquirer/prompts. And I also want to get
as well. Oops. Make sure I spell that
correctly. And that's because we're
going to create av file. And this file
is going to have an API key for Gemini.
So, we can just come over here. We can
paste in that Gemini API key. And this
allows us to interact with the Gemini a
AI. The reason I chose Gemini is because
they have a free tier that you can use.
So you don't need to pay any money to
actually try to test this out. So now we
can come in here. We can go into our
client and we can make sure that we use
all this information, specifically that
inquirer prompt. So first I'm just going
to log a simple message that says you
are connected. So that'll show up inside
of the console. And then I'm going to
set up a simple while loop that's just
going to infinitely loop forever because
that's going to essentially be our main
menu where we select our different
options like execute a tool, a prompt,
and so on. And I'm going to come in here
and I'm just going to say const option
is equal to a wait. I'm going to call
select, which comes from that prompt,
and it essentially gives me a select
option inside of the command line. It's
going to pass in a message that says,
"What
would you
like to do?" And then we can pass it in
all of our different choices. And our
choices are going to be query, which
would just be calling our AI directly.
We can also call a tool directly, we can
call a resource directly, or we can call
a prompt directly. There we go. So now
that gives us all of our different
options. we can just set up a simple
switch statement on our option just like
this to call each one of those. So let's
start with one of the more basic cases
which is just going to be calling a tool
directly. So the first thing I want to
do is I want to essentially create a
select that's going to be for getting a
tool. So we can say we're going to
select a tool name. We can just say
select a tool and then I can list out my
choices which is going to be all of my
different tools. So I can take my tools
I can map through each tool and I can
just return information in that format.
So, I have here a name, which is my
tool.anotations.title.
Or if we don't have a title, we can just
use the tool.name. That's just going to
be the nonhuman readable version. Our
value is our tool.name.
Just like that. And then finally, a
description that will show up on the
element for us to make it a little bit
easier to read is just going to be our
tool.escription. There we go. So, that's
just all the information for our
different choices. And then we can just
come in here. We'll do a quick console
log that just says our tool name so we
can test to see if this is working. Now,
inside of our package JSON, we need to
create a script for this. So, we're just
going to say that this is going to be
our client
dev. And that'll just run tsx
source/client.ts.
There we go. So, now I can run npm
runclientdev.
And we should hopefully see our command
line tool pop up. You can see what would
you like to do? I have all my options.
I'm going to click tools. You can see my
two different tools popped up. They have
human readable names with descriptions.
And if I click on the tool, it prints
out what tool I selected because we
haven't implemented any of the rest of
the logic yet. So let's implement the
basic logic for that. Essentially, I'm
going to see if we can find a tool with
that particular name. If we can't, I'm
going to log out an error. And then if
we find a tool, we're going to call this
handle tool function. And the reason I'm
putting this in a function is just to
make it a little bit easier to work
with. So let's get that tool type
imported. Make sure this is an async
function. And now we can work on
actually calling this tool. Now the
really nice thing is we have an MCP.all
whole tool function that allows us to
pass in the name of our tool, which is
tool.name. And it allows us to pass in
our arguments, which is just going to be
some arguments that we're going to
create. And it allows us to call our
tool with that information. Now, in our
case, we need to get those arguments.
So, we're going to say const args, which
is just going to be a record of strings
that map to other strings. That's going
to be an empty object. There we go. And
then what I want to do is I want to go
and loop through
each one of my different arguments. So
I'll say value just like that of
object.ent entries. I'm going to pass in
my tool.input schema.properties
and we'll default that to an empty
object. So essentially what I'm doing is
if I have parameters, I'm going to be
looping through each one of those and
that'll give me a value as well as a key
for all the different information that
we need. Now if we look at this
properties object, you can essentially
see here it has an unknown type. It
doesn't really know what those
properties are supposed to be, but our
properties will specifically have a type
keyword on them that we can work off of.
So what I can do is I can say I want to
take an argument for the particular key.
So if my parameter name was age, this
would say age right here. And then what
I want to do is I want to say await
input which is another prompt that I can
get. And this will allow me to get input
user from the data. And I can say
whatever I want. For example, message
enter value for key just like that. And
then I can put what the type that's
going to be as well by saying value.ype.
And this is going to give me an error
because this is an unknown type. So,
we're just going to give that a little
bit of a cast right here. So, we're
going to say it's as type string. Just
like that. Make sure I get my parenthesy
in the correct location. That gets rid
of that error for me. And then I can
just put a colon right there. So, what
this little bit of code right here does
is essentially it's looping through
every single one of the parameters that
I defined on my server. It's asking the
user to give their input for the name of
the parameter and it tells them the type
so that way they know what the type of
that parameter is as well. Next, we can
come down here and we can get the result
for that which is just calling await on
that. And this is going to be
essentially a bunch of information that
comes directly from the server. So we
can just parse out that information and
log it out to the user. So we'll say
console.log res.content and we
specifically want to get the text
property of this. Now in our case again
this returns an unknown type which is a
little bit annoying. So we're just going
to get around that by casting that
directly because we don't really care
about the TypeScript errors. So we know
that this is going to return to us an
array and that array is going to have a
text which is a string inside of it.
Then we just want to make sure we get
the first property of that and we want
to get the text portion. So, this should
hopefully work by actually calling out
our tool and passing along whatever
information we want to it. So, let's go
ahead and try that out. We're going to
close our program and restart it. We're
going to go to tools and we're going to
try to create a user. And you can see
the first parameter is name. So, let's
pass in a name of Kyle. Email, we'll say
test.com. For the address, we'll just
put in some random numbers. And for the
phone number, let's just do some letters
for this one. So, it's really easy to
see it in our database. When we hit
enter, you can see it says user 9
successfully created. And if we look
inside of our data, you can see here we
have user 9 with all that data that I
passed in. So you can see that this
properly called the tool on our server,
sent across the information, saved it in
our database, and then returned to us a
response that told us the ID for that
user. So right there, we have a really
rudimentary implementation of how to
call tools. Let's go ahead and work on
actually calling out resources next. So
we can come in here. We can say that our
case for this one is going to be
resources. I'll just copy this down
because it's going to be pretty similar.
Resources. This is going to be our
resource URI. inside of our message, we
want to select a resource and then we
need to loop through our resources. Now,
I'm going to copy this over because it's
very similar to what we did with our
tools. So, let's come in here where our
tools are at. Get rid of all that code.
Paste this down. And essentially what
I'm doing is I'm looping through my
resources as well as my templates. And
I'm just mapping the name, URI, and
description. So, we have that
information. In the case of template is
a URI template, so they're slightly
different in formatting, but otherwise
it gives us the exact same information
and will return to us a URI. Then down
here, the same thing we did with tool,
we can do the exact same thing to get
the URI. So I can just copy that over
again. You can see we're looking at our
resources defined. If we have a URI for
that and getting that portion and if
it's a template, we're making sure we
get the URI template and we're mapping
that to this particular string. But of
course, it could be undefined. So if it
is null, we can just say that the
resource was not found. Otherwise, we
can come in here and we can call handle
resource and we can pass it in the URI
for the resource that we want to call on
our server and then we can create that
function. So let's come up here, rename
this to handle resource. This is going
to take in a URI and this is just going
to be a string. Now for this one, it's
going to be similar but slightly
different. We want to get the final URI
because our URI could have dynamic
parameters that we need to replace. So
that's going to start out by entering
equal to our URI and then we're going to
use this regular expression. All this
regular expression does, I know it looks
really complicated. Essentially just
matches everything that is text between
two brackets like this. So it'll just
match whatever the name is directly
inside those brackets. and we can use
that to actually replace that
information. So now we have our param
matches. That's just taking our URI and
getting all of essentially the matches
that we have. I want to loop through
each of those matches. So here instead
of this object entries, I can say that
we want to get a param match of our
param matches. So I'm just looping
through all of our different parameter
matches. And I specifically only want to
do this if our param matches is not
equal to null because we could have no
matches at all. So if we have no
matches, just skip that completely.
There we go. So now what I can do inside
of here is essentially replace this
content by saying that I want to do a
param value just like that. That's going
to call this and I essentially say I
want to enter a value for whatever my
parameter name is.
So I can say param name just like that.
And to get my param name
just like that I can essentially take my
param match and I just want to replace
all of these brackets with an empty
string. And I want to do the exact same
thing with the closing bracket as well,
just to essentially remove those
brackets from it. So it only shows the
name and not the brackets. So I'm
essentially asking them, enter me a
value for each of the URI parameters
that I get. And it's going to give me a
value. And all I want to do is I want to
take my final URI and I want to set it
equal to my final URI and I want to
replace there we go, replace my param
match with my param value. So I'm just
replacing that dynamic parameter with
whatever the user types in. Then down
here we can read a resource which is a
URL parameter that we have. This is
relatively straightforward. All I need
to do is pass it in my URI which is
going to be that final URI that I
create. Then it's going to give me back
some information. So if we go ahead and
we look at that information we can see
here our res.contents.
That contents is going to be an array
and this array is going to have some
information on it. Specifically some
text and of course it gives us a type of
unknown a little bit annoying. So we're
just going to cast that to a string
type. Then what we can do with that
particular text once we have it is we
can parse that as JSON because we
already know this is JSON. You could
read the mime type and do different
things with it. But we know this is
JSON. So we're just going to do a quick
JSON parse. And we're going to wrap that
just like that. And we'll come in here
with null and two to make it a little
bit of a prettier formatting. So now
we're just JSON parsing whatever the
contents get passed back to us is. Also,
I shouldn't be putting this pretty
formatting on here. Instead, I want to
take this object and then I want to
convert it back to a string. So I'll say
JSON.stringify.
And now we can come in here and we can
pretty print that however we want. Now
the reason I'm converting it from a
string to an object and back to a string
is because the data returned to us is
essentially the minimized version. If I
go ahead and I list my server and I try
to read that resource, we come in here
and I read out my users. You can see it
this minimized ugly version. I want to
print out a pretty version. So this just
allows me to print a pretty version of
my JSON instead of that ugly minified
version. And that is all we need to do
to create our resource. Essentially, we
just took our dynamic parameters,
replaced them with input from the user,
and then printed out the JSON to them.
So, let's go ahead and run that and see
if it works. We can come into resources.
I'm going to get all of the users, and
you can see it printed out all of the
users inside of here, just like that in
this pretty JSON format. Now, let's go
ahead and try to get an individual user,
and you can see we have to enter a value
for the user ID. Let's enter in three.
And now, we get all the information for
user 3 being printed out. So, now we
have tools and resources entirely done,
which is great. We might as well knock
out prompts next because that's
essentially the exact same thing as what
we've already been doing. I'm going to
paste this over here because essentially
we're just doing a select that selects a
prompt. We loop through our pro prompts
and map the name, value, and description
properly. We make sure our prompt
exists. And then if it does, we're going
to call this handle prompt function that
we're going to create. So we'll say
handle prompt. This is going to take in
a prompt which is of the prompt type.
There we go. And this is actually going
to be very similar to what we did with
tools because we need to get all of our
different arguments. So, I'm actually
just going to copy that. I'm going to
come into my handle prompt. I'm going to
paste that down. And we're going to fix
it up a little bit. The first main
difference is that inside of prompt, it
doesn't actually return our arguments as
an object, but instead it's an array.
So, we can say prompt.arguments. This is
going to be an array of values. We can
default it to an empty array, and then
that's going to here return to us each
individual argument. So, now we have our
argument right here, which has a bunch
of different information. So inside of
our args, we can just take the name of
our argument and we can ask the person
to enter a value for whatever that
argname is. And we're just ignoring the
description and everything for now. So
essentially all I'm doing is I'm looping
through all the arguments if there are
any and asking the user to enter
information for each one of those. Then
what I can do is I can come down here
and I can say mcp.getprompt.
Get prompt is going to take the name of
our prompt and it's going to take in our
arguments
just like that. So very similar to what
we did when we did our tool, we can say
that we want to get our response is
equal to awaiting that. And then we can
just render out whatever that response
is. And this response could be multiple
different messages. But the important
thing is we actually need to make sure
that we actually use this prompt inside
of our AI chatbot. So what I'm going to
do is I'm going to loop through each one
of my messages.
Response messages. Just like that. Then
what I want to do is I want to console
log awaiting calling a new function
called handle server message prompt. I'm
going to pass it in a message just like
that. So, we'll create a message or a
function with that exact name. There we
go. Now, this is mostly just a helper
function so that we can make this a
little bit easier to work with. It's
going to take in a message which is a
prompt message. There we go. If our
message content type is not equal to
text, then we're going to ignore it
completely because we're only supporting
text in this particular use case because
it doesn't make sense to render like
images and so on inside of our
application. We just don't really care
about that. Next, what we're going to do
is we're going to console log what our
message is. So, we're going to say
console.log message.content.ext.
This is so the user can see what the
prompt is. And then we can say, do we
want to run this or not? By using the
confirm function, this allows a user to
say yes or no if they want to use this.
So, we can just come in here with a
simple message that says, would you like
to run the above prompt? And we'll
default this to true, just like that.
because more often than not, they
probably want to run the prompt if
they're asking for it. If they don't
want to run it,
then we're just going to return.
Otherwise, we will actually run what
that prompt is. So, let me make sure
this is an async function. I also should
make sure I import confirm from the
correct location. So, I want to make
sure I import this function. There we
go. That's getting imported. And now
what I can do is I can actually call out
to our API or to our AI, sorry, that
we're going to be using. Now, I'm going
to be using an AI library to make this a
little bit easier because calling the AI
directly is a little bit of cumbersome.
So, we're going to npmi the package AI
as well as the package AI SDK Google
because we're specifically using Gemini
for this project. You can use whatever
AI you want though. It really does not
matter which one you use. So, now we can
come in here and we can say we want to
await calling a function called generate
text coming from AI. This is a really
easy function to use that is great when
you want a text response which is what
we are particularly working with. So, we
can call the Google function. And this
allows us to get a model from Google. In
our case, we're going to use Gemini 2.0
and we're going to use Flash because
that has a free tier for us. And then
our prompt right here is just going to
be whatever our messages. So
message.content.ext.
Now to make sure our Google is actually
hooked up to our proper API key, instead
of directly importing this up here,
instead we're going to import the
ability to create a Google generative
AI. And down here, we'll just say Google
equals create Google generative AI. And
then we'll pass in our API key, which is
our process.
API key. There we go. So now we actually
have our API key hooked up to that. So
it should hopefully make the request to
Gemini using that API key. You can
generate your own API key relatively
easily. It's it's completely free, at
least for the starter package, which is
really nice. So you don't need to pay
for it. And that gives us some text that
we have. And all we need to do is return
that to the user. So we can say const
text is equal to and we can return that
text down to the user. So, now let's go
ahead and we're going to test that out.
We're going to make sure our prompt is
working properly. We're going to go down
to prompts. I'm going to expand this a
little bit so it's easier to see. You
can see we only have one prompt to
choose, which is generate fake user.
I'll click enter on that. Let's give it
a name of Bob. There we go. And now you
can see it's saying generate a fake user
with the name Bob. The user should have
a realistic email address and phone
number. Would you like to run the above
prompt? Yeah, we're going to run that.
We can go through and run that. And of
course, we're getting an error. The
reason we're getting an error is because
I forgot to use. So all the way up here,
we're going to import
env/config.
That should set up our environment
variable. So now we should hopefully
have our environment variable hooked up,
which should make all of this work. So
let's do that prompt. We're going to
enter that same name, Bob. We're going
to run this command, and we should
hopefully get a response back to us
after it calls the API. As you can see,
it called the AI, and you can see it
gave us a bunch of information on a fake
user from this Gemini AI. Again, you can
hook it up whatever AI you want. Doesn't
really matter. This is just for
demonstration purposes. So we have
tools, resources, and prompts done. The
next thing is to work on query because
we can actually pass along tools and so
on to our actual AI to make it work with
those tools automatically just like it
does in GitHub copilot. So let's go
ahead and take a look at that. Inside of
our switch statement, we'll add another
case here.
This one is going to be for query. And
this is just going to call handle query.
Just like that. And we're going to
actually pass it in all of our tools so
it knows what tools we have access to.
Then we can go ahead and just create
that function
handle query. Tools is just going to be
a tool array. Now the first thing we're
going to do is we're just going to ask
the user enter whatever your query is
that you want to ask the AI. So this is
just what is your prompt. Then what
we're going to do is we're going to try
to get a bunch of information.
Specifically, we're going to get text as
well as tool results
by calling await generate
text. There we go. And this generate
text is going to take in the exact same
model we used down here. So let me just
copy that model over.
Paste that in. Of course, I failed on my
copying. There we go. Paste in what our
model is to get the exact same one. Our
prompt here is just going to be whatever
our query is. And then importantly,
we're going to have tools that we're
going to pass in. And this tools is just
going to be reformatted into the proper
format because it needs an object
instead of an array. A little bit
annoying, but we can go ahead and we can
do a little bit of a reduce to reduce
this down to what we want.
So we want to take whatever our current
object looks like and then we want to
add in our current tool.name as a new
value inside of our object. It should
take in a description which is our
tool.escription.
It should take in parameters
that is our JSON schema which is
unfortunate. It comes in JSON schema
already but we need to format it using
this JSON schema function from the AI
library to actually make it work. So
we'll say tool.input
schema. that essentially converts this
schema into the correct format to use
with this AI library. So just formatting
between different libraries. And then we
have an execute function. Essentially
what this allows us to do is it says,
hey, if I want to call a tool, what do I
do? And if we pass an execute function,
it'll just call this execute function
when it wants to execute a tool. So we
can come in here. This is going to be an
async function that takes in arguments
which are a record of string and any
because we don't know what the other
type is. And then we can actually return
some data from here. So all I want to do
is I just want to call
oops mcp.call tool and we want to call a
tool that has a name for our particular
tool which is tool.name
and our arguments
are going to be these args right here.
There we go. And then I can just return
whatever that result is. So we'll just
put a return right here. There we go. So
essentially I'm saying if you want to
call a tool just call this function
right here and return that information
to the user by calling tool and
returning whatever the result is. Now,
we also need to make sure that this
reduce function has a default value.
We're going to default it to an empty
object. And this specifically will be
typed as a tool set. Just like that,
because that is what this particular
object is. So, now we're getting not
only text, but we're also possibly
getting tool results back. The reason
for that is because if we told the AI,
hey, create me a brand new user with
this name, email, address, and phone
number. It'll say, oh, I have a tool for
that. I'm going to call that tool, call
this execute function, and I'm going to
give you what the result of that tool
is, which in our case is like user
created with ID7 and so on. So what we
need to do is we need to make sure we
log out the results for our text or our
tool results. So all the way down here,
we can just do a simple console.log. In
our case, we have our text. So if we
have text, let's log that out. If we
don't have any text, then we're going to
take our tool results. We want to get
the very first result from that because
it could be an array. And then what I
want to do is I want to get the result.
I want to get the content. I want to get
the first piece of the content. And then
I want to get the text. Now,
unfortunately, again, we're getting
unknown or never technically as the type
here because we're using dynamic
parameters here instead of hard-coded
parameters. The easiest way to do this
is just we're just going to ignore this
TypeScript error for now. So, we'll just
say expect error to ignore that error.
But, this is the proper format being
returned to us. I just don't want to
deal with the TypeScript for it.
Finally, we'll come in here and we can
just say no text generated. Just like
that. So, it's either going to return to
us our text, it's going to return to us
what the tool says, or it's going to
return just no text generated. And
that's all we need to do to handle this
query. So, let's see if that works as
well. I'm going to close out of this.
Restart our server. I'm going to use
query. And of course, we're getting an
error because I forgot to put in a wait
up here. There we go. So, now let's try
that again. Hopefully, we quick query.
It's going to ask us for our query. I'll
say, "How are you?" Should hopefully
just return to me some random AR
response. There you go. You can see it
returned a response. Let's do another
query. And I say, can you create
a random user with the name Kyle? Let's
see if it is able to do that. It might
ask me for other information. I cannot
fulfill this request. Create random user
does not accept any arguments. So I'm
going to come in here with another query
that says okay just create
a random user. So hopefully it'll call
it now. And of course we're getting
method not found. This is because we
have one last thing to implement which
is sampling. So before we dive into
sampling, I first want to make sure our
query works for creating a user with
specific information. So I could say
create a user with the name Kyle,
email test.com,
address random address, and phone random
phone. Let's see if this actually works.
You can see user 10 created. And if we
look at our user data, go down to number
10, it has that information. So it was
able to use that tool indirectly without
us telling us to use it directly, which
is really great and important. So now we
need to make sure we have sampling
working because that's the final step.
Now, unfortunately, sampling isn't as
easy to set up as everything else
because it's not built into the library
directly, but it's relatively easy to
do. We can come in here and we can say
set request handler. And what this
allows us to do is handle any request
that follows a specific schema. And we
have the ability to use the create
message request schema. So, we're
saying, hey, I'm going to expect a
request that is in this create message
request schema format. And when I get
that, I want to take in a request right
here, and I want to do something with
that. So essentially I know that this is
going to be the schema for my sampling
create message which is what's passed
down from our server when we call that
tool. Now if we take a look you'll
notice that we have an array of
messages. It could ask for multiple
messages. So what I'm going to do is I'm
going to just loop through all of our
messages. So I'm going to say for const
message of request.params
messages. For each one of these I want
to call that handle server message. I
want to pass it along my message and I
want to just put this inside of some
type of text just like that. So, this is
going to return to me a string or
undefined depending on if we ran this
request or not. And I want to put these
inside of an array. So, we'll say text
is equal to a string array. By default,
it's an empty array. And if my text is
not equal to null, then I just want to
take my text and I want to push in that
brand new text. So, essentially, every
time I call something successfully on
the AI, I want to push the result of
that into this array. Then, I can take
that response and send it back to the
MCP server so it can use it for its
particular use case. So I can come in
here. I can just say that the role is
going to be user. Doesn't really matter
too much. Our model here is going to be
the model that actually is running this.
So we can say Gemini 2.0
flash. There we go. We can come in here
with a stop reason why this thing is
stopping. We're going to call it end
turn. Essentially saying that this is
the end of the client's interactions and
it's back to the server. Then finally,
we can pass along the content which is
all the stuff we're sending to them
which is going to be text. and it's
specifically going to be a text that
just takes that text array and it's
going to join it on a new line
character. There we go. So now we're
just combining all that together. And if
I make sure I put a comma here, it
should hopefully be able to now
understand how sampling works. And we
should see that inside of our
application. So let's go ahead. I'm
going to use the tool for creating a
random user. And you can see immediately
I'm getting back a response which is
that sampling. It says generate fake
user data. The user should have a
realistic name, email address, blah blah
blah. Would you like to run the above
prompt? This is what's coming from the
server to sample our AI. I'll say yes to
run that. It's going to run that. Send
that information back to the actual
server. And now you can see the server
created user 11. And if we open up our
JSON, you can see we have a random user
number 11. It looks like the format was
not exactly what I expected, but it's
good enough for our particular use case.
Obviously, if this is a real world
application, I would have some type of
schema validation before I save things
in my database. But again, this is
purely for testing, just to show you the
interactions between these clients and
these servers. Now, if you enjoyed this
video and want to see more AI related
content, let me know down in the
comments. And also, if you want to see a
massive project that has tons of AI
integrations, I'll link that right over
here. It's over 10 hours long and has
tons of different AI features built into
it. With that said, thank you very much
for watching and have a good
Loading video analysis...