The Model Context Protocol (MCP) is an open standard created by Anthropic that defines how AI models communicate with external tools, data sources, and services. Think of it as the "USB-C of AI" - a universal connector that lets any AI agent talk to any tool through a standardized interface.
Since its launch, MCP has crossed 97 million monthly SDK downloads and has been adopted by every major AI provider: Anthropic, OpenAI, Google, Microsoft, and Amazon. In this guide, we'll explore everything you need to know to build with MCP.
Why MCP Exists
Before MCP, every AI application had to build custom integrations for each tool it wanted to use. Want your AI to read files? Write custom code. Query a database? More custom code. Post to Slack? Yet another integration.
This created an N×M problem: N AI applications each needed M custom integrations, leading to duplicated effort and fragmented ecosystems.
MCP solves this with a single protocol that any AI client can use to communicate with any MCP server:
Core Concepts
MCP has three fundamental primitives:
1. Tools
Tools are functions that the AI can call. They represent actions like "create a file", "query a database", or "send a message". Each tool has a name, description, and a JSON Schema for its parameters.
{ name: "create_issue", description: "Create a new GitHub issue", inputSchema: { type: "object", properties: { title: { type: "string", description: "Issue title" }, body: { type: "string", description: "Issue body" }, repo: { type: "string", description: "Repository name" } }, required: ["title", "repo"] } }
2. Resources
Resources are data that the AI can read. They represent files, database records, API responses, or any other data source. Resources are identified by URIs.
{ uri: "file:///Users/dev/project/README.md", name: "Project README", mimeType: "text/markdown" }
3. Prompts
Prompts are reusable templates that help structure interactions. They can include dynamic parameters and are useful for standardizing common workflows.
{ name: "code_review", description: "Review code changes for quality and security", arguments: [ { name: "diff", description: "The code diff to review", required: true } ] }
Architecture
MCP follows a client-server architecture:
- Host: The AI application (Claude Desktop, Cursor, your custom app)
- Client: Maintains a 1:1 connection with an MCP server
- Server: Exposes tools, resources, and prompts to the client
- Transport: Communication happens via JSON-RPC 2.0 over stdio (local) or Server-Sent Events (remote)
Building an MCP Server
Let's build a practical MCP server that interacts with a todo list stored in a JSON file.
Setup
mkdir mcp-todo-server && cd mcp-todo-server npm init -y npm install @modelcontextprotocol/sdk zod
Server Implementation
// src/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import fs from "fs"; const TODO_FILE = "./todos.json"; function readTodos(): { id: number; text: string; done: boolean }[] { if (!fs.existsSync(TODO_FILE)) return []; return JSON.parse(fs.readFileSync(TODO_FILE, "utf-8")); } function writeTodos(todos: { id: number; text: string; done: boolean }[]) { fs.writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2)); } const server = new McpServer({ name: "todo-server", version: "1.0.0", }); // Tool: Add a todo server.tool( "add_todo", "Add a new todo item", { text: z.string().describe("The todo text") }, async ({ text }) => { const todos = readTodos(); const newTodo = { id: Date.now(), text, done: false }; todos.push(newTodo); writeTodos(todos); return { content: [{ type: "text", text: `Added: "${text}"` }] }; } ); // Tool: List todos server.tool( "list_todos", "List all todo items", {}, async () => { const todos = readTodos(); const list = todos .map((t) => `${t.done ? "✅" : "⬜"} [${t.id}] ${t.text}`) .join("\n"); return { content: [{ type: "text", text: list || "No todos yet." }] }; } ); // Tool: Complete a todo server.tool( "complete_todo", "Mark a todo as completed", { id: z.number().describe("The todo ID to complete") }, async ({ id }) => { const todos = readTodos(); const todo = todos.find((t) => t.id === id); if (!todo) return { content: [{ type: "text", text: "Todo not found." }] }; todo.done = true; writeTodos(todos); return { content: [{ type: "text", text: `Completed: "${todo.text}"` }] }; } ); // Resource: Current todos as a readable resource server.resource( "todos://list", "Current todo list", async () => ({ contents: [{ uri: "todos://list", mimeType: "application/json", text: JSON.stringify(readTodos(), null, 2), }], }) ); // Start the server const transport = new StdioServerTransport(); await server.connect(transport);
Configuration
To use this server with Claude Desktop, add it to your config:
{ "mcpServers": { "todo": { "command": "npx", "args": ["tsx", "/path/to/mcp-todo-server/src/index.ts"] } } }
Building an MCP Client
You can also build a custom client that connects to any MCP server:
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const transport = new StdioClientTransport({ command: "npx", args: ["tsx", "./src/index.ts"], }); const client = new Client({ name: "my-client", version: "1.0.0" }); await client.connect(transport); // List available tools const { tools } = await client.listTools(); console.log("Available tools:", tools.map((t) => t.name)); // Call a tool const result = await client.callTool({ name: "add_todo", arguments: { text: "Write MCP blog post" }, }); console.log(result);
Popular MCP Servers
The MCP ecosystem has grown rapidly. Here are some of the most popular servers:
| Server | Description | Use Case |
|---|---|---|
| GitHub | Create issues, PRs, manage repos | Development workflows |
| Slack | Send messages, manage channels | Team communication |
| PostgreSQL | Query and manage databases | Data access |
| Filesystem | Read, write, and search files | Local development |
| Puppeteer | Browser automation and scraping | Web testing |
| Sentry | Error monitoring and debugging | Production support |
| Supabase | Database, auth, storage | Backend operations |
MCP vs A2A (Agent-to-Agent)
While MCP handles agent-to-tool communication, Google's A2A (Agent-to-Agent) protocol handles agent-to-agent communication. They are complementary:
- MCP: How an AI agent uses tools (vertical integration)
- A2A: How AI agents collaborate with each other (horizontal integration)
Best Practices
- Keep servers focused: One server per domain (GitHub server, Slack server, etc.). Don't build monolithic servers.
- Validate inputs with Zod: Always validate tool inputs with proper schemas.
- Handle errors gracefully: Return meaningful error messages, not stack traces.
- Use resources for read-only data: If the AI only needs to read data, expose it as a resource rather than a tool.
- Add proper descriptions: Good tool and parameter descriptions help the AI understand when and how to use each tool.
- Test with the MCP Inspector: Use
npx @modelcontextprotocol/inspectorto test your server interactively.
Getting Started
# Create a new MCP server from template npx @modelcontextprotocol/create-server my-server # Test with the MCP Inspector npx @modelcontextprotocol/inspector npx tsx ./src/index.ts
Conclusion
MCP has become the de facto standard for AI tool use in 2026. Whether you're building custom AI agents, extending Claude Code, or creating integrations for existing platforms, understanding MCP is essential. The protocol is simple enough to learn in an afternoon but powerful enough to build production-grade AI workflows.
Next steps:
- Explore the MCP Specification
- Browse existing MCP servers for inspiration
- Build your first server with the TypeScript or Python SDK
- Test it with Claude Desktop or the MCP Inspector