- Published on
Build Your Own MCP Server: Protocol Internals, Hands-On Guide, and 100+ Ecosystem Deep Dive
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. Dissecting MCP Protocol Internals
- 2. Building an MCP Server with Python FastMCP (Hands-On 1)
- 3. Building an MCP Server with TypeScript SDK (Hands-On 2)
- 4. Testing and Debugging MCP Servers
- 5. Deploying MCP Servers
- 6. 100+ MCP Server Ecosystem Roundup
- 7. 10 Ideas for Your Own MCP Server
- 8. MCP Security Best Practices
- 9. The Future of MCP: 2026 Outlook
- 10. Quiz
- 11. References
1. Dissecting MCP Protocol Internals
MCP (Model Context Protocol) is a standard protocol for connecting AI models with external systems. In this post, we go beyond the introductory overview and deeply examine the internal workings of the protocol, then walk through building MCP servers from scratch.
1-1. JSON-RPC 2.0 Message Structure
All MCP communication follows the JSON-RPC 2.0 specification. Messages fall into three types.
Request: Sent when a client or server asks the other side to perform an operation.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "Seoul"
}
}
}
Response: Returns the result for a request.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Seoul: 15 degrees, partly cloudy"
}
]
}
}
Notification: A one-way message that expects no response. Note the absence of an id field.
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Error Response: Returned when request processing fails.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params: city is required"
}
}
Key error codes:
| Code | Meaning | Description |
|---|---|---|
| -32700 | Parse Error | Failed to parse JSON |
| -32600 | Invalid Request | Malformed request |
| -32601 | Method Not Found | Non-existent method |
| -32602 | Invalid Params | Invalid parameters |
| -32603 | Internal Error | Server internal error |
1-2. Connection Lifecycle
An MCP connection goes through clearly defined phases:
Phase 1 - Initialize
The client sends an initialize request, exchanging protocol versions and capabilities with the server.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "Claude Desktop",
"version": "1.5.0"
}
}
}
The server responds:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
}
},
"serverInfo": {
"name": "Weather MCP Server",
"version": "1.0.0"
}
}
}
Phase 2 - Initialized
The client sends a notifications/initialized notification to declare initialization is complete.
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Phase 3 - Normal Operation
Client and server can now freely exchange messages:
- Client requests available tools via
tools/list - Client invokes a specific tool via
tools/call - Client reads resources via
resources/read - Server notifies about tool list changes via
notifications/tools/list_changed
Phase 4 - Shutdown
When the client terminates the connection, the transport layer cleans up.
1-3. Transport Methods
MCP supports two transport methods.
stdio (Standard Input/Output)
The most common method, running MCP server processes locally.
- Client spawns the server process
- Sends requests via stdin, receives responses via stdout
- Each message is delimited by newlines
- Debug logs go to stderr
- Used primarily with Claude Desktop and Claude Code
Client Server Process
| |
|--- spawn process -------->|
| |
|--- stdin: JSON-RPC ------>|
|<-- stdout: JSON-RPC -----|
| |
|--- stdin: JSON-RPC ------>|
|<-- stdout: JSON-RPC -----|
| |
|--- kill process --------->|
Streamable HTTP
Used for connecting to remote servers. The March 2025 spec update replaced the older HTTP+SSE approach.
- Requests sent via HTTP POST
- Server streams responses via SSE (Server-Sent Events)
- Suitable for remote deployment and multi-tenant scenarios
- Uses
Mcp-Session-Idheader for session management
Client HTTP Server
| |
|--- POST /mcp ----------->|
| (initialize) |
|<-- SSE: result -----------|
|<-- Mcp-Session-Id --------|
| |
|--- POST /mcp ----------->|
| (tools/call) |
|<-- SSE: result -----------|
| |
|--- DELETE /mcp ---------->|
| (session close) |
Which Transport to Choose?
| Criteria | stdio | Streamable HTTP |
|---|---|---|
| Use Case | Local dev, desktop apps | Remote servers, cloud deployment |
| Setup Difficulty | Very easy | Moderate |
| Security | Local process isolation | Requires OAuth 2.0, TLS |
| Multi-user | Not possible | Possible |
| Network | Not required | Required |
| Example | Claude Desktop | Team-shared MCP server |
1-4. Three Core Primitives
MCP servers expose three types of capabilities to AI.
Resources - Read-only Data
Provides data identified by URIs. This includes file contents, DB schemas, API responses, and more.
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/schema.sql"
}
}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"contents": [
{
"uri": "file:///project/schema.sql",
"mimeType": "text/plain",
"text": "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));"
}
]
}
}
Characteristics:
- Application uses as context (user-controlled)
- URI-based identification (file://, db://, api://, etc.)
- Can subscribe for change notifications
- Supports text or binary (base64) content
Tools - Executable Functions
Defines functions that AI models can invoke. Input parameters are specified using JSON Schema.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/list"
}
Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
]
}
}
Characteristics:
- Model autonomously invokes (model-controlled)
- JSON Schema validates parameter types
- May have side effects
- May require user confirmation (security)
Prompts - Reusable Prompt Templates
Pre-defines frequently used prompt patterns.
{
"jsonrpc": "2.0",
"id": 4,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"language": "python"
}
}
}
Response:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"description": "Code review prompt for Python",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review the following Python code for best practices, bugs, and performance issues."
}
}
]
}
}
1-5. Capability Negotiation Details
During initialization, client and server exchange their supported features. Servers only need to declare the primitives they intend to provide.
Server Capabilities Example (Tools only):
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
This server only provides Tools and can notify when the tool list changes. It does not provide Resources or Prompts.
Full-featured Server:
{
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true },
"logging": {}
}
}
Client Capabilities Example:
{
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
}
}
roots: Client provides information about working directories/projectssampling: Server can reverse-invoke the client's LLM
1-6. Sampling: Reverse LLM Invocation from Server
Sampling is a unique MCP feature that allows servers to send requests back to the client's AI model.
{
"jsonrpc": "2.0",
"id": 10,
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Summarize this error log: Connection timeout after 30s..."
}
}
],
"maxTokens": 500,
"modelPreferences": {
"hints": [{ "name": "claude-sonnet-4-20250514" }]
}
}
}
Use cases:
- Server asks AI to analyze collected log data
- Server asks AI to summarize complex data
- Intermediate judgment requests in agent workflows
Note that Sampling requires user confirmation for security. The client enforces human-in-the-loop control.
2. Building an MCP Server with Python FastMCP (Hands-On 1)
FastMCP is the easiest way to build MCP servers in Python. It is also the official high-level API for the MCP Python SDK.
2-1. Weather MCP Server (Simplest Example)
Installation:
pip install fastmcp
weather_server.py:
from fastmcp import FastMCP
# Create MCP server instance
mcp = FastMCP("Weather Server")
@mcp.tool()
def get_weather(city: str) -> str:
"""Get current weather for a city.
Args:
city: Name of the city (e.g., Seoul, Tokyo, New York)
"""
# In production, you would call a weather API
weather_data = {
"Seoul": "15 degrees, partly cloudy",
"Tokyo": "18 degrees, sunny",
"New York": "10 degrees, rainy",
"London": "8 degrees, foggy",
}
result = weather_data.get(city, f"Weather data not available for {city}")
return f"Weather in {city}: {result}"
@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
"""Get weather forecast for a city.
Args:
city: Name of the city
days: Number of days to forecast (1-7)
"""
if days < 1 or days > 7:
return "Days must be between 1 and 7"
return f"{days}-day forecast for {city}: Mostly sunny with temperatures 10-20C"
@mcp.resource("weather://cities")
def list_cities() -> str:
"""List all supported cities."""
cities = ["Seoul", "Tokyo", "New York", "London", "Paris"]
return "\n".join(cities)
if __name__ == "__main__":
mcp.run()
Running the server:
# Run directly (stdio mode)
python weather_server.py
# Or use FastMCP CLI
fastmcp run weather_server.py
# Test with MCP Inspector
fastmcp dev weather_server.py
Claude Desktop integration (claude_desktop_config.json):
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/path/to/weather_server.py"]
}
}
}
Claude Code integration (.mcp.json):
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["weather_server.py"]
}
}
}
2-2. Database MCP Server (Production-Ready)
Let's build a database MCP server, one of the most commonly used patterns in practice.
import sqlite3
import json
from fastmcp import FastMCP
mcp = FastMCP("Database Server")
DB_PATH = "myapp.db"
def get_connection():
"""Get database connection."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
@mcp.resource("db://schema")
def get_schema() -> str:
"""Get the database schema information."""
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT sql FROM sqlite_master WHERE type='table' ORDER BY name"
)
tables = cursor.fetchall()
conn.close()
schema_lines = []
for table in tables:
if table[0]:
schema_lines.append(table[0])
return "\n\n".join(schema_lines)
@mcp.resource("db://tables")
def list_tables() -> str:
"""List all tables in the database."""
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
)
tables = [row[0] for row in cursor.fetchall()]
conn.close()
return json.dumps(tables, indent=2)
@mcp.tool()
def query_database(sql: str) -> str:
"""Execute a read-only SQL query and return results.
Args:
sql: SQL SELECT query to execute
"""
# Security: Only allow SELECT
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries are allowed for safety"
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
if not rows:
return "No results found"
columns = [description[0] for description in cursor.description]
results = []
for row in rows:
results.append(dict(zip(columns, row)))
return json.dumps(results, indent=2, ensure_ascii=False)
except Exception as e:
return f"Query error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def insert_record(table: str, data: str) -> str:
"""Insert a record into a table.
Args:
table: Table name
data: JSON string of column-value pairs
"""
try:
record = json.loads(data)
except json.JSONDecodeError:
return "Error: Invalid JSON data"
columns = ", ".join(record.keys())
placeholders = ", ".join(["?" for _ in record])
values = list(record.values())
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
f"INSERT INTO {table} ({columns}) VALUES ({placeholders})",
values,
)
conn.commit()
return f"Successfully inserted record with id {cursor.lastrowid}"
except Exception as e:
return f"Insert error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def update_record(table: str, record_id: int, data: str) -> str:
"""Update a record in a table.
Args:
table: Table name
record_id: ID of the record to update
data: JSON string of column-value pairs to update
"""
try:
updates = json.loads(data)
except json.JSONDecodeError:
return "Error: Invalid JSON data"
set_clause = ", ".join([f"{k} = ?" for k in updates.keys()])
values = list(updates.values()) + [record_id]
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
f"UPDATE {table} SET {set_clause} WHERE id = ?", values
)
conn.commit()
return f"Successfully updated {cursor.rowcount} record(s)"
except Exception as e:
return f"Update error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def delete_record(table: str, record_id: int) -> str:
"""Delete a record from a table.
Args:
table: Table name
record_id: ID of the record to delete
"""
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(f"DELETE FROM {table} WHERE id = ?", [record_id])
conn.commit()
return f"Successfully deleted {cursor.rowcount} record(s)"
except Exception as e:
return f"Delete error: {str(e)}"
finally:
conn.close()
if __name__ == "__main__":
mcp.run()
This server provides:
- Resources: DB schema information and table listings
- Tools: SELECT queries, INSERT, UPDATE, DELETE operations
Tell Claude "Show me the 10 most recently registered users" and it will automatically invoke the query_database tool.
2-3. Filesystem MCP Server
import os
from pathlib import Path
from fastmcp import FastMCP
mcp = FastMCP("Filesystem Server")
# Security: Only allow access to specific directories
ALLOWED_DIRS = [
Path("/Users/dev/projects"),
Path("/Users/dev/documents"),
]
def is_path_allowed(path: str) -> bool:
"""Check if the path is within allowed directories."""
resolved = Path(path).resolve()
return any(
resolved == allowed or allowed in resolved.parents
for allowed in ALLOWED_DIRS
)
@mcp.tool()
def read_file(path: str) -> str:
"""Read contents of a file.
Args:
path: Absolute file path
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
with open(path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return f"Error: File not found: {path}"
except UnicodeDecodeError:
return "Error: File is not a text file"
@mcp.tool()
def write_file(path: str, content: str) -> str:
"""Write content to a file.
Args:
path: Absolute file path
content: Content to write
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"Successfully wrote to {path}"
except Exception as e:
return f"Error writing file: {str(e)}"
@mcp.tool()
def list_directory(path: str) -> str:
"""List contents of a directory.
Args:
path: Absolute directory path
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
entries = []
for entry in sorted(Path(path).iterdir()):
entry_type = "dir" if entry.is_dir() else "file"
size = entry.stat().st_size if entry.is_file() else 0
entries.append(f"[{entry_type}] {entry.name} ({size} bytes)")
return "\n".join(entries)
except FileNotFoundError:
return f"Error: Directory not found: {path}"
@mcp.tool()
def search_files(directory: str, pattern: str) -> str:
"""Search for files matching a glob pattern.
Args:
directory: Directory to search in
pattern: Glob pattern (e.g., '*.py', '**/*.md')
"""
if not is_path_allowed(directory):
return "Error: Access denied."
matches = list(Path(directory).glob(pattern))
if not matches:
return "No files found matching the pattern"
return "\n".join(str(m) for m in matches[:50])
@mcp.resource("fs://allowed-dirs")
def get_allowed_dirs() -> str:
"""List allowed directories."""
return "\n".join(str(d) for d in ALLOWED_DIRS)
if __name__ == "__main__":
mcp.run()
Key security points:
ALLOWED_DIRSrestricts accessible directoriesPath.resolve()prevents symlink bypass attacks- Parent directory traversal (
../) is blocked
3. Building an MCP Server with TypeScript SDK (Hands-On 2)
TypeScript is the second most popular language for MCP server development. The official @modelcontextprotocol/sdk package is available.
3-1. Basic Structure
Installation:
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
src/index.ts (basic structure):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
// Create server instance
const server = new McpServer({
name: 'My MCP Server',
version: '1.0.0',
})
// Register a Tool
server.tool(
'hello',
'Say hello to someone',
{
name: z.string().describe('Name of the person'),
},
async ({ name }) => {
return {
content: [
{
type: 'text',
text: `Hello, ${name}! Welcome to MCP.`,
},
],
}
}
)
// Register a Resource
server.resource('info', 'server://info', async (uri) => {
return {
contents: [
{
uri: uri.href,
mimeType: 'text/plain',
text: 'This is My MCP Server v1.0.0',
},
],
}
})
// Connect transport and run
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('MCP Server running on stdio')
}
main().catch(console.error)
Run:
npx tsc
node dist/index.js
Zod-based parameter validation is the key advantage of the TypeScript SDK. You get both type safety and automatic JSON Schema generation.
3-2. GitHub Issue MCP Server (Production)
Let's build a practical GitHub issue management MCP server.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const server = new McpServer({
name: 'GitHub Issue Server',
version: '1.0.0',
})
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
const BASE_URL = 'https://api.github.com'
// Common headers
function getHeaders() {
return {
Authorization: `Bearer ${GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'MCP-GitHub-Server',
}
}
// Tool: List issues
server.tool(
'list_issues',
'List issues in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
state: z.enum(['open', 'closed', 'all']).default('open').describe('Issue state filter'),
labels: z.string().optional().describe('Comma-separated label names'),
per_page: z.number().min(1).max(100).default(10).describe('Results per page'),
},
async ({ owner, repo, state, labels, per_page }) => {
const params = new URLSearchParams({
state,
per_page: String(per_page),
})
if (labels) params.set('labels', labels)
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues?${params}`, {
headers: getHeaders(),
})
if (!response.ok) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${response.status} ${response.statusText}`,
},
],
}
}
const issues = await response.json()
const formatted = issues.map(
(issue: Record<string, unknown>) =>
`#${issue.number} [${issue.state}] ${issue.title}\n Labels: ${
(issue.labels as Array<Record<string, string>>).map((l) => l.name).join(', ') || 'none'
}\n Created: ${issue.created_at}`
)
return {
content: [
{
type: 'text' as const,
text: formatted.join('\n\n') || 'No issues found',
},
],
}
}
)
// Tool: Create issue
server.tool(
'create_issue',
'Create a new issue in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
title: z.string().describe('Issue title'),
body: z.string().optional().describe('Issue body (Markdown)'),
labels: z.array(z.string()).optional().describe('Labels to add'),
assignees: z.array(z.string()).optional().describe('Usernames to assign'),
},
async ({ owner, repo, title, body, labels, assignees }) => {
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ title, body, labels, assignees }),
})
if (!response.ok) {
const error = await response.text()
return {
content: [
{
type: 'text' as const,
text: `Error creating issue: ${response.status} - ${error}`,
},
],
}
}
const issue = await response.json()
return {
content: [
{
type: 'text' as const,
text: `Created issue #${issue.number}: ${issue.title}\nURL: ${issue.html_url}`,
},
],
}
}
)
// Tool: Close issue
server.tool(
'close_issue',
'Close an issue in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
issue_number: z.number().describe('Issue number'),
comment: z.string().optional().describe('Optional closing comment'),
},
async ({ owner, repo, issue_number, comment }) => {
if (comment) {
await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ body: comment }),
})
}
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}`, {
method: 'PATCH',
headers: getHeaders(),
body: JSON.stringify({ state: 'closed' }),
})
if (!response.ok) {
return {
content: [
{
type: 'text' as const,
text: `Error closing issue: ${response.status}`,
},
],
}
}
return {
content: [
{
type: 'text' as const,
text: `Closed issue #${issue_number} successfully`,
},
],
}
}
)
// Resource: Repository info
server.resource('repo-info', 'github://repo-info', async (uri) => {
return {
contents: [
{
uri: uri.href,
mimeType: 'text/plain',
text: 'GitHub Issue MCP Server - Manage issues via AI',
},
],
}
})
// Prompt: Issue triage
server.prompt(
'triage_issues',
'Generate a prompt for triaging open issues',
{ owner: z.string(), repo: z.string() },
({ owner, repo }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please triage the open issues in ${owner}/${repo}. For each issue:
1. Assess priority (critical/high/medium/low)
2. Suggest appropriate labels
3. Recommend if it should be assigned to someone
4. Provide a brief action plan`,
},
},
],
})
)
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('GitHub Issue MCP Server running')
}
main().catch(console.error)
package.json:
{
"name": "github-issue-mcp",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
},
"devDependencies": {
"typescript": "^5.7.0",
"@types/node": "^22.0.0"
}
}
Claude Desktop integration:
{
"mcpServers": {
"github-issues": {
"command": "node",
"args": ["/path/to/github-issue-mcp/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
4. Testing and Debugging MCP Servers
4-1. MCP Inspector
MCP Inspector is the official tool for locally testing MCP servers.
# Run directly with npx
npx @modelcontextprotocol/inspector
# Run with a specific server
npx @modelcontextprotocol/inspector python weather_server.py
# FastMCP dev mode (auto-connects Inspector)
fastmcp dev weather_server.py
Inspector capabilities:
- Browse all tools, resources, and prompts
- Invoke tools and inspect results
- View JSON-RPC message logs in real-time
- Monitor connection status
4-2. Claude Desktop Integration
Configuration file location on macOS:
# Open config file
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows:
%APPDATA%\Claude\claude_desktop_config.json
Configuration example:
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/absolute/path/to/weather_server.py"]
},
"database": {
"command": "python",
"args": ["/absolute/path/to/db_server.py"],
"env": {
"DB_PATH": "/data/myapp.db"
}
},
"github": {
"command": "node",
"args": ["/absolute/path/to/github-mcp/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
}
}
}
}
4-3. Claude Code Integration
Create a .mcp.json file at the project root:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["./tools/my_mcp_server.py"]
}
}
}
Or use global settings:
claude mcp add my-server python ./tools/my_mcp_server.py
4-4. Logging Strategy
Debug logs in MCP servers should go to stderr. Stdout is reserved exclusively for JSON-RPC messages.
Python:
import sys
import logging
# Configure logging to stderr
logging.basicConfig(
level=logging.DEBUG,
stream=sys.stderr,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("my-mcp-server")
@mcp.tool()
def my_tool(param: str) -> str:
logger.debug(f"Tool called with param: {param}")
try:
result = do_something(param)
logger.info(f"Tool succeeded: {result}")
return result
except Exception as e:
logger.error(f"Tool failed: {e}")
return f"Error: {str(e)}"
TypeScript:
// Log to stderr
function log(level: string, message: string) {
console.error(`[${new Date().toISOString()}] [${level}] ${message}`)
}
server.tool('my_tool', 'Description', { param: z.string() }, async ({ param }) => {
log('DEBUG', `Tool called with: ${param}`)
try {
const result = await doSomething(param)
log('INFO', `Success: ${result}`)
return { content: [{ type: 'text', text: result }] }
} catch (error) {
log('ERROR', `Failed: ${error}`)
return { content: [{ type: 'text', text: `Error: ${error}` }] }
}
})
4-5. Error Handling Patterns
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
mcp = FastMCP("Robust Server")
@mcp.tool()
def safe_operation(param: str) -> str:
"""A tool with proper error handling."""
# Input validation
if not param or len(param) > 1000:
raise ToolError("Parameter must be 1-1000 characters")
try:
result = external_api_call(param)
return result
except ConnectionError:
raise ToolError("Failed to connect to external service. Please try again.")
except TimeoutError:
raise ToolError("Request timed out. The service may be temporarily unavailable.")
except Exception as e:
# Hide details for unexpected errors, return generic message
logging.error(f"Unexpected error: {e}", exc_info=True)
raise ToolError("An unexpected error occurred. Check server logs for details.")
5. Deploying MCP Servers
5-1. Cloudflare Workers Deployment
Cloudflare Workers lets you deploy MCP servers serverlessly across the globe.
# Create project
npm create cloudflare@latest -- my-mcp-server
cd my-mcp-server
npm install @modelcontextprotocol/sdk
src/index.ts (Workers version):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
export default {
async fetch(request: Request): Promise<Response> {
const server = new McpServer({
name: 'Cloudflare MCP Server',
version: '1.0.0',
})
// Register Tools
server.tool('hello', 'Say hello', { name: z.string() }, async ({ name }) => ({
content: [{ type: 'text', text: `Hello ${name} from the edge!` }],
}))
// Handle Streamable HTTP transport
// In practice, use the MCP SDK's HTTP transport handler
return new Response('MCP Server Running', { status: 200 })
},
}
# Deploy
npx wrangler deploy
5-2. Docker Containerization
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
# MCP server communicates via stdio
CMD ["node", "dist/index.js"]
docker build -t my-mcp-server .
docker run -i my-mcp-server
Connect Docker server from Claude Desktop:
{
"mcpServers": {
"my-server": {
"command": "docker",
"args": ["run", "-i", "--rm", "my-mcp-server"]
}
}
}
5-3. npm/PyPI Package Distribution
npm package:
{
"name": "@myorg/mcp-weather",
"version": "1.0.0",
"bin": {
"mcp-weather": "./dist/index.js"
}
}
npm publish --access public
Users can instantly use it:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["-y", "@myorg/mcp-weather"]
}
}
}
PyPI package:
pip install build twine
python -m build
twine upload dist/*
{
"mcpServers": {
"weather": {
"command": "uvx",
"args": ["mcp-weather"]
}
}
}
5-4. Smithery Marketplace
Smithery (smithery.ai) is a dedicated MCP server registry. Registration process:
- Push MCP server code to a GitHub repository
- Add a
smithery.yamlconfiguration file - Connect the repo on the Smithery website
- Automatic build and deployment
# smithery.yaml
name: my-weather-mcp
description: Weather MCP server for AI assistants
icon: cloud-sun
startCommand:
type: stdio
configSchema:
type: object
properties:
API_KEY:
type: string
description: Weather API key
required:
- API_KEY
commandFunction:
- node
- dist/index.js
5-5. Adding OAuth 2.0 Authentication
Authentication is essential for remote MCP servers. OAuth 2.0 support was formalized in the June 2025 spec.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
const server = new McpServer({
name: 'Authenticated Server',
version: '1.0.0',
})
// OAuth 2.0 middleware (Streamable HTTP transport)
async function validateToken(token: string): Promise<boolean> {
const response = await fetch('https://auth.example.com/introspect', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `token=${token}`,
})
const data = await response.json()
return data.active === true
}
// Authenticate in HTTP handler
async function handleRequest(req: Request): Promise<Response> {
const authHeader = req.headers.get('Authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 })
}
const token = authHeader.slice(7)
if (!(await validateToken(token))) {
return new Response('Invalid token', { status: 403 })
}
// Continue processing MCP request...
return new Response('OK')
}
6. 100+ MCP Server Ecosystem Roundup
As of March 2026, the MCP ecosystem is growing explosively. Here is a categorized overview of major servers.
6-1. Data and Analytics
| Server | Features | Use Case |
|---|---|---|
| Grafana MCP | Dashboard queries, metric queries, alert management | AI-driven monitoring automation |
| PostgreSQL MCP | SQL queries, schema exploration, data analysis | Natural language to SQL |
| MongoDB MCP | Collection CRUD, aggregation pipelines | NoSQL data exploration |
| ClickHouse MCP | Analytical queries, log retrieval | Log-based troubleshooting |
| BigQuery MCP | Large-scale data analysis | Data team productivity |
| MySQL MCP | SQL queries, table management | RDBMS operations |
| Redis MCP | Cache inspection, key-value management | Cache debugging |
| Elasticsearch MCP | Full-text search, index management | Log analysis automation |
| Snowflake MCP | Data warehouse queries | Business intelligence |
| DuckDB MCP | Local analytical queries | Fast data exploration |
| Prometheus MCP | Metric queries (PromQL) | System monitoring |
| Datadog MCP | Metrics, dashboards, alerts | Unified observability |
Grafana MCP Example:
User: "Find servers where CPU usage exceeded 80% in the last 24 hours"
Claude: [Calls Grafana MCP query_metrics tool]
-> PromQL: avg by (instance)(cpu_usage_percent > 80)
-> Results: server-03 (avg 87%), server-07 (avg 92%)
6-2. Developer Tools
| Server | Features | Use Case |
|---|---|---|
| GitHub MCP | Issues, PRs, Actions, code search | Code review automation |
| GitLab MCP | Pipelines, merge requests | CI/CD management |
| Jira MCP | Issue tracking, sprint management | PM workflow automation |
| Linear MCP | Issue/project management | Modern issue tracker |
| Sentry MCP | Error tracking, performance monitoring | Debugging automation |
| CircleCI MCP | CI/CD pipeline management | Build monitoring |
| npm MCP | Package search, version checks | Dependency management |
| Snyk MCP | Security vulnerability scanning | Security audit automation |
| SonarQube MCP | Code quality analysis | Code review assistance |
| Postman MCP | API testing, collection management | API development efficiency |
6-3. Documentation and Visualization
| Server | Features | Use Case |
|---|---|---|
| Mermaid MCP | Diagram generation (flowchart, sequence, ER) | Documentation automation |
| PPT/Slides MCP | Presentation creation/editing | Report automation |
| Notion MCP | Pages, database CRUD | Knowledge management |
| Google Docs MCP | Document read/edit | Collaborative documentation |
| Confluence MCP | Wiki page management | Technical documentation |
| Excalidraw MCP | Whiteboard diagrams | Architecture diagrams |
| Google Sheets MCP | Spreadsheet read/edit | Data reports |
| Obsidian MCP | Markdown note management | Personal knowledge base |
6-4. Communication
| Server | Features | Use Case |
|---|---|---|
| Slack MCP | Channel messages, search, DMs | Team communication automation |
| Discord MCP | Server/channel management | Community management |
| Email MCP | Read/send/search emails | Email automation |
| Microsoft Teams MCP | Chat, meeting management | Enterprise collaboration |
| Telegram MCP | Bot messages, channel management | Notification automation |
| Twilio MCP | SMS, voice calls | Customer communication |
6-5. Cloud and Infrastructure
| Server | Features | Use Case |
|---|---|---|
| AWS MCP | S3, Lambda, EC2 management | Infrastructure automation |
| Kubernetes MCP | Cluster management, Pod inspection | K8s operations |
| Docker MCP | Container/image management | Dev environment management |
| Terraform MCP | IaC execution, state inspection | Infrastructure code generation |
| Vercel MCP | Deployment, env vars, logs | Frontend deployment |
| GCP MCP | Google Cloud service management | GCP infrastructure automation |
| Azure MCP | Azure service management | Enterprise cloud |
| Cloudflare MCP | Workers, DNS, CDN | Edge computing management |
| Pulumi MCP | IaC (programming languages) | Code-based infrastructure |
| Ansible MCP | Configuration management | Server provisioning |
6-6. AI and ML
| Server | Features | Use Case |
|---|---|---|
| HuggingFace MCP | Model search, inference | ML workflows |
| Weights and Biases MCP | Experiment tracking, metrics | ML experiment management |
| LangChain MCP | Chain/agent execution | AI pipelines |
| Ollama MCP | Local LLM management | Private AI execution |
| Replicate MCP | Cloud model execution | ML model deployment |
| Pinecone MCP | Vector DB management | RAG implementation |
| Weaviate MCP | Vector search | Semantic search |
| ChromaDB MCP | Local vector DB | Embedding management |
6-7. Browser and Scraping
| Server | Features | Use Case |
|---|---|---|
| Playwright MCP | Browser automation, screenshots | Web testing |
| Puppeteer MCP | Chrome automation | Crawling |
| Brave Search MCP | Web search API | Real-time information for AI |
| Firecrawl MCP | Website crawling, markdown conversion | RAG data collection |
| Browserbase MCP | Cloud browsers | Large-scale web automation |
| Exa MCP | Semantic web search | High-quality search results |
| Tavily MCP | AI-optimized search | Research automation |
6-8. Files and Storage
| Server | Features | Use Case |
|---|---|---|
| Filesystem MCP | Local file read/write | File management |
| Google Drive MCP | File search/download | Cloud file management |
| S3 MCP | Bucket/object management | Cloud storage |
| Dropbox MCP | File sync, sharing | File collaboration |
| OneDrive MCP | Microsoft file management | Enterprise files |
| Box MCP | Enterprise file management | Enterprise content management |
6-9. Design and Media
| Server | Features | Use Case |
|---|---|---|
| Figma MCP | Design file viewing, component extraction | Design-to-code conversion |
| Canva MCP | Design creation/editing | Marketing materials |
| FFmpeg MCP | Audio/video conversion | Media processing |
| Sharp MCP | Image processing | Image optimization |
6-10. Specialized Servers
| Server | Features | Use Case |
|---|---|---|
| Stripe MCP | Payments, subscription management | Payment systems |
| Shopify MCP | Store, product management | E-commerce |
| SendGrid MCP | Email delivery | Marketing emails |
| Airtable MCP | Spreadsheet DB | No-code data management |
| Supabase MCP | BaaS (DB, Auth, Storage) | Full-stack development |
| Firebase MCP | Google BaaS | Mobile/web backends |
| Neon MCP | Serverless PostgreSQL | Database management |
| PlanetScale MCP | Serverless MySQL | Scalable DB |
| Turso MCP | Edge SQLite | Distributed database |
7. 10 Ideas for Your Own MCP Server
Building your own MCP server dramatically expands AI's usefulness. Here are practical ideas.
7-1. Personal Blog MCP
An MCP server for managing MDX-file-based blogs.
from fastmcp import FastMCP
import glob
import yaml
mcp = FastMCP("Blog Manager")
BLOG_DIR = "/path/to/blog/data"
@mcp.tool()
def list_posts(tag: str = "") -> str:
"""List all blog posts, optionally filtered by tag."""
posts = glob.glob(f"{BLOG_DIR}/**/*.mdx", recursive=True)
results = []
for post_path in sorted(posts, reverse=True):
with open(post_path) as f:
content = f.read()
if content.startswith("---"):
fm_end = content.index("---", 3)
frontmatter = yaml.safe_load(content[3:fm_end])
if tag and tag not in frontmatter.get("tags", []):
continue
results.append(
f"- {frontmatter['title']} ({frontmatter['date']})"
)
return "\n".join(results[:20])
@mcp.tool()
def create_post(title: str, tags: str, content: str) -> str:
"""Create a new blog post."""
# Implementation...
return "Post created successfully"
7-2. Stock/Crypto Price MCP
Query real-time prices and analyze portfolios.
7-3. Calendar/Schedule MCP
Integrate with Google Calendar or Apple Calendar to manage schedules.
7-4. CI/CD Pipeline Monitoring MCP
Monitor GitHub Actions, Jenkins, CircleCI pipeline status.
7-5. Kubernetes Log Analysis MCP
Wrap kubectl to analyze Pod logs and detect anomalies.
7-6. Figma Design Extraction MCP
Extract design components via Figma API and convert to code.
7-7. Translation MCP
Provide multilingual translation via DeepL or Google Translate API.
7-8. Personal Notes/Wiki MCP
Let AI navigate and edit Obsidian or Logseq vaults.
7-9. Healthcare Data MCP
Analyze Apple Health or Fitbit data.
7-10. Smart Home IoT Control MCP
Control lights, temperature, and security systems via Home Assistant API.
from fastmcp import FastMCP
import httpx
mcp = FastMCP("Smart Home")
HA_URL = "http://homeassistant.local:8123"
HA_TOKEN = "your_long_lived_token"
@mcp.tool()
def control_light(entity_id: str, action: str) -> str:
"""Control a smart light.
Args:
entity_id: Home Assistant entity ID (e.g., light.living_room)
action: 'on' or 'off'
"""
service = "turn_on" if action == "on" else "turn_off"
response = httpx.post(
f"{HA_URL}/api/services/light/{service}",
headers={"Authorization": f"Bearer {HA_TOKEN}"},
json={"entity_id": entity_id},
)
return f"Light {entity_id}: {action} (status: {response.status_code})"
@mcp.tool()
def get_temperature(entity_id: str) -> str:
"""Get temperature from a sensor."""
response = httpx.get(
f"{HA_URL}/api/states/{entity_id}",
headers={"Authorization": f"Bearer {HA_TOKEN}"},
)
data = response.json()
return f"Temperature: {data['state']}{data['attributes'].get('unit_of_measurement', '')}"
8. MCP Security Best Practices
MCP servers grant AI access to your systems, making security critically important.
8-1. Input Validation
Python (Pydantic):
from pydantic import BaseModel, Field, validator
class QueryInput(BaseModel):
sql: str = Field(..., max_length=5000)
timeout: int = Field(default=30, ge=1, le=300)
@validator("sql")
def validate_sql(cls, v):
forbidden = ["DROP", "DELETE", "TRUNCATE", "ALTER"]
upper = v.upper()
for keyword in forbidden:
if keyword in upper:
raise ValueError(f"Forbidden SQL keyword: {keyword}")
return v
TypeScript (Zod):
const querySchema = z.object({
sql: z
.string()
.max(5000)
.refine(
(sql) => {
const forbidden = ['DROP', 'DELETE', 'TRUNCATE', 'ALTER']
const upper = sql.toUpperCase()
return !forbidden.some((kw) => upper.includes(kw))
},
{ message: 'Forbidden SQL keyword detected' }
),
timeout: z.number().min(1).max(300).default(30),
})
8-2. Principle of Least Privilege
- DB servers: Use read-only accounts (SELECT only)
- File servers: Restrict allowed directories, resolve symlinks
- API servers: Use tokens with only required scopes
- Network: Block internal network access
8-3. Rate Limiting
import time
from collections import defaultdict
class RateLimiter:
def __init__(self, max_calls: int = 10, window: int = 60):
self.max_calls = max_calls
self.window = window
self.calls = defaultdict(list)
def check(self, key: str) -> bool:
now = time.time()
self.calls[key] = [
t for t in self.calls[key] if now - t < self.window
]
if len(self.calls[key]) >= self.max_calls:
return False
self.calls[key].append(now)
return True
limiter = RateLimiter(max_calls=20, window=60)
@mcp.tool()
def rate_limited_tool(param: str) -> str:
if not limiter.check("default"):
return "Rate limit exceeded. Please wait before trying again."
return do_work(param)
8-4. Secret Management
import os
# Load secrets from environment variables (never hardcode)
DB_PASSWORD = os.environ.get("DB_PASSWORD")
API_KEY = os.environ.get("API_KEY")
if not DB_PASSWORD or not API_KEY:
raise RuntimeError("Required environment variables not set")
Pass environment variables in Claude Desktop config:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["server.py"],
"env": {
"DB_PASSWORD": "secret_from_keychain",
"API_KEY": "sk-xxx"
}
}
}
}
8-5. Audit Logging
import json
import sys
from datetime import datetime
def audit_log(tool_name: str, params: dict, result: str, success: bool):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"tool": tool_name,
"params": params,
"success": success,
"result_length": len(result),
}
print(json.dumps(log_entry), file=sys.stderr)
8-6. Sandboxing
Use Docker or virtual environments to isolate MCP servers:
# docker-compose.yml
services:
mcp-server:
build: .
read_only: true
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:size=100M
mem_limit: 512m
cpus: '0.5'
networks:
- mcp-net
volumes:
- ./allowed-data:/data:ro
networks:
mcp-net:
driver: bridge
internal: true # Block external internet access
9. The Future of MCP: 2026 Outlook
9-1. Agent-to-Agent MCP Communication
Currently MCP focuses mainly on human(user)-AI tool connections. In 2026, scenarios where AI agents communicate directly with each other via MCP will expand.
For example:
- A code review agent asks a security analysis agent to scan for vulnerabilities via MCP
- A data pipeline agent asks a monitoring agent to check for anomalies
- A customer support agent queries an order management agent for order status
9-2. Multimodal MCP
Current MCP is text-centric, but multimodal extensions for images, audio, and video are underway.
- Image resources: Deliver screenshots, charts, and design files directly to AI
- Audio processing: AI analyzes meeting recordings
- Video analysis: AI monitors security camera footage
9-3. MCP Registry (Central Server Registry)
Like npm or PyPI, an official registry for searching and installing MCP servers is being developed. Smithery currently serves this role, but a more standardized registry is forthcoming.
9-4. Enterprise MCP Gateway
Enterprise environments need a central gateway for managing MCP servers:
- Access control: Who can use which MCP servers
- Audit logs: Record all tool invocations
- Cost management: Rate limiting API calls
- Security policies: Control access to sensitive data
9-5. Linux Foundation AAIF Governance
In late 2025, MCP was donated to the Linux Foundation's AI and AI Foundation (AAIF). This means MCP governance is transitioning from a single company (Anthropic) to an open community.
Expected benefits:
- More transparent spec development process
- Increased participation from diverse companies
- Strengthened interoperability standards
- Possible certification programs
10. Quiz
Let's test what you have learned.
Q1. What is MCP's underlying protocol, and what are the three message types?
Answer: MCP is based on JSON-RPC 2.0. The three message types are:
- Request: Has an id field, expects a response
- Response: Returns results for a request
- Notification: No id field, one-way message
Q2. What are MCP's two transport methods and their primary use cases?
Answer:
- stdio: Communicates via stdin/stdout of local processes. Used when running local servers from Claude Desktop or Claude Code.
- Streamable HTTP: Communicates via HTTP POST and SSE. Used for remote server deployment and multi-tenant environments.
Q3. How do the three core primitives (Resources, Tools, Prompts) differ?
Answer:
- Resources: Read-only data via URIs. Used by applications as context. (User-controlled)
- Tools: Executable functions invoked by AI models. Parameters defined by JSON Schema. May have side effects. (Model-controlled)
- Prompts: Pre-defined reusable prompt templates. (User-controlled)
Q4. What is the simplest way to define an MCP tool in FastMCP?
Answer: Use the @mcp.tool() decorator. The function's docstring becomes the tool description, and type hints are automatically converted to JSON Schema.
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool()
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
Q5. What are the 5 most important MCP server security principles?
Answer:
- Input validation: Validate all parameters with Zod/Pydantic
- Least privilege: Grant only necessary permissions (read-only DB accounts, etc.)
- Rate limiting: Limit API call frequency
- Secret management: Use environment variables, never hardcode
- Audit logging: Record all tool invocations
Additionally, sandboxing (Docker isolation) and OAuth 2.0 authentication are important.
11. References
- MCP Official Specification - Full MCP spec
- MCP Official Documentation - Getting started guide and tutorials
- MCP GitHub Repository - SDKs and reference servers
- FastMCP Documentation - Python FastMCP framework
- MCP TypeScript SDK - TypeScript SDK
- MCP Python SDK - Python SDK
- Awesome MCP Servers - Community MCP server list
- Smithery - MCP server registry
- MCP Inspector - MCP server testing tool
- Claude Desktop MCP Setup Guide - Claude Desktop integration
- Claude Code MCP Setup - Claude Code integration
- Grafana MCP Server - Official Grafana MCP server
- GitHub MCP Server - Official GitHub MCP server
- PostgreSQL MCP Server - PostgreSQL MCP
- Playwright MCP Server - Microsoft Playwright MCP
- Slack MCP Server - Slack MCP
- Filesystem MCP Server - Filesystem MCP
- Cloudflare MCP Workers - Cloudflare deployment guide
- MCP OAuth 2.0 Specification - Authentication spec
- Linux Foundation AAIF - MCP governance
- Anthropic MCP Blog - MCP announcement
- OpenAI MCP Support - OpenAI adopts MCP
- Google MCP Support - Google supports MCP