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
Resources: The Library of Context
Resources: The Library of Context
Resources: The Library of Context
Teaching AI to read your world • The difference between showing and doing • Building context that scales
Teaching AI to read your world • The difference between showing and doing • Building context that scales
Teaching AI to read your world • The difference between showing and doing • Building context that scales
The Read-Only Revolution
The Read-Only Revolution
In 2023, a fascinating pattern emerged in the early MCP community. Developers kept trying to make Tools do everything—read files, write files, delete files, all in one. Then someone asked a simple question on the community Discord:
In 2023, a fascinating pattern emerged in the early MCP community. Developers kept trying to make Tools do everything—read files, write files, delete files, all in one. Then someone asked a simple question on the community Discord:
"What if reading and writing were fundamentally different operations?"
"What if reading and writing were fundamentally different operations?"
This sparked the insight that became Resources: AI needs to observe far more than it needs to act.
This sparked the insight that became Resources: AI needs to observe far more than it needs to act.
Think about your own workflow. For every file you modify, how many do you read? For every database record you update, how many do you query? The ratio is often 100:1 or higher. Resources embrace this reality.
Think about your own workflow. For every file you modify, how many do you read? For every database record you update, how many do you query? The ratio is often 100:1 or higher. Resources embrace this reality.
Revelation
// The revelation: Separate reading from writing // ❌ Old approach: Everything is a tool { name: 'file_operation', inputSchema: { operation: { enum: ['read', 'write', 'delete'] }, path: { type: 'string' }, content: { type: 'string' } // Only used for write } } // ✅ New approach: Resources for reading, Tools for writing // Resource: { uri: 'file:///project/src', name: 'Source Code', mimeType: 'text/directory' } // Tool: { name: 'write_file', inputSchema: { path: { type: 'string' }, content: { type: 'string' } } }
This separation unlocked something powerful: Resources can be cached, prefetched, indexed, and optimized in ways that Tools cannot.
This separation unlocked something powerful: Resources can be cached, prefetched, indexed, and optimized in ways that Tools cannot.



Cache
Permissions
Cache
Permissions
8.1 Resource vs Tool Visualizer
8.1 Resource vs Tool Visualizer
The URI: Your Resource's Address
The URI: Your Resource's Address
The MCP community learned from decades of web architecture: URIs (Uniform Resource Identifiers) are incredibly powerful. Here's how successful MCP resources use them:
The MCP community learned from decades of web architecture: URIs (Uniform Resource Identifiers) are incredibly powerful. Here's how successful MCP resources use them:
URI Pattern
URI Pattern
// Community-evolved URI patterns // 1. Hierarchical Resources (filesystem-like) 'file:///home/user/documents/report.md' 'file:///home/user/documents/' // Directory listing // 2. Database Resources 'postgres://database/users/123' // Specific record 'postgres://database/users?status=active' // Query // 3. API Resources 'github://repos/owner/name/issues/42' 'github://repos/owner/name/issues?state=open' // 4. Virtual Resources (computed on-demand) 'system://memory' // Current memory usage 'system://cpu' // CPU statistics 'analytics://dashboard/weekly-summary' // 5. Composite Resources 'project://my-app/overview' // Combines multiple data sources // Implementation showing URI best practices class ResourceProvider { async resolveUri(uri) { const parsed = new URL(uri); switch (parsed.protocol) { case 'file:': return this.handleFileResource(parsed.pathname, parsed.search); case 'postgres:': return this.handleDatabaseResource( parsed.hostname, // database name parsed.pathname, // table/record path parsed.search // query parameters ); case 'analytics:': return this.handleAnalyticsResource(parsed.pathname); default: throw new Error(`Unsupported protocol: ${parsed.protocol}`); } } async handleFileResource(path, queryString) { const params = new URLSearchParams(queryString); // Support filtering via query params if (params.has('extension')) { return this.listFilesByExtension(path, params.get('extension')); } if (params.has('modified')) { return this.listRecentlyModified(path, params.get('modified')); } // Default: return file content or directory listing const stats = await fs.stat(path); if (stats.isDirectory()) { const entries = await fs.readdir(path, { withFileTypes: true }); return { mimeType: 'application/json', data: entries.map(entry => ({ name: entry.name, type: entry.isDirectory() ? 'directory' : 'file', uri: `file://${path}/${entry.name}` })) }; } else { const content = await fs.readFile(path, 'utf-8'); return { mimeType: mime.lookup(path) || 'text/plain', data: content }; } } }





