MCP got one sentence in the first post of this series, as one reason agents got practical, with no real explanation. This post is that explanation, plus a server I actually ran the protocol against.
This post continues my series on building agents. The previous one covered AI agent vs Zapier/Make.
The problem MCP solves
Every tool in this series so far was wired directly into one project. Fine once — until a second project wants the same tool, and you're copying the function, schema, and description, keeping both in sync by hand. MCP's fix: expose a tool once, as a server, and any MCP-compatible client can use it without new integration code for that pairing.
flowchart TB
subgraph "Without MCP: custom glue per pair"
App1[App 1] -->|custom code| ToolA[Tool A]
App1 -->|custom code| ToolB[Tool B]
App2[App 2] -->|custom code| ToolA
App2 -->|custom code| ToolB
end
subgraph "With MCP: one server, any client"
AppX[App 1] -->|MCP| ServerA[Tool A as an MCP server]
AppY[App 2] -->|MCP| ServerA
AppX -->|MCP| ServerB[Tool B as an MCP server]
AppY -->|MCP| ServerB
end
Why now
Anthropic introduced MCP in November 2024. OpenAI and Google both adopted it within months. In December 2025, Anthropic donated the protocol to the Agentic AI Foundation, a vendor-neutral Linux Foundation project co-founded with Block and OpenAI. A single company's protocol became shared industry infrastructure.
A real server
// mcp/weather-server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const weatherData: Record<string, { condition: string; temp_c: number }> = {
Dhaka: { condition: 'humid and cloudy', temp_c: 31 },
Chittagong: { condition: 'light rain', temp_c: 29 },
};
const server = new McpServer({ name: 'weather-server', version: '1.0.0' });
server.registerTool(
'get_weather',
{
title: 'Get Weather',
description: 'Get the current weather condition and temperature in Celsius for a named city.',
inputSchema: { city: z.string() },
},
async ({ city }) => {
const data = weatherData[city];
const result = data ? { city, ...data } : { error: `no weather data for ${city}` };
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
Same name, same description, same one field as the get_weather tool from the tool-calling post — inputSchema just takes Zod validators instead of hand-written JSON Schema, and the SDK converts it.
Testing it against the real protocol
// mcp/test-client.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
async function main() {
const transport = new StdioClientTransport({ command: 'npx', args: ['tsx', 'mcp/weather-server.ts'] });
const client = new Client({ name: 'test-client', version: '1.0.0' });
await client.connect(transport);
const tools = await client.listTools();
console.log(tools);
const result = await client.callTool({ name: 'get_weather', arguments: { city: 'Dhaka' } });
console.log(result);
await client.close();
}
main();
This spawns the server as a real subprocess and does the actual MCP handshake — not a mock.
The bug that took a rewrite
The first version ended with a bare top-level await server.connect(transport). Type-checked fine. The client connection closed immediately on run. The cause: this file's execution transforms it to a module format that doesn't support top-level await, crashing the process before the server could listen — nothing to do with MCP itself, but exactly where an MCP server's entrypoint sits. Wrapping it in an async main() fixed it completely.
Common mistakes
- Top-level await at the entrypoint — wrap it in an async function.
- Treating a vague MCP tool description as lower-stakes than an inline one — it's the same field, now shared across every project that connects.
- Connecting to a third-party server without reviewing what it exposes — a tool description is server-controlled text the model reads as an instruction.
- Building a server for a tool that already has one in a public registry.
Read the full version
I write each post in English and Bangla. The full version is on my blog: What Is MCP?
Have you built or connected to an MCP server yet?
Top comments (0)