Learn how to expose data through MCP resources, handle different data types, and build dynamic resource systems
Resources are the foundation of data access in MCP. They represent any data that an AI application might need to read - from simple configuration files to complex database queries. Think of resources as a universal API for exposing information.
A well-designed resource should be:
<CodeExample title="Resource Structure" language="typescript" code={`interface Resource { uri: string; // Unique identifier (e.g., "file:///path/to/file") name: string; // Human-readable name description?: string; // Optional description mimeType?: string; // Content type (default: text/plain) }
interface ResourceContent { uri: string; mimeType: string; text?: string; // Text content blob?: string; // Base64-encoded binary }`} highlightLines={[2, 9, 10, 11]} />
Understanding the resource lifecycle helps you build robust providers:
resources/list
resources/read
with specific URIBest Practice: Design your URI scheme to be hierarchical and meaningful. Use prefixes like config://
, db://
, or file://
to categorize resources.
The anatomy of an MCP resource
Static resources are the simplest type - they return fixed content that doesn't change often. They're perfect for configuration, metadata, and system information.
<CodeExample title="Static Resource Implementation" language="typescript" code={`// Define static resources const staticResources = [ { uri: 'config://app/settings', name: 'Application Settings', description: 'Current application configuration', mimeType: 'application/json' }, { uri: 'config://app/version', name: 'Version Information', description: 'Application version and build info', mimeType: 'application/json' } ];
// Implement list handler server.setRequestHandler('resources/list', async () => { return { resources: staticResources }; });
// Implement read handler server.setRequestHandler('resources/read', async (request) => { const { uri } = request.params;
switch (uri) { case 'config://app/settings': return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ theme: 'dark', language: 'en', debugMode: false }, null, 2) }] };
case 'config://app/version':
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({
version: '1.0.0',
buildDate: new Date().toISOString(),
nodeVersion: process.version
}, null, 2)
}]
};
default:
throw new Error(\`Resource not found: \${uri}\`);
} });`} />
As your application grows, organize static resources logically:
Pro Tip: Use consistent naming patterns. For example, prefix all configuration with config://
and all system info with system://
.
Expose configuration and metadata as resources
Module content not available.
As your MCP servers grow more sophisticated, you'll need advanced patterns to handle large datasets and complex requirements.
When you have many resources, pagination prevents overwhelming clients:
<CodeExample title="Paginated Resource Lists" language="typescript" code={`interface PaginatedListRequest { params: { cursor?: string; limit?: number; }; }
server.setRequestHandler('resources/list', async (request: PaginatedListRequest) => { const { cursor, limit = 100 } = request.params;
// Get all resources (in practice, from database with LIMIT/OFFSET) const allResources = await getAllResources();
// Find starting position const startIndex = cursor ? allResources.findIndex(r => r.uri === cursor) + 1 : 0;
// Get page of resources const pageResources = allResources.slice(startIndex, startIndex + limit);
// Prepare response with nextCursor const response = { resources: pageResources };
// Add next cursor if there are more resources if (startIndex + limit < allResources.length) { response.nextCursor = pageResources[pageResources.length - 1].uri; }
return response; });`} highlightLines={[8, 9, 15, 16, 19, 26, 27, 28]} />
Allow clients to filter resources by type, pattern, or attributes:
<CodeExample title="Filtered Resource Lists" language="typescript" code={`interface FilteredListRequest { params: { filter?: { mimeType?: string; uriPattern?: string; tags?: string[]; }; }; }
server.setRequestHandler('resources/list', async (request: FilteredListRequest) => { const { filter } = request.params; let resources = await getAllResources();
if (filter) { // Filter by MIME type if (filter.mimeType) { resources = resources.filter(r => r.mimeType === filter.mimeType); }
// Filter by URI pattern
if (filter.uriPattern) {
const pattern = new RegExp(filter.uriPattern);
resources = resources.filter(r => pattern.test(r.uri));
}
// Filter by tags (if your resources have tags)
if (filter.tags && filter.tags.length > 0) {
resources = resources.filter(r =>
r.tags && filter.tags.some(tag => r.tags.includes(tag))
);
}
}
return { resources }; });`} />
For expensive operations, implement intelligent caching:
<CodeExample title="Resource Caching Strategy" language="typescript" code={`class CachedResourceProvider { private cache = new Map<string, { content: any; timestamp: number }>(); private cacheTimeout = 5 * 60 * 1000; // 5 minutes
async readResource(uri: string) { // Check cache first const cached = this.cache.get(uri); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.content; }
// Generate fresh content
const content = await this.generateContent(uri);
// Cache the result
this.cache.set(uri, {
content,
timestamp: Date.now()
});
return content;
}
// Invalidate cache when resources change invalidateCache(uri?: string) { if (uri) { this.cache.delete(uri); } else { this.cache.clear(); } } }`} />
Handle large resource collections with pagination
Support filtering resources by type or attributes
For real-time applications, implement resource subscriptions to notify clients when data changes.
<CodeExample title="Resource Update Notifications" language="typescript" code={`import { EventEmitter } from 'events';
class ResourceManager extends EventEmitter { private resources = new Map<string, Resource>();
updateResource(uri: string, updates: Partial<Resource>) { const existing = this.resources.get(uri); if (!existing) throw new Error('Resource not found');
const updated = { ...existing, ...updates };
this.resources.set(uri, updated);
// Emit update event
this.emit('resource:updated', {
uri,
resource: updated
});
}
deleteResource(uri: string) { if (!this.resources.delete(uri)) { throw new Error('Resource not found'); }
// Emit delete event
this.emit('resource:deleted', { uri });
} }
// In your server setup const resourceManager = new ResourceManager();
// Subscribe to changes resourceManager.on('resource:updated', ({ uri, resource }) => { // Send notification to connected clients server.sendNotification('resources/updated', { uri, resource }); });
resourceManager.on('resource:deleted', ({ uri }) => { server.sendNotification('resources/deleted', { uri }); });`} />
Monitor file system changes for automatic updates:
<CodeExample title="File System Watching" language="typescript" code={`import { watch } from 'fs';
class WatchedFileProvider { private watchers = new Map<string, any>();
constructor(private basePath: string, private server: Server) { this.setupWatcher(); }
private setupWatcher() { const watcher = watch(this.basePath, { recursive: true }, (eventType, filename) => { if (!filename) return;
const fullPath = path.join(this.basePath, filename);
const uri = \`file://\${fullPath}\`;
if (eventType === 'change') {
// File modified
this.server.sendNotification('resources/updated', { uri });
} else if (eventType === 'rename') {
// File created or deleted
this.checkFileExists(fullPath).then(exists => {
if (exists) {
this.server.sendNotification('resources/created', { uri });
} else {
this.server.sendNotification('resources/deleted', { uri });
}
});
}
});
this.watchers.set(this.basePath, watcher);
}
private async checkFileExists(path: string): Promise<boolean> { try { await fs.access(path); return true; } catch { return false; } }
cleanup() { this.watchers.forEach(watcher => watcher.close()); this.watchers.clear(); } }`} />
Notify clients when resources change
Check your understanding of MCP resource providers
1. What is the primary purpose of MCP resources?
2. Which fields are required for a resource?
3. Resources can only return text content, not binary data.
True or False question
Correct Answer: A
False! Resources can return binary data using the 'blob' field with base64 encoding.
4. Complete the resource read handler to return JSON content: