A reference manual for people who design and build MCP (Model Context Protocol) ecosystems
A reference manual for people who design and build MCP (Model Context Protocol) ecosystems
A reference manual for people who design and build MCP (Model Context Protocol) ecosystems
The Tool Maker's Journey
The Tool Maker's Journey
The Tool Maker's Journey
From simple file readers to complex integrations • The art of exposing capabilities • When to use a hammer vs. a Swiss Army knife
From simple file readers to complex integrations • The art of exposing capabilities • When to use a hammer vs. a Swiss Army knife
From simple file readers to complex integrations • The art of exposing capabilities • When to use a hammer vs. a Swiss Army knife
The Philosophy of Good Tools
The Philosophy of Good Tools
In 1978, Brian Kernighan wrote: "Make each program do one thing well." This Unix philosophy has guided tool makers for decades. But with MCP, we face a new question: How do we make tools that AI can use well? The answer isn't in the code—it's in understanding how AI thinks about capabilities. Let's explore this through a real example. Here are two ways to build the same tool:
In 1978, Brian Kernighan wrote: "Make each program do one thing well." This Unix philosophy has guided tool makers for decades. But with MCP, we face a new question: How do we make tools that AI can use well? The answer isn't in the code—it's in understanding how AI thinks about capabilities. Let's explore this through a real example. Here are two ways to build the same tool:
❌ The "Everything Button"
❌ The "Everything Button"
{ name: 'manage_files', description: 'Manage files - read, write, delete, move, copy, search, etc.', inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['read', 'write', 'delete', 'move', 'copy', 'search', 'list'] }, path: { type: 'string' }, content: { type: 'string' }, destination: { type: 'string' }, query: { type: 'string' } // ... 15 more properties for different operations } } }
✅ The Unix Philosophy Approach
✅ The Unix Philosophy Approach
[ { name: 'read_file', description: 'Read contents of a file', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'File path to read' } }, required: ['path'] } }, { name: 'write_file', description: 'Write content to a file', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'File path to write' }, content: { type: 'string', description: 'Content to write' } }, required: ['path', 'content'] } }, { name: 'search_files', description: 'Search for files containing specific text', inputSchema: { type: 'object', properties: { directory: { type: 'string', description: 'Directory to search in' }, query: { type: 'string', description: 'Text to search for' } }, required: ['directory', 'query'] } } ]
Why does the second approach work better? Because AI can:
Understand each tool's purpose instantly
Combine tools naturally for complex tasks
Provide better error messages when something fails
Learn which tool to use without ambiguity
Understand each tool's purpose instantly
Combine tools naturally for complex tasks
Provide better error messages when something fails
Learn which tool to use without ambiguity


rESPONSE TIME:
200 MS
low Error clarity:
“error”
“Invalid”
Combination Flexibility:
Not able to combine
rESPONSE TIME:
40 MS
high Error clarity:
“Missing field: note_id”
“Invalid date format”
Combination Flexibility:
Able to combine

rESPONSE TIME:
200 MS
low Error clarity:
“error”
“Invalid”
Combination Flexibility:
Not able to combine
rESPONSE TIME:
40 MS
high Error clarity:
“Missing field: note_id”
“Invalid date format”
Combination Flexibility:
Able to combine

rESPONSE TIME:
200 MS
low Error clarity:
“error”
“Invalid”
Combination Flexibility:
Not able to combine
rESPONSE TIME:
40 MS
high Error clarity:
“Missing field: note_id”
“Invalid date format”
Combination Flexibility:
Able to combine
7.1 Tool Design Workshop
7.1 Tool Design Workshop
The Anatomy of a Great Tool
The Anatomy of a Great Tool
Let's dissect what makes a tool genuinely useful by building a real-world example: a Git analytics tool.
Let's dissect what makes a tool genuinely useful by building a real-world example: a Git analytics tool.
Git analytics server
Git analytics server
// git-analytics-server.js - A tool that helps understand your coding patterns import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { simpleGit } from 'simple-git'; import path from 'path'; class GitAnalytics { constructor(repoPath) { this.git = simpleGit(repoPath); this.repoPath = repoPath; } // Tool 1: Analyze commit patterns async analyzeCommitPatterns(days = 30) { const since = new Date(); since.setDate(since.getDate() - days); const log = await this.git.log({ since: since.toISOString(), '--stat': true }); // Group commits by hour of day and day of week const patterns = { byHour: new Array(24).fill(0), byDayOfWeek: new Array(7).fill(0), byAuthor: {}, totalCommits: log.all.length, averageFilesChanged: 0, mostActiveFiles: {} }; let totalFiles = 0; for (const commit of log.all) { const date = new Date(commit.date); patterns.byHour[date.getHours()]++; patterns.byDayOfWeek[date.getDay()]++; // Track by author const author = commit.author_name; patterns.byAuthor[author] = (patterns.byAuthor[author] || 0) + 1; // Parse file changes from stat if (commit.diff && commit.diff.files) { totalFiles += commit.diff.files.length; for (const file of commit.diff.files) { patterns.mostActiveFiles[file.file] = (patterns.mostActiveFiles[file.file] || 0) + 1; } } } patterns.averageFilesChanged = totalFiles / log.all.length; // Sort most active files patterns.mostActiveFiles = Object.entries(patterns.mostActiveFiles) .sort(([,a], [,b]) => b - a) .slice(0, 10) .reduce((obj, [file, count]) => { obj[file] = count; return obj; }, {}); return patterns; } // Tool 2: Find code hotspots (files that change together) async findHotspots(threshold = 0.7) { const log = await this.git.log({ '--stat': true, '-n': 1000 // Last 1000 commits }); // Build co-occurrence matrix const coOccurrence = {}; for (const commit of log.all) { if (!commit.diff?.files || commit.diff.files.length < 2) continue; const files = commit.diff.files.map(f => f.file); // For each pair of files in this commit for (let i = 0; i < files.length; i++) { for (let j = i + 1; j < files.length; j++) { const pair = [files[i], files[j]].sort().join('::'); coOccurrence[pair] = (coOccurrence[pair] || 0) + 1; } } } // Find files that change together frequently const hotspots = Object.entries(coOccurrence) .filter(([_, count]) => count >= threshold * 10) // At least 7 times for default threshold .sort(([,a], [,b]) => b - a) .slice(0, 20) .map(([pair, count]) => { const [file1, file2] = pair.split('::'); return { file1, file2, count, correlation: count / 100 }; }); return hotspots; } // Tool 3: Generate productivity insights async getProductivityInsights() { const patterns = await this.analyzeCommitPatterns(90); // Last 90 days const insights = []; // Most productive hour const maxHour = patterns.byHour.indexOf(Math.max(...patterns.byHour)); insights.push({ type: 'productive_time', insight: `Most productive hour: ${maxHour}:00-${maxHour+1}:00`, data: { hour: maxHour, commits: patterns.byHour[maxHour] } }); // Work-life balance const weekendCommits = patterns.byDayOfWeek[0] + patterns.byDayOfWeek[6]; const weekdayCommits = patterns.totalCommits - weekendCommits; const weekendRatio = weekendCommits / patterns.totalCommits; if (weekendRatio > 0.3) { insights.push({ type: 'work_life_balance', insight: `${(weekendRatio * 100).toFixed(1)}% of commits on weekends - consider taking breaks!`, data: { weekendRatio, weekendCommits, weekdayCommits } }); } // File focus const topFiles = Object.entries(patterns.mostActiveFiles).slice(0, 3); insights.push({ type: 'file_focus', insight: `Top focus areas: ${topFiles.map(([f]) => path.basename(f)).join(', ')}`, data: { files: topFiles } }); // Team collaboration const authorCount = Object.keys(patterns.byAuthor).length; if (authorCount > 1) { const topContributor = Object.entries(patterns.byAuthor) .sort(([,a], [,b]) => b - a)[0]; insights.push({ type: 'collaboration', insight: `${authorCount} contributors, led by ${topContributor[0]} (${topContributor[1]} commits)`, data: { authorCount, topContributor } }); } return insights; } } // MCP Server setup const server = new Server( { name: 'git-analytics', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Initialize GitAnalytics with current directory or specified path const repoPath = process.argv[2] || process.cwd(); const analytics = new GitAnalytics(repoPath); // Tool handlers server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'analyze_commit_patterns': { const patterns = await analytics.analyzeCommitPatterns(args.days || 30); // Format response for readability const response = `# Commit Pattern Analysis (Last ${args.days || 30} days) ## Summary - Total commits: ${patterns.totalCommits} - Average files per commit: ${patterns.averageFilesChanged.toFixed(1)} - Contributors: ${Object.keys(patterns.byAuthor).length} ## Most Active Hours ${patterns.byHour.map((count, hour) => count > 0 ? `${hour}:00 - ${count} commits ${'█'.repeat(Math.ceil(count/5))}` : '' ).filter(Boolean).join('\n')} ## By Day of Week ${['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day, i) => `${day}: ${patterns.byDayOfWeek[i]} commits` ).join('\n')} ## Most Active Files ${Object.entries(patterns.mostActiveFiles) .map(([file, count]) => `- ${file}: ${count} changes`) .join('\n')}`; return { content: [{ type: 'text', text: response }] }; } case 'find_code_hotspots': { const hotspots = await analytics.findHotspots(args.threshold || 0.7); const response = hotspots.length > 0 ? `# Code Hotspots (Files that change together) ${hotspots.map(h => `## ${path.basename(h.file1)} ↔ ${path.basename(h.file2)} - Changed together ${h.count} times - Correlation: ${(h.correlation * 100).toFixed(1)}% - Files: ${h.file1} and ${h.file2}`).join('\n\n')} 💡 Consider: Are these files tightly coupled? Could they be refactored?` : 'No significant code hotspots found. Your code has good separation of concerns!'; return { content: [{ type: 'text', text: response }] }; } case 'get_productivity_insights': { const insights = await analytics.getProductivityInsights(); const response = `# Git Productivity Insights ${insights.map(insight => { switch(insight.type) { case 'productive_time': return `🕐 **Peak Productivity**: ${insight.insight}`; case 'work_life_balance': return `⚖️ **Work-Life Balance**: ${insight.insight}`; case 'file_focus': return `🎯 **Focus Areas**: ${insight.insight}`; case 'collaboration': return `👥 **Team Dynamics**: ${insight.insight}`; default: return `💡 ${insight.insight}`; } }).join('\n\n')}`; return { content: [{ type: 'text', text: response }] }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { // Helpful error messages if (error.message.includes('does not exist')) { return { content: [{ type: 'text', text: `Error: Not a git repository. Please run this server in a git repository directory.` }] }; } throw error; } }); // Tool definitions with clear schemas server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'analyze_commit_patterns', description: 'Analyze git commit patterns to understand coding habits', inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Number of days to analyze (default: 30)', minimum: 1, maximum: 365 } } } }, { name: 'find_code_hotspots', description: 'Find files that frequently change together (potential coupling)', inputSchema: { type: 'object', properties: { threshold: { type: 'number', description: 'Correlation threshold 0-1 (default: 0.7)', minimum: 0.5, maximum: 1.0 } } } }, { name: 'get_productivity_insights', description: 'Get personalized insights about coding productivity and patterns', inputSchema: { type: 'object', properties: {} } } ] }; }); const transport = new StdioServerTransport(); await server.connect(transport); console.error(`Git Analytics Server running for repository: ${repoPath}`);
This tool demonstrates several key principles:
This tool demonstrates several key principles:
Clear Purpose
Each tool does one specific thing
Rich Output
Returns formatted, actionable information
Smart Defaults
Works without parameters but accepts customization
Error Guidance
Helpful messages when things go wrong
Insights, Not Data
Interprets data to provide value

Heatmap
>>
Spikes at 10am–1pm
>>
Spikes at 8pm–11pm

Heatmap
>>
Spikes at 10am–1pm
>>
Spikes at 8pm–11pm

Heatmap
>>
Spikes at 10am–1pm
>>
Spikes at 8pm–11pm

File Relationship Graph
>>
Frequent co-change
>>
12 commits last month

File Relationship Graph
>>
Frequent co-change
>>
12 commits last month

File Relationship Graph
>>
Frequent co-change
>>
12 commits last month

Productivity insights
>>
70% commits on week
>>
You - 120 commits

Productivity insights
>>
70% commits on week
>>
You - 120 commits

Productivity insights
>>
70% commits on week
>>
You - 120 commits
The Goldilocks Principle
The Goldilocks Principle
Tools can be too simple or too complex. Finding "just right" is an art:
Tools can be too simple or too complex. Finding "just right" is an art:
SImple
SImple
// ❌ Too Simple - Not useful enough { name: 'get_time', description: 'Get current time' // Why: Claude already knows the time } // ❌ Too Complex - Cognitive overload { name: 'analyze_project', description: 'Analyze project code, dependencies, performance, security, tests, docs...', inputSchema: { // 50+ parameters } // Why: Too many responsibilities, unclear what it actually does } // ✅ Just Right - Clear purpose, composable { name: 'analyze_dependencies', description: 'Analyze project dependencies for security and update status', inputSchema: { type: 'object', properties: { includeDevDependencies: { type: 'boolean', description: 'Include dev dependencies in analysis', default: false }, securityOnly: { type: 'boolean', description: 'Only report security issues', default: false } } } // Why: Specific purpose, clear options, composable with other tools }
Bookmark Manager
// bookmark-manager.js - Thoughtfully designed bookmark tool import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import Database from 'better-sqlite3'; import fetch from 'node-fetch'; import { JSDOM } from 'jsdom'; import path from 'path'; import os from 'os'; class BookmarkManager { constructor() { // Store in user's home directory const dbPath = path.join(os.homedir(), '.mcp-bookmarks.db'); this.db = new Database(dbPath); this.initDatabase(); } initDatabase() { this.db.exec(` CREATE TABLE IF NOT EXISTS bookmarks ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, description TEXT, tags TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, accessed_count INTEGER DEFAULT 0, last_accessed DATETIME, is_archived BOOLEAN DEFAULT 0 ) `); } // Smart extraction of metadata async extractMetadata(url) { try { const response = await fetch(url, { timeout: 5000 }); const html = await response.text(); const dom = new JSDOM(html); const doc = dom.window.document; // Try various metadata sources const title = doc.querySelector('meta[property="og:title"]')?.content || doc.querySelector('title')?.textContent || new URL(url).hostname; const description = doc.querySelector('meta[property="og:description"]')?.content || doc.querySelector('meta[name="description"]')?.content || ''; // Auto-generate tags from URL and content const autoTags = []; const urlParts = new URL(url).hostname.split('.'); autoTags.push(urlParts[urlParts.length - 2]); // Domain name if (url.includes('github.com')) autoTags.push('code', 'github'); if (url.includes('youtube.com')) autoTags.push('video', 'youtube'); if (url.includes('arxiv.org')) autoTags.push('research', 'paper'); return { title: title.trim().substring(0, 200), description: description.trim().substring(0, 500), suggestedTags: autoTags }; } catch (error) { console.error('Failed to extract metadata:', error); return { title: new URL(url).hostname, description: '', suggestedTags: [] }; } } // Tool implementations async addBookmark(url, { title, description, tags = [] } = {}) { // Extract metadata if not provided if (!title || !description) { const metadata = await this.extractMetadata(url); title = title || metadata.title; description = description || metadata.description; tags = [...tags, ...metadata.suggestedTags]; } try { const stmt = this.db.prepare(` INSERT INTO bookmarks (url, title, description, tags) VALUES (?, ?, ?, ?) `); const result = stmt.run( url, title, description, [...new Set(tags)].join(',') // Unique tags ); return { id: result.lastInsertRowid, url, title, description, tags: [...new Set(tags)] }; } catch (error) { if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') { // Update existing bookmark const updateStmt = this.db.prepare(` UPDATE bookmarks SET accessed_count = accessed_count + 1, last_accessed = CURRENT_TIMESTAMP WHERE url = ? `); updateStmt.run(url); const existing = this.db.prepare('SELECT * FROM bookmarks WHERE url = ?').get(url); return { ...existing, tags: existing.tags.split(','), updated: true }; } throw error; } } searchBookmarks({ query, tags = [], limit = 20 }) { let sql = ` SELECT * FROM bookmarks WHERE is_archived = 0 `; const params = []; if (query) { sql += ` AND (title LIKE ? OR description LIKE ? OR url LIKE ?)`; const searchTerm = `%${query}%`; params.push(searchTerm, searchTerm, searchTerm); } if (tags.length > 0) { const tagConditions = tags.map(() => `tags LIKE ?`).join(' AND '); sql += ` AND (${tagConditions})`; tags.forEach(tag => params.push(`%${tag}%`)); } sql += ` ORDER BY accessed_count DESC, created_at DESC LIMIT ?`; params.push(limit); const results = this.db.prepare(sql).all(...params); return results.map(bookmark => ({ ...bookmark, tags: bookmark.tags ? bookmark.tags.split(',') : [] })); } getSmartSuggestions() { // Get recently accessed const recent = this.db.prepare(` SELECT * FROM bookmarks WHERE last_accessed IS NOT NULL ORDER BY last_accessed DESC LIMIT 5 `).all(); // Get frequently accessed const frequent = this.db.prepare(` SELECT * FROM bookmarks WHERE accessed_count > 2 ORDER BY accessed_count DESC LIMIT 5 `).all(); // Get by time of day const hour = new Date().getHours(); const timeBasedTags = hour < 12 ? ['news', 'morning'] : hour < 17 ? ['work', 'reference'] : ['entertainment', 'leisure']; const timeBased = this.db.prepare(` SELECT * FROM bookmarks WHERE ${timeBasedTags.map(() => 'tags LIKE ?').join(' OR ')} LIMIT 3 `).all(...timeBasedTags.map(tag => `%${tag}%`)); return { recent: recent.map(b => ({ ...b, tags: b.tags?.split(',') || [] })), frequent: frequent.map(b => ({ ...b, tags: b.tags?.split(',') || [] })), recommended: timeBased.map(b => ({ ...b, tags: b.tags?.split(',') || [] })) }; } } // MCP Server setup const server = new Server( { name: 'bookmark-manager', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); const bookmarks = new BookmarkManager(); server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'add_bookmark': { const result = await bookmarks.addBookmark(args.url, { title: args.title, description: args.description, tags: args.tags }); return { content: [{ type: 'text', text: result.updated ? `Updated existing bookmark: ${result.title}` : `Added bookmark: ${result.title}\nTags: ${result.tags.join(', ')}` }] }; } case 'search_bookmarks': { const results = bookmarks.searchBookmarks({ query: args.query, tags: args.tags, limit: args.limit }); if (results.length === 0) { return { content: [{ type: 'text', text: 'No bookmarks found matching your search.' }] }; } const response = results.map(b => `**${b.title}**\n${b.url}\n${b.description || 'No description'}\nTags: ${b.tags.join(', ')}\n` ).join('\n---\n\n'); return { content: [{ type: 'text', text: `Found ${results.length} bookmarks:\n\n${response}` }] }; } case 'get_bookmark_suggestions': { const suggestions = bookmarks.getSmartSuggestions(); const response = `# Bookmark Suggestions ## Recently Used ${suggestions.recent.map(b => `- **${b.title}** - ${b.url}`).join('\n') || 'None yet'} ## Frequently Accessed ${suggestions.frequent.map(b => `- **${b.title}** (${b.accessed_count} times)`).join('\n') || 'None yet'} ## Recommended for This Time ${suggestions.recommended.map(b => `- **${b.title}** - ${b.tags.join(', ')}`).join('\n') || 'None yet'}`; return { content: [{ type: 'text', text: response }] }; } default: throw new Error(`Unknown tool: ${name}`); } }); server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'add_bookmark', description: 'Add or update a bookmark with automatic metadata extraction', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to bookmark' }, title: { type: 'string', description: 'Optional title (auto-extracted if not provided)' }, description: { type: 'string', description: 'Optional description (auto-extracted if not provided)' }, tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' } }, required: ['url'] } }, { name: 'search_bookmarks', description: 'Search bookmarks by text or tags', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search in title, description, and URL' }, tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (AND operation)' }, limit: { type: 'number', description: 'Maximum results to return', default: 20 } } } }, { name: 'get_bookmark_suggestions', description: 'Get smart bookmark suggestions based on usage patterns', inputSchema: { type: 'object', properties: {} } } ] }; }); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Bookmark Manager Server running...');
Not too simple
Auto-extracts metadata, tracks usage, provides suggestions
Not too complex
Just three focused tools that work together
Just right
Solves a real problem with thoughtful features



Not too complex
Not too simple
Just right.
Not too complex
Not too simple
Just right.
Not too complex
Not too simple
Just right.
7.2 MCP Principle
7.2 MCP Principle
The Composition Symphony
The Composition Symphony
Great tools work better together. Let's see how our tools can orchestrate:
Great tools work better together. Let's see how our tools can orchestrate:
Connect CLaude
Connect CLaude
// Example: A week in review using multiple tools You: Can you give me a summary of my week? Claude: I'll analyze your week across different areas. Let me gather information from your various tools. [Claude uses git-analytics to check coding activity] [Claude uses bookmark-manager to see what you've been reading] [Claude uses note-keeper to review your notes] [Claude uses calendar tool to check meetings] Here's your week in review: ## 💻 Coding Activity You had a productive week with 47 commits across 3 projects: - Peak productivity was Wednesday 2-4 PM (12 commits) - Main focus: Refactoring the authentication module - You worked with Sarah on 3 pull requests ## 📚 Learning & Research You bookmarked 15 articles, primarily about: - Rust async programming (5 articles) - System design patterns (4 articles) - MCP development (6 articles) Most visited: "Understanding Rust Lifetimes" (accessed 4 times) ## 📝 Notes & Ideas You created 8 notes this week: - 3 about the authentication refactor - 2 brainstorming session notes - 3 learning notes about Rust Key insight from your notes: "Consider using JWT refresh tokens for better security" ## 📅 Time Management - 12 meetings (18 hours total) - Longest focused coding session: Thursday morning (3.5 hours) - You maintained good work-life balance with no weekend commits Would you like me to help you plan next week based on these patterns?



7.3 Composition Playground
7.3 Composition Playground
This is the power of composition - simple tools combining to create rich insights.
This is the power of composition - simple tools combining to create rich insights.
Error Handling: Failing Gracefully
Error Handling: Failing Gracefully
The difference between a good tool and a great tool is how it fails:
The difference between a good tool and a great tool is how it fails:
Performance Metrics
Performance Metrics
// Error handling patterns for MCP tools class ToolError extends Error { constructor(message, code, suggestions = []) { super(message); this.code = code; this.suggestions = suggestions; } } // Pattern 1: Validate inputs early function validateUrl(url) { try { const parsed = new URL(url); if (!['http:', 'https:'].includes(parsed.protocol)) { throw new ToolError( 'URL must use HTTP or HTTPS protocol', 'INVALID_PROTOCOL', [`Try: https://${parsed.host}${parsed.pathname}`] ); } return parsed; } catch (error) { if (error instanceof ToolError) throw error; throw new ToolError( 'Invalid URL format', 'INVALID_URL', [ 'Make sure URL includes protocol (https://)', 'Check for special characters that need encoding', `Example: https://example.com/page` ] ); } } // Pattern 2: Graceful degradation async function fetchWithFallback(url, options = {}) { try { const response = await fetch(url, { timeout: 5000, ...options }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.text(); } catch (error) { // Try alternative approaches if (error.message.includes('timeout')) { // Try with longer timeout try { const response = await fetch(url, { timeout: 15000, ...options }); return await response.text(); } catch { throw new ToolError( 'Website is not responding', 'TIMEOUT', [ 'Check if the website is online', 'Try again in a few moments', 'Consider using cached data if available' ] ); } } if (error.message.includes('ENOTFOUND')) { throw new ToolError( 'Website not found', 'NOT_FOUND', [ 'Check the URL for typos', 'Verify the website exists', `Try searching for: ${new URL(url).hostname}` ] ); } throw error; } } // Pattern 3: Progressive enhancement async function analyzeText(text, options = {}) { const results = { basic: null, advanced: null, premium: null }; // Always provide basic analysis results.basic = { wordCount: text.split(/\s+/).length, charCount: text.length, lineCount: text.split('\n').length }; // Try advanced analysis try { if (options.includeReadability) { results.advanced = await calculateReadability(text); } } catch (error) { console.warn('Advanced analysis failed:', error); // Continue with basic results } // Try premium features if available try { if (options.includeSentiment && hasSentimentAPI()) { results.premium = await analyzeSentiment(text); } } catch (error) { console.warn('Premium analysis unavailable:', error); } return results; } // Pattern 4: Helpful error responses server.setRequestHandler('tools/call', async (request) => { try { return await handleToolCall(request); } catch (error) { if (error instanceof ToolError) { // Structured error response return { content: [{ type: 'text', text: `❌ ${error.message}\n\n` + `Error code: ${error.code}\n\n` + `💡 Suggestions:\n${error.suggestions.map(s => `- ${s}`).join('\n')}` }] }; } // Unexpected errors - still helpful console.error('Unexpected error:', error); return { content: [{ type: 'text', text: `❌ An unexpected error occurred: ${error.message}\n\n` + `💡 Try:\n` + `- Checking your input parameters\n` + `- Ensuring the server has necessary permissions\n` + `- Reviewing the server logs for details` }] }; } });

