MCP (Model Context Protocol) Tutorial: Complete Guide for Developers
Monday, Dec 23, 2024
If you’re a developer who has started working with AI and LLMs (Large Language Models), you’ve probably experienced this frustration: “How can I get this AI to access my data or tools?”
This is where Model Context Protocol (MCP) comes in as a game-changing solution. MCP is an open standard developed by Anthropic to seamlessly connect AI with various data sources and tools.
In this tutorial, I’ll share a complete guide about MCP—from basic concepts, architecture, to creating your own MCP server with code examples you can practice right away.
What Is Model Context Protocol (MCP)?
Model Context Protocol (MCP) is an open-source protocol that provides a standard way to connect AI applications with external data sources and tools. Think of MCP like a “USB port” for AI—a universal interface that allows various AI models to connect with different applications.
Simple Analogy
Before MCP existed, every developer had to create custom integrations for each AI + data source combination. It was like the old days when every phone had a different charger—super inconvenient!
MCP is like USB-C becoming the universal standard. With MCP:
- One protocol for all connections
- Reusable components that can be used across various AI clients
- Standardization that simplifies development and maintenance
Why Is MCP Important for Developers?
- Eliminates Boilerplate Code: No more writing repetitive integration code for each AI platform
- Interoperability: MCP servers you create can be used in Claude, VS Code, and other clients that support MCP
- Security First: MCP is designed with security as a priority—data stays on the server side, AI can only access through defined interfaces
- Scalability: Modular architecture makes scaling and maintenance easier
- Community Driven: As an open standard, there are many MCP servers already created by the community that you can use directly
MCP Architecture: Host, Client, and Server
To understand MCP well, you need to know its three main components:
1. MCP Host
Host is the main application users interact with AI through. Examples include:
- Claude Desktop
- Cursor IDE
- VS Code with MCP-supporting extensions
- Custom applications you build
The host is responsible for:
- Managing MCP client lifecycle
- Managing permissions and security
- Providing UI for users
2. MCP Client
Client is a component within the host that is responsible for:
- Maintaining connections to MCP servers (1 client : 1 server)
- Sending requests to servers
- Receiving and processing responses from servers
Each MCP client can only connect to one server, but a single host can have multiple clients connected to different servers.
3. MCP Server
Server is where the magic happens! Servers expose:
- Tools: Functions that AI can call to perform actions
- Resources: Data or content that AI can read
- Prompts: Prompt templates that can be used
Servers can run as:
- Local process: Running on the same machine as the host
- Remote service: Running in the cloud or a separate server
Architecture Diagram
┌─────────────────────────────────────────────────────┐
│ MCP HOST │
│ (Claude Desktop / Cursor / VS Code) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Client │ │ Client │ │ Client │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼─────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server │ │ Server │ │ Server │
│(GitHub) │ │ (DB) │ │(Custom) │
└─────────┘ └─────────┘ └─────────┘
How MCP Works: JSON-RPC, Tools, Resources, and Prompts
MCP uses JSON-RPC 2.0 as its communication format. This is a lightweight and well-established protocol in the industry.
Transport Layer
MCP supports two types of transport:
1. stdio (Standard Input/Output)
- Server runs as a subprocess
- Communication via stdin/stdout
- Suitable for local development and tools like Claude Desktop
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["path/to/server.js"]
}
}
}
2. HTTP with SSE (Server-Sent Events)
- Server runs as an HTTP service
- Uses SSE for streaming responses
- Suitable for remote deployment
Three Main MCP Primitives
1. Tools
Tools are functions that AI can call to perform specific actions. Examples:
search_database: Search data in a databasesend_email: Send an emailcreate_file: Create a new file
// Tool definition example
{
name: "get_weather",
description: "Get current weather for a city",
inputSchema: {
type: "object",
properties: {
city: {
type: "string",
description: "City name"
}
},
required: ["city"]
}
}
2. Resources
Resources provide data or content that AI can read. Unlike tools, resources are more read-only and contextual.
// Resource example
{
uri: "file:///docs/readme.md",
name: "Project README",
mimeType: "text/markdown"
}
Resources are useful for:
- Giving document context to AI
- Providing reference data
- Exposing file system or database content
3. Prompts
Prompts are templates that can be used to guide AI interactions. This helps standardize how users interact with specific capabilities.
// Prompt example
{
name: "code_review",
description: "Review code for best practices",
arguments: [
{
name: "language",
description: "Programming language",
required: true
}
]
}
MCP Communication Lifecycle
- Initialization: Client and server exchange capability and versioning info
- Discovery: Client requests the list of available tools, resources, and prompts
- Execution: Client calls tools or requests resources as needed
- Shutdown: Graceful termination when no longer needed
Environment Setup for MCP Development
Now let’s set up the environment to start developing your own MCP server.
Prerequisites
Make sure you have:
- Node.js 18+ or Python 3.10+
- Claude Desktop or another MCP client for testing
- Code editor (VS Code recommended)
Install MCP SDK
For TypeScript/JavaScript:
# Create new project
mkdir my-mcp-server
cd my-mcp-server
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Setup TypeScript config:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Update package.json:
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
}
}
Simple MCP Server Example
Let’s create a simple but functional MCP server—a server that can provide weather information.
Project Structure
my-mcp-server/
├── src/
│ └── index.ts
├── package.json
└── tsconfig.json
MCP Server Code
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Initialize server
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
// Define input schema
const GetWeatherSchema = z.object({
city: z.string().describe("City name to check weather"),
});
// List available tools
server.tool(
"get_weather",
"Get weather information for a specific city",
GetWeatherSchema.shape,
async ({ city }) => {
// Simulated weather data (in production, you would call a weather API)
const weatherData = {
"New York": { temp: 15, condition: "Partly cloudy", humidity: 65 },
"Los Angeles": { temp: 24, condition: "Sunny", humidity: 45 },
"Chicago": { temp: 8, condition: "Cloudy", humidity: 70 },
"Miami": { temp: 28, condition: "Sunny", humidity: 75 },
};
const weather = weatherData[city as keyof typeof weatherData];
if (!weather) {
return {
content: [
{
type: "text" as const,
text: `Sorry, weather data for ${city} is not available. Available cities: New York, Los Angeles, Chicago, Miami`,
},
],
};
}
return {
content: [
{
type: "text" as const,
text: `Weather in ${city}:\n- Temperature: ${weather.temp}°C\n- Condition: ${weather.condition}\n- Humidity: ${weather.humidity}%`,
},
],
};
}
);
// Add resource for documentation
server.resource(
"weather://docs",
"Weather server usage documentation",
async () => ({
contents: [
{
uri: "weather://docs",
mimeType: "text/plain",
text: `
Weather MCP Server
==================
This server provides weather information for cities.
Available tools:
- get_weather: Get weather information for a specific city
Supported cities:
- New York
- Los Angeles
- Chicago
- Miami
`.trim(),
},
],
})
);
// Add prompt template
server.prompt(
"weather_check",
"Template for checking weather",
[
{
name: "city",
description: "City name",
required: true,
},
],
async ({ city }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Please check the weather in ${city} and give recommendations for suitable outdoor activities.`,
},
},
],
})
);
// Run server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch(console.error);
Build and Test
# Build project
npm run build
# Test by running directly
npm run dev
Configure in Claude Desktop
To use the MCP server in Claude Desktop, add the following configuration:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/path/to/my-mcp-server/dist/index.js"]
}
}
}
Restart Claude Desktop, and now you can ask about the weather!
Popular MCP Servers and Tools
One of MCP’s strengths is its growing ecosystem. Here are some popular MCP servers you can use directly:
1. Filesystem Server
Server for local file system access. Useful for:
- Reading and writing files
- Browsing directories
- File manipulation
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/directory"
]
}
}
}
2. GitHub Server
Integration with GitHub for:
- Reading repositories
- Creating and managing issues
- Reviewing pull requests
3. Database Servers
Various MCP servers for databases:
- PostgreSQL: Query and manipulate PostgreSQL data
- SQLite: Access local SQLite database
- MongoDB: Integration with MongoDB
4. DataForSEO Server
For SEO and marketing needs:
- Keyword research
- SERP analysis
- Backlink analysis
5. Web Browser Server
Enables AI to:
- Read web page content
- Screenshot pages
- Interact with web elements
Finding MCP Servers
You can find MCP servers at:
- Official MCP Servers: github.com/modelcontextprotocol/servers
- Community Collections: Various lists on GitHub
- npm/PyPI: Search with keyword “mcp-server”
MCP Development Best Practices
From experience developing various MCP servers, here are best practices you should follow:
1. Security First
// ❌ Don't expose all files
server.tool("read_file", async ({ path }) => {
return fs.readFileSync(path);
});
// ✅ Validate and restrict access
const ALLOWED_DIRS = ["/app/data", "/app/public"];
server.tool("read_file", async ({ path }) => {
const resolvedPath = path.resolve(path);
const isAllowed = ALLOWED_DIRS.some((dir) =>
resolvedPath.startsWith(dir)
);
if (!isAllowed) {
throw new Error("Access denied");
}
return fs.readFileSync(resolvedPath);
});
2. Descriptive Tool Definitions
// ❌ Not descriptive enough
server.tool("search", async ({ q }) => {
// ...
});
// ✅ Clear and informative
server.tool(
"search_products",
"Search products in the catalog. Returns up to 10 results sorted by relevance. Supports filtering by category and price range.",
{
query: z.string().describe("Search keywords"),
category: z.string().optional().describe("Product category filter"),
minPrice: z.number().optional().describe("Minimum price in USD"),
maxPrice: z.number().optional().describe("Maximum price in USD"),
},
async (params) => {
// implementation
}
);
3. Good Error Handling
server.tool("api_call", async ({ endpoint }) => {
try {
const response = await fetch(endpoint);
if (!response.ok) {
return {
content: [{
type: "text",
text: `API error: ${response.status} - ${response.statusText}`,
}],
isError: true,
};
}
const data = await response.json();
return {
content: [{
type: "text",
text: JSON.stringify(data, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to call API: ${error.message}`,
}],
isError: true,
};
}
});
4. Logging and Debugging
// Use stderr for logging (stdout reserved for MCP protocol)
console.error("[INFO] Server started");
console.error("[DEBUG] Processing request:", requestId);
console.error("[ERROR] Failed to connect:", error.message);
5. Rate Limiting and Caching
import { LRUCache } from "lru-cache";
const cache = new LRUCache<string, any>({
max: 100,
ttl: 1000 * 60 * 5, // 5 minutes
});
server.tool("expensive_operation", async ({ query }) => {
const cacheKey = `query:${query}`;
// Check cache first
const cached = cache.get(cacheKey);
if (cached) {
console.error("[CACHE] Hit for:", query);
return cached;
}
// Perform expensive operation
const result = await performExpensiveOperation(query);
// Store in cache
cache.set(cacheKey, result);
return result;
});
6. Graceful Shutdown
process.on("SIGINT", async () => {
console.error("[INFO] Shutting down gracefully...");
// Cleanup connections, save state, etc.
await cleanup();
process.exit(0);
});
process.on("SIGTERM", async () => {
console.error("[INFO] Received SIGTERM, shutting down...");
await cleanup();
process.exit(0);
});
Testing MCP Servers
Testing is an important part of development. Here’s how to test MCP servers:
1. Manual Testing with MCP Inspector
# Install MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
MCP Inspector provides a UI for:
- Viewing lists of tools, resources, and prompts
- Running tools with custom input
- Debugging responses
2. Automated Testing
// test/server.test.ts
import { describe, it, expect } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
describe("Weather Server", () => {
it("should return weather for valid city", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
// Setup client and server...
const result = await client.callTool({
name: "get_weather",
arguments: { city: "New York" },
});
expect(result.content[0].text).toContain("New York");
expect(result.content[0].text).toContain("°C");
});
});
MCP with Python
If you’re more comfortable with Python, MCP also supports the Python SDK:
# Install: pip install mcp
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
server = Server("weather-server")
@server.tool()
async def get_weather(city: str) -> list[TextContent]:
"""Get weather information for a city."""
weather_data = {
"New York": {"temp": 15, "condition": "Cloudy"},
"Los Angeles": {"temp": 24, "condition": "Sunny"},
}
weather = weather_data.get(city)
if not weather:
return [TextContent(
type="text",
text=f"Weather data for {city} not available"
)]
return [TextContent(
type="text",
text=f"Weather in {city}: {weather['temp']}°C, {weather['condition']}"
)]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Debugging Tips
1. Check Logs
MCP logs can be found at:
- Claude Desktop:
~/Library/Logs/Claude/(macOS) - Custom apps: Depends on logging implementation
2. Common Issues
Server doesn’t appear in Claude:
- Make sure the path to the server is correct
- Check JSON config syntax
- Restart Claude Desktop
Tool not being called:
- Make sure the tool description is clear enough
- Check if AI understands when to use the tool
“Connection refused” error:
- Server might have crashed on startup
- Check stderr output for error messages
3. Development Mode
Use tsx for development with hot reload:
# Install tsx globally
npm install -g tsx
# Run with watch mode
tsx watch src/index.ts
Real-World Use Cases
1. Personal Knowledge Base
Create an MCP server that connects to Notion or Obsidian for:
- Searching notes
- Creating new notes
- Updating existing notes
2. Development Workflow
MCP server for:
- Running tests
- Deploying applications
- Managing Git operations
3. Business Intelligence
Connect AI with:
- Company databases
- Analytics platforms
- Reporting tools
4. Content Creation
Like what I use on this blog:
- Keyword research with DataForSEO
- Image generation
- Content publishing
The Future of MCP
MCP is still very new (launched November 2024), but adoption is very fast:
- IDE Integration: Cursor, VS Code, and other IDEs are starting to support MCP
- Enterprise Adoption: Large companies are starting to develop internal MCP servers
- Community Growth: Thousands of MCP servers have been created by the community
- Standardization: MCP is in the process of joining the Linux Foundation
As a developer, now is the right time to:
- Learn and experiment with MCP
- Contribute to the ecosystem by creating MCP servers
- Integrate MCP into your development workflow
Conclusion and Next Steps
MCP (Model Context Protocol) is a breakthrough in how we integrate AI with tools and data. By understanding the Host-Client-Server concept, as well as the Tools-Resources-Prompts primitives, you now have a strong foundation to start developing your own MCP servers.
Next Steps
- Install Claude Desktop and try some existing MCP servers
- Create a simple MCP server following the examples in this tutorial
- Explore MCP servers from the community on GitHub
- Join the MCP community for discussions and sharing experiences
Additional Resources
Happy experimenting with MCP! If you have questions or want to discuss, feel free to reach out. Happy coding! 🚀