How Does The Model Context Protocol Work?
Now we understand a little bit about what MCP might be used for, and why it's interesting, let's actually look at how it works.
To understand how it works we're going to have to understand a diagram that Anthropic posted, which shows the architecture of MCP:
Let's explain it step by step.
MCP Hosts and Clients
The MCP host is the program that's going to access the MCP servers. This might be Claude Desktop, Cursor, Windsurf, or any other application that supports MCP.
This host probably uses an LLM of some kind. That LLM will be able to call tools that are defined in the MCP server.
On this host, you're going to run multiple clients - each client will maintain a relationship to a single MCP server. When the host starts up - i.e. when you start Cursor - each client will connect to an MCP server.
So, you can have one host with multiple clients, each interacting with a different server.
You can also build your own clients - and we may get to that in a future video. For now, check out MCP's documentation.
The MCP Server
The MCP server is the server that's going to be running the tools that the host wants to call.
This server could be running locally, or it could be running on a remote server.
One thing to note is that the server could be a completely different language to the host. Just like the front end and back end of an application can be in different languages, the host and server can too.
The Transport and the Protocol
The client connects to its server using a transport. This transport is responsible for sending messages between the client and the server.
There are currently two supported transports. You can communicate via stdio
- in other words, via the terminal. Or you can communicate through HTTP via server-sent events. This is useful if you want to run your server on a remote machine. We'll cover both of these later.
But what do the messages sent across this transport actually look like? Well, this is the protocol.
It's a set of JSON shapes that the client and server can send to each other, based on JSON-RPC 2.0. They look like this:
type Request = {jsonrpc: "2.0";id: number | string;method: string;params?: object;};type Response = {jsonrpc: "2.0";id: number | string;result?: object;error?: {code: number;message: string;data?: unknown;};};
Requests contain a method
that the server should perform, and the params
that should be passed to that method.
Responses contain a result
if the method was successful, or an error
if it wasn't.
They each have an id
which is used to match up the request with the response, and a jsonrpc
field which tracks the version of the underlying jsonrpc
protocol.
An Example Communication
To drill this in, let's look at an example. Let's imagine we have a tool called createGitHubIssue
which creates an issue on GitHub.
We'll skip over some of the preliminary steps - the MCP client initializing the connection - and start with the client sending a request to the server.
1. List The Tools
// Client sends...{"jsonrpc": "2.0","id": 1,"method": "tools/list"}
This is a request to list all of the tools that are available on the server. The server will respond with a list of tools:
// ...server sends back:{"jsonrpc": "2.0","id": 1,"tools": [{"name": "createGitHubIssue","description": "Create a GitHub issue","inputSchema": {"type": "object","properties": {"title": { "type": "string" },"body": { "type": "string" },"labels": {"type": "array","items": { "type": "string" }}}}}]}
Note how the server responds with the name of the tool, a description, and the required inputs for the tool. The input is a JSON schema describing a title, body and labels.
2. Call The Tool
The client decides to call the tool, and sends a request:
// Client sends...{"jsonrpc": "2.0","id": 2,"method": "tools/call","params": {"name": "createGitHubIssue","arguments": {"title": "My Issue","body": "This is the body of my issue","labels": ["bug"]}}}
This describes the name
of the tool to be called, and the arguments
it should be passed.
The server responds with the result of the tool:
// ...server sends back:{"jsonrpc": "2.0","id": 2,"content": [{"type": "text","text": "Issue 143 created successfully!"}],"isError": false}
It returns an array of content parts. These content parts can be text
- as shown above - or image
(for images) and resource
for binary data. It also returns an optional isError
flag.
If the tool call had errored, the returned object would look slightly different:
// ...if error, server sends back:{"jsonrpc": "2.0","id": 2,"content": [{"type": "text","text": "Error creating issue: Unauthorized"}],"isError": true}
This tells the client that the tool call was unsuccessful.
Conclusion
This is the basic structure of how MCP works. We have a host that runs clients, which connect to servers. The clients and servers communicate using a transport and a protocol.