unauthorized
Close

unauthorized
Close

unauthorized
Close
7.4 Error Scenario Simulator
7.4 Error Scenario Simulator
Performance: Speed Matters
Performance: Speed Matters
Let's look at real performance optimization for MCP tools:
Let's look at real performance optimization for MCP tools:
Performance Responsive
Performance Responsive
// Performance patterns for responsive tools // Pattern 1: Streaming responses for long operations async function* analyzeDirectory(dirPath) { const files = await fs.readdir(dirPath); yield { type: 'progress', message: `Found ${files.length} files to analyze` }; let processed = 0; for (const file of files) { const filePath = path.join(dirPath, file); const stats = await fs.stat(filePath); if (stats.isFile()) { const analysis = await analyzeFile(filePath); processed++; yield { type: 'result', file: file, analysis: analysis, progress: processed / files.length }; } } yield { type: 'complete', total: processed }; } // Pattern 2: Intelligent caching class CachedAnalyzer { constructor() { this.cache = new Map(); this.cacheStats = { hits: 0, misses: 0 }; } getCacheKey(filePath, mtime) { return `${filePath}:${mtime}`; } async analyzeWithCache(filePath) { const stats = await fs.stat(filePath); const cacheKey = this.getCacheKey(filePath, stats.mtime.getTime()); // Check cache if (this.cache.has(cacheKey)) { this.cacheStats.hits++; return this.cache.get(cacheKey); } this.cacheStats.misses++; // Perform analysis const result = await this.performAnalysis(filePath); // Cache result this.cache.set(cacheKey, result); // Limit cache size if (this.cache.size > 1000) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } return result; } } // Pattern 3: Batch operations class BatchProcessor { constructor(batchSize = 10, delayMs = 100) { this.batchSize = batchSize; this.delayMs = delayMs; this.queue = []; this.processing = false; } async add(item) { this.queue.push(item); if (!this.processing) { this.processing = true; setTimeout(() => this.processBatch(), this.delayMs); } } async processBatch() { const batch = this.queue.splice(0, this.batchSize); if (batch.length > 0) { // Process batch in parallel const results = await Promise.all( batch.map(item => this.processItem(item)) ); // Notify results batch.forEach((item, i) => { item.resolve(results[i]); }); } if (this.queue.length > 0) { setTimeout(() => this.processBatch(), this.delayMs); } else { this.processing = false; } } } // Pattern 4: Early termination async function searchWithEarlyTermination(query, options = {}) { const maxResults = options.maxResults || 10; const timeLimit = options.timeLimit || 5000; const startTime = Date.now(); const results = []; for await (const result of searchGenerator(query)) { results.push(result); // Check termination conditions if (results.length >= maxResults) { break; } if (Date.now() - startTime > timeLimit) { console.warn(`Search time limit exceeded, returning ${results.length} results`); break; } } return results; }



7.5 Performance Lab
7.5 Performance Lab
Testing Your Tools
Testing Your Tools
Great tools have great tests:
Great tools have great tests:
Testing PAttern
Testing PAttern
// Testing patterns for MCP tools import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { TestClient } from '@modelcontextprotocol/sdk/testing'; describe('GitAnalytics Tool', () => { let client; let testRepoPath; beforeEach(async () => { // Create test repository testRepoPath = await createTestRepo(); // Create test client client = new TestClient({ command: 'node', args: ['./git-analytics-server.js', testRepoPath] }); await client.start(); }); afterEach(async () => { await client.stop(); await cleanupTestRepo(testRepoPath); }); describe('analyze_commit_patterns', () => { it('should analyze commit patterns correctly', async () => { // Create test commits await createTestCommits(testRepoPath, [ { message: 'Initial commit', date: '2024-03-01T09:00:00Z' }, { message: 'Add feature', date: '2024-03-01T14:00:00Z' }, { message: 'Fix bug', date: '2024-03-02T10:00:00Z' } ]); // Call tool const result = await client.callTool('analyze_commit_patterns', { days: 30 }); // Verify results expect(result.content[0].text).toContain('Total commits: 3'); expect(result.content[0].text).toContain('9:00 - 1 commits'); expect(result.content[0].text).toContain('14:00 - 1 commits'); }); it('should handle empty repository gracefully', async () => { const result = await client.callTool('analyze_commit_patterns', { days: 30 }); expect(result.content[0].text).toContain('Total commits: 0'); }); }); describe('error handling', () => { it('should provide helpful error for non-git directory', async () => { // Point to non-git directory const nonGitClient = new TestClient({ command: 'node', args: ['./git-analytics-server.js', '/tmp'] }); await nonGitClient.start(); const result = await nonGitClient.callTool('analyze_commit_patterns'); expect(result.content[0].text).toContain('Not a git repository'); await nonGitClient.stop(); }); }); }); // Integration test example describe('Multi-tool Integration', () => { it('should work with bookmark manager', async () => { // Start both servers const gitClient = await TestClient.start('./git-analytics-server.js'); const bookmarkClient = await TestClient.start('./bookmark-manager.js'); // Simulate workflow const gitInsights = await gitClient.callTool('get_productivity_insights'); // Extract recommendation from insights const recommendation = extractRecommendation(gitInsights); // Save as bookmark const bookmark = await bookmarkClient.callTool('add_bookmark', { url: 'https://example.com/productivity-tips', title: 'Productivity Tips', tags: ['productivity', 'automated'] }); expect(bookmark.content[0].text).toContain('Added bookmark'); }); });



Get immediate feedback
Run test
7.6 Test Workshop
7.6 Test Workshop
The Tool Maker's Mindset
The Tool Maker's Mindset
Let's be honest—things don't always work the first time. Here's how to debug MCP connections:
Let's be honest—things don't always work the first time. Here's how to debug MCP connections:
1. Start with the conversation
1. Start with the conversation
User
I wish I could...
I wish I could...
Your tool
Now you can!
Now you can!
2. Design for discovery
2. Design for discovery
// Bad: Generic names 'process_data' 'handle_request' 'do_operation' // Good: Specific, discoverable names 'analyze_code_complexity' 'generate_test_cases' 'optimize_image_size'
3. Embrace constraints
3. Embrace constraints
Each tool should do ONE thing well
Clear inputs produce clear outputs
Errors should guide, not frustrate
Each tool should do ONE thing well
Clear inputs produce clear outputs
Errors should guide, not frustrate
4. Think in workflows
4. Think in workflows
How will this tool combine with others?
What's the next logical step after using this tool?
What context might the AI need?
How will this tool combine with others?
What's the next logical step after using this tool?
What context might the AI need?

Scenario challenges
Solution #1
+210 Votes
Solution #2
+189 Votes
Solution #2
+105 Votes

Scenario challenges
Solution #1
+210 Votes
Solution #2
+189 Votes
Solution #2
+105 Votes

Scenario challenges
Solution #1
+210 Votes
Solution #2
+189 Votes
Solution #2
+105 Votes

Scenario challenges
Solution #1
+210 Votes
Solution #2
+189 Votes
Solution #2
+105 Votes

Scenario challenges
Solution #1
+210 Votes
Solution #2
+189 Votes
Solution #2
+105 Votes
7.7 Tool Design Challenge
7.7 Tool Design Challenge
Real Tools in Production
Real Tools in Production
Let's examine some production MCP tools and what makes them excellent
Let's examine some production MCP tools and what makes them excellent
File System
File System
// From the official MCP filesystem server { name: 'read_file', description: 'Read the complete contents of a file', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Path to the file to read' } }, required: ['path'] } } // Why it's good: // - Crystal clear purpose // - Minimal parameters // - Obvious behavior // - Composable with write_file, search_file, etc. // From the Slack MCP server { name: 'send_message', description: 'Send a message to a Slack channel', inputSchema: { type: 'object', properties: { channel: { type: 'string', description: 'Channel name (without #) or channel ID' }, text: { type: 'string', description: 'Message text (supports basic Slack markdown)' }, thread_ts: { type: 'string', description: 'Thread timestamp to reply in thread (optional)' } }, required: ['channel', 'text'] } }
Why it’s good:
Why it’s good:
Covers 80% use case simply (channel + text)
Optional parameter for advanced use (threads)
Clear description of format expectations
Returns useful data (timestamp, permalink)
Covers 80% use case simply (channel + text)
Optional parameter for advanced use (threads)
Clear description of format expectations
Returns useful data (timestamp, permalink)
Your Tool Portfolio
Your Tool Portfolio
As you build more tools, think about creating a cohesive portfolio:
As you build more tools, think about creating a cohesive portfolio:
My tool suite
My tool suite
// A well-designed tool portfolio example const myToolSuite = { // Data Tools 'csv-analyzer': 'Analyze CSV files for patterns and anomalies', 'json-transformer': 'Transform JSON data between formats', 'sql-query-builder': 'Build and execute safe SQL queries', // Productivity Tools 'task-tracker': 'Track tasks with time estimates', 'meeting-summarizer': 'Summarize meeting notes into actions', 'email-drafter': 'Draft emails from bullet points', // Development Tools 'code-reviewer': 'Review code for common issues', 'dependency-analyzer': 'Analyze and update dependencies', 'test-generator': 'Generate test cases from code', // Personal Tools 'habit-tracker': 'Track daily habits and streaks', 'expense-categorizer': 'Categorize expenses automatically', 'recipe-suggester': 'Suggest recipes from available ingredients' }; // These tools work together: // - csv-analyzer → sql-query-builder (analyze then query) // - meeting-summarizer → task-tracker (extract then track) // - code-reviewer → test-generator (review then test)



7.8 Portfolio Builder
7.8 Portfolio Builder
The Future You're Building
The Future You're Building
Every tool you create expands what's possible with AI:
Every tool you create expands what's possible with AI:
Building MCP
Building MCP
// Today: Single-purpose tools await mcp.callTool('read_file', { path: 'data.csv' }); // Tomorrow: Intelligent tool chains await mcp.orchestrate('analyze business data and create report'); // MCP automatically: // 1. Reads multiple data sources // 2. Combines and analyzes // 3. Generates visualizations // 4. Creates report // 5. Sends to stakeholders // The future: Adaptive tools that learn await mcp.evolve('improve my workflow'); // MCP observes your patterns and creates custom tools

old tools
Single-purpose
Intelligent tool chains
Adaptive tools
Now
Future

old tools
Single-purpose
Intelligent tool chains
Adaptive tools
Now
Future

old tools
Single-purpose
Intelligent tool chains
Adaptive tools
Now
Future
7.9 Tool Evolution Timeline
7.9 Tool Evolution Timeline