🎯 Introduction to MCP & Claude Code Integration
📋 What is MCP and Why It Matters
Model Control Protocol (MCP) is a revolutionary framework that allows Large Language Models like Claude to interact with external systems, tools, and data sources in a secure, standardized way. Think of it as a bridge that connects Claude’s intelligence with your development environment, databases, APIs, and custom tools.
For Claude Code users, MCP servers unlock powerful capabilities:
- Custom Tool Integration: Connect Claude to your proprietary tools and systems
- Real-Time Data Access: Query live databases, APIs, and services
- Workflow Automation: Automate complex development tasks across multiple systems
- Enhanced Context: Provide Claude with rich, contextual information from your environment
🎯 What We’ll Build in This Series
This multi-part series will take you from MCP basics to advanced implementations:
Part 1 (This Post): Basic MCP server setup and simple custom tools Part 2: Advanced MCP patterns, database integration, and API connectors Part 3: Production deployment, security, and scaling considerations
💡 Learning Goal: By the end of this post, you’ll have a working MCP server with custom tools that Claude Code can use to enhance your development workflow
🏗️ MCP Architecture Overview
graph TD
A[Claude Code] --> B[MCP Client]
B --> C[MCP Server]
C --> D[Tool Registry]
C --> E[Resource Manager]
C --> F[Prompt Templates]
D --> G[File System Tools]
D --> H[Database Tools]
D --> I[API Tools]
D --> J[Custom Tools]
E --> K[Local Files]
E --> L[Remote APIs]
E --> M[Databases]
F --> N[System Prompts]
F --> O[Context Templates]
style A fill:#4285f4
style C fill:#34a853
style D fill:#fbbc04
style E fill:#ea4335
🚀 Part 1: Basic MCP Server Setup
🔧 Prerequisites & Environment Setup
Before we begin, ensure you have the following installed:
Required Software:
- Python 3.8+ or Node.js 18+ (we’ll cover both implementations)
- Claude Code (latest version)
- Git for version control
- Code editor (VS Code recommended)
System Requirements:
- macOS, Linux, or Windows with WSL2
- At least 4GB RAM (8GB recommended)
- Internet connection for package installations
📦 Installing MCP SDK
For Python Implementation:
1# Create a new project directory
2mkdir my-mcp-server
3cd my-mcp-server
4
5# Create virtual environment
6python -m venv mcp-env
7source mcp-env/bin/activate # On Windows: mcp-env\Scripts\activate
8
9# Install MCP SDK
10pip install mcp
11
12# Install additional dependencies
13pip install asyncio aiofiles requests
For TypeScript Implementation:
1# Create a new project directory
2mkdir my-mcp-server-ts
3cd my-mcp-server-ts
4
5# Initialize npm project
6npm init -y
7
8# Install MCP SDK
9npm install @modelcontextprotocol/sdk
10
11# Install development dependencies
12npm install -D typescript @types/node ts-node
13npm install axios fs-extra
🏗️ Creating Your First MCP Server
Let’s start with a basic MCP server that provides simple utility tools:
Python Implementation (server.py):
1#!/usr/bin/env python3
2"""
3Basic MCP Server - Part 1
4Provides simple utility tools for Claude Code
5"""
6
7import asyncio
8import json
9import logging
10from datetime import datetime
11from pathlib import Path
12from typing import Dict, List, Any
13
14from mcp.server.models import InitializeResult
15from mcp.server import Server, NotificationOptions
16from mcp.server.stdio import stdio_server
17from mcp.types import (
18 Resource,
19 Tool,
20 TextContent,
21 ImageContent,
22 EmbeddedResource,
23)
24
25# Configure logging
26logging.basicConfig(level=logging.INFO)
27logger = logging.getLogger("basic-mcp-server")
28
29# Create MCP server instance
30server = Server("basic-mcp-server")
31
32@server.list_tools()
33async def handle_list_tools() -> List[Tool]:
34 """
35 Define available tools that Claude Code can use
36 """
37 return [
38 Tool(
39 name="get_current_time",
40 description="Get the current date and time in various formats",
41 inputSchema={
42 "type": "object",
43 "properties": {
44 "format": {
45 "type": "string",
46 "description": "Time format: 'iso', 'human', or 'unix'",
47 "enum": ["iso", "human", "unix"]
48 },
49 "timezone": {
50 "type": "string",
51 "description": "Timezone (default: UTC)",
52 "default": "UTC"
53 }
54 },
55 "required": ["format"]
56 }
57 ),
58 Tool(
59 name="file_stats",
60 description="Get detailed statistics about files and directories",
61 inputSchema={
62 "type": "object",
63 "properties": {
64 "path": {
65 "type": "string",
66 "description": "Path to file or directory to analyze"
67 },
68 "recursive": {
69 "type": "boolean",
70 "description": "Include subdirectories in analysis",
71 "default": False
72 }
73 },
74 "required": ["path"]
75 }
76 ),
77 Tool(
78 name="generate_uuid",
79 description="Generate UUIDs in various formats",
80 inputSchema={
81 "type": "object",
82 "properties": {
83 "count": {
84 "type": "integer",
85 "description": "Number of UUIDs to generate",
86 "default": 1,
87 "minimum": 1,
88 "maximum": 100
89 },
90 "format": {
91 "type": "string",
92 "description": "UUID format: 'standard', 'compact', or 'upper'",
93 "enum": ["standard", "compact", "upper"],
94 "default": "standard"
95 }
96 }
97 }
98 ),
99 Tool(
100 name="system_info",
101 description="Get system information including OS, Python version, and environment",
102 inputSchema={
103 "type": "object",
104 "properties": {}
105 }
106 )
107 ]
108
109@server.call_tool()
110async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
111 """
112 Handle tool execution requests from Claude Code
113 """
114
115 if name == "get_current_time":
116 format_type = arguments.get("format", "iso")
117 timezone = arguments.get("timezone", "UTC")
118
119 now = datetime.now()
120
121 if format_type == "iso":
122 result = now.isoformat()
123 elif format_type == "human":
124 result = now.strftime("%A, %B %d, %Y at %I:%M:%S %p")
125 elif format_type == "unix":
126 result = str(int(now.timestamp()))
127 else:
128 result = now.isoformat()
129
130 return [TextContent(
131 type="text",
132 text=f"Current time ({format_type} format): {result}"
133 )]
134
135 elif name == "file_stats":
136 import os
137 import stat
138 from pathlib import Path
139
140 path_str = arguments.get("path")
141 recursive = arguments.get("recursive", False)
142
143 try:
144 path = Path(path_str)
145
146 if not path.exists():
147 return [TextContent(
148 type="text",
149 text=f"Error: Path '{path_str}' does not exist"
150 )]
151
152 stats = {
153 "path": str(path.absolute()),
154 "exists": path.exists(),
155 "is_file": path.is_file(),
156 "is_directory": path.is_dir(),
157 "is_symlink": path.is_symlink()
158 }
159
160 if path.exists():
161 stat_info = path.stat()
162 stats.update({
163 "size_bytes": stat_info.st_size,
164 "size_human": _human_readable_size(stat_info.st_size),
165 "modified": datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
166 "created": datetime.fromtimestamp(stat_info.st_ctime).isoformat(),
167 "permissions": oct(stat.S_IMODE(stat_info.st_mode))
168 })
169
170 if path.is_dir() and recursive:
171 file_count = 0
172 dir_count = 0
173 total_size = 0
174
175 for item in path.rglob("*"):
176 if item.is_file():
177 file_count += 1
178 try:
179 total_size += item.stat().st_size
180 except (OSError, IOError):
181 pass
182 elif item.is_dir():
183 dir_count += 1
184
185 stats["recursive_stats"] = {
186 "total_files": file_count,
187 "total_directories": dir_count,
188 "total_size_bytes": total_size,
189 "total_size_human": _human_readable_size(total_size)
190 }
191
192 return [TextContent(
193 type="text",
194 text=f"File statistics:\n{json.dumps(stats, indent=2)}"
195 )]
196
197 except Exception as e:
198 return [TextContent(
199 type="text",
200 text=f"Error analyzing path '{path_str}': {str(e)}"
201 )]
202
203 elif name == "generate_uuid":
204 import uuid
205
206 count = arguments.get("count", 1)
207 format_type = arguments.get("format", "standard")
208
209 uuids = []
210 for _ in range(count):
211 new_uuid = uuid.uuid4()
212
213 if format_type == "standard":
214 uuids.append(str(new_uuid))
215 elif format_type == "compact":
216 uuids.append(str(new_uuid).replace("-", ""))
217 elif format_type == "upper":
218 uuids.append(str(new_uuid).upper())
219
220 result = "\n".join(uuids) if count > 1 else uuids[0]
221
222 return [TextContent(
223 type="text",
224 text=f"Generated {count} UUID(s) in {format_type} format:\n{result}"
225 )]
226
227 elif name == "system_info":
228 import platform
229 import sys
230 import os
231
232 info = {
233 "system": platform.system(),
234 "platform": platform.platform(),
235 "architecture": platform.architecture(),
236 "processor": platform.processor(),
237 "python_version": sys.version,
238 "python_executable": sys.executable,
239 "current_directory": os.getcwd(),
240 "environment_variables": {
241 key: value for key, value in os.environ.items()
242 if key.startswith(('PATH', 'HOME', 'USER', 'SHELL', 'PYTHON'))
243 }
244 }
245
246 return [TextContent(
247 type="text",
248 text=f"System Information:\n{json.dumps(info, indent=2)}"
249 )]
250
251 else:
252 return [TextContent(
253 type="text",
254 text=f"Error: Unknown tool '{name}'"
255 )]
256
257def _human_readable_size(size_bytes: int) -> str:
258 """Convert bytes to human readable format"""
259 if size_bytes == 0:
260 return "0 B"
261
262 size_names = ["B", "KB", "MB", "GB", "TB"]
263 i = 0
264 size = float(size_bytes)
265
266 while size >= 1024.0 and i < len(size_names) - 1:
267 size /= 1024.0
268 i += 1
269
270 return f"{size:.1f} {size_names[i]}"
271
272async def main():
273 """
274 Main server entry point
275 """
276 logger.info("Starting Basic MCP Server...")
277
278 # Run the server using stdin/stdout for communication with Claude Code
279 async with stdio_server() as (read_stream, write_stream):
280 await server.run(
281 read_stream,
282 write_stream,
283 InitializeResult(
284 protocolVersion="2024-11-05",
285 capabilities=server.get_capabilities(
286 notification_options=NotificationOptions(),
287 experimental_capabilities={}
288 )
289 )
290 )
291
292if __name__ == "__main__":
293 asyncio.run(main())
Helper Utility File (utils.py):
1"""
2Utility functions for MCP server
3"""
4
5import json
6import logging
7from pathlib import Path
8from typing import Dict, Any, List
9from datetime import datetime
10
11logger = logging.getLogger(__name__)
12
13class MCPServerUtils:
14 """Utility class for common MCP server operations"""
15
16 @staticmethod
17 def validate_path(path_str: str) -> Path:
18 """Validate and return Path object"""
19 try:
20 path = Path(path_str).resolve()
21 return path
22 except Exception as e:
23 raise ValueError(f"Invalid path '{path_str}': {e}")
24
25 @staticmethod
26 def safe_json_dumps(data: Any, indent: int = 2) -> str:
27 """Safely serialize data to JSON"""
28 try:
29 return json.dumps(data, indent=indent, default=str)
30 except Exception as e:
31 logger.error(f"JSON serialization error: {e}")
32 return f"Error serializing data: {e}"
33
34 @staticmethod
35 def log_tool_call(tool_name: str, arguments: Dict[str, Any]):
36 """Log tool execution for debugging"""
37 logger.info(f"Tool called: {tool_name}")
38 logger.debug(f"Arguments: {arguments}")
39
40 @staticmethod
41 def create_error_response(error_message: str) -> str:
42 """Create standardized error response"""
43 return f"Error: {error_message}"
44
45 @staticmethod
46 def format_file_listing(path: Path, max_items: int = 50) -> Dict[str, Any]:
47 """Format directory listing with metadata"""
48 if not path.is_dir():
49 raise ValueError(f"Path '{path}' is not a directory")
50
51 items = []
52 count = 0
53
54 try:
55 for item in sorted(path.iterdir()):
56 if count >= max_items:
57 break
58
59 try:
60 stat = item.stat()
61 items.append({
62 "name": item.name,
63 "type": "directory" if item.is_dir() else "file",
64 "size": stat.st_size if item.is_file() else None,
65 "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
66 "permissions": oct(stat.S_IMODE(stat.st_mode))
67 })
68 count += 1
69 except (OSError, IOError) as e:
70 logger.warning(f"Could not access {item}: {e}")
71 continue
72
73 except PermissionError:
74 raise ValueError(f"Permission denied accessing directory '{path}'")
75
76 return {
77 "directory": str(path),
78 "total_items_shown": len(items),
79 "items": items,
80 "truncated": count >= max_items
81 }
🔧 Configuration File Setup
Create a configuration file to manage server settings:
config.json:
1{
2 "server": {
3 "name": "basic-mcp-server",
4 "version": "1.0.0",
5 "description": "Basic MCP server for Claude Code development",
6 "author": "Your Name"
7 },
8 "logging": {
9 "level": "INFO",
10 "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
11 "file": "mcp-server.log"
12 },
13 "tools": {
14 "enabled": [
15 "get_current_time",
16 "file_stats",
17 "generate_uuid",
18 "system_info"
19 ],
20 "rate_limits": {
21 "default": 100,
22 "file_stats": 50
23 }
24 },
25 "security": {
26 "allowed_paths": [
27 ".",
28 "~/",
29 "/tmp"
30 ],
31 "blocked_paths": [
32 "/etc/passwd",
33 "/etc/shadow",
34 "~/.ssh"
35 ]
36 }
37}
📋 Claude Code Configuration
Now configure Claude Code to use your MCP server:
Claude Code Settings (settings.json):
1{
2 "mcpServers": {
3 "basic-tools": {
4 "command": "python",
5 "args": ["/path/to/your/server.py"],
6 "env": {
7 "PYTHONPATH": "/path/to/your/project"
8 },
9 "description": "Basic utility tools for development",
10 "enabled": true,
11 "timeout": 30000
12 }
13 }
14}
Alternative: Using npm script for TypeScript:
1{
2 "mcpServers": {
3 "basic-tools-ts": {
4 "command": "npm",
5 "args": ["run", "start:mcp"],
6 "cwd": "/path/to/your/typescript/project",
7 "description": "TypeScript MCP server with basic tools",
8 "enabled": true
9 }
10 }
11}
🧪 Testing Your MCP Server
🔍 Manual Testing with Claude Code
- Start Claude Code with your MCP server configuration
- Verify Connection - You should see your MCP server listed in the status bar
- Test Basic Tools - Try these example prompts:
Test Prompt Examples:
# Test time tool
"What's the current time in human-readable format?"
# Test file statistics
"Can you analyze the stats of my project directory?"
# Test UUID generation
"Generate 5 UUIDs in compact format"
# Test system info
"Show me information about the current system"
📊 Expected Results
Successful Time Tool Response:
I'll get the current time for you in human-readable format.
Current time (human format): Friday, September 27, 2025 at 10:30:45 AM
File Statistics Response Example:
File statistics:
{
"path": "/Users/username/project",
"exists": true,
"is_file": false,
"is_directory": true,
"is_symlink": false,
"size_bytes": 4096,
"size_human": "4.0 KB",
"modified": "2025-09-27T10:30:00",
"created": "2025-09-27T09:15:00",
"permissions": "0o755"
}
🐛 Troubleshooting Common Issues
Problem: MCP Server Not Starting
1# Check Python path and dependencies
2python -c "import mcp; print('MCP installed successfully')"
3
4# Verify server script syntax
5python -m py_compile server.py
6
7# Check logs
8tail -f mcp-server.log
Problem: Claude Code Can’t Connect
- Verify the
command
path in settings.json - Ensure Python/Node.js is in PATH
- Check file permissions on server script
- Validate JSON syntax in configuration files
Problem: Tools Not Working
- Check server logs for error messages
- Verify tool schema matches expected inputs
- Test individual functions in isolation
- Ensure all required dependencies are installed
🚀 Extending Your Basic Server
🔧 Adding More Sophisticated Tools
Let’s add a few more useful tools to demonstrate extensibility:
Enhanced Server Features (server_extended.py):
1# Add these additional tools to your server
2
3@server.call_tool()
4async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
5 """Extended tool handler with more capabilities"""
6
7 # ... (previous tools) ...
8
9 elif name == "search_files":
10 pattern = arguments.get("pattern", "*")
11 directory = arguments.get("directory", ".")
12 case_sensitive = arguments.get("case_sensitive", False)
13
14 try:
15 path = Path(directory)
16 if not path.exists() or not path.is_dir():
17 return [TextContent(type="text", text=f"Directory '{directory}' not found")]
18
19 import fnmatch
20 matches = []
21
22 for file_path in path.rglob("*"):
23 if file_path.is_file():
24 filename = file_path.name
25 if not case_sensitive:
26 if fnmatch.fnmatch(filename.lower(), pattern.lower()):
27 matches.append(str(file_path.relative_to(path)))
28 else:
29 if fnmatch.fnmatch(filename, pattern):
30 matches.append(str(file_path.relative_to(path)))
31
32 result = {
33 "search_pattern": pattern,
34 "directory": str(path.absolute()),
35 "matches_found": len(matches),
36 "files": matches[:50] # Limit results
37 }
38
39 if len(matches) > 50:
40 result["note"] = "Results limited to first 50 matches"
41
42 return [TextContent(
43 type="text",
44 text=f"File search results:\n{json.dumps(result, indent=2)}"
45 )]
46
47 except Exception as e:
48 return [TextContent(type="text", text=f"Search error: {str(e)}")]
49
50 elif name == "calculate_expression":
51 expression = arguments.get("expression", "")
52
53 try:
54 # Safe evaluation of mathematical expressions
55 import ast
56 import operator
57
58 # Allowed operations for safety
59 operators = {
60 ast.Add: operator.add,
61 ast.Sub: operator.sub,
62 ast.Mult: operator.mul,
63 ast.Div: operator.truediv,
64 ast.Pow: operator.pow,
65 ast.USub: operator.neg,
66 ast.UAdd: operator.pos,
67 }
68
69 def eval_expr(node):
70 if isinstance(node, ast.Num):
71 return node.n
72 elif isinstance(node, ast.BinOp):
73 return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))
74 elif isinstance(node, ast.UnaryOp):
75 return operators[type(node.op)](eval_expr(node.operand))
76 else:
77 raise TypeError(f"Unsupported operation: {node}")
78
79 tree = ast.parse(expression, mode='eval')
80 result = eval_expr(tree.body)
81
82 return [TextContent(
83 type="text",
84 text=f"Expression: {expression}\nResult: {result}"
85 )]
86
87 except Exception as e:
88 return [TextContent(
89 type="text",
90 text=f"Calculation error for '{expression}': {str(e)}"
91 )]
📈 Performance Monitoring
Add basic performance monitoring to your server:
performance_monitor.py:
1import time
2import psutil
3from functools import wraps
4from typing import Dict, Any
5
6class PerformanceMonitor:
7 def __init__(self):
8 self.tool_stats = {}
9 self.server_start_time = time.time()
10
11 def monitor_tool(self, func):
12 """Decorator to monitor tool performance"""
13 @wraps(func)
14 async def wrapper(name: str, arguments: Dict[str, Any], *args, **kwargs):
15 start_time = time.time()
16 memory_before = psutil.Process().memory_info().rss
17
18 try:
19 result = await func(name, arguments, *args, **kwargs)
20
21 end_time = time.time()
22 memory_after = psutil.Process().memory_info().rss
23
24 # Record statistics
25 self.record_tool_stats(name, {
26 'execution_time': end_time - start_time,
27 'memory_delta': memory_after - memory_before,
28 'success': True
29 })
30
31 return result
32
33 except Exception as e:
34 end_time = time.time()
35 self.record_tool_stats(name, {
36 'execution_time': end_time - start_time,
37 'memory_delta': 0,
38 'success': False,
39 'error': str(e)
40 })
41 raise
42
43 return wrapper
44
45 def record_tool_stats(self, tool_name: str, stats: Dict[str, Any]):
46 """Record performance statistics for a tool"""
47 if tool_name not in self.tool_stats:
48 self.tool_stats[tool_name] = {
49 'call_count': 0,
50 'total_time': 0,
51 'avg_time': 0,
52 'success_count': 0,
53 'error_count': 0
54 }
55
56 tool_stats = self.tool_stats[tool_name]
57 tool_stats['call_count'] += 1
58 tool_stats['total_time'] += stats['execution_time']
59 tool_stats['avg_time'] = tool_stats['total_time'] / tool_stats['call_count']
60
61 if stats['success']:
62 tool_stats['success_count'] += 1
63 else:
64 tool_stats['error_count'] += 1
65
66 def get_stats_summary(self) -> Dict[str, Any]:
67 """Get performance summary"""
68 return {
69 'server_uptime': time.time() - self.server_start_time,
70 'tool_statistics': self.tool_stats,
71 'system_info': {
72 'cpu_percent': psutil.cpu_percent(),
73 'memory_percent': psutil.virtual_memory().percent,
74 'disk_usage': psutil.disk_usage('/').percent
75 }
76 }
🎯 Best Practices & Security Considerations
🔒 Security Guidelines
1. Input Validation:
1def validate_file_path(path_str: str, allowed_paths: List[str]) -> bool:
2 """Validate file paths against allowed directories"""
3 try:
4 path = Path(path_str).resolve()
5
6 for allowed in allowed_paths:
7 allowed_path = Path(allowed).resolve()
8 if path.is_relative_to(allowed_path):
9 return True
10
11 return False
12 except Exception:
13 return False
2. Rate Limiting:
1from collections import defaultdict
2import time
3
4class RateLimiter:
5 def __init__(self):
6 self.calls = defaultdict(list)
7 self.limits = {
8 'default': 100, # 100 calls per minute
9 'file_operations': 50,
10 'system_operations': 20
11 }
12
13 def is_allowed(self, tool_name: str) -> bool:
14 now = time.time()
15 minute_ago = now - 60
16
17 # Clean old entries
18 self.calls[tool_name] = [
19 call_time for call_time in self.calls[tool_name]
20 if call_time > minute_ago
21 ]
22
23 # Check limit
24 limit = self.limits.get(tool_name, self.limits['default'])
25 return len(self.calls[tool_name]) < limit
3. Error Handling:
1def safe_tool_execution(func):
2 """Decorator for safe tool execution with error handling"""
3 @wraps(func)
4 async def wrapper(*args, **kwargs):
5 try:
6 return await func(*args, **kwargs)
7 except FileNotFoundError as e:
8 return [TextContent(type="text", text=f"File not found: {e}")]
9 except PermissionError as e:
10 return [TextContent(type="text", text=f"Permission denied: {e}")]
11 except Exception as e:
12 logger.error(f"Tool execution error: {e}")
13 return [TextContent(type="text", text=f"Unexpected error: {e}")]
14
15 return wrapper
📋 Testing Your Setup
Create a test script to verify everything works:
test_mcp_server.py:
1#!/usr/bin/env python3
2"""
3Test script for MCP server functionality
4"""
5
6import asyncio
7import json
8import subprocess
9import sys
10from pathlib import Path
11
12async def test_server_startup():
13 """Test if the server starts correctly"""
14 try:
15 process = subprocess.Popen([
16 sys.executable, 'server.py'
17 ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
18
19 # Send initialize message
20 init_message = {
21 "jsonrpc": "2.0",
22 "id": 1,
23 "method": "initialize",
24 "params": {
25 "protocolVersion": "2024-11-05",
26 "capabilities": {}
27 }
28 }
29
30 process.stdin.write((json.dumps(init_message) + '\n').encode())
31 process.stdin.flush()
32
33 # Wait for response
34 response = process.stdout.readline()
35 process.terminate()
36
37 if response:
38 print("✅ Server startup test passed")
39 return True
40 else:
41 print("❌ Server startup test failed")
42 return False
43
44 except Exception as e:
45 print(f"❌ Server startup test error: {e}")
46 return False
47
48def test_configuration():
49 """Test configuration file validity"""
50 config_file = Path("config.json")
51
52 if not config_file.exists():
53 print("❌ Configuration file not found")
54 return False
55
56 try:
57 with open(config_file) as f:
58 config = json.load(f)
59
60 required_keys = ['server', 'logging', 'tools']
61 for key in required_keys:
62 if key not in config:
63 print(f"❌ Missing configuration key: {key}")
64 return False
65
66 print("✅ Configuration test passed")
67 return True
68
69 except json.JSONDecodeError as e:
70 print(f"❌ Configuration JSON error: {e}")
71 return False
72 except Exception as e:
73 print(f"❌ Configuration test error: {e}")
74 return False
75
76def test_dependencies():
77 """Test if all required dependencies are available"""
78 required_modules = ['mcp', 'asyncio', 'pathlib', 'json']
79
80 for module in required_modules:
81 try:
82 __import__(module)
83 print(f"✅ {module} available")
84 except ImportError:
85 print(f"❌ {module} not available")
86 return False
87
88 return True
89
90async def main():
91 """Run all tests"""
92 print("🧪 Testing MCP Server Setup...\n")
93
94 tests = [
95 ("Dependencies", test_dependencies),
96 ("Configuration", test_configuration),
97 ("Server Startup", test_server_startup),
98 ]
99
100 results = []
101 for test_name, test_func in tests:
102 print(f"Running {test_name} test...")
103 if asyncio.iscoroutinefunction(test_func):
104 result = await test_func()
105 else:
106 result = test_func()
107 results.append(result)
108 print()
109
110 # Summary
111 passed = sum(results)
112 total = len(results)
113
114 print(f"📊 Test Results: {passed}/{total} tests passed")
115
116 if passed == total:
117 print("🎉 All tests passed! Your MCP server is ready for Claude Code.")
118 else:
119 print("⚠️ Some tests failed. Please check the issues above.")
120
121if __name__ == "__main__":
122 asyncio.run(main())
🎯 What’s Next: Part 2 Preview
In Part 2 of this series, we’ll explore:
🚀 Advanced MCP Features
- Database Integration: Connect to PostgreSQL, MySQL, and SQLite
- API Connectors: Build tools for REST API integration
- Real-time Data: WebSocket connections and live data streaming
- File Processing: Advanced file manipulation and parsing tools
📊 Example Advanced Tools
- SQL Query Tool: Execute database queries with safety constraints
- API Testing Tool: Test and validate REST endpoints
- Log Analyzer: Parse and analyze log files
- Git Integration: Git operations and repository analysis
🏗️ Production Considerations
- Docker Deployment: Containerize your MCP servers
- Load Balancing: Handle multiple Claude Code instances
- Monitoring: Advanced metrics and health checks
- Security: Authentication, authorization, and audit logging
🎉 Conclusion
Congratulations! You’ve successfully built and configured your first MCP server for Claude Code. This basic setup provides:
✅ Four utility tools that Claude can use to help with development tasks ✅ Proper error handling and security considerations ✅ Extensible architecture ready for advanced features ✅ Testing framework to validate your setup ✅ Performance monitoring for optimization
Your Claude Code instance can now:
- Get system information and current time
- Analyze file and directory statistics
- Generate UUIDs in various formats
- Execute safe mathematical calculations
- Search for files using patterns
🔗 Helpful Resources
- MCP Documentation: Official MCP Protocol Docs
- Claude Code Settings: Configuration guide in Claude Code documentation
- GitHub Examples: MCP Server Examples Repository
- Community: Join the MCP developer community for support and examples
📝 Quick Reference Commands
1# Start your MCP server (for testing)
2python server.py
3
4# Test configuration
5python test_mcp_server.py
6
7# Check logs
8tail -f mcp-server.log
9
10# Validate JSON configuration
11python -m json.tool config.json
Next: Stay tuned for Part 2 where we’ll build production-ready MCP servers with database integration, API connectors, and advanced tooling capabilities!
🏷️ Tags & Categories
Tags: AI, mcp, claude-code, development-tools, automation, python, typescript, tooling Categories: AI, development-tools, automation Difficulty: Beginner to Intermediate Time to Complete: 2-3 hours