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?