Resources That Tell Stories
Resources That Tell Stories
The best resources don't just provide data—they provide context and narrative. Here's a real example evolved from community feedback:
The best resources don't just provide data—they provide context and narrative. Here's a real example evolved from community feedback:
Code Context Server
Code Context Server
// code-context-server.js - A resource that understands your code import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import * as ts from 'typescript'; import * as babel from '@babel/parser'; import path from 'path'; import fs from 'fs/promises'; class CodeContextProvider { constructor(projectRoot) { this.projectRoot = projectRoot; this.cache = new Map(); } // Provide rich context about a code file async getFileContext(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const ext = path.extname(filePath); let context = { uri: `file://${filePath}`, content: content, metadata: { lines: content.split('\n').length, size: content.length, lastModified: (await fs.stat(filePath)).mtime } }; // Language-specific analysis if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) { context.analysis = await this.analyzeJavaScript(content, filePath); } else if (['.py'].includes(ext)) { context.analysis = await this.analyzePython(content); } // Find related files context.related = await this.findRelatedFiles(filePath); // Extract TODOs and FIXMEs context.annotations = this.extractAnnotations(content); return context; } async analyzeJavaScript(content, filePath) { const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); try { const ast = babel.parse(content, { sourceType: 'module', plugins: [ 'jsx', isTypeScript && 'typescript', 'classProperties', 'asyncGenerators', 'objectRestSpread' ].filter(Boolean) }); const analysis = { imports: [], exports: [], functions: [], classes: [], complexity: 0 }; // Traverse AST to extract structure babel.traverse(ast, { ImportDeclaration(path) { analysis.imports.push({ source: path.node.source.value, specifiers: path.node.specifiers.map(spec => spec.local.name) }); }, ExportNamedDeclaration(path) { if (path.node.declaration) { analysis.exports.push({ type: 'named', name: path.node.declaration.id?.name || 'anonymous' }); } }, FunctionDeclaration(path) { analysis.functions.push({ name: path.node.id?.name || 'anonymous', params: path.node.params.length, async: path.node.async, lines: path.node.loc.end.line - path.node.loc.start.line }); analysis.complexity += this.calculateComplexity(path); }, ClassDeclaration(path) { analysis.classes.push({ name: path.node.id?.name || 'anonymous', methods: path.node.body.body .filter(member => member.type === 'ClassMethod') .map(method => method.key.name) }); } }); return analysis; } catch (error) { console.error('Failed to parse JavaScript:', error); return { parseError: error.message }; } } async findRelatedFiles(filePath) { const related = []; const fileName = path.basename(filePath, path.extname(filePath)); const dirPath = path.dirname(filePath); // Look for test files const testPatterns = [ `${fileName}.test.*`, `${fileName}.spec.*`, `__tests__/${fileName}.*` ]; // Look for related files (same name, different extension) const files = await fs.readdir(dirPath); for (const file of files) { const baseName = path.basename(file, path.extname(file)); // Test files if (file.includes('.test.') || file.includes('.spec.')) { if (baseName.includes(fileName)) { related.push({ type: 'test', uri: `file://${path.join(dirPath, file)}` }); } } // Style files if (baseName === fileName && ['.css', '.scss', '.module.css'].includes(path.extname(file))) { related.push({ type: 'style', uri: `file://${path.join(dirPath, file)}` }); } // Component files if (baseName === fileName && file !== path.basename(filePath)) { related.push({ type: 'related', uri: `file://${path.join(dirPath, file)}` }); } } return related; } extractAnnotations(content) { const annotations = []; const lines = content.split('\n'); const patterns = [ { pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|NOTE):?\s*(.+)/, type: 'comment' }, { pattern: /#\s*(TODO|FIXME|HACK|XXX|NOTE):?\s*(.+)/, type: 'comment' }, { pattern: /@(todo|deprecated|throws|param|returns)/, type: 'jsdoc' } ]; lines.forEach((line, index) => { for (const { pattern, type } of patterns) { const match = line.match(pattern); if (match) { annotations.push({ line: index + 1, type: type, tag: match[1], text: match[2] || line.trim(), severity: match[1] === 'FIXME' ? 'high' : 'normal' }); } } }); return annotations; } calculateComplexity(path) { let complexity = 1; path.traverse({ IfStatement() { complexity++; }, ForStatement() { complexity++; }, WhileStatement() { complexity++; }, DoWhileStatement() { complexity++; }, SwitchCase() { complexity++; }, CatchClause() { complexity++; }, ConditionalExpression() { complexity++; }, LogicalExpression({ node }) { if (node.operator === '&&' || node.operator === '||') { complexity++; } } }); return complexity; } } // MCP Server implementation const server = new Server( { name: 'code-context', version: '1.0.0', }, { capabilities: { resources: {} } } ); const projectRoot = process.argv[2] || process.cwd(); const provider = new CodeContextProvider(projectRoot); // List available resources server.setRequestHandler('resources/list', async () => { // Scan for code files const codeFiles = await scanForCodeFiles(projectRoot); return { resources: [ { uri: `code:///${projectRoot}`, name: 'Project Overview', mimeType: 'application/json', description: 'Overview of the entire project structure' }, ...codeFiles.slice(0, 100).map(file => ({ uri: `code://file${file}`, name: path.relative(projectRoot, file), mimeType: 'application/json', description: 'Rich context about this code file' })), { uri: `code:///search`, name: 'Code Search', mimeType: 'application/json', description: 'Search across all code files' } ] }; }); // Read specific resources server.setRequestHandler('resources/read', async (request) => { const { uri } = request.params; const parsed = new URL(uri); if (parsed.pathname === '/' && parsed.hostname === '') { // Project overview const overview = await generateProjectOverview(projectRoot); return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify(overview, null, 2) }] }; } if (parsed.pathname.startsWith('/file')) { // Specific file context const filePath = parsed.pathname.substring(5); // Remove '/file' prefix const context = await provider.getFileContext(filePath); return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify(context, null, 2) }] }; } if (parsed.pathname === '/search') { // Search functionality const query = parsed.searchParams.get('q'); const type = parsed.searchParams.get('type') || 'all'; const results = await searchCode(projectRoot, query, type); return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify(results, null, 2) }] }; } throw new Error(`Unknown resource: ${uri}`); }); // Helper functions async function scanForCodeFiles(dir, files = []) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { // Skip common directories if (!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) { await scanForCodeFiles(fullPath, files); } } else if (entry.isFile()) { // Include code files if (/\.(js|jsx|ts|tsx|py|java|cpp|c|h|go|rs|rb|php)$/.test(entry.name)) { files.push(fullPath); } } } return files; } async function generateProjectOverview(projectRoot) { const files = await scanForCodeFiles(projectRoot); const overview = { root: projectRoot, statistics: { totalFiles: files.length, byLanguage: {}, totalLines: 0 }, structure: {}, dependencies: await analyzeDependencies(projectRoot), recentActivity: await getRecentActivity(projectRoot) }; // Analyze files for (const file of files) { const ext = path.extname(file); const content = await fs.readFile(file, 'utf-8'); const lines = content.split('\n').length; overview.statistics.totalLines += lines; overview.statistics.byLanguage[ext] = (overview.statistics.byLanguage[ext] || 0) + 1; } return overview; } const transport = new StdioServerTransport(); await server.connect(transport); console.error(`Code Context Server running for: ${projectRoot}`);
This tool demonstrates several key principles:
This tool demonstrates several key principles:
Rich Context
Don't just return file contents—provide analysis
Relationships
Show how files relate to each other
Search Capability
Resources can accept query parameters
Performance
Cache expensive computations
Progressive Disclosure
Overview → File → Specific analysis

File relation
Code Structure
ANNOTATIONS

File relation
Code Structure
ANNOTATIONS

File relation
Code Structure
ANNOTATIONS
8.2 Tool Design Challenge
8.2 Tool Design Challenge
The Subscription Pattern
The Subscription Pattern
One of the most powerful MCP features discovered by the community is resource subscriptions—the ability for Claude to watch resources for changes:
One of the most powerful MCP features discovered by the community is resource subscriptions—the ability for Claude to watch resources for changes:
MCP Feature
One of the most powerful MCP features discovered by the community is resource subscriptions—the ability for Claude to watch resources for changes:// Resource subscriptions: Real-time awareness class SubscribableResourceProvider { constructor() { this.subscribers = new Map(); this.watchers = new Map(); } // Allow clients to subscribe to resources async subscribe(uri, subscriber) { if (!this.subscribers.has(uri)) { this.subscribers.set(uri, new Set()); // Start watching this resource this.startWatching(uri); } this.subscribers.get(uri).add(subscriber); // Return current value immediately return await this.readResource(uri); } async unsubscribe(uri, subscriber) { const subs = this.subscribers.get(uri); if (subs) { subs.delete(subscriber); // Stop watching if no more subscribers if (subs.size === 0) { this.stopWatching(uri); this.subscribers.delete(uri); } } } // Example: Watch file system resources startWatching(uri) { if (uri.startsWith('file://')) { const filePath = uri.substring(7); const watcher = fs.watch(filePath, async (eventType, filename) => { // Resource changed, notify subscribers const newContent = await this.readResource(uri); for (const subscriber of this.subscribers.get(uri) || []) { subscriber.notify({ uri: uri, changeType: eventType, contents: newContent }); } }); this.watchers.set(uri, watcher); } } stopWatching(uri) { const watcher = this.watchers.get(uri); if (watcher) { watcher.close(); this.watchers.delete(uri); } } } // Real-world example: Log file monitoring class LogMonitorResource { async provideResource(uri) { if (uri === 'logs://application/current') { const logPath = '/var/log/app.log'; // Return recent logs const content = await this.getTailContent(logPath, 1000); return { uri: uri, mimeType: 'text/plain', content: content, subscription: { supported: true, events: ['append', 'rotate'] } }; } } async getTailContent(filePath, bytes) { const stats = await fs.stat(filePath); const start = Math.max(0, stats.size - bytes); const stream = fs.createReadStream(filePath, { start: start }); let content = ''; for await (const chunk of stream) { content += chunk; } return content; } // Watch for new log entries watchLog(filePath, callback) { let size = fs.statSync(filePath).size; return fs.watch(filePath, async (eventType) => { if (eventType === 'change') { const newSize = fs.statSync(filePath).size; if (newSize > size) { // Read new content const stream = fs.createReadStream(filePath, { start: size, end: newSize }); let newContent = ''; for await (const chunk of stream) { newContent += chunk; } callback({ type: 'append', content: newContent }); size = newSize; } else if (newSize < size) { // Log rotated callback({ type: 'rotate', content: await this.getTailContent(filePath, 1000) }); size = newSize; } } }); } }



Database
Database
api docs
api docs
fodlers
fodlers
8.3 Subscription Simulatort
8.3 Subscription Simulatort
Efficient Resource Design
Efficient Resource Design
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:
Lazy Resource Provider
Lazy Resource Provider
// Pattern 1: Lazy Loading class LazyResourceProvider { async listResources() { // Don't compute everything upfront return [ { uri: 'expensive://computation', name: 'Complex Analysis', mimeType: 'application/json', // Just describe what's available metadata: { estimatedTime: '2-5 seconds', requiresComputation: true } } ]; } async readResource(uri) { // Only compute when actually requested if (uri === 'expensive://computation') { return await this.performExpensiveComputation(); } } } // Pattern 2: Progressive Detail class ProgressiveResourceProvider { async readResource(uri) { const url = new URL(uri); const detail = url.searchParams.get('detail') || 'summary'; switch (detail) { case 'summary': // Fast, lightweight response return { mimeType: 'application/json', data: await this.getSummary() }; case 'detailed': // More computation, more data return { mimeType: 'application/json', data: await this.getDetailedAnalysis() }; case 'full': // Everything, might be slow return { mimeType: 'application/json', data: await this.getFullAnalysis() }; } } } // Pattern 3: Smart Caching class CachedResourceProvider { constructor() { this.cache = new Map(); this.cacheMetadata = new Map(); } async readResource(uri) { // Check cache validity const cached = this.cache.get(uri); const metadata = this.cacheMetadata.get(uri); if (cached && metadata) { // Different cache strategies for different resources const cacheStrategy = this.getCacheStrategy(uri); switch (cacheStrategy.type) { case 'time-based': if (Date.now() - metadata.timestamp < cacheStrategy.ttl) { return cached; } break; case 'content-based': const currentHash = await this.computeHash(uri); if (currentHash === metadata.hash) { return cached; } break; case 'hybrid': // Use time-based for fast check, content-based for accuracy if (Date.now() - metadata.timestamp < cacheStrategy.softTtl) { return cached; } const currentHash = await this.computeHash(uri); if (currentHash === metadata.hash) { // Content unchanged, refresh timestamp metadata.timestamp = Date.now(); return cached; } break; } } // Cache miss or invalid, compute fresh const fresh = await this.computeResource(uri); this.cache.set(uri, fresh); this.cacheMetadata.set(uri, { timestamp: Date.now(), hash: await this.computeHash(uri), accessCount: 0 }); // Implement cache eviction this.evictIfNeeded(); return fresh; } evictIfNeeded() { const maxSize = 100; // Max cache entries if (this.cache.size > maxSize) { // LRU eviction based on access patterns const entries = Array.from(this.cacheMetadata.entries()) .sort((a, b) => { const scoreA = a[1].accessCount / (Date.now() - a[1].timestamp); const scoreB = b[1].accessCount / (Date.now() - b[1].timestamp); return scoreA - scoreB; }); // Evict lowest scoring entries const toEvict = entries.slice(0, this.cache.size - maxSize); for (const [uri] of toEvict) { this.cache.delete(uri); this.cacheMetadata.delete(uri); } } } } // Pattern 4: Batch Loading class BatchResourceProvider { constructor() { this.batchQueue = []; this.batchTimer = null; } async readResource(uri) { return new Promise((resolve, reject) => { this.batchQueue.push({ uri, resolve, reject }); if (!this.batchTimer) { this.batchTimer = setTimeout(() => this.processBatch(), 10); } }); } async processBatch() { const batch = this.batchQueue.splice(0); this.batchTimer = null; // Group by resource type for efficient loading const grouped = batch.reduce((acc, item) => { const type = new URL(item.uri).protocol; acc[type] = acc[type] || []; acc[type].push(item); return acc; }, {}); // Process each group in parallel await Promise.all( Object.entries(grouped).map(async ([type, items]) => { const uris = items.map(item => item.uri); const results = await this.batchLoad(type, uris); items.forEach((item, index) => { item.resolve(results[index]); }); }) ); } async batchLoad(type, uris) { switch (type) { case 'database:': // Single query for multiple records const ids = uris.map(uri => new URL(uri).pathname); return await this.db.query( 'SELECT * FROM records WHERE id IN (?)', [ids] ); case 'api:': // Batch API request return await this.api.batchGet(uris); default: // Fallback to individual loads return await Promise.all(uris.map(uri => this.loadSingle(uri))); } } }

resource
control hub

resource
control hub

resource
control hub
8.4 Performance Profiler
8.4 Performance Profiler
Resources That Scale
Resources That Scale
As MCP adoption grew, the community discovered patterns for building resources that scale from personal use to enterprise deployments:
As MCP adoption grew, the community discovered patterns for building resources that scale from personal use to enterprise deployments:
Enterpirse Ready
Enterpirse Ready
// Enterprise-ready resource provider class ScalableResourceProvider { constructor(config) { this.config = config; // Connection pooling for databases this.dbPool = createPool({ max: config.maxConnections || 20, idleTimeoutMillis: 30000 }); // Distributed cache (Redis) this.cache = new Redis({ cluster: config.cacheCluster, retry_strategy: (options) => Math.min(options.attempt * 100, 3000) }); // Rate limiting per client this.rateLimiter = new RateLimiter({ points: config.rateLimit || 100, duration: 60 // per minute }); // Metrics collection this.metrics = new MetricsCollector(config.metrics); } async readResource(uri, context) { const startTime = Date.now(); try { // Check rate limits await this.rateLimiter.consume(context.clientId); // Try distributed cache first const cached = await this.cache.get(uri); if (cached) { this.metrics.increment('cache.hits'); return JSON.parse(cached); } this.metrics.increment('cache.misses'); // Route to appropriate handler const result = await this.routeRequest(uri, context); // Cache successful results if (result) { await this.cache.set( uri, JSON.stringify(result), 'EX', this.getCacheTTL(uri) ); } return result; } catch (error) { this.metrics.increment('errors', { error: error.code }); throw error; } finally { this.metrics.histogram('request.duration', Date.now() - startTime); } } async routeRequest(uri, context) { const url = new URL(uri); // Apply access control if (!await this.checkAccess(url, context)) { throw new Error('Access denied'); } // Route based on protocol switch (url.protocol) { case 'db:': return await this.handleDatabaseResource(url, context); case 'api:': return await this.handleAPIResource(url, context); case 'compute:': return await this.handleComputeResource(url, context); default: throw new Error(`Unknown protocol: ${url.protocol}`); } } async handleDatabaseResource(url, context) { const [database, table, id] = url.pathname.split('/').filter(Boolean); // Use connection from pool const client = await this.dbPool.connect(); try { // Apply row-level security const query = ` SELECT * FROM ${table} WHERE id = $1 AND organization_id = $2 AND ($3 = ANY(allowed_roles) OR allowed_roles IS NULL) `; const result = await client.query(query, [ id, context.organizationId, context.userRole ]); return { mimeType: 'application/json', data: result.rows[0] || null }; } finally { client.release(); } } async handleComputeResource(url, context) { const computation = url.pathname.substring(1); // Check if computation is already running const existingJob = await this.cache.get(`job:${computation}:${context.organizationId}`); if (existingJob) { const job = JSON.parse(existingJob); if (job.status === 'completed') { return job.result; } else { return { mimeType: 'application/json', data: { status: 'processing', progress: job.progress, estimatedCompletion: job.estimatedCompletion } }; } } // Start new computation job const jobId = await this.startComputeJob(computation, context); return { mimeType: 'application/json', data: { status: 'started', jobId: jobId, pollUri: `compute://${computation}?jobId=${jobId}` } }; } } // Community pattern: Resource middleware class ResourceMiddleware { constructor() { this.middlewares = []; } use(middleware) { this.middlewares.push(middleware); } async process(uri, context, next) { let index = 0; const dispatch = async () => { if (index >= this.middlewares.length) { return await next(); } const middleware = this.middlewares[index++]; return await middleware(uri, context, dispatch); }; return await dispatch(); } } // Example middleware: Audit logging async function auditMiddleware(uri, context, next) { const startTime = Date.now(); const result = await next(); await auditLog.record({ timestamp: new Date(), userId: context.userId, organizationId: context.organizationId, resource: uri, duration: Date.now() - startTime, success: true }); return result; } // Example middleware: Transform responses async function transformMiddleware(uri, context, next) { const result = await next(); // Add metadata to all responses return { ...result, metadata: { timestamp: new Date().toISOString(), version: '1.0', cacheable: true } }; }

resource
load
control hub

resource
load
control hub

resource
load
control hub
8.4 Scale Simulator
8.4 Scale Simulator
The Semantic Resource
The Semantic Resource
The community's latest innovation is semantic resources—resources that understand meaning, not just data:
The community's latest innovation is semantic resources—resources that understand meaning, not just data:
Testing Pattern MCP
Testing Pattern MCP
// 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'); }); });



node cluster
node cluster
highlighted node
highlighted node
8.5 Knowledge Explorer
8.5 Knowledge Explorer
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:
Do’s
Design for caching
Resources should be cacheable by default
Use standard MIME types
Help Claude understand your data format
Provide metadata
Include size, update time, and other helpful context
Support query parameters
Make resources filterable and searchable
Version your schemas
Resources evolve, plan for it
❌ DON'Ts
Mix reading and writing
That's what Tools are for
Compute everything eagerly
Lazy loading improves performance
Ignore errors
Graceful degradation is better than crashes
Forget about scale
Personal tools often become team tools
Skimp on documentation
Good descriptions help Claude help users

File Resource
file:///home/user/documents/report.md
System Resource
system://cpu
API Resource
github://repos/owner/name/issues?state=open
Composite Resource
project://my-app/overview

File Resource
file:///home/user/documents/report.md
System Resource
system://cpu
API Resource
github://repos/owner/name/issues?state=open
Composite Resource
project://my-app/overview

File Resource
file:///home/user/documents/report.md
System Resource
system://cpu
API Resource
github://repos/owner/name/issues?state=open
Composite Resource
project://my-app/overview
8.6 Tool Design Challenge
8.6 Tool Design Challenge
Building Resources That Matter
Building Resources That Matter
The best resources solve real problems. Here's a final example that brings together everything we've learned:
The best resources solve real problems. Here's a final example that brings together everything we've learned:
Learning Tracker
Learning Tracker
// Personal Learning Tracker - A resource that helps Claude help you learn class LearningTrackerResource { constructor() { this.db = new Database('./learning-tracker.db'); this.initSchema(); } async provideResources() { return [ { uri: 'learning://progress/overview', name: 'Learning Progress Overview', mimeType: 'application/json', description: 'Your overall learning progress across all topics' }, { uri: 'learning://topics/{topic}', name: 'Topic Progress', mimeType: 'application/json', description: 'Detailed progress for a specific topic' }, { uri: 'learning://recommendations', name: 'Learning Recommendations', mimeType: 'application/json', description: 'Personalized recommendations based on your progress' }, { uri: 'learning://insights/weekly', name: 'Weekly Learning Insights', mimeType: 'application/json', description: 'Analysis of your learning patterns' } ]; } async readResource(uri, context) { const url = new URL(uri); switch (url.pathname) { case '/progress/overview': return await this.getProgressOverview(context.userId); case '/recommendations': return await this.getRecommendations(context.userId); case '/insights/weekly': return await this.getWeeklyInsights(context.userId); default: if (url.pathname.startsWith('/topics/')) { const topic = url.pathname.substring(8); return await this.getTopicProgress(context.userId, topic); } } } async getProgressOverview(userId) { const topics = await this.db.query(` SELECT topic, COUNT(DISTINCT concept) as concepts_learned, AVG(confidence) as avg_confidence, MAX(last_reviewed) as last_activity, SUM(practice_time) as total_time FROM learning_progress WHERE user_id = ? GROUP BY topic ORDER BY last_activity DESC `, [userId]); const streaks = await this.calculateStreaks(userId); const momentum = await this.calculateMomentum(userId); return { mimeType: 'application/json', data: { summary: { totalTopics: topics.length, totalConcepts: topics.reduce((sum, t) => sum + t.concepts_learned, 0), currentStreak: streaks.current, longestStreak: streaks.longest, momentum: momentum }, topics: topics.map(t => ({ ...t, masteryLevel: this.calculateMastery(t.avg_confidence, t.concepts_learned), nextReview: this.calculateNextReview(t.last_activity, t.avg_confidence) })) } }; } async getRecommendations(userId) { // Analyze learning patterns const patterns = await this.analyzeLearningPatterns(userId); // Get current progress const progress = await this.getProgressOverview(userId); // Generate personalized recommendations const recommendations = []; // 1. Spaced repetition recommendations const dueForReview = await this.getDueForReview(userId); if (dueForReview.length > 0) { recommendations.push({ type: 'review', priority: 'high', title: 'Time for Review', description: `${dueForReview.length} concepts are due for review to maintain retention`, concepts: dueForReview.slice(0, 5) }); } // 2. Weak area strengthening const weakAreas = progress.data.topics .filter(t => t.avg_confidence < 0.6) .slice(0, 3); if (weakAreas.length > 0) { recommendations.push({ type: 'strengthen', priority: 'medium', title: 'Strengthen Foundations', description: 'These topics need more practice', topics: weakAreas }); } // 3. Learning path suggestions const nextTopics = await this.suggestNextTopics(userId, patterns); if (nextTopics.length > 0) { recommendations.push({ type: 'explore', priority: 'medium', title: 'Ready for New Challenges', description: 'Based on your progress, you\'re ready for these topics', topics: nextTopics }); } // 4. Optimal learning time if (patterns.bestTimeOfDay) { recommendations.push({ type: 'timing', priority: 'low', title: 'Optimal Learning Time', description: `Your data shows you learn best around ${patterns.bestTimeOfDay}`, data: patterns.timeAnalysis }); } return { mimeType: 'application/json', data: { recommendations: recommendations, learningVelocity: patterns.velocity, strengths: patterns.strengths, improvementAreas: patterns.improvements } }; } } // This resource enables conversations like: /* You: How's my Python learning going? Claude: Let me check your learning progress. [Claude reads learning://progress/overview] [Claude reads learning://topics/python] Your Python journey is going well! Here's what I see: 📊 **Progress Overview**: - Concepts learned: 47 - Average confidence: 78% - Current streak: 12 days 🔥 - Total practice time: 18.5 hours 💪 **Strengths**: - Functions and control flow (92% confidence) - Data structures (85% confidence) - File handling (81% confidence) 🎯 **Areas for Improvement**: - Decorators (45% confidence) - Async programming (52% confidence) - Class inheritance (58% confidence) [Claude reads learning://recommendations] 📚 **Recommendations**: 1. **Review Session Needed**: 5 concepts are due for review today to maintain your retention - List comprehensions (last reviewed 7 days ago) - Lambda functions (last reviewed 9 days ago) 2. **Strengthen Decorators**: Your confidence is lowest here. Try: - Building a simple @timer decorator - Creating a @cache decorator - Understanding @property 3. **Optimal Learning Time**: Your data shows you retain information best between 9-11 AM Would you like me to create a practice session for decorators or help you review the concepts due today? */



Answers feel limited.
Answers feel rich + precise.
Answers feel limited.
Answers feel rich + precise.
Answers feel limited.
Answers feel rich + precise.
8.7 Resource Impact Visualizer
8.7 Resource Impact Visualizer
Your Resource Journey
Your Resource Journey
Resources are the eyes through which AI sees your world. Every resource you build expands what's possible:
Resources are the eyes through which AI sees your world. Every resource you build expands what's possible:
Today: Claude reads your files and databases
Today: Claude reads your files and databases
Tomorrow: Claude understands your entire digital context
Tomorrow: Claude understands your entire digital context
The Future: Claude becomes a true extension of your capabilities
The Future: Claude becomes a true extension of your capabilities



8.8 Resource Builder Toolkit
8.8 Resource Builder Toolkit