MCP is basically extending the capabilities of the LLM/AI/Agents you have been using and customizing it, to include your side of the story into the native AI capabilities. But what is our side of story? Let’s take up a scenario. We are building a internal AI chatbot for our team, now the use case can be anything here and may be one day our design guys wanted to know more about the sales. Now details about sales can be brought from company’s internal database, but if our internal chatbot is just an wrapper around a LLM, then the LLM doesn’t have access to our company’s internal database, there comes the use of MCP. With the help of MCP, we can query and get the exact required data-piece from our internal database provide it as a context to the LLM and then the LLM can properly structure as per the requirement and present to the users (design guys).
The mechanism behind it
MCP was created by Anthropic (the company behind Claude). You can find the official spec here: https://modelcontextprotocol.io/specification/2025-06-18.
Before diving in, you need an AI client capable of using MCPs. Here’s a long list: https://modelcontextprotocol.io/clients. Some popular ones include Claude Code, Cursor, VS Code, Claude Desktop, Warp, and LangChain (essentially software with a built-in LLM).
So, how do LLMs and MCP servers actually talk? Anthropic built the flow on one of the most fundamental software building blocks: Standard In / Standard Out (STDIO).
Why STDIO?
- It’s supported by virtually every language and operating system.
- It’s the most portable communication method.
- Any process can read from stdin and write to stdout without special dependencies or network configuration.
On top of STDIO, MCP uses JSON-RPC for message formatting. You just send JSON objects as text lines—simple to implement in any language.
A typical MCP flow looks like this:
- The client launches an MCP server as a subprocess.
- The client sends JSON-RPC requests to the MCP server’s stdin.
- The server processes requests and writes JSON-RPC responses to stdout.
- The client reads and handles those responses.
Developing a MCP server
import { Server } from "@anthropic-ai/mcp-sdk/server";
import { StdioServerTransport } from "@anthropic-ai/mcp-sdk/transports/stdio";
import fetch from "node-fetch";
import { z } from "zod";
const server = new Server({ name: "weather", version: "1.0.0" });
server.tool(
"get_weather",
"Get weather for a city",
z.object({ city: z.string() }),
async ({ city }) => ({
content: [
{ type: "text", text: await (await fetch(`https://wttr.in/${city}?format=3`)).text() }
]
})
);
server.connect(new StdioServerTransport());
// Once connected in a client (e.g., Cursor), you can simply ask
// "What’s the weather in London?" and the IDE will fetch the right data.
// Without leaving your coding-flow, now you know what is the weather in your girlfriend's city
When building an MCP server, developers often use a validator like Zod. Why? Because it makes your tools opinionated—clearly defining required inputs. This helps MCP clients/agents/LLMs know exactly what parameters must be sent, making the interaction more reliable.
Different faces of MCP
Now the MCPs consist of different feature. Take it as different types of MCPs. What are those :
- Tools : Tools are meant to handed over to a LLM and then the LLM can decide which/whether to use the tools or not. Think of it like handing a toolbox to your general contractor who is working on your house: they could use one of the tools if they found something that needed it, or if not they just won’t use it.
- Resources : Resources on the other hand are meant to provide context to the LLM. This would be more like handing a paper full of information to the contractor and telling them to use it. You decided that you wanted to send it, and you decided what you wanted to put on it. It can accept parameters also, but while you are attaching the Resource as context, there you have to add the parameter manually, rather than LLM filling it up for you. So, we can safely say, parameters in Resources are not dynamic and kind of static.
- Tools vs Resources : Frankly, so far, Resources are way less used than tools. But I can see some occasions they potentially could be. Let’s say you have a Dropbox folder full of files that are indeed useful to a project, but you don’t want the LLM to be able to access them whenever it wants. You could use a resource to judicially add that context yourself when you want it to be there, but not go as far as to let the LLM do it whenever it wants with a tool. Another example will be fetching user details from your database, now we can build a MCP server which can call our database and retrieve information about a user. If we implement it through a tool, then we would make the tool accept param like a UserID or Username, and when we ask our LLM about that user, it calls to tool and get back the user details. If we implement the same in Resources way, then we need to add our whole DB as resource, and only when we need we will add that as context to our LLM and ask it about any user, and it will get back to us with that detail.
- Prompts : The idea for a MCP Prompt is to built a dynamic prompt or more like a prompt template. Imagine a scenario where different teams in an startup are using the same prompt but by tweaking just a little. For an example : a prompt like please review [this] against our company specifications. (where the company specifications is constant and changes over time). In this case, we can replace [this], by anything according to the team, Design team can send their designs, Development team can send their code, Product team can send their ideas. If we turn this into a MCP Prompt, each time they don’t have manually add Company Specs in their prompt and also not chase the latest version of it.
- Roots : This is just a way to expose a subset of your file system to your LLM. For Claude Desktop, it’d be like saying “hey Claude, you have access to any file in this directory” and it would then be able to operate on those files. This somewhat already works in things like Cursor or VS Code because obviously they get to work on the files that are open in the workspace they’re in – this is just a way of formalizing that process and giving it a name.
- Sampling : It is a way to perform a prompt through your LLM but that prompt will be given by your MCP Sampling and not you. Imagine a case where you have to invoke the same prompt again and again (may be for testing purpose), now keeping that prompt somewhere and copying and pasting is tiresome task. Rather, we will convert that prompt as a sample and invoke just the sampling, which will in turn call the LLM by itself with that prompt and get the reply of LLM back for us.
- Elicitation : Elicitation is a just a fancy word that your MCP server can ask follow up questions. Think of an a tool that the MCP server could call to ask you to get your full address so it could fill out a form for you or one where it could ask for your specific GitHub username before it generates a report on GitHub usage. There’s all sorts of reasons that a tool could need more information from the user. As of now we just have to hope that the LLM is smart enough to correlate this data for us, but this way we can just make it deterministic that correct information gets gathered as specified by the tool.
Do we really need a MCP here/Bad MCPs
Not all MCP servers are created equal. Some are:
- Redundant: thin wrappers on APIs or duplicating what LLMs already do well.
- Not useful: extending capabilities in ways that don’t add real value.
- Misfit: useful, but not aligned with your use case.
In these cases, remove them from your agent’s capabilities.
Worst case: a server is useful but also a bad actor. Example:
- An MCP server analyzes your database schema and suggests improvements.
- But behind the scenes, it dumps sensitive data to an external server.
Clearly, that’s dangerous.
Security of MCP
In worst case scenarios, you may encounter an MCP server that is useful but is also a bad actor at the same time. A good example of this might an MCP server that analyzes your database schema and suggests ways to the LLM to improve it. Sounds useful, right? But in the process, what if the MCP server just dumped all your sensitive data and sent it to a foreign server? Seems bad, right?
The idea of Remote MCP Server
With STDIO like transport, it works locally. It means without downloading them we will not able to hook them up with any client. Apart from that, it could be a security liability also, where we are letting other’s code running on our machine where the man running the code is an “AI Agent”. To solve issues like this, here comes : Remote MCP Server, with which an LLM can make MCP calls to a remote server (just like an API), but here the Transport changes a bit. We have two options here one is SSE (Server Sent Events) and Streamable HTTP. The SSE method is already redundant, so most of the current remote MCPs are based on the Streamable HTTP. And if you don’t want to create a MCP server by your own, then the easiest way I have found to let other people use your MCP is through MCP marketplace-tools like SmitheryAI.
That’s all the 101 of MCP for now, I will keep on adding about MCP developments here. Thanks for reading this through 🌈