What MCP Is — and What It Isn't
MCP is a protocol — a standardized communication contract between an AI host application and external capability providers. It is not a library, a cloud service, or a specific tool. Think of it as USB-C for AI: a single standard connector that lets any compliant tool plug into any compliant AI host, regardless of who built either side.
Before MCP, every AI integration was bespoke. You wanted Claude to read your database? Write a custom integration in the host app. You wanted Cursor to access your internal docs? Another bespoke solution. MCP collapses all of this into one protocol: build an MCP server once, and it works with every MCP-compatible host — Claude Desktop, Cursor, VS Code extensions, custom agents, and any application built with the Anthropic SDK.
Architecture: Three Roles
MCP defines three distinct roles in every interaction:
What an MCP Server Can Expose
MCP servers can expose three categories of capability to the AI:
- Tools — functions the AI can call to take actions or retrieve data (search the web, query a database, send an email)
- Resources — read-only data the AI can access (files, database rows, API responses, live documentation)
- Prompts — reusable prompt templates with arguments (useful for standardizing how tasks are requested across a team)
Building an MCP Server in TypeScript
The official MCP SDK for TypeScript is the easiest starting point. This example creates a server with one tool that fetches weather data:
# Initialize project
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
// src/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // 1. Create the server const server = new McpServer({ name: "weather-server", version: "1.0.0", }); // 2. Register a tool server.tool( "get_weather", "Get current weather for a city", { city: z.string().describe("City name") }, async ({ city }) => { // Replace with real API call const data = await fetch( `https://api.example.com/weather?q=${city}` ).then(r => r.json()); return { content: [{ type: "text", text: `${city}: ${data.temp}°C, ${data.condition}`, }], }; } ); // 3. Register a resource (read-only data) server.resource( "config", "app://config", async () => ({ contents: [{ uri: "app://config", text: JSON.stringify({ version: "1.0", env: "production" }), }], }) ); // 4. Connect via stdio transport const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP server running"); // stderr only — stdout is the protocol
# Run the server
npx tsx src/index.ts
Building an MCP Server in Python
The Python SDK uses a decorator pattern that keeps server code readable:
# Install
pip install mcp
# server.py from mcp.server.fastmcp import FastMCP import httpx mcp = FastMCP("weather-server") # Tool — AI can call this @mcp.tool() async def get_weather(city: str) -> str: """Get current weather for a city.""" async with httpx.AsyncClient() as client: r = await client.get(f"https://api.example.com/weather?q={city}") data = r.json() return f"{city}: {data['temp']}°C, {data['condition']}" # Resource — AI can read this @mcp.resource("docs://readme") def get_readme() -> str: """Project README.""" with open("README.md") as f: return f.read() # Prompt template @mcp.prompt() def analyze_weather(city: str) -> str: return f"Fetch weather for {city} and summarize in one sentence." if __name__ == "__main__": mcp.run() # stdio transport by default
Connecting to Claude Desktop
Add your server to Claude Desktop's config file. The location varies by OS:
# macOS ~/Library/Application Support/Claude/claude_desktop_config.json # Windows %APPDATA%\Claude\claude_desktop_config.json
// claude_desktop_config.json { "mcpServers": { "weather": { "command": "npx", "args": ["tsx", "/absolute/path/to/src/index.ts"] }, "weather-python": { "command": "python", "args": ["/absolute/path/to/server.py"], "env": { "API_KEY": "your-key-here" } } } }
Restart Claude Desktop after editing. The AI will now see your tools listed in the tool picker and can call them during any conversation. No further configuration needed — the AI discovers available tools automatically at connection time.
"MCP is plumbing. It doesn't tell the AI what to do — it gives the AI the pipes to reach things it couldn't reach before. What the AI does with those pipes is determined by Skills, prompts, and the task at hand."
MCP vs Skills: Where the Boundary Is
| Aspect | MCP | Skills |
|---|---|---|
| What it is | Protocol for external connectivity | Packaged AI workflows / slash commands |
| Layer | Infrastructure (how AI accesses tools) | Behavior (what the AI does) |
| Triggered by | AI autonomously, based on task needs | User explicitly (/command) or AI matching |
| Built with | TypeScript / Python server code | Markdown prompts, CLAUDE.md, config files |
| Use when | AI needs to reach external systems | You want repeatable, named workflows |
MCP and Skills are complementary, not competing. A Skill can invoke MCP tools internally. An MCP server doesn't know or care what Skill triggered the tool call. The right mental model: MCP is the toolbox, Skills are the instructions for how to use the tools on a specific job. For a deeper look at Skills and how to build them, see the companion